async def python_command(event): reply_to = event.message dest = await event.get_input_chat() async for x in AsyncCodeExecutor(event.pattern_match.group(1), arg_dict=dict(event=event)): if type(x) is not str: x = repr(x) if x == '': x = repr(x) reply_to = await event.client.send_message(dest, x, reply_to=reply_to) await event.reply('✅')
async def test_executor_builtins(scope): codeblock = inspect.cleandoc(""" def ensure_builtins(): return ValueError """) return_data = [] async for result in AsyncCodeExecutor(codeblock, scope): return_data.append(result) assert len(return_data) == 1 assert return_data[0] is None assert 'ensure_builtins' in scope.globals, "Checking function remains defined" assert callable(scope.globals['ensure_builtins']), "Checking defined is callable" assert scope.globals['ensure_builtins']() == ValueError, "Checking defined return consistent"
async def jsk_python(self, ctx: commands.Context, *, argument: codeblock_converter): """ Direct evaluation of Python code. """ arg_dict = get_var_dict_from_ctx(ctx, SCOPE_PREFIX) arg_dict["_"] = self.last_result scope = self.scope try: async with ReplResponseReactor(ctx.message): with self.submit(ctx): executor = AsyncCodeExecutor(argument.content, scope, arg_dict=arg_dict) async for send, result in AsyncSender(executor): if result is None: continue self.last_result = result if isinstance(result, discord.File): send(await ctx.send(file=result)) elif isinstance(result, discord.Embed): send(await ctx.send(embed=result)) elif isinstance(result, PaginatorInterface): send(await result.send_to(ctx)) else: if not isinstance(result, str): # repr all non-strings result = repr(result) if len(result) > 2000: # inconsistency here, results get wrapped in codeblocks when they are too large # but don't if they're not. probably not that bad, but noting for later review paginator = WrappedPaginator(prefix='```py', suffix='```', max_size=1985) paginator.add_line(result) interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) send(await interface.send_to(ctx)) else: if result.strip() == '': result = "\u200b" send(await ctx.send(result.replace(self.bot.http.token, "[token omitted]"))) finally: scope.clear_intersection(arg_dict)
async def jsk_python_inspect(self, ctx: commands.Context, *, argument: codeblock_converter): # pylint: disable=too-many-locals """ Evaluation of Python code with inspect information. """ arg_dict = get_var_dict_from_ctx(ctx, SCOPE_PREFIX) arg_dict["_"] = self.last_result scope = self.scope try: async with ReplResponseReactor(ctx.message): with self.submit(ctx): executor = AsyncCodeExecutor(argument.content, scope, arg_dict=arg_dict) async for send, result in AsyncSender(executor): self.last_result = result header = repr(result).replace("``", "`\u200b`").replace(self.bot.http.token, "[token omitted]") if len(header) > 485: header = header[0:482] + "..." lines = [f"=== {header} ===", ""] for name, res in all_inspections(result): lines.append(f"{name:16.16} :: {res}") text = "\n".join(lines) # Guild's advertised limit minus 1KiB for the HTTP content filesize_threshold = (ctx.guild.filesize_limit if ctx.guild else 8 * 1024 * 1024) - 1024 if len(text) < filesize_threshold: send(await ctx.send(file=discord.File( filename="inspection.prolog", fp=io.BytesIO(text.encode('utf-8')) ))) else: paginator = WrappedPaginator(prefix="```prolog", max_size=1985) paginator.add_line(text) interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) send(await interface.send_to(ctx)) finally: scope.clear_intersection(arg_dict)
async def jsk_python_inspect( self, ctx: commands.Context, *, argument: codeblock_converter ): """ Evaluation of Python code with inspect information. """ arg_dict = get_var_dict_from_ctx(ctx, SCOPE_PREFIX) arg_dict["_"] = self.last_result scope = self.scope try: async with ReplResponseReactor(ctx.message): with self.submit(ctx): executor = AsyncCodeExecutor( argument.content, scope, arg_dict=arg_dict ) async for send, result in AsyncSender(executor): self.last_result = result header = ( repr(result) .replace("``", "`\u200b`") .replace(self.bot.http.token, "[token omitted]") ) if len(header) > 485: header = header[0:482] + "..." paginator = WrappedPaginator( prefix=f"```prolog\n=== {header} ===\n", max_size=1985, ) for name, res in all_inspections(result): paginator.add_line(f"{name:16.16} :: {res}") interface = PaginatorInterface( ctx.bot, paginator, owner=ctx.author ) send(await interface.send_to(ctx)) finally: scope.clear_intersection(arg_dict)
async def jsk_python(self, ctx: commands.Context, *, argument: CodeblockConverter): """ Direct evaluation of Python code. """ arg_dict = get_var_dict_from_ctx(ctx) scope = self.scope scope.clean() arg_dict["_"] = self.last_result async with ReplResponseReactor(ctx.message): with self.submit(ctx): async for result in AsyncCodeExecutor( argument.content, scope, arg_dict=arg_dict ): if result is None: continue self.last_result = result if isinstance(result, discord.File): await ctx.send(file=result) elif isinstance(result, discord.Embed): await ctx.send(embed=result) else: if not isinstance(result, str): # repr all non-strings result = repr(result) if len(result) > 2000: result = result[0:1997] + "..." elif result.strip() == "": result = "\u200b" await ctx.send( result.replace(self.bot.http.token, "[token omitted]") )
async def jsk_python(self, ctx: commands.Context, *, argument: codeblock_converter): """ Direct evaluation of Python code. """ arg_dict = get_var_dict_from_ctx(ctx, SCOPE_PREFIX) arg_dict["_"] = self.last_result scope = self.scope try: async with ReplResponseReactor(ctx.message): with self.submit(ctx): executor = AsyncCodeExecutor(argument.content, scope, arg_dict=arg_dict) async for send, result in AsyncSender(executor): if result is None: continue self.last_result = result if isinstance(result, discord.File): send(await ctx.send(file=result)) elif isinstance(result, discord.Embed): send(await ctx.send(embed=result)) elif isinstance(result, PaginatorInterface): send(await result.send_to(ctx)) else: if not isinstance(result, str): # repr all non-strings result = repr(result) # Guild's advertised limit minus 1KiB for the HTTP content filesize_threshold = (ctx.guild.filesize_limit if ctx.guild else 8 * 1024 * 1024) - 1024 if len(result) <= 2000: if result.strip() == '': result = "\u200b" send(await ctx.send(result.replace(self.bot.http.token, "[token omitted]"))) elif len(result) < filesize_threshold: # Discord's desktop and web client now supports an interactive file content # display for files encoded in UTF-8. # Since this avoids escape issues and is more intuitive than pagination for # long results, it will now be prioritized over PaginatorInterface if the # resultant content is below the filesize threshold send(await ctx.send(file=discord.File( filename="output.py", fp=io.BytesIO(result.encode('utf-8')) ))) else: # inconsistency here, results get wrapped in codeblocks when they are too large # but don't if they're not. probably not that bad, but noting for later review paginator = WrappedPaginator(prefix='```py', suffix='```', max_size=1985) paginator.add_line(result) interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) send(await interface.send_to(ctx)) finally: scope.clear_intersection(arg_dict)
async def internal_test(self): scope = Scope() return_data = [] async for result in AsyncCodeExecutor('3 + 4', scope): return_data.append(result) self.assertEqual(len(return_data), 1, msg="Checking eval produces single result") self.assertEqual(return_data[0], 7, msg="Checking eval result is consistent") return_data = [] async for result in AsyncCodeExecutor('return 3 + 9', scope): return_data.append(result) self.assertEqual(len(return_data), 1, msg="Checking manual return produces single result") self.assertEqual(return_data[0], 12, msg="Checking return result is consistent") return_data = [] async for result in AsyncCodeExecutor('b = 12 + 82', scope): return_data.append(result) self.assertEqual(len(return_data), 1, msg="Checking that assignment returns") self.assertIsNone(return_data[0], msg="Checking assignment returns None") return_data = [] async for result in AsyncCodeExecutor('b', scope): return_data.append(result) self.assertEqual(len(return_data), 1, msg="Checking variable eval returns") self.assertEqual(return_data[0], 94, msg="Checking retained variable consistent") scope.update_globals({'d': 41}) return_data = [] async for result in AsyncCodeExecutor('c = d + 7; c', scope): return_data.append(result) self.assertEqual(len(return_data), 1, msg="Checking multi-expression implicitly returns") self.assertEqual(return_data[0], 48, msg="Checking last expression return is consistent") return_data = [] async for result in AsyncCodeExecutor('yield 30; yield 40', scope): return_data.append(result) self.assertEqual(len(return_data), 2, msg="Checking two yields returns two results") self.assertEqual(return_data[0], 30, msg="Checking first yield consistency") self.assertEqual(return_data[1], 40, msg="Checking second yield consistency") return_data = [] async for result in AsyncCodeExecutor('yield 60; 70', scope): return_data.append(result) self.assertEqual(len(return_data), 2, msg="Checking multi-statement implicitly yields") self.assertEqual(return_data[0], 60, msg="Checking explicit yield consistent") self.assertEqual(return_data[1], 70, msg="Checking implicit yield consistent") return_data = [] async for result in AsyncCodeExecutor('90; 100', scope): return_data.append(result) self.assertEqual(len(return_data), 1, msg="Checking multi-statement implicitly returns") self.assertEqual(return_data[0], 100, msg="Checking implicit return consistent") arg_dict = {'_cool_data': 45, '_not_so_cool': 400} return_data = [] async for result in AsyncCodeExecutor('_cool_data + _not_so_cool', scope, arg_dict=arg_dict): return_data.append(result) self.assertEqual(len(return_data), 1, msg="Checking arg dictionary expression returned") self.assertEqual(return_data[0], 445, msg="Checking arg dictionary expression consistent") scope.clean() hit_exception = False try: async for result in AsyncCodeExecutor('_cool_data', scope): pass except NameError: hit_exception = True self.assertTrue(hit_exception, msg="Checking private locals removed") scope2 = Scope() scope2.update(scope) self.assertEqual(scope.globals, scope2.globals, msg="Checking scope globals copied") self.assertEqual(scope.locals, scope2.locals, msg="Checking scope locals copied") scope.update_locals({'e': 7}) self.assertIn('e', scope.locals, msg="Checking scope locals updated") self.assertNotIn('e', scope2.locals, msg="Checking scope clone locals not updated") scope.clean() codeblock = inspect.cleandoc(""" def ensure_builtins(): return ValueError """) async for result in AsyncCodeExecutor(codeblock, scope): pass scope.clean() self.assertIn('ensure_builtins', scope.globals, msg="Checking function remains defined") self.assertTrue(callable(scope.globals['ensure_builtins']), msg="Checking defined is callable") self.assertEqual(scope.globals['ensure_builtins'](), ValueError, msg="Checking defined retuurn consistent") if sys.version_info >= (3, 7): codeblock = inspect.cleandoc(""" eval(''' 3 + 4 ''') """) return_data = [] async for result in AsyncCodeExecutor(codeblock, scope): return_data.append(result) self.assertEqual(len(return_data), 1, msg="Checking multi-line docstring eval returns") self.assertEqual(return_data[0], 7, msg="Checking eval return consistent") scope.clean() scope.update_globals({'add_numbers': self.add_numbers}) return_data = [] async for result in AsyncCodeExecutor("await add_numbers(10, 12)", scope): return_data.append(result) self.assertEqual(len(return_data), 1, msg="Checking await returns result") self.assertEqual(return_data[0], 22, msg="Checking await result consistent") return_data = [] async for result in AsyncCodeExecutor("", scope): return_data.append(result) self.assertEqual(len(return_data), 1, msg="Checking empty eval returns") self.assertIsNone(return_data[0], msg="Checking empty eval returns None") return_data = [] async for result in AsyncCodeExecutor("# this is a comment", scope): return_data.append(result) self.assertEqual(len(return_data), 1, msg="Checking eval of only comment returns") self.assertIsNone(return_data[0], msg="Checking eval of only comment returns None")