Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add aeval: Asynchronous Equivalent to eval in aioconsole #120

Merged
merged 8 commits into from
Aug 31, 2024

Conversation

Akm0d
Copy link
Contributor

@Akm0d Akm0d commented Aug 25, 2024

This PR introduces a new aeval function to the aioconsole library, providing an asynchronous equivalent to Python's built-in eval. The aeval function enhances aioconsole by enabling users to evaluate expressions asynchronously within an interactive console environment, complementing the existing aexec function.

Key Features of aeval:

Asynchronous Evaluation:

  • The aeval function allows for the evaluation of expressions that may involve asynchronous operations. This is particularly useful in environments where asynchronous code is prevalent, such as web servers or other I/O-bound applications.

Behavior Consistent with eval:

  • aeval functions in a manner consistent with Python’s built-in eval. It evaluates single-line expressions and raises the same exceptions as eval for invalid input.
  • Notably, aeval does not handle multi-line inputs or statements (e.g., function definitions, loops), maintaining strict compatibility with the eval behavior which is limited to single expressions.

Error Handling:

  • The function gracefully handles syntax errors and other exceptions, providing clear feedback when issues arise during the evaluation of expressions, just as eval does.

Seamless Integration with Existing aexec:

  • The implementation of aeval builds upon the aexec function, ensuring consistent behavior and making use of the existing compilation and execution mechanisms within aioconsole.

Example Usage:

import asyncio
from aioconsole import aeval

async def main():
    local_namespace = {"x": 10}
    result = await aeval("x + 5", local_namespace)
    print(result)  # Outputs: 15

asyncio.run(main())

This PR ensures that aeval behaves exactly like the built-in eval, with the added capability of handling asynchronous functions, making it a powerful tool for asynchronous interactive environments.

@vxgmichel
Copy link
Owner

Hi @Akm0d and thanks for taking the time to make this PR.

I'm not against introducing an aeval function, but in this case I would like it to behave similarly to the builtin eval.

For instance eval("def foo(): return 42") raises a SyntaxError but your implementation does not.

Moreover, eval("x = 1\nx + 1") also raises a SyntaxError but your implementation returns 2.

As far as I remember, implementing aeval was not trivial which is why I did not take the time to do it in the first place :)

Copy link

codecov bot commented Aug 26, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 94.22%. Comparing base (7837517) to head (43306a0).
Report is 9 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #120      +/-   ##
==========================================
+ Coverage   94.11%   94.22%   +0.10%     
==========================================
  Files          11       11              
  Lines         833      848      +15     
==========================================
+ Hits          784      799      +15     
  Misses         49       49              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@Akm0d
Copy link
Contributor Author

Akm0d commented Aug 26, 2024

thanks for taking a look so quickly! i'll do some fine tuning to resolve what you pointed out

@Akm0d
Copy link
Contributor Author

Akm0d commented Aug 26, 2024

@vxgmichel I updated the code to ensure that aeval functions exactly the same as the builtin eval (except that it handles async expressions of course).

I also modified my tests to compare the results of aeval and eval in a wide variety of edge cases.

@vxgmichel
Copy link
Owner

Oh I'm very surprised that ast.parse("await asyncio.sleep(1)", mode="eval") actually works, well done 👍

The test comparing eval to aeval for a variety of cases looks very good, but we're still missing cases for expressions that includes an await.

@Akm0d
Copy link
Contributor Author

Akm0d commented Aug 27, 2024

Ok I've added more test cases to achieve full code coverage. Also raising the same TypeError as eval when local is not a dict.

Copy link
Owner

@vxgmichel vxgmichel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

I'll have to fix the python 3.12 compatibility though

aioconsole/execute.py Outdated Show resolved Hide resolved
@vxgmichel vxgmichel merged commit b279f7c into vxgmichel:main Aug 31, 2024
25 checks passed
@vxgmichel
Copy link
Owner

Merged 🎉
Thanks a lot for the PR @Akm0d

@vxgmichel vxgmichel mentioned this pull request Aug 31, 2024
@vxgmichel
Copy link
Owner

Release in v0.8.0 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants