workers-py 1.6.1__py3-none-any.whl → 1.6.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pywrangler/cli.py +17 -54
- pywrangler/metadata.py +1 -1
- pywrangler/py.typed +0 -0
- pywrangler/sync.py +110 -244
- pywrangler/types.py +5 -4
- pywrangler/utils.py +214 -7
- {workers_py-1.6.1.dist-info → workers_py-1.6.2.dist-info}/METADATA +3 -2
- workers_py-1.6.2.dist-info/RECORD +12 -0
- workers_py-1.6.1.dist-info/RECORD +0 -11
- {workers_py-1.6.1.dist-info → workers_py-1.6.2.dist-info}/WHEEL +0 -0
- {workers_py-1.6.1.dist-info → workers_py-1.6.2.dist-info}/entry_points.txt +0 -0
pywrangler/cli.py
CHANGED
|
@@ -2,13 +2,17 @@ import logging
|
|
|
2
2
|
import subprocess
|
|
3
3
|
import sys
|
|
4
4
|
import textwrap
|
|
5
|
+
from typing import Never
|
|
6
|
+
|
|
5
7
|
import click
|
|
6
8
|
|
|
9
|
+
from .sync import sync
|
|
7
10
|
from .utils import (
|
|
8
|
-
setup_logging,
|
|
9
|
-
write_success,
|
|
10
11
|
WRANGLER_COMMAND,
|
|
11
12
|
WRANGLER_CREATE_COMMAND,
|
|
13
|
+
check_wrangler_version,
|
|
14
|
+
setup_logging,
|
|
15
|
+
write_success,
|
|
12
16
|
)
|
|
13
17
|
|
|
14
18
|
setup_logging()
|
|
@@ -16,7 +20,7 @@ logger = logging.getLogger("pywrangler")
|
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
class ProxyToWranglerGroup(click.Group):
|
|
19
|
-
def get_help(self, ctx):
|
|
23
|
+
def get_help(self, ctx: click.Context) -> str:
|
|
20
24
|
"""Override to add custom help content."""
|
|
21
25
|
# Get the default help text
|
|
22
26
|
help_text = super().get_help(ctx)
|
|
@@ -28,6 +32,7 @@ class ProxyToWranglerGroup(click.Group):
|
|
|
28
32
|
capture_output=True,
|
|
29
33
|
text=True,
|
|
30
34
|
timeout=10,
|
|
35
|
+
check=False,
|
|
31
36
|
)
|
|
32
37
|
if result.returncode == 0:
|
|
33
38
|
wrangler_help = result.stdout
|
|
@@ -47,7 +52,7 @@ class ProxyToWranglerGroup(click.Group):
|
|
|
47
52
|
|
|
48
53
|
return help_text
|
|
49
54
|
|
|
50
|
-
def get_command(self, ctx, cmd_name):
|
|
55
|
+
def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command:
|
|
51
56
|
command = super().get_command(ctx, cmd_name)
|
|
52
57
|
|
|
53
58
|
if command is None:
|
|
@@ -58,11 +63,9 @@ class ProxyToWranglerGroup(click.Group):
|
|
|
58
63
|
remaining_args = []
|
|
59
64
|
|
|
60
65
|
if cmd_name in ["dev", "publish", "deploy", "versions"]:
|
|
61
|
-
|
|
66
|
+
sync(force=False)
|
|
62
67
|
|
|
63
68
|
if cmd_name == "dev":
|
|
64
|
-
from .sync import check_wrangler_version
|
|
65
|
-
|
|
66
69
|
check_wrangler_version()
|
|
67
70
|
|
|
68
71
|
if cmd_name == "init":
|
|
@@ -78,7 +81,7 @@ class ProxyToWranglerGroup(click.Group):
|
|
|
78
81
|
return command
|
|
79
82
|
|
|
80
83
|
|
|
81
|
-
def get_version():
|
|
84
|
+
def get_version() -> str:
|
|
82
85
|
"""Get the version of pywrangler."""
|
|
83
86
|
try:
|
|
84
87
|
from importlib.metadata import version
|
|
@@ -91,8 +94,7 @@ def get_version():
|
|
|
91
94
|
@click.group(cls=ProxyToWranglerGroup)
|
|
92
95
|
@click.option("--debug", is_flag=True, help="Enable debug logging")
|
|
93
96
|
@click.version_option(version=get_version(), prog_name="pywrangler")
|
|
94
|
-
|
|
95
|
-
def app(ctx, debug=False):
|
|
97
|
+
def app(debug: bool = False) -> None:
|
|
96
98
|
"""
|
|
97
99
|
A CLI tool for Cloudflare Workers.
|
|
98
100
|
Use 'sync' command for Python package setup.
|
|
@@ -118,7 +120,7 @@ def app(ctx, debug=False):
|
|
|
118
120
|
type=click.Path(exists=True, dir_okay=False, readable=True),
|
|
119
121
|
help="Path to Wrangler configuration file",
|
|
120
122
|
)
|
|
121
|
-
def types_command(outdir
|
|
123
|
+
def types_command(outdir: str | None, config: str | None) -> Never:
|
|
122
124
|
from .types import wrangler_types
|
|
123
125
|
|
|
124
126
|
wrangler_types(outdir, config)
|
|
@@ -127,56 +129,17 @@ def types_command(outdir=None, config=None):
|
|
|
127
129
|
|
|
128
130
|
@app.command("sync")
|
|
129
131
|
@click.option("--force", is_flag=True, help="Force sync even if no changes detected")
|
|
130
|
-
def sync_command(force=False
|
|
132
|
+
def sync_command(force: bool = False) -> None:
|
|
131
133
|
"""
|
|
132
134
|
Installs Python packages from pyproject.toml into src/vendor.
|
|
133
135
|
|
|
134
136
|
Also creates a virtual env for Workers that you can use for testing.
|
|
135
137
|
"""
|
|
136
|
-
|
|
137
|
-
from .sync import (
|
|
138
|
-
check_requirements_txt,
|
|
139
|
-
check_wrangler_config,
|
|
140
|
-
is_sync_needed,
|
|
141
|
-
create_pyodide_venv,
|
|
142
|
-
create_workers_venv,
|
|
143
|
-
parse_requirements,
|
|
144
|
-
install_requirements,
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
# Check if requirements.txt does not exist.
|
|
148
|
-
check_requirements_txt()
|
|
149
|
-
|
|
150
|
-
# Check if sync is needed based on file timestamps
|
|
151
|
-
sync_needed = force or is_sync_needed()
|
|
152
|
-
if not sync_needed:
|
|
153
|
-
if directly_requested:
|
|
154
|
-
logger.warning(
|
|
155
|
-
"pyproject.toml hasn't changed since last sync, use --force to ignore timestamp check"
|
|
156
|
-
)
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
# Check to make sure a wrangler config file exists.
|
|
160
|
-
check_wrangler_config()
|
|
161
|
-
|
|
162
|
-
# Create .venv-workers if it doesn't exist
|
|
163
|
-
create_workers_venv()
|
|
164
|
-
|
|
165
|
-
# Set up Pyodide virtual env
|
|
166
|
-
create_pyodide_venv()
|
|
167
|
-
|
|
168
|
-
# Generate requirements.txt from pyproject.toml by directly parsing the TOML file then install into vendor folder.
|
|
169
|
-
requirements = parse_requirements()
|
|
170
|
-
if not requirements:
|
|
171
|
-
logger.warning(
|
|
172
|
-
"No dependencies found in [project.dependencies] section of pyproject.toml."
|
|
173
|
-
)
|
|
174
|
-
install_requirements(requirements)
|
|
175
|
-
|
|
138
|
+
sync(force, directly_requested=True)
|
|
176
139
|
write_success("Sync process completed successfully.")
|
|
177
140
|
|
|
178
141
|
|
|
179
|
-
def _proxy_to_wrangler(command_name, args_list):
|
|
142
|
+
def _proxy_to_wrangler(command_name: str, args_list: list[str]) -> Never:
|
|
180
143
|
command_to_run = WRANGLER_COMMAND + [command_name] + args_list
|
|
181
144
|
logger.info(f"Passing command to npx wrangler: {' '.join(command_to_run)}")
|
|
182
145
|
try:
|
|
@@ -189,7 +152,7 @@ def _proxy_to_wrangler(command_name, args_list):
|
|
|
189
152
|
click.get_current_context().exit(1)
|
|
190
153
|
|
|
191
154
|
|
|
192
|
-
def _proxy_to_create_cloudflare(args_list):
|
|
155
|
+
def _proxy_to_create_cloudflare(args_list: list[str]) -> Never:
|
|
193
156
|
command_to_run = WRANGLER_CREATE_COMMAND + args_list
|
|
194
157
|
logger.info(f"Passing command to npx create-cloudflare: {' '.join(command_to_run)}")
|
|
195
158
|
try:
|
pywrangler/metadata.py
CHANGED
pywrangler/py.typed
ADDED
|
File without changes
|
pywrangler/sync.py
CHANGED
|
@@ -1,43 +1,48 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
-
import re
|
|
4
3
|
import shutil
|
|
5
4
|
import tempfile
|
|
5
|
+
from collections.abc import Iterator
|
|
6
6
|
from contextlib import contextmanager
|
|
7
|
-
from datetime import datetime
|
|
8
7
|
from pathlib import Path
|
|
9
|
-
from typing import Literal
|
|
10
8
|
|
|
11
9
|
import click
|
|
12
|
-
import pyjson5
|
|
13
10
|
|
|
14
11
|
from .utils import (
|
|
15
|
-
|
|
12
|
+
check_uv_version,
|
|
13
|
+
check_wrangler_config,
|
|
16
14
|
find_pyproject_toml,
|
|
15
|
+
get_project_root,
|
|
16
|
+
get_pyodide_index,
|
|
17
|
+
get_python_version,
|
|
18
|
+
get_uv_pyodide_interp_name,
|
|
19
|
+
read_pyproject_toml,
|
|
20
|
+
run_command,
|
|
17
21
|
)
|
|
18
|
-
from .metadata import PYTHON_COMPAT_VERSIONS
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
import tomllib # Standard in Python 3.11+
|
|
22
|
-
except ImportError:
|
|
23
|
-
import tomli as tomllib # For Python < 3.11
|
|
24
22
|
|
|
25
23
|
logger = logging.getLogger(__name__)
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
VENV_REQUIREMENTS_PATH = VENV_WORKERS_PATH / "temp-venv-requirements.txt"
|
|
25
|
+
|
|
26
|
+
def get_venv_workers_path() -> Path:
|
|
27
|
+
return get_project_root() / ".venv-workers"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_venv_workers_token_path() -> Path:
|
|
31
|
+
return get_venv_workers_path() / ".synced"
|
|
35
32
|
|
|
36
33
|
|
|
37
|
-
def
|
|
38
|
-
|
|
34
|
+
def get_vendor_token_path() -> Path:
|
|
35
|
+
return get_project_root() / "python_modules/.synced"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_pyodide_venv_path() -> Path:
|
|
39
|
+
return get_venv_workers_path() / "pyodide-venv"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def check_requirements_txt() -> None:
|
|
43
|
+
old_requirements_txt = get_project_root() / "requirements.txt"
|
|
39
44
|
if old_requirements_txt.is_file():
|
|
40
|
-
with open(old_requirements_txt
|
|
45
|
+
with open(old_requirements_txt) as f:
|
|
41
46
|
requirements = f.read().splitlines()
|
|
42
47
|
logger.warning(
|
|
43
48
|
"Specifying Python Packages in requirements.txt is no longer supported, please use pyproject.toml instead.\n"
|
|
@@ -54,118 +59,6 @@ def check_requirements_txt():
|
|
|
54
59
|
raise click.exceptions.Exit(code=1)
|
|
55
60
|
|
|
56
61
|
|
|
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
|
-
|
|
169
62
|
def _get_venv_python_version() -> str | None:
|
|
170
63
|
"""
|
|
171
64
|
Retrieves the Python version from the virtual environment.
|
|
@@ -173,10 +66,11 @@ def _get_venv_python_version() -> str | None:
|
|
|
173
66
|
Returns:
|
|
174
67
|
The Python version string or None if it cannot be determined.
|
|
175
68
|
"""
|
|
69
|
+
venv_workers_path = get_venv_workers_path()
|
|
176
70
|
venv_python = (
|
|
177
|
-
|
|
71
|
+
venv_workers_path / "Scripts" / "python.exe"
|
|
178
72
|
if os.name == "nt"
|
|
179
|
-
else
|
|
73
|
+
else venv_workers_path / "bin" / "python"
|
|
180
74
|
)
|
|
181
75
|
if not venv_python.is_file():
|
|
182
76
|
return None
|
|
@@ -190,136 +84,74 @@ def _get_venv_python_version() -> str | None:
|
|
|
190
84
|
return result.stdout.strip()
|
|
191
85
|
|
|
192
86
|
|
|
193
|
-
def create_workers_venv():
|
|
87
|
+
def create_workers_venv() -> None:
|
|
194
88
|
"""
|
|
195
|
-
Creates a virtual environment at `
|
|
89
|
+
Creates a virtual environment at `venv_workers_path` if it doesn't exist.
|
|
196
90
|
"""
|
|
197
|
-
wanted_python_version =
|
|
91
|
+
wanted_python_version = get_python_version()
|
|
198
92
|
logger.debug(f"Using python version from wrangler config: {wanted_python_version}")
|
|
199
93
|
|
|
200
|
-
|
|
94
|
+
venv_workers_path = get_venv_workers_path()
|
|
95
|
+
if venv_workers_path.is_dir():
|
|
201
96
|
installed_version = _get_venv_python_version()
|
|
202
97
|
if installed_version:
|
|
203
98
|
if wanted_python_version in installed_version:
|
|
204
99
|
logger.debug(
|
|
205
|
-
f"Virtual environment at {
|
|
100
|
+
f"Virtual environment at {venv_workers_path} already exists."
|
|
206
101
|
)
|
|
207
102
|
return
|
|
208
103
|
|
|
209
104
|
logger.warning(
|
|
210
|
-
f"Recreating virtual environment at {
|
|
105
|
+
f"Recreating virtual environment at {venv_workers_path} due to Python version mismatch. "
|
|
211
106
|
f"Found {installed_version}, expected {wanted_python_version}"
|
|
212
107
|
)
|
|
213
108
|
else:
|
|
214
109
|
logger.warning(
|
|
215
|
-
f"Could not determine python version for {
|
|
110
|
+
f"Could not determine python version for {venv_workers_path}, recreating."
|
|
216
111
|
)
|
|
217
112
|
|
|
218
|
-
shutil.rmtree(
|
|
113
|
+
shutil.rmtree(venv_workers_path)
|
|
219
114
|
|
|
220
|
-
logger.debug(f"Creating virtual environment at {
|
|
115
|
+
logger.debug(f"Creating virtual environment at {venv_workers_path}...")
|
|
221
116
|
run_command(
|
|
222
117
|
[
|
|
223
118
|
"uv",
|
|
224
119
|
"venv",
|
|
225
|
-
str(
|
|
120
|
+
str(venv_workers_path),
|
|
226
121
|
"--python",
|
|
227
122
|
f"python{wanted_python_version}",
|
|
228
123
|
]
|
|
229
124
|
)
|
|
230
125
|
|
|
231
126
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
def check_uv_version():
|
|
237
|
-
res = run_command(["uv", "--version"], capture_output=True)
|
|
238
|
-
ver_str = res.stdout.split(" ")[1]
|
|
239
|
-
ver = tuple(int(x) for x in ver_str.split("."))
|
|
240
|
-
if ver >= MIN_UV_VERSION:
|
|
241
|
-
return
|
|
242
|
-
min_version_str = ".".join(str(x) for x in MIN_UV_VERSION)
|
|
243
|
-
logger.error(f"uv version at least {min_version_str} required, have {ver_str}.")
|
|
244
|
-
logger.error("Update uv with `uv self update`.")
|
|
245
|
-
raise click.exceptions.Exit(code=1)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def check_wrangler_version():
|
|
249
|
-
"""
|
|
250
|
-
Check that the installed wrangler version is at least 4.42.1.
|
|
251
|
-
|
|
252
|
-
Raises:
|
|
253
|
-
click.exceptions.Exit: If wrangler is not installed or version is too old.
|
|
254
|
-
"""
|
|
255
|
-
result = run_command(
|
|
256
|
-
["npx", "--yes", "wrangler", "--version"], capture_output=True, check=False
|
|
257
|
-
)
|
|
258
|
-
if result.returncode != 0:
|
|
259
|
-
logger.error("Failed to get wrangler version. Is wrangler installed?")
|
|
260
|
-
logger.error("Install wrangler with: npm install wrangler@latest")
|
|
261
|
-
raise click.exceptions.Exit(code=1)
|
|
262
|
-
|
|
263
|
-
# Parse version from output like "wrangler 4.42.1" or " ⛅️ wrangler 4.42.1"
|
|
264
|
-
version_line = result.stdout.strip()
|
|
265
|
-
# Extract version number using regex
|
|
266
|
-
version_match = re.search(r"(\d+)\.(\d+)\.(\d+)", version_line)
|
|
267
|
-
|
|
268
|
-
if not version_match:
|
|
269
|
-
logger.error(f"Could not parse wrangler version from: {version_line}")
|
|
270
|
-
logger.error("Install wrangler with: npm install wrangler@latest")
|
|
271
|
-
raise click.exceptions.Exit(code=1)
|
|
272
|
-
|
|
273
|
-
major, minor, patch = map(int, version_match.groups())
|
|
274
|
-
current_version = (major, minor, patch)
|
|
275
|
-
|
|
276
|
-
if current_version < MIN_WRANGLER_VERSION:
|
|
277
|
-
min_version_str = ".".join(str(x) for x in MIN_WRANGLER_VERSION)
|
|
278
|
-
current_version_str = ".".join(str(x) for x in current_version)
|
|
279
|
-
logger.error(
|
|
280
|
-
f"wrangler version at least {min_version_str} required, have {current_version_str}."
|
|
281
|
-
)
|
|
282
|
-
logger.error("Update wrangler with: npm install wrangler@latest")
|
|
283
|
-
raise click.exceptions.Exit(code=1)
|
|
284
|
-
|
|
285
|
-
logger.debug(
|
|
286
|
-
f"wrangler version {'.'.join(str(x) for x in current_version)} is sufficient"
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
def create_pyodide_venv():
|
|
291
|
-
if PYODIDE_VENV_PATH.is_dir():
|
|
127
|
+
def create_pyodide_venv() -> None:
|
|
128
|
+
pyodide_venv_path = get_pyodide_venv_path()
|
|
129
|
+
if pyodide_venv_path.is_dir():
|
|
292
130
|
logger.debug(
|
|
293
|
-
f"Pyodide virtual environment at {
|
|
131
|
+
f"Pyodide virtual environment at {pyodide_venv_path} already exists."
|
|
294
132
|
)
|
|
295
133
|
return
|
|
296
134
|
|
|
297
135
|
check_uv_version()
|
|
298
|
-
logger.debug(f"Creating Pyodide virtual environment at {
|
|
299
|
-
|
|
300
|
-
interp_name =
|
|
136
|
+
logger.debug(f"Creating Pyodide virtual environment at {pyodide_venv_path}...")
|
|
137
|
+
pyodide_venv_path.parent.mkdir(parents=True, exist_ok=True)
|
|
138
|
+
interp_name = get_uv_pyodide_interp_name()
|
|
301
139
|
run_command(["uv", "python", "install", interp_name])
|
|
302
|
-
run_command(["uv", "venv",
|
|
140
|
+
run_command(["uv", "venv", pyodide_venv_path, "--python", interp_name])
|
|
303
141
|
|
|
304
142
|
|
|
305
143
|
def parse_requirements() -> list[str]:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
logger.info(f"Found {len(dependencies)} dependencies.")
|
|
315
|
-
return dependencies
|
|
316
|
-
except tomllib.TOMLDecodeError as e:
|
|
317
|
-
logger.error(f"Error parsing {PYPROJECT_TOML_PATH}: {str(e)}")
|
|
318
|
-
raise click.exceptions.Exit(code=1)
|
|
144
|
+
pyproject_data = read_pyproject_toml()
|
|
145
|
+
|
|
146
|
+
# Extract dependencies from [project.dependencies]
|
|
147
|
+
dependencies = pyproject_data.get("project", {}).get("dependencies", [])
|
|
148
|
+
|
|
149
|
+
logger.info(f"Found {len(dependencies)} dependencies.")
|
|
150
|
+
return dependencies
|
|
319
151
|
|
|
320
152
|
|
|
321
153
|
@contextmanager
|
|
322
|
-
def temp_requirements_file(requirements: list[str]):
|
|
154
|
+
def temp_requirements_file(requirements: list[str]) -> Iterator[str]:
|
|
323
155
|
# Write dependencies to a requirements.txt-style temp file.
|
|
324
156
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt") as temp_file:
|
|
325
157
|
temp_file.write("\n".join(requirements))
|
|
@@ -327,8 +159,8 @@ def temp_requirements_file(requirements: list[str]):
|
|
|
327
159
|
yield temp_file.name
|
|
328
160
|
|
|
329
161
|
|
|
330
|
-
def _install_requirements_to_vendor(requirements: list[str]):
|
|
331
|
-
vendor_path =
|
|
162
|
+
def _install_requirements_to_vendor(requirements: list[str]) -> None:
|
|
163
|
+
vendor_path = get_project_root() / "python_modules"
|
|
332
164
|
logger.debug(f"Using vendor path: {vendor_path}")
|
|
333
165
|
|
|
334
166
|
if len(requirements) == 0:
|
|
@@ -339,7 +171,7 @@ def _install_requirements_to_vendor(requirements: list[str]):
|
|
|
339
171
|
|
|
340
172
|
# Install packages into vendor directory
|
|
341
173
|
vendor_path.mkdir(parents=True, exist_ok=True)
|
|
342
|
-
relative_vendor_path = vendor_path.relative_to(
|
|
174
|
+
relative_vendor_path = vendor_path.relative_to(get_project_root())
|
|
343
175
|
logger.info(
|
|
344
176
|
f"Installing packages into [bold]{relative_vendor_path}[/bold]...",
|
|
345
177
|
extra={"markup": True},
|
|
@@ -354,21 +186,21 @@ def _install_requirements_to_vendor(requirements: list[str]):
|
|
|
354
186
|
"-r",
|
|
355
187
|
requirements_file,
|
|
356
188
|
"--extra-index-url",
|
|
357
|
-
|
|
189
|
+
get_pyodide_index(),
|
|
358
190
|
"--index-strategy",
|
|
359
191
|
"unsafe-best-match",
|
|
360
192
|
],
|
|
361
|
-
env=os.environ | {"VIRTUAL_ENV":
|
|
193
|
+
env=os.environ | {"VIRTUAL_ENV": get_pyodide_venv_path()},
|
|
362
194
|
)
|
|
363
|
-
pyv =
|
|
195
|
+
pyv = get_python_version()
|
|
364
196
|
shutil.rmtree(vendor_path)
|
|
365
197
|
shutil.copytree(
|
|
366
|
-
|
|
198
|
+
get_pyodide_venv_path() / f"lib/python{pyv}/site-packages", vendor_path
|
|
367
199
|
)
|
|
368
200
|
|
|
369
201
|
# Create a pyvenv.cfg file in python_modules to mark it as a virtual environment
|
|
370
202
|
(vendor_path / "pyvenv.cfg").touch()
|
|
371
|
-
|
|
203
|
+
get_vendor_token_path().touch()
|
|
372
204
|
|
|
373
205
|
logger.info(
|
|
374
206
|
f"Packages installed in [bold]{relative_vendor_path}[/bold].",
|
|
@@ -376,9 +208,11 @@ def _install_requirements_to_vendor(requirements: list[str]):
|
|
|
376
208
|
)
|
|
377
209
|
|
|
378
210
|
|
|
379
|
-
def _install_requirements_to_venv(requirements: list[str]):
|
|
211
|
+
def _install_requirements_to_venv(requirements: list[str]) -> None:
|
|
380
212
|
# Create a requirements file for .venv-workers that includes pyodide-py
|
|
381
|
-
|
|
213
|
+
venv_workers_path = get_venv_workers_path()
|
|
214
|
+
project_root = get_project_root()
|
|
215
|
+
relative_venv_workers_path = venv_workers_path.relative_to(project_root)
|
|
382
216
|
requirements = requirements.copy()
|
|
383
217
|
requirements.append("pyodide-py")
|
|
384
218
|
|
|
@@ -395,16 +229,17 @@ def _install_requirements_to_venv(requirements: list[str]):
|
|
|
395
229
|
"-r",
|
|
396
230
|
requirements_file,
|
|
397
231
|
],
|
|
398
|
-
env=os.environ | {"VIRTUAL_ENV":
|
|
232
|
+
env=os.environ | {"VIRTUAL_ENV": venv_workers_path},
|
|
399
233
|
)
|
|
400
|
-
|
|
234
|
+
|
|
235
|
+
get_venv_workers_token_path().touch()
|
|
401
236
|
logger.info(
|
|
402
237
|
f"Packages installed in [bold]{relative_venv_workers_path}[/bold].",
|
|
403
238
|
extra={"markup": True},
|
|
404
239
|
)
|
|
405
240
|
|
|
406
241
|
|
|
407
|
-
def install_requirements(requirements: list[str]):
|
|
242
|
+
def install_requirements(requirements: list[str]) -> None:
|
|
408
243
|
_install_requirements_to_vendor(requirements)
|
|
409
244
|
_install_requirements_to_venv(requirements)
|
|
410
245
|
|
|
@@ -415,19 +250,50 @@ def _is_out_of_date(token: Path, time: float) -> bool:
|
|
|
415
250
|
return time > token.stat().st_mtime
|
|
416
251
|
|
|
417
252
|
|
|
418
|
-
def is_sync_needed():
|
|
253
|
+
def is_sync_needed() -> bool:
|
|
419
254
|
"""
|
|
420
255
|
Checks if pyproject.toml has been modified since the last sync.
|
|
421
256
|
|
|
422
257
|
Returns:
|
|
423
258
|
bool: True if sync is needed, False otherwise
|
|
424
259
|
"""
|
|
425
|
-
|
|
426
|
-
if not
|
|
260
|
+
pyproject_toml_path = find_pyproject_toml()
|
|
261
|
+
if not pyproject_toml_path.is_file():
|
|
427
262
|
# If pyproject.toml doesn't exist, we need to abort anyway
|
|
428
263
|
return True
|
|
429
264
|
|
|
430
|
-
pyproject_mtime =
|
|
431
|
-
return _is_out_of_date(
|
|
432
|
-
|
|
265
|
+
pyproject_mtime = pyproject_toml_path.stat().st_mtime
|
|
266
|
+
return _is_out_of_date(get_vendor_token_path(), pyproject_mtime) or _is_out_of_date(
|
|
267
|
+
get_venv_workers_token_path(), pyproject_mtime
|
|
433
268
|
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def sync(force: bool = False, directly_requested: bool = False) -> None:
|
|
272
|
+
# Check if requirements.txt does not exist.
|
|
273
|
+
check_requirements_txt()
|
|
274
|
+
|
|
275
|
+
# Check if sync is needed based on file timestamps
|
|
276
|
+
sync_needed = force or is_sync_needed()
|
|
277
|
+
if not sync_needed:
|
|
278
|
+
if directly_requested:
|
|
279
|
+
logger.warning(
|
|
280
|
+
"pyproject.toml hasn't changed since last sync, use --force to ignore timestamp check"
|
|
281
|
+
)
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
# Check to make sure a wrangler config file exists.
|
|
285
|
+
check_wrangler_config()
|
|
286
|
+
|
|
287
|
+
# Create .venv-workers if it doesn't exist
|
|
288
|
+
create_workers_venv()
|
|
289
|
+
|
|
290
|
+
# Set up Pyodide virtual env
|
|
291
|
+
create_pyodide_venv()
|
|
292
|
+
|
|
293
|
+
# Generate requirements.txt from pyproject.toml by directly parsing the TOML file then install into vendor folder.
|
|
294
|
+
requirements = parse_requirements()
|
|
295
|
+
if not requirements:
|
|
296
|
+
logger.warning(
|
|
297
|
+
"No dependencies found in [project.dependencies] section of pyproject.toml."
|
|
298
|
+
)
|
|
299
|
+
install_requirements(requirements)
|
pywrangler/types.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from .utils import WRANGLER_COMMAND, run_command
|
|
2
|
-
from tempfile import TemporaryDirectory
|
|
3
|
-
from pathlib import Path
|
|
4
1
|
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from tempfile import TemporaryDirectory
|
|
4
|
+
|
|
5
|
+
from .utils import WRANGLER_COMMAND, run_command
|
|
5
6
|
|
|
6
7
|
logger = logging.getLogger(__name__)
|
|
7
8
|
|
|
@@ -26,7 +27,7 @@ PACKAGE_JSON = """
|
|
|
26
27
|
"""
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
def wrangler_types(outdir_arg: str | None, config: str | None, /):
|
|
30
|
+
def wrangler_types(outdir_arg: str | None, config: str | None, /) -> None:
|
|
30
31
|
args = ["types"]
|
|
31
32
|
if config:
|
|
32
33
|
args += ["--config", config]
|
pywrangler/utils.py
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import re
|
|
2
3
|
import subprocess
|
|
4
|
+
import tomllib
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from functools import cache
|
|
3
7
|
from pathlib import Path
|
|
8
|
+
from typing import Literal, TypedDict, cast
|
|
4
9
|
|
|
5
10
|
import click
|
|
6
|
-
|
|
11
|
+
import pyjson5
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.logging import RichHandler
|
|
7
14
|
from rich.theme import Theme
|
|
8
15
|
|
|
16
|
+
from .metadata import PYTHON_COMPAT_VERSIONS
|
|
17
|
+
|
|
9
18
|
WRANGLER_COMMAND = ["npx", "--yes", "wrangler"]
|
|
10
19
|
WRANGLER_CREATE_COMMAND = ["npx", "--yes", "create-cloudflare"]
|
|
11
20
|
|
|
@@ -16,7 +25,7 @@ RUNNING_LEVEL = 15
|
|
|
16
25
|
OUTPUT_LEVEL = 16
|
|
17
26
|
|
|
18
27
|
|
|
19
|
-
def setup_logging():
|
|
28
|
+
def setup_logging() -> None:
|
|
20
29
|
console = Console(
|
|
21
30
|
theme=Theme(
|
|
22
31
|
{
|
|
@@ -44,17 +53,17 @@ def setup_logging():
|
|
|
44
53
|
logging.addLevelName(OUTPUT_LEVEL, "OUTPUT")
|
|
45
54
|
|
|
46
55
|
|
|
47
|
-
def write_success(msg):
|
|
56
|
+
def write_success(msg: str) -> None:
|
|
48
57
|
logging.log(SUCCESS_LEVEL, msg)
|
|
49
58
|
|
|
50
59
|
|
|
51
60
|
def run_command(
|
|
52
61
|
command: list[str | Path],
|
|
53
62
|
cwd: Path | None = None,
|
|
54
|
-
env: dict | None = None,
|
|
63
|
+
env: dict[str, str | Path] | None = None,
|
|
55
64
|
check: bool = True,
|
|
56
65
|
capture_output: bool = False,
|
|
57
|
-
):
|
|
66
|
+
) -> subprocess.CompletedProcess[str]:
|
|
58
67
|
"""
|
|
59
68
|
Runs a command and handles logging and errors.
|
|
60
69
|
|
|
@@ -85,12 +94,13 @@ def run_command(
|
|
|
85
94
|
logger.error(
|
|
86
95
|
f"Error running command: {' '.join(str(arg) for arg in command)}\nExit code: {e.returncode}\nOutput:\n{e.stdout.strip() if e.stdout else ''}{e.stderr.strip() if e.stderr else ''}"
|
|
87
96
|
)
|
|
88
|
-
raise click.exceptions.Exit(code=e.returncode)
|
|
97
|
+
raise click.exceptions.Exit(code=e.returncode) from None
|
|
89
98
|
except FileNotFoundError:
|
|
90
99
|
logger.error(f"Command not found: {command[0]}. Is it installed and in PATH?")
|
|
91
|
-
raise click.exceptions.Exit(code=1)
|
|
100
|
+
raise click.exceptions.Exit(code=1) from None
|
|
92
101
|
|
|
93
102
|
|
|
103
|
+
@cache
|
|
94
104
|
def find_pyproject_toml() -> Path:
|
|
95
105
|
"""
|
|
96
106
|
Search for pyproject.toml starting from current working directory and going up the directory tree.
|
|
@@ -112,3 +122,200 @@ def find_pyproject_toml() -> Path:
|
|
|
112
122
|
f"pyproject.toml not found in {Path.cwd().resolve()} or any parent directories"
|
|
113
123
|
)
|
|
114
124
|
raise click.exceptions.Exit(code=1)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class PyProjectProject(TypedDict):
|
|
128
|
+
dependencies: list[str]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class PyProject(TypedDict):
|
|
132
|
+
project: PyProjectProject
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def read_pyproject_toml() -> PyProject:
|
|
136
|
+
pyproject_toml = find_pyproject_toml()
|
|
137
|
+
logger.debug(f"Reading {pyproject_toml}...")
|
|
138
|
+
try:
|
|
139
|
+
with open(pyproject_toml, "rb") as f:
|
|
140
|
+
return cast(PyProject, tomllib.load(f))
|
|
141
|
+
except tomllib.TOMLDecodeError as e:
|
|
142
|
+
logger.error(f"Error parsing {pyproject_toml}: {str(e)}")
|
|
143
|
+
raise click.exceptions.Exit(code=1) from None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_project_root() -> Path:
|
|
147
|
+
return find_pyproject_toml().parent
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
MIN_UV_VERSION = (0, 8, 10)
|
|
151
|
+
MIN_WRANGLER_VERSION = (4, 42, 1)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def check_uv_version() -> None:
|
|
155
|
+
res = run_command(["uv", "--version"], capture_output=True)
|
|
156
|
+
ver_str = res.stdout.split(" ")[1]
|
|
157
|
+
ver = tuple(int(x) for x in ver_str.split("."))
|
|
158
|
+
if ver >= MIN_UV_VERSION:
|
|
159
|
+
return
|
|
160
|
+
min_version_str = ".".join(str(x) for x in MIN_UV_VERSION)
|
|
161
|
+
logger.error(f"uv version at least {min_version_str} required, have {ver_str}.")
|
|
162
|
+
logger.error("Update uv with `uv self update`.")
|
|
163
|
+
raise click.exceptions.Exit(code=1)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def check_wrangler_version() -> None:
|
|
167
|
+
"""
|
|
168
|
+
Check that the installed wrangler version is at least 4.42.1.
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
click.exceptions.Exit: If wrangler is not installed or version is too old.
|
|
172
|
+
"""
|
|
173
|
+
result = run_command(
|
|
174
|
+
["npx", "--yes", "wrangler", "--version"], capture_output=True, check=False
|
|
175
|
+
)
|
|
176
|
+
if result.returncode != 0:
|
|
177
|
+
logger.error("Failed to get wrangler version. Is wrangler installed?")
|
|
178
|
+
logger.error("Install wrangler with: npm install wrangler@latest")
|
|
179
|
+
raise click.exceptions.Exit(code=1)
|
|
180
|
+
|
|
181
|
+
# Parse version from output like "wrangler 4.42.1" or " ⛅️ wrangler 4.42.1"
|
|
182
|
+
version_line = result.stdout.strip()
|
|
183
|
+
# Extract version number using regex
|
|
184
|
+
version_match = re.search(r"(\d+)\.(\d+)\.(\d+)", version_line)
|
|
185
|
+
|
|
186
|
+
if not version_match:
|
|
187
|
+
logger.error(f"Could not parse wrangler version from: {version_line}")
|
|
188
|
+
logger.error("Install wrangler with: npm install wrangler@latest")
|
|
189
|
+
raise click.exceptions.Exit(code=1)
|
|
190
|
+
|
|
191
|
+
major, minor, patch = map(int, version_match.groups())
|
|
192
|
+
current_version = (major, minor, patch)
|
|
193
|
+
|
|
194
|
+
if current_version < MIN_WRANGLER_VERSION:
|
|
195
|
+
min_version_str = ".".join(str(x) for x in MIN_WRANGLER_VERSION)
|
|
196
|
+
current_version_str = ".".join(str(x) for x in current_version)
|
|
197
|
+
logger.error(
|
|
198
|
+
f"wrangler version at least {min_version_str} required, have {current_version_str}."
|
|
199
|
+
)
|
|
200
|
+
logger.error("Update wrangler with: npm install wrangler@latest")
|
|
201
|
+
raise click.exceptions.Exit(code=1)
|
|
202
|
+
|
|
203
|
+
logger.debug(
|
|
204
|
+
f"wrangler version {'.'.join(str(x) for x in current_version)} is sufficient"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def check_wrangler_config() -> None:
|
|
209
|
+
PROJECT_ROOT = get_project_root()
|
|
210
|
+
wrangler_jsonc = PROJECT_ROOT / "wrangler.jsonc"
|
|
211
|
+
wrangler_toml = PROJECT_ROOT / "wrangler.toml"
|
|
212
|
+
if not wrangler_jsonc.is_file() and not wrangler_toml.is_file():
|
|
213
|
+
logger.error(
|
|
214
|
+
f"{wrangler_jsonc} or {wrangler_toml} not found in {PROJECT_ROOT}."
|
|
215
|
+
)
|
|
216
|
+
raise click.exceptions.Exit(code=1)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class WranglerConfig(TypedDict, total=False):
|
|
220
|
+
compatibility_date: str
|
|
221
|
+
compatibility_flags: list[str]
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _parse_wrangler_config() -> WranglerConfig:
|
|
225
|
+
"""
|
|
226
|
+
Parse wrangler configuration from either wrangler.toml or wrangler.jsonc.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
dict: Parsed configuration data
|
|
230
|
+
"""
|
|
231
|
+
PROJECT_ROOT = get_project_root()
|
|
232
|
+
wrangler_toml = PROJECT_ROOT / "wrangler.toml"
|
|
233
|
+
wrangler_jsonc = PROJECT_ROOT / "wrangler.jsonc"
|
|
234
|
+
|
|
235
|
+
if wrangler_toml.is_file():
|
|
236
|
+
try:
|
|
237
|
+
with open(wrangler_toml, "rb") as f:
|
|
238
|
+
return cast(WranglerConfig, tomllib.load(f))
|
|
239
|
+
except tomllib.TOMLDecodeError as e:
|
|
240
|
+
logger.error(f"Error parsing {wrangler_toml}: {e}")
|
|
241
|
+
raise click.exceptions.Exit(code=1) from None
|
|
242
|
+
|
|
243
|
+
if wrangler_jsonc.is_file():
|
|
244
|
+
try:
|
|
245
|
+
with open(wrangler_jsonc) as f:
|
|
246
|
+
content = f.read()
|
|
247
|
+
return cast(WranglerConfig, pyjson5.loads(content))
|
|
248
|
+
except (pyjson5.Json5DecoderException, ValueError) as e:
|
|
249
|
+
logger.error(f"Error parsing {wrangler_jsonc}: {e}")
|
|
250
|
+
raise click.exceptions.Exit(code=1) from None
|
|
251
|
+
|
|
252
|
+
return {}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@cache
|
|
256
|
+
def get_python_version() -> Literal["3.12", "3.13"]:
|
|
257
|
+
"""
|
|
258
|
+
Determine Python version from wrangler configuration.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Python version string
|
|
262
|
+
"""
|
|
263
|
+
config = _parse_wrangler_config()
|
|
264
|
+
|
|
265
|
+
if not config:
|
|
266
|
+
logger.error("No wrangler config found")
|
|
267
|
+
raise click.exceptions.Exit(code=1)
|
|
268
|
+
|
|
269
|
+
compat_flags = config.get("compatibility_flags", [])
|
|
270
|
+
compat_date_str = config.get("compatibility_date", None)
|
|
271
|
+
if compat_date_str is None:
|
|
272
|
+
logger.error("No compatibility_date specified in wrangler config")
|
|
273
|
+
raise click.exceptions.Exit(code=1)
|
|
274
|
+
try:
|
|
275
|
+
compat_date = datetime.strptime(compat_date_str, "%Y-%m-%d")
|
|
276
|
+
except ValueError:
|
|
277
|
+
logger.error(
|
|
278
|
+
f"Invalid compatibility_date format: {config.get('compatibility_date')}"
|
|
279
|
+
)
|
|
280
|
+
raise click.exceptions.Exit(code=1) from None
|
|
281
|
+
|
|
282
|
+
# Check if python_workers base flag is present (required for Python workers)
|
|
283
|
+
if "python_workers" not in compat_flags:
|
|
284
|
+
logger.error("`python_workers` compat flag not specified in wrangler config")
|
|
285
|
+
raise click.exceptions.Exit(code=1)
|
|
286
|
+
|
|
287
|
+
# Find the most specific Python version based on compat flags and date
|
|
288
|
+
# Sort by version descending to prioritize newer versions
|
|
289
|
+
sorted_versions = sorted(
|
|
290
|
+
PYTHON_COMPAT_VERSIONS, key=lambda x: x.version, reverse=True
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
for py_version in sorted_versions:
|
|
294
|
+
# Check if the specific compat flag is present
|
|
295
|
+
if py_version.compat_flag in compat_flags:
|
|
296
|
+
return py_version.version
|
|
297
|
+
|
|
298
|
+
# For versions with compat_date, also check the date requirement
|
|
299
|
+
if py_version.compat_date and compat_date >= py_version.compat_date:
|
|
300
|
+
return py_version.version
|
|
301
|
+
|
|
302
|
+
logger.error("Could not determine Python version from wrangler config")
|
|
303
|
+
raise click.exceptions.Exit(code=1)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def get_uv_pyodide_interp_name() -> str:
|
|
307
|
+
match get_python_version():
|
|
308
|
+
case "3.12":
|
|
309
|
+
v = "3.12.7"
|
|
310
|
+
case "3.13":
|
|
311
|
+
v = "3.13.2"
|
|
312
|
+
return f"cpython-{v}-emscripten-wasm32-musl"
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def get_pyodide_index() -> str:
|
|
316
|
+
match get_python_version():
|
|
317
|
+
case "3.12":
|
|
318
|
+
v = "0.27.7"
|
|
319
|
+
case "3.13":
|
|
320
|
+
v = "0.28.3"
|
|
321
|
+
return "https://index.pyodide.org/" + v
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: workers-py
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.2
|
|
4
4
|
Summary: A set of libraries and tools for Python Workers
|
|
5
5
|
Project-URL: Homepage, https://github.com/cloudflare/workers-py
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/cloudflare/workers-py/issues
|
|
7
7
|
Classifier: License :: OSI Approved :: MIT License
|
|
8
8
|
Classifier: Operating System :: OS Independent
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
11
|
Requires-Dist: click<9.0.0,>=8.0.0
|
|
12
12
|
Requires-Dist: pyjson5>=1.6.0
|
|
13
13
|
Requires-Dist: pyodide-cli
|
|
14
14
|
Requires-Dist: pyodide-py
|
|
15
15
|
Requires-Dist: rich>=13.0.0
|
|
16
|
+
Requires-Dist: workers-runtime-sdk>=0.1.0
|
|
16
17
|
Provides-Extra: build
|
|
17
18
|
Requires-Dist: uv~=0.5.23; extra == 'build'
|
|
18
19
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
pywrangler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
pywrangler/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
|
|
3
|
+
pywrangler/cli.py,sha256=8C4iBntruyMkQqOb0KfH_H0QNGYYWEBtKFA7b7zGSmE,5467
|
|
4
|
+
pywrangler/metadata.py,sha256=ndh584ALzshSXwduTmqVczfF5Mpn7z0F9ztn3Dugf70,408
|
|
5
|
+
pywrangler/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
pywrangler/sync.py,sha256=zn3396_8Hotu_6FnlQwdITgdUxze50tbO5xVzwjUxIU,9498
|
|
7
|
+
pywrangler/types.py,sha256=hYJ6hNIjWb0faBGW82AWfCBsF_JPb7sXKCXPtFM3Mhk,1183
|
|
8
|
+
pywrangler/utils.py,sha256=AHF-0VMdgQPVFkZfanHEQNyoNpAFbOstaXDNbNb2T5Y,10368
|
|
9
|
+
workers_py-1.6.2.dist-info/METADATA,sha256=yXqeNFXPfH92_6v7dtFKw8fYExJjYtsbpXOw2pQb5OY,1774
|
|
10
|
+
workers_py-1.6.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
workers_py-1.6.2.dist-info/entry_points.txt,sha256=pt6X-Nv5-gSiKUwrnvLwzlSXs9yP37m7zdTAi8f6nAM,50
|
|
12
|
+
workers_py-1.6.2.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
pywrangler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
pywrangler/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
|
|
3
|
-
pywrangler/cli.py,sha256=voUaP2tHCwxznzZ45O6ZAJ91vbp6q9UNb7H2OlgnZ_k,6596
|
|
4
|
-
pywrangler/metadata.py,sha256=vttmaCtouSr9ADj8ncvNGqeaWEGFP8pamH2T6ohFjnA,408
|
|
5
|
-
pywrangler/sync.py,sha256=fFavYhbvBXF4memxrJLnMJpApET_3fx9NmJ_O6NDzxw,14217
|
|
6
|
-
pywrangler/types.py,sha256=RyOTVFFC75vJ0NxGzXWJGouRpQ5k4gsf3Jsiq-i_ans,1174
|
|
7
|
-
pywrangler/utils.py,sha256=JOPCXy_O_RZ-ClSSWL5pel2Iltyclj_JV13IxKr_WRo,3382
|
|
8
|
-
workers_py-1.6.1.dist-info/METADATA,sha256=0oipVsEgOgZhdUXJp9ckB7HWqGVvqqsh2sbcQBL1yIY,1732
|
|
9
|
-
workers_py-1.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
10
|
-
workers_py-1.6.1.dist-info/entry_points.txt,sha256=pt6X-Nv5-gSiKUwrnvLwzlSXs9yP37m7zdTAi8f6nAM,50
|
|
11
|
-
workers_py-1.6.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|