Skip to content

Commit 1ab25e6

Browse files
committed
🚀 Move python code to separate module
1 parent b727656 commit 1ab25e6

14 files changed

+1098
-387
lines changed

.gitignore

+141-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
.idea/**/usage.statistics.xml
88
.idea/**/dictionaries
99
.idea/**/shelf
10+
.idea/
1011

1112
# Sensitive or high-churn files
1213
.idea/**/dataSources/
@@ -90,4 +91,143 @@ hs_err_pid*
9091
output/
9192

9293
.gradle/
93-
build/
94+
build/
95+
96+
# Byte-compiled / optimized / DLL files
97+
__pycache__/
98+
*.py[cod]
99+
*$py.class
100+
101+
# C extensions
102+
*.so
103+
104+
# Distribution / packaging
105+
.Python
106+
build/
107+
develop-eggs/
108+
dist/
109+
downloads/
110+
eggs/
111+
.eggs/
112+
lib/
113+
lib64/
114+
parts/
115+
sdist/
116+
var/
117+
wheels/
118+
share/python-wheels/
119+
*.egg-info/
120+
.installed.cfg
121+
*.egg
122+
MANIFEST
123+
124+
# PyInstaller
125+
# Usually these files are written by a python script from a template
126+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
127+
*.manifest
128+
*.spec
129+
130+
# Installer logs
131+
pip-log.txt
132+
pip-delete-this-directory.txt
133+
134+
# Unit test / coverage reports
135+
htmlcov/
136+
.tox/
137+
.nox/
138+
.coverage
139+
.coverage.*
140+
.cache
141+
nosetests.xml
142+
coverage.xml
143+
*.cover
144+
*.py,cover
145+
.hypothesis/
146+
.pytest_cache/
147+
cover/
148+
149+
# Translations
150+
*.mo
151+
*.pot
152+
153+
# Django stuff:
154+
*.log
155+
local_settings.py
156+
db.sqlite3
157+
db.sqlite3-journal
158+
159+
# Flask stuff:
160+
instance/
161+
.webassets-cache
162+
163+
# Scrapy stuff:
164+
.scrapy
165+
166+
# Sphinx documentation
167+
docs/_build/
168+
169+
# PyBuilder
170+
.pybuilder/
171+
target/
172+
173+
# Jupyter Notebook
174+
.ipynb_checkpoints
175+
176+
# IPython
177+
profile_default/
178+
ipython_config.py
179+
180+
# pyenv
181+
# For a library or package, you might want to ignore these files since the code is
182+
# intended to run in multiple environments; otherwise, check them in:
183+
# .python-version
184+
185+
# pipenv
186+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
187+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
188+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
189+
# install all needed dependencies.
190+
#Pipfile.lock
191+
192+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
193+
__pypackages__/
194+
195+
# Celery stuff
196+
celerybeat-schedule
197+
celerybeat.pid
198+
199+
# SageMath parsed files
200+
*.sage.py
201+
202+
# Environments
203+
.env
204+
.venv
205+
env/
206+
venv/
207+
ENV/
208+
env.bak/
209+
venv.bak/
210+
211+
# Spyder project settings
212+
.spyderproject
213+
.spyproject
214+
215+
# Rope project settings
216+
.ropeproject
217+
218+
# mkdocs documentation
219+
/site
220+
221+
# mypy
222+
.mypy_cache/
223+
.dmypy.json
224+
dmypy.json
225+
226+
# Pyre type checker
227+
.pyre/
228+
229+
# pytype static type analyzer
230+
.pytype/
231+
232+
# Cython debug symbols
233+
cython_debug/

async-pydevd/__init__.py

Whitespace-only changes.

async-pydevd/async_pydevd/__init__.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from pathlib import Path
2+
3+
import nest_asyncio
4+
5+
ROOT: Path = Path(__file__).parent
6+
7+
FILES: tuple[Path, ...] = (
8+
Path(nest_asyncio.__file__),
9+
ROOT / "asyncio_patch.py",
10+
ROOT / "async_eval.py",
11+
ROOT / "pydevd_patch.py",
12+
ROOT / "pydevd_main.py",
13+
)
14+
15+
16+
def generate() -> str:
17+
return "\n".join(p.read_text("utf-8") for p in FILES).replace('"""', "'''").strip()
18+
19+
20+
__all__ = ["generate"]
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import ast
2+
import sys
3+
import textwrap
4+
5+
6+
# Async equivalent of builtin eval function
7+
def async_eval(expr: str, _globals: dict = None, _locals: dict = None):
8+
if _locals is None:
9+
_locals = {}
10+
11+
if _globals is None:
12+
_globals = {}
13+
14+
expr = textwrap.indent(expr, " ")
15+
expr = f"async def _():\n{expr}"
16+
17+
parsed_stmts = ast.parse(expr).body[0]
18+
for node in parsed_stmts.body:
19+
ast.increment_lineno(node)
20+
21+
last_stmt = parsed_stmts.body[-1]
22+
23+
if isinstance(last_stmt, ast.Expr):
24+
return_expr = ast.copy_location(ast.Return(last_stmt), last_stmt)
25+
return_expr.value = return_expr.value.value
26+
parsed_stmts.body[-1] = return_expr
27+
28+
parsed_fn = ast.parse(
29+
f"""\
30+
async def __async_exec_func__(__locals__=__locals__):
31+
try:
32+
pass
33+
finally:
34+
__locals__.update(locals())
35+
del __locals__['__locals__']
36+
37+
import asyncio
38+
39+
__async_exec_func_result__ = asyncio.get_event_loop().run_until_complete(__async_exec_func__())
40+
"""
41+
)
42+
43+
parsed_fn.body[0].body[0].body = parsed_stmts.body
44+
45+
try:
46+
code = compile(parsed_fn, filename="<ast>", mode="exec")
47+
except (SyntaxError, TypeError):
48+
parsed_stmts.body[-1] = last_stmt
49+
parsed_fn.body[0].body[0].body = parsed_stmts.body
50+
code = compile(parsed_fn, filename="<ast>", mode="exec")
51+
52+
_updated_locals = {
53+
**_locals,
54+
"__locals__": _locals,
55+
}
56+
_updated_globals = {
57+
**_globals,
58+
**_updated_locals,
59+
}
60+
61+
exec(code, _updated_globals, _updated_locals)
62+
return _updated_locals["__async_exec_func_result__"]
63+
64+
65+
sys.__async_eval__ = async_eval
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import asyncio
2+
import functools
3+
import sys
4+
5+
6+
def _patch_asyncio_set_get_new():
7+
if sys.platform.lower().startswith("win"):
8+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
9+
10+
apply()
11+
12+
def _patch_loop_if_not_patched(loop):
13+
if not hasattr(loop, "_nest_patched"):
14+
_patch_loop(loop)
15+
16+
def _patch_asyncio_api(func):
17+
@functools.wraps(func)
18+
def wrapper(*args, **kwargs):
19+
loop = func(*args, **kwargs)
20+
_patch_loop_if_not_patched(loop)
21+
return loop
22+
23+
return wrapper
24+
25+
asyncio.get_event_loop = _patch_asyncio_api(asyncio.get_event_loop)
26+
asyncio.new_event_loop = _patch_asyncio_api(asyncio.new_event_loop)
27+
28+
_set_event_loop = asyncio.set_event_loop
29+
30+
@functools.wraps(asyncio.set_event_loop)
31+
def set_loop_wrapper(loop):
32+
_patch_loop_if_not_patched(loop)
33+
_set_event_loop(loop)
34+
35+
asyncio.set_event_loop = set_loop_wrapper
36+
37+
38+
_patch_asyncio_set_get_new()
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import sys
2+
from runpy import run_path
3+
4+
run_path(sys.argv.pop(1), {}, "__main__")
+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
def is_async_code(code: str) -> bool:
2+
return "__async_eval__" not in code and ("await" in code or "async" in code)
3+
4+
5+
def make_code_async(code: str) -> str:
6+
if not code:
7+
return code
8+
9+
if is_async_code(code):
10+
code = code.replace("@" + "LINE" + "@", "\n")
11+
return f"__import__('sys').__async_eval__({code!r}, globals(), locals())"
12+
13+
return code
14+
15+
16+
# 1. Add ability to evaluate async expression
17+
from _pydevd_bundle import pydevd_vars
18+
19+
original_evaluate = pydevd_vars.evaluate_expression
20+
21+
22+
def evaluate_expression(thread_id: int, frame_id: int, expression: str, doExec: bool):
23+
if is_async_code(expression):
24+
doExec = False
25+
26+
return original_evaluate(thread_id, frame_id, make_code_async(expression), doExec)
27+
28+
29+
pydevd_vars.evaluate_expression = evaluate_expression
30+
31+
# 2. Add ability to use async breakpoint conditions
32+
from _pydevd_bundle.pydevd_breakpoints import LineBreakpoint
33+
34+
35+
def normalize_line_breakpoint(line_breakpoint: LineBreakpoint) -> None:
36+
line_breakpoint.expression = make_code_async(line_breakpoint.expression)
37+
line_breakpoint.condition = make_code_async(line_breakpoint.condition)
38+
39+
40+
original_init = LineBreakpoint.__init__
41+
42+
43+
def line_breakpoint_init(self: LineBreakpoint, *args, **kwargs):
44+
original_init(self, *args, **kwargs)
45+
normalize_line_breakpoint(self)
46+
47+
48+
LineBreakpoint.__init__ = line_breakpoint_init
49+
50+
# 3. Add ability to use async code in console
51+
from _pydevd_bundle import pydevd_console_integration
52+
53+
original_console_exec = pydevd_console_integration.console_exec
54+
55+
56+
def console_exec(thread_id: int, frame_id: int, expression: str, dbg):
57+
return original_console_exec(thread_id, frame_id, make_code_async(expression), dbg)
58+
59+
60+
pydevd_console_integration.console_exec = console_exec
61+
62+
# 4. Add ability to use async code
63+
from _pydev_bundle.pydev_console_types import Command
64+
65+
66+
def command_run(self):
67+
text = make_code_async(self.code_fragment.text)
68+
symbol = self.symbol_for_fragment(self.code_fragment)
69+
70+
self.more = self.interpreter.runsource(text, "<input>", symbol)
71+
72+
73+
Command.run = command_run

async-pydevd/poetry.lock

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

async-pydevd/pyproject.toml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[tool.poetry]
2+
name = "async-pydevd"
3+
version = "0.1.0"
4+
description = "pydevd with async/await support"
5+
authors = ["Yurii Karabas <[email protected]>"]
6+
license = "MIT"
7+
8+
[tool.poetry.dependencies]
9+
python = "^3.9"
10+
nest-asyncio = "^1.4.3"
11+
12+
[tool.poetry.dev-dependencies]
13+
14+
[build-system]
15+
requires = ["poetry-core>=1.0.0"]
16+
build-backend = "poetry.core.masonry.api"

0 commit comments

Comments
 (0)