Skip to content

Commit ceefd43

Browse files
authored
chore: refactor some code from sync to utils and get rid of path globals (#46)
1 parent f66fb4b commit ceefd43

File tree

7 files changed

+571
-697
lines changed

7 files changed

+571
-697
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ name = "workers-py"
77
version = "1.6.1"
88
description = "A set of libraries and tools for Python Workers"
99
readme = "README.md"
10-
requires-python = ">=3.10"
10+
requires-python = ">=3.11"
1111
classifiers = [
1212
"Programming Language :: Python :: 3",
1313
"License :: OSI Approved :: MIT License",

src/pywrangler/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
write_success,
1010
WRANGLER_COMMAND,
1111
WRANGLER_CREATE_COMMAND,
12+
check_wrangler_config,
1213
)
1314

1415
setup_logging()
@@ -136,7 +137,6 @@ def sync_command(force=False, directly_requested=True):
136137
# This module is imported locally because it searches for pyproject.toml at the top-level.
137138
from .sync import (
138139
check_requirements_txt,
139-
check_wrangler_config,
140140
is_sync_needed,
141141
create_pyodide_venv,
142142
create_workers_venv,

src/pywrangler/sync.py

Lines changed: 63 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,42 @@
44
import shutil
55
import tempfile
66
from contextlib import contextmanager
7-
from datetime import datetime
87
from pathlib import Path
9-
from typing import Literal
108

119
import click
12-
import pyjson5
10+
import tomllib
1311

1412
from .utils import (
1513
run_command,
1614
find_pyproject_toml,
15+
get_python_version,
16+
get_pyodide_index,
17+
get_uv_pyodide_interp_name,
18+
get_project_root,
1719
)
18-
from .metadata import PYTHON_COMPAT_VERSIONS
1920

20-
try:
21-
import tomllib # Standard in Python 3.11+
22-
except ImportError:
23-
import tomli as tomllib # For Python < 3.11
2421

2522
logger = logging.getLogger(__name__)
2623

27-
# Define paths
28-
PYPROJECT_TOML_PATH = find_pyproject_toml()
29-
PROJECT_ROOT = PYPROJECT_TOML_PATH.parent
30-
VENV_WORKERS_PATH = PROJECT_ROOT / ".venv-workers"
31-
VENV_WORKERS_TOKEN = PROJECT_ROOT / ".venv-workers/.synced"
32-
PYODIDE_VENV_PATH = VENV_WORKERS_PATH / "pyodide-venv"
33-
VENDOR_TOKEN = PROJECT_ROOT / "python_modules/.synced"
34-
VENV_REQUIREMENTS_PATH = VENV_WORKERS_PATH / "temp-venv-requirements.txt"
24+
25+
def get_venv_workers_path():
26+
return get_project_root() / ".venv-workers"
27+
28+
29+
def get_venv_workers_token_path():
30+
return get_venv_workers_path() / ".synced"
31+
32+
33+
def get_vendor_token_path():
34+
return get_project_root() / "python_modules/.synced"
35+
36+
37+
def get_pyodide_venv_path():
38+
return get_venv_workers_path() / "pyodide-venv"
3539

3640

3741
def check_requirements_txt():
38-
old_requirements_txt = PROJECT_ROOT / "requirements.txt"
42+
old_requirements_txt = get_project_root() / "requirements.txt"
3943
if old_requirements_txt.is_file():
4044
with open(old_requirements_txt, "r") as f:
4145
requirements = f.read().splitlines()
@@ -54,129 +58,18 @@ def check_requirements_txt():
5458
raise click.exceptions.Exit(code=1)
5559

5660

57-
def check_wrangler_config():
58-
wrangler_jsonc = PROJECT_ROOT / "wrangler.jsonc"
59-
wrangler_toml = PROJECT_ROOT / "wrangler.toml"
60-
if not wrangler_jsonc.is_file() and not wrangler_toml.is_file():
61-
logger.error(
62-
f"{wrangler_jsonc} or {wrangler_toml} not found in {PROJECT_ROOT}."
63-
)
64-
raise click.exceptions.Exit(code=1)
65-
66-
67-
def _parse_wrangler_config() -> dict:
68-
"""
69-
Parse wrangler configuration from either wrangler.toml or wrangler.jsonc.
70-
71-
Returns:
72-
dict: Parsed configuration data
73-
"""
74-
wrangler_toml = PROJECT_ROOT / "wrangler.toml"
75-
wrangler_jsonc = PROJECT_ROOT / "wrangler.jsonc"
76-
77-
if wrangler_toml.is_file():
78-
try:
79-
with open(wrangler_toml, "rb") as f:
80-
return tomllib.load(f)
81-
except tomllib.TOMLDecodeError as e:
82-
logger.error(f"Error parsing {wrangler_toml}: {e}")
83-
raise click.exceptions.Exit(code=1)
84-
85-
if wrangler_jsonc.is_file():
86-
try:
87-
with open(wrangler_jsonc, "r") as f:
88-
content = f.read()
89-
return pyjson5.loads(content)
90-
except (pyjson5.Json5DecoderError, ValueError) as e:
91-
logger.error(f"Error parsing {wrangler_jsonc}: {e}")
92-
raise click.exceptions.Exit(code=1)
93-
94-
return {}
95-
96-
97-
def _get_python_version() -> Literal["3.12", "3.13"]:
98-
"""
99-
Determine Python version from wrangler configuration.
100-
101-
Returns:
102-
Python version string
103-
"""
104-
config = _parse_wrangler_config()
105-
106-
if not config:
107-
logger.error("No wrangler config found")
108-
raise click.exceptions.Exit(code=1)
109-
110-
compat_flags = config.get("compatibility_flags", [])
111-
112-
if "compatibility_date" not in config:
113-
logger.error("No compatibility_date specified in wrangler config")
114-
raise click.exceptions.Exit(code=1)
115-
try:
116-
compat_date = datetime.strptime(config.get("compatibility_date"), "%Y-%m-%d")
117-
except ValueError:
118-
logger.error(
119-
f"Invalid compatibility_date format: {config.get('compatibility_date')}"
120-
)
121-
raise click.exceptions.Exit(code=1)
122-
123-
# Check if python_workers base flag is present (required for Python workers)
124-
if "python_workers" not in compat_flags:
125-
logger.error("`python_workers` compat flag not specified in wrangler config")
126-
raise click.exceptions.Exit(code=1)
127-
128-
# Find the most specific Python version based on compat flags and date
129-
# Sort by version descending to prioritize newer versions
130-
sorted_versions = sorted(
131-
PYTHON_COMPAT_VERSIONS, key=lambda x: x.version, reverse=True
132-
)
133-
134-
for py_version in sorted_versions:
135-
# Check if the specific compat flag is present
136-
if py_version.compat_flag in compat_flags:
137-
return py_version.version
138-
139-
# For versions with compat_date, also check the date requirement
140-
if (
141-
py_version.compat_date
142-
and compat_date
143-
and compat_date >= py_version.compat_date
144-
):
145-
return py_version.version
146-
147-
logger.error("Could not determine Python version from wrangler config")
148-
raise click.exceptions.Exit(code=1)
149-
150-
151-
def _get_uv_pyodide_interp_name():
152-
match _get_python_version():
153-
case "3.12":
154-
v = "3.12.7"
155-
case "3.13":
156-
v = "3.13.2"
157-
return f"cpython-{v}-emscripten-wasm32-musl"
158-
159-
160-
def _get_pyodide_index():
161-
match _get_python_version():
162-
case "3.12":
163-
v = "0.27.7"
164-
case "3.13":
165-
v = "0.28.3"
166-
return "https://index.pyodide.org/" + v
167-
168-
16961
def _get_venv_python_version() -> str | None:
17062
"""
17163
Retrieves the Python version from the virtual environment.
17264
17365
Returns:
17466
The Python version string or None if it cannot be determined.
17567
"""
68+
venv_workers_path = get_venv_workers_path()
17669
venv_python = (
177-
VENV_WORKERS_PATH / "Scripts" / "python.exe"
70+
venv_workers_path / "Scripts" / "python.exe"
17871
if os.name == "nt"
179-
else VENV_WORKERS_PATH / "bin" / "python"
72+
else venv_workers_path / "bin" / "python"
18073
)
18174
if not venv_python.is_file():
18275
return None
@@ -192,37 +85,38 @@ def _get_venv_python_version() -> str | None:
19285

19386
def create_workers_venv():
19487
"""
195-
Creates a virtual environment at `VENV_WORKERS_PATH` if it doesn't exist.
88+
Creates a virtual environment at `venv_workers_path` if it doesn't exist.
19689
"""
197-
wanted_python_version = _get_python_version()
90+
wanted_python_version = get_python_version()
19891
logger.debug(f"Using python version from wrangler config: {wanted_python_version}")
19992

200-
if VENV_WORKERS_PATH.is_dir():
93+
venv_workers_path = get_venv_workers_path()
94+
if venv_workers_path.is_dir():
20195
installed_version = _get_venv_python_version()
20296
if installed_version:
20397
if wanted_python_version in installed_version:
20498
logger.debug(
205-
f"Virtual environment at {VENV_WORKERS_PATH} already exists."
99+
f"Virtual environment at {venv_workers_path} already exists."
206100
)
207101
return
208102

209103
logger.warning(
210-
f"Recreating virtual environment at {VENV_WORKERS_PATH} due to Python version mismatch. "
104+
f"Recreating virtual environment at {venv_workers_path} due to Python version mismatch. "
211105
f"Found {installed_version}, expected {wanted_python_version}"
212106
)
213107
else:
214108
logger.warning(
215-
f"Could not determine python version for {VENV_WORKERS_PATH}, recreating."
109+
f"Could not determine python version for {venv_workers_path}, recreating."
216110
)
217111

218-
shutil.rmtree(VENV_WORKERS_PATH)
112+
shutil.rmtree(venv_workers_path)
219113

220-
logger.debug(f"Creating virtual environment at {VENV_WORKERS_PATH}...")
114+
logger.debug(f"Creating virtual environment at {venv_workers_path}...")
221115
run_command(
222116
[
223117
"uv",
224118
"venv",
225-
str(VENV_WORKERS_PATH),
119+
str(venv_workers_path),
226120
"--python",
227121
f"python{wanted_python_version}",
228122
]
@@ -288,24 +182,26 @@ def check_wrangler_version():
288182

289183

290184
def create_pyodide_venv():
291-
if PYODIDE_VENV_PATH.is_dir():
185+
pyodide_venv_path = get_pyodide_venv_path()
186+
if pyodide_venv_path.is_dir():
292187
logger.debug(
293-
f"Pyodide virtual environment at {PYODIDE_VENV_PATH} already exists."
188+
f"Pyodide virtual environment at {pyodide_venv_path} already exists."
294189
)
295190
return
296191

297192
check_uv_version()
298-
logger.debug(f"Creating Pyodide virtual environment at {PYODIDE_VENV_PATH}...")
299-
PYODIDE_VENV_PATH.parent.mkdir(parents=True, exist_ok=True)
300-
interp_name = _get_uv_pyodide_interp_name()
193+
logger.debug(f"Creating Pyodide virtual environment at {pyodide_venv_path}...")
194+
pyodide_venv_path.parent.mkdir(parents=True, exist_ok=True)
195+
interp_name = get_uv_pyodide_interp_name()
301196
run_command(["uv", "python", "install", interp_name])
302-
run_command(["uv", "venv", PYODIDE_VENV_PATH, "--python", interp_name])
197+
run_command(["uv", "venv", pyodide_venv_path, "--python", interp_name])
303198

304199

305200
def parse_requirements() -> list[str]:
306-
logger.debug(f"Reading dependencies from {PYPROJECT_TOML_PATH}...")
201+
pyproject_toml_path = find_pyproject_toml()
202+
logger.debug(f"Reading dependencies from {pyproject_toml_path}...")
307203
try:
308-
with open(PYPROJECT_TOML_PATH, "rb") as f:
204+
with open(pyproject_toml_path, "rb") as f:
309205
pyproject_data = tomllib.load(f)
310206

311207
# Extract dependencies from [project.dependencies]
@@ -314,7 +210,7 @@ def parse_requirements() -> list[str]:
314210
logger.info(f"Found {len(dependencies)} dependencies.")
315211
return dependencies
316212
except tomllib.TOMLDecodeError as e:
317-
logger.error(f"Error parsing {PYPROJECT_TOML_PATH}: {str(e)}")
213+
logger.error(f"Error parsing {pyproject_toml_path}: {str(e)}")
318214
raise click.exceptions.Exit(code=1)
319215

320216

@@ -328,7 +224,7 @@ def temp_requirements_file(requirements: list[str]):
328224

329225

330226
def _install_requirements_to_vendor(requirements: list[str]):
331-
vendor_path = PROJECT_ROOT / "python_modules"
227+
vendor_path = get_project_root() / "python_modules"
332228
logger.debug(f"Using vendor path: {vendor_path}")
333229

334230
if len(requirements) == 0:
@@ -339,7 +235,7 @@ def _install_requirements_to_vendor(requirements: list[str]):
339235

340236
# Install packages into vendor directory
341237
vendor_path.mkdir(parents=True, exist_ok=True)
342-
relative_vendor_path = vendor_path.relative_to(PROJECT_ROOT)
238+
relative_vendor_path = vendor_path.relative_to(get_project_root())
343239
logger.info(
344240
f"Installing packages into [bold]{relative_vendor_path}[/bold]...",
345241
extra={"markup": True},
@@ -354,21 +250,21 @@ def _install_requirements_to_vendor(requirements: list[str]):
354250
"-r",
355251
requirements_file,
356252
"--extra-index-url",
357-
_get_pyodide_index(),
253+
get_pyodide_index(),
358254
"--index-strategy",
359255
"unsafe-best-match",
360256
],
361-
env=os.environ | {"VIRTUAL_ENV": PYODIDE_VENV_PATH},
257+
env=os.environ | {"VIRTUAL_ENV": get_pyodide_venv_path()},
362258
)
363-
pyv = _get_python_version()
259+
pyv = get_python_version()
364260
shutil.rmtree(vendor_path)
365261
shutil.copytree(
366-
PYODIDE_VENV_PATH / f"lib/python{pyv}/site-packages", vendor_path
262+
get_pyodide_venv_path() / f"lib/python{pyv}/site-packages", vendor_path
367263
)
368264

369265
# Create a pyvenv.cfg file in python_modules to mark it as a virtual environment
370266
(vendor_path / "pyvenv.cfg").touch()
371-
VENDOR_TOKEN.touch()
267+
get_vendor_token_path().touch()
372268

373269
logger.info(
374270
f"Packages installed in [bold]{relative_vendor_path}[/bold].",
@@ -378,7 +274,9 @@ def _install_requirements_to_vendor(requirements: list[str]):
378274

379275
def _install_requirements_to_venv(requirements: list[str]):
380276
# Create a requirements file for .venv-workers that includes pyodide-py
381-
relative_venv_workers_path = VENV_WORKERS_PATH.relative_to(PROJECT_ROOT)
277+
venv_workers_path = get_venv_workers_path()
278+
project_root = get_project_root()
279+
relative_venv_workers_path = venv_workers_path.relative_to(project_root)
382280
requirements = requirements.copy()
383281
requirements.append("pyodide-py")
384282

@@ -395,9 +293,10 @@ def _install_requirements_to_venv(requirements: list[str]):
395293
"-r",
396294
requirements_file,
397295
],
398-
env=os.environ | {"VIRTUAL_ENV": VENV_WORKERS_PATH},
296+
env=os.environ | {"VIRTUAL_ENV": venv_workers_path},
399297
)
400-
VENV_WORKERS_TOKEN.touch()
298+
299+
get_venv_workers_token_path().touch()
401300
logger.info(
402301
f"Packages installed in [bold]{relative_venv_workers_path}[/bold].",
403302
extra={"markup": True},
@@ -422,12 +321,12 @@ def is_sync_needed():
422321
Returns:
423322
bool: True if sync is needed, False otherwise
424323
"""
425-
426-
if not PYPROJECT_TOML_PATH.is_file():
324+
pyproject_toml_path = find_pyproject_toml()
325+
if not pyproject_toml_path.is_file():
427326
# If pyproject.toml doesn't exist, we need to abort anyway
428327
return True
429328

430-
pyproject_mtime = PYPROJECT_TOML_PATH.stat().st_mtime
431-
return _is_out_of_date(VENDOR_TOKEN, pyproject_mtime) or _is_out_of_date(
432-
VENV_WORKERS_TOKEN, pyproject_mtime
329+
pyproject_mtime = pyproject_toml_path.stat().st_mtime
330+
return _is_out_of_date(get_vendor_token_path(), pyproject_mtime) or _is_out_of_date(
331+
get_venv_workers_token_path(), pyproject_mtime
433332
)

0 commit comments

Comments
 (0)