workers-py 1.1.7__tar.gz → 1.2.0__tar.gz

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.
@@ -11,20 +11,16 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
13
  - uses: actions/checkout@v4
14
-
14
+
15
15
  - name: Set up Python
16
16
  uses: actions/setup-python@v5
17
17
  with:
18
- python-version: '3.12'
19
-
18
+ python-version: '3.13'
19
+
20
20
  - name: Install uv
21
21
  run: |
22
22
  pip install uv
23
-
24
- - name: Lint with Ruff
25
- run: |
26
- uvx ruff check .
27
-
28
- - name: Format with Ruff
23
+
24
+ - name: Run pre-commit
29
25
  run: |
30
- uvx ruff format --check .
26
+ uvx pre-commit run -a
@@ -16,20 +16,20 @@ jobs:
16
16
 
17
17
  steps:
18
18
  - uses: actions/checkout@v4
19
-
19
+
20
20
  - name: Set up Python ${{ matrix.python-version }}
21
21
  uses: actions/setup-python@v5
22
22
  with:
23
23
  python-version: ${{ matrix.python-version }}
24
-
24
+
25
25
  - name: Install uv
26
26
  run: |
27
27
  pip install uv
28
-
28
+
29
29
  - name: Install project
30
30
  run: |
31
31
  uv pip install --system -e .
32
-
32
+
33
33
  - name: Run tests
34
34
  run: |
35
35
  uv run pytest
@@ -0,0 +1,41 @@
1
+ exclude: (^.*patches|.*\.cgi$|^packages/micropip/src/micropip/externals|^benchmark/benchmarks$)
2
+ default_language_version:
3
+ python: "3.13"
4
+ repos:
5
+ - repo: https://github.com/pre-commit/pre-commit-hooks
6
+ rev: "v5.0.0"
7
+ hooks:
8
+ - id: check-added-large-files
9
+ - id: check-case-conflict
10
+ - id: check-merge-conflict
11
+ - id: check-symlinks
12
+ - id: check-yaml
13
+ - id: debug-statements
14
+ - id: end-of-file-fixer
15
+ - id: mixed-line-ending
16
+ - id: trailing-whitespace
17
+ - id: requirements-txt-fixer
18
+
19
+ - repo: https://github.com/astral-sh/ruff-pre-commit
20
+ rev: "v0.9.1"
21
+ hooks:
22
+ - id: ruff
23
+ args: [--fix]
24
+ - id: ruff-format
25
+
26
+ - repo: https://github.com/shellcheck-py/shellcheck-py
27
+ rev: "v0.10.0.1"
28
+ hooks:
29
+ - id: shellcheck
30
+
31
+ - repo: https://github.com/codespell-project/codespell
32
+ rev: "v2.3.0"
33
+ hooks:
34
+ - id: codespell
35
+ args:
36
+ [
37
+ "--ignore-words-list",
38
+ "ommit",
39
+ ]
40
+ ci:
41
+ autoupdate_schedule: "quarterly"
@@ -2,6 +2,24 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.2.0 (2025-09-26)
6
+
7
+ ### Features
8
+
9
+ - Use uv instead of pyodide-build to manage pyodide install and venv
10
+ ([#30](https://github.com/cloudflare/workers-py/pull/30),
11
+ [`1629919`](https://github.com/cloudflare/workers-py/commit/16299198db73f1e3efb99eb6ef928fc46978acd9))
12
+
13
+
14
+ ## v1.1.8 (2025-09-25)
15
+
16
+ ### Bug Fixes
17
+
18
+ - Sync: Use a token that we write only after sync succeeds
19
+ ([#29](https://github.com/cloudflare/workers-py/pull/29),
20
+ [`64bc90a`](https://github.com/cloudflare/workers-py/commit/64bc90ac3832e094e096130f87992d0899e6b8fc))
21
+
22
+
5
23
  ## v1.1.7 (2025-08-28)
6
24
 
7
25
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workers-py
3
- Version: 1.1.7
3
+ Version: 1.2.0
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
@@ -20,7 +20,7 @@ Description-Content-Type: text/markdown
20
20
  A set of libraries and tools for Python Workers.
21
21
 
22
22
 
23
- ## Pywrangler
23
+ ## Pywrangler
24
24
 
25
25
  A CLI tool for managing vendored packages in a Python Workers project.
26
26
 
@@ -3,7 +3,7 @@
3
3
  A set of libraries and tools for Python Workers.
4
4
 
5
5
 
6
- ## Pywrangler
6
+ ## Pywrangler
7
7
 
8
8
  A CLI tool for managing vendored packages in a Python Workers project.
9
9
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "workers-py"
7
- version = "1.1.7"
7
+ version = "1.2.0"
8
8
  description = "A set of libraries and tools for Python Workers"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -25,7 +25,8 @@ dev = [
25
25
  "click>=8.0.0",
26
26
  "black>=23.0.0",
27
27
  "ruff>=0.1.0",
28
- "mypy>=1.0.0"
28
+ "mypy>=1.0.0",
29
+ "pre-commit>=4.3.0",
29
30
  ]
30
31
 
31
32
  [project.optional-dependencies]
@@ -106,7 +106,6 @@ def sync_command(force=False, directly_requested=True):
106
106
  create_pyodide_venv,
107
107
  create_workers_venv,
108
108
  parse_requirements,
109
- install_pyodide_build,
110
109
  install_requirements,
111
110
  )
112
111
 
@@ -129,7 +128,6 @@ def sync_command(force=False, directly_requested=True):
129
128
  create_workers_venv()
130
129
 
131
130
  # Set up Pyodide virtual env
132
- install_pyodide_build()
133
131
  create_pyodide_venv()
134
132
 
135
133
  # Generate requirements.txt from pyproject.toml by directly parsing the TOML file then install into vendor folder.
@@ -1,8 +1,10 @@
1
1
  import logging
2
2
  import os
3
3
  import shutil
4
- from pathlib import Path
5
4
  import tempfile
5
+ from contextlib import contextmanager
6
+ from pathlib import Path
7
+ from typing import Literal
6
8
 
7
9
  import click
8
10
 
@@ -22,7 +24,9 @@ logger = logging.getLogger(__name__)
22
24
  PYPROJECT_TOML_PATH = find_pyproject_toml()
23
25
  PROJECT_ROOT = PYPROJECT_TOML_PATH.parent
24
26
  VENV_WORKERS_PATH = PROJECT_ROOT / ".venv-workers"
27
+ VENV_WORKERS_TOKEN = PROJECT_ROOT / ".venv-workers/.synced"
25
28
  PYODIDE_VENV_PATH = VENV_WORKERS_PATH / "pyodide-venv"
29
+ VENDOR_TOKEN = PROJECT_ROOT / "python_modules/.synced"
26
30
  VENV_REQUIREMENTS_PATH = VENV_WORKERS_PATH / "temp-venv-requirements.txt"
27
31
 
28
32
 
@@ -56,8 +60,33 @@ def check_wrangler_config():
56
60
  raise click.exceptions.Exit(code=1)
57
61
 
58
62
 
59
- def _get_python_version():
60
- return os.environ.get("_PYWRANGLER_PYTHON_VERSION", "3.12")
63
+ def _get_python_version() -> Literal["3.12", "3.13"]:
64
+ res = os.environ.get("_PYWRANGLER_PYTHON_VERSION", "3.12")
65
+ match res:
66
+ case "3.12" | "3.13":
67
+ return res
68
+ case _:
69
+ raise ValueError(
70
+ f"Unexpected value for Python version '{res}', expected '3.12' or '3.13'"
71
+ )
72
+
73
+
74
+ def _get_uv_pyodide_interp_name():
75
+ match _get_python_version():
76
+ case "3.12":
77
+ v = "3.12.7"
78
+ case "3.13":
79
+ v = "3.13.2"
80
+ return f"cpython-{v}-emscripten-wasm32-musl"
81
+
82
+
83
+ def _get_pyodide_index():
84
+ match _get_python_version():
85
+ case "3.12":
86
+ v = "0.27.7"
87
+ case "3.13":
88
+ v = "0.28.3"
89
+ return "https://index.pyodide.org/" + v
61
90
 
62
91
 
63
92
  def _get_venv_python_version() -> str | None:
@@ -123,65 +152,18 @@ def create_workers_venv():
123
152
  )
124
153
 
125
154
 
126
- def _get_pyodide_cli_path():
127
- venv_bin_path = VENV_WORKERS_PATH / ("Scripts" if os.name == "nt" else "bin")
128
- pyodide_cli_path = venv_bin_path / ("pyodide.exe" if os.name == "nt" else "pyodide")
129
- return pyodide_cli_path
130
-
131
-
132
- def install_pyodide_build():
133
- pyodide_cli_path = _get_pyodide_cli_path()
134
-
135
- if pyodide_cli_path.is_file():
136
- logger.debug(
137
- f"pyodide-build CLI already found at {pyodide_cli_path} (skipping install.)"
138
- )
139
- return
140
-
141
- logger.debug(
142
- f"Installing pyodide-build in {VENV_WORKERS_PATH} using 'uv pip install'..."
143
- )
144
- venv_bin_path = pyodide_cli_path.parent
145
-
146
- # Ensure the python executable path is correct for the venv
147
- venv_python_executable = venv_bin_path / (
148
- "python.exe" if os.name == "nt" else "python"
149
- )
150
- if not venv_python_executable.is_file():
151
- logger.error(f"Python executable not found at {venv_python_executable}")
152
- raise click.exceptions.Exit(code=1)
153
-
154
- run_command(["uv", "pip", "install", "-p", str(venv_python_executable), "pip"])
155
-
156
- run_command(
157
- [
158
- "uv",
159
- "pip",
160
- "install",
161
- "-p",
162
- str(venv_python_executable),
163
- "pyodide-build==0.30.7",
164
- ]
165
- )
166
-
167
-
168
155
  def create_pyodide_venv():
169
- pyodide_cli_path = _get_pyodide_cli_path()
170
156
  if PYODIDE_VENV_PATH.is_dir():
171
157
  logger.debug(
172
158
  f"Pyodide virtual environment at {PYODIDE_VENV_PATH} already exists."
173
159
  )
174
160
  return
175
161
 
176
- # Workaround to fix caching issue on some machines.
177
- #
178
- # Fix is here: pyodide/pyodide-build#239
179
- logger.debug("Installing xbuildenv...")
180
- run_command([str(pyodide_cli_path), "xbuildenv", "install"])
181
-
182
162
  logger.debug(f"Creating Pyodide virtual environment at {PYODIDE_VENV_PATH}...")
183
163
  PYODIDE_VENV_PATH.parent.mkdir(parents=True, exist_ok=True)
184
- run_command([str(pyodide_cli_path), "venv", str(PYODIDE_VENV_PATH)])
164
+ interp_name = _get_uv_pyodide_interp_name()
165
+ run_command(["uv", "python", "install", interp_name])
166
+ run_command(["uv", "venv", PYODIDE_VENV_PATH, "--python", interp_name])
185
167
 
186
168
 
187
169
  def parse_requirements() -> list[str]:
@@ -200,6 +182,15 @@ def parse_requirements() -> list[str]:
200
182
  raise click.exceptions.Exit(code=1)
201
183
 
202
184
 
185
+ @contextmanager
186
+ def temp_requirements_file(requirements: list[str]):
187
+ # Write dependencies to a requirements.txt-style temp file.
188
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".txt") as temp_file:
189
+ temp_file.write("\n".join(requirements))
190
+ temp_file.flush()
191
+ yield temp_file.name
192
+
193
+
203
194
  def _install_requirements_to_vendor(requirements: list[str]):
204
195
  vendor_path = PROJECT_ROOT / "python_modules"
205
196
  logger.debug(f"Using vendor path: {vendor_path}")
@@ -210,95 +201,72 @@ def _install_requirements_to_vendor(requirements: list[str]):
210
201
  )
211
202
  return
212
203
 
213
- # Write dependencies to a requirements.txt-style temp file.
214
- with tempfile.NamedTemporaryFile(
215
- mode="w", suffix=".txt", dir=PYODIDE_VENV_PATH
216
- ) as temp_file:
217
- temp_file.write("\n".join(requirements))
218
- temp_file.flush()
219
- temp_file_path = Path(temp_file.name)
220
-
221
- # Install packages into vendor directory
222
- vendor_path.mkdir(parents=True, exist_ok=True)
223
- pyodide_venv_pip_path = (
224
- PYODIDE_VENV_PATH
225
- / ("Scripts" if os.name == "nt" else "bin")
226
- / ("pip.exe" if os.name == "nt" else "pip")
227
- )
228
- relative_vendor_path = vendor_path.relative_to(PROJECT_ROOT)
229
- logger.info(
230
- f"Installing packages into [bold]{relative_vendor_path}[/bold] using Pyodide pip...",
231
- extra={"markup": True},
232
- )
204
+ # Install packages into vendor directory
205
+ vendor_path.mkdir(parents=True, exist_ok=True)
206
+ relative_vendor_path = vendor_path.relative_to(PROJECT_ROOT)
207
+ logger.info(
208
+ f"Installing packages into [bold]{relative_vendor_path}[/bold]...",
209
+ extra={"markup": True},
210
+ )
211
+ with temp_requirements_file(requirements) as requirements_file:
233
212
  run_command(
234
213
  [
235
- str(pyodide_venv_pip_path),
214
+ "uv",
215
+ "pip",
236
216
  "install",
237
- "-t",
238
- str(vendor_path),
217
+ "--no-build",
239
218
  "-r",
240
- str(temp_file_path),
241
- ]
219
+ requirements_file,
220
+ "--extra-index-url",
221
+ _get_pyodide_index(),
222
+ "--index-strategy",
223
+ "unsafe-best-match",
224
+ ],
225
+ env=os.environ | {"VIRTUAL_ENV": PYODIDE_VENV_PATH},
226
+ )
227
+ pyv = _get_python_version()
228
+ shutil.rmtree(vendor_path)
229
+ shutil.copytree(
230
+ PYODIDE_VENV_PATH / f"lib/python{pyv}/site-packages", vendor_path
242
231
  )
243
232
 
244
- # Create a pyvenv.cfg file in python_modules to mark it as a virtual environment
245
- (vendor_path / "pyvenv.cfg").touch()
233
+ # Create a pyvenv.cfg file in python_modules to mark it as a virtual environment
234
+ (vendor_path / "pyvenv.cfg").touch()
235
+ VENDOR_TOKEN.touch()
246
236
 
247
- logger.info(
248
- f"Packages installed in [bold]{relative_vendor_path}[/bold].",
249
- extra={"markup": True},
250
- )
237
+ logger.info(
238
+ f"Packages installed in [bold]{relative_vendor_path}[/bold].",
239
+ extra={"markup": True},
240
+ )
251
241
 
252
242
 
253
243
  def _install_requirements_to_venv(requirements: list[str]):
254
244
  # Create a requirements file for .venv-workers that includes webtypy and pyodide-py
255
- VENV_REQUIREMENTS_PATH.parent.mkdir(parents=True, exist_ok=True)
256
-
245
+ relative_venv_workers_path = VENV_WORKERS_PATH.relative_to(PROJECT_ROOT)
257
246
  requirements = requirements.copy()
258
247
  requirements.append("webtypy")
259
248
  requirements.append("pyodide-py")
260
249
 
261
- # Write dependencies to a requirements.txt-style temp file.
262
- with tempfile.NamedTemporaryFile(
263
- mode="w", suffix=".txt", dir=VENV_REQUIREMENTS_PATH.parent
264
- ) as temp_file:
265
- temp_file.write("\n".join(requirements))
266
- temp_file.flush()
267
- temp_file_path = Path(temp_file.name)
268
-
269
- # Install packages into .venv-workers so that user's IDE can see the packages.
270
- venv_bin_path = VENV_WORKERS_PATH / ("Scripts" if os.name == "nt" else "bin")
271
- venv_python_executable = venv_bin_path / (
272
- "python.exe" if os.name == "nt" else "python"
250
+ logger.info(
251
+ f"Installing packages into [bold]{relative_venv_workers_path}[/bold]...",
252
+ extra={"markup": True},
253
+ )
254
+ with temp_requirements_file(requirements) as requirements_file:
255
+ run_command(
256
+ [
257
+ "uv",
258
+ "pip",
259
+ "install",
260
+ "-r",
261
+ requirements_file,
262
+ ],
263
+ env=os.environ | {"VIRTUAL_ENV": VENV_WORKERS_PATH},
273
264
  )
274
-
275
- # For nicer logs, output the relative path.
276
- relative_venv_workers_path = VENV_WORKERS_PATH.relative_to(PROJECT_ROOT)
277
- if venv_python_executable.is_file():
278
- logger.info(
279
- f"Installing packages into [bold]{relative_venv_workers_path}[/bold] using uv pip...",
280
- extra={"markup": True},
281
- )
282
- run_command(
283
- [
284
- "uv",
285
- "pip",
286
- "install",
287
- "-p",
288
- venv_python_executable,
289
- "-r",
290
- str(temp_file_path),
291
- ]
292
- )
293
- logger.info(
294
- f"Packages installed in [bold]{relative_venv_workers_path}[/bold].",
295
- extra={"markup": True},
296
- )
297
- else:
298
- logger.warning(
299
- f"Python executable not found at {venv_python_executable}. Skipping installation in [bold]{relative_venv_workers_path}[/bold].",
300
- extra={"markup": True},
301
- )
265
+ VENV_WORKERS_TOKEN.touch()
266
+ logger.info(
267
+ f"Packages installed in [bold]{relative_venv_workers_path}[/bold].",
268
+ extra={"markup": True},
269
+ )
302
270
 
303
271
 
304
272
  def install_requirements(requirements: list[str]):
@@ -306,6 +274,12 @@ def install_requirements(requirements: list[str]):
306
274
  _install_requirements_to_venv(requirements)
307
275
 
308
276
 
277
+ def _is_out_of_date(token: Path, time: float) -> bool:
278
+ if not token.exists():
279
+ return True
280
+ return time > token.stat().st_mtime
281
+
282
+
309
283
  def is_sync_needed():
310
284
  """
311
285
  Checks if pyproject.toml has been modified since the last sync.
@@ -319,21 +293,6 @@ def is_sync_needed():
319
293
  return True
320
294
 
321
295
  pyproject_mtime = PYPROJECT_TOML_PATH.stat().st_mtime
322
-
323
- # Check if .venv-workers exists and get its timestamp
324
- if not VENV_WORKERS_PATH.is_dir():
325
- return True
326
-
327
- venv_mtime = VENV_WORKERS_PATH.stat().st_mtime
328
- venv_needs_update = pyproject_mtime > venv_mtime
329
- if venv_needs_update:
330
- return True
331
-
332
- # Check if vendor directory exists and get its timestamp
333
- vendor_path = PROJECT_ROOT / "python_modules"
334
- if not vendor_path.is_dir():
335
- return True
336
-
337
- vendor_mtime = vendor_path.stat().st_mtime
338
- vendor_needs_update = pyproject_mtime > vendor_mtime
339
- return vendor_needs_update
296
+ return _is_out_of_date(VENDOR_TOKEN, pyproject_mtime) or _is_out_of_date(
297
+ VENV_WORKERS_TOKEN, pyproject_mtime
298
+ )
File without changes
@@ -135,29 +135,17 @@ def test_sync_command_integration(dependencies, clean_test_dir):
135
135
 
136
136
  # Create a test wrangler.jsonc file
137
137
  create_test_wrangler_jsonc("src/worker.py")
138
+ project_root = Path.cwd().resolve()
138
139
 
139
- # Save the current directory
140
- original_dir = os.getcwd()
140
+ # Get the absolute path to the package root
141
+ # Run the pywrangler CLI directly using uvx
142
+ print("\nRunning pywrangler sync...")
143
+ sync_cmd = ["uvx", "--from", project_root, "pywrangler", "sync"]
141
144
 
142
- try:
143
- # Change to the test directory
144
- os.chdir(TEST_DIR)
145
-
146
- # Get the absolute path to the package root
147
- project_root = Path(original_dir)
148
-
149
- # Run the pywrangler CLI directly using uvx
150
- print("\nRunning pywrangler sync...")
151
- sync_cmd = ["uvx", "--from", str(project_root), "pywrangler", "sync"]
152
-
153
- result = subprocess.run(sync_cmd, capture_output=True, text=True)
154
- print(f"\nCommand output:\n{result.stdout}")
155
- if result.stderr:
156
- print(f"Command errors:\n{result.stderr}")
157
-
158
- finally:
159
- # Change back to the original directory
160
- os.chdir(original_dir)
145
+ result = subprocess.run(sync_cmd, capture_output=True, text=True, cwd=TEST_DIR)
146
+ print(f"\nCommand output:\n{result.stdout}")
147
+ if result.stderr:
148
+ print(f"Command errors:\n{result.stderr}")
161
149
 
162
150
  # Check that the command succeeded
163
151
  assert result.returncode == 0, (
@@ -233,23 +221,12 @@ def test_sync_command_handles_missing_pyproject():
233
221
  assert not (temp_path / "pyproject.toml").exists()
234
222
 
235
223
  # Save original directory
236
- original_dir = os.getcwd()
237
-
238
- try:
239
- # Change to temp directory
240
- os.chdir(temp_path)
241
-
242
- # Get the absolute path to the package root
243
- project_root = Path(original_dir)
224
+ project_root = Path.cwd().resolve()
244
225
 
245
- # Run pywrangler sync from the temp directory (should fail)
246
- sync_cmd = ["uvx", "--from", str(project_root), "pywrangler", "sync"]
226
+ # Run pywrangler sync from the temp directory (should fail)
227
+ sync_cmd = ["uvx", "--from", project_root, "pywrangler", "sync"]
247
228
 
248
- result = subprocess.run(sync_cmd, capture_output=True, text=True)
249
-
250
- finally:
251
- # Change back to original directory
252
- os.chdir(original_dir)
229
+ result = subprocess.run(sync_cmd, capture_output=True, text=True, cwd=temp_path)
253
230
 
254
231
  # Check that the command failed with the expected error
255
232
  assert result.returncode != 0
@@ -447,27 +424,15 @@ def test_sync_command_finds_pyproject_in_parent_directory(clean_test_dir):
447
424
  subdir = TEST_DIR / "subproject"
448
425
  subdir.mkdir()
449
426
 
450
- # Save the original directory
451
- original_dir = os.getcwd()
452
-
453
- try:
454
- # Change to the subdirectory
455
- os.chdir(subdir)
456
-
457
- # Get the absolute path to the package root
458
- project_root = Path(original_dir)
427
+ project_root = Path.cwd().resolve()
459
428
 
460
- # Run the pywrangler CLI from the subdirectory
461
- sync_cmd = ["uvx", "--from", str(project_root), "pywrangler", "sync"]
429
+ # Run the pywrangler CLI from the subdirectory
430
+ sync_cmd = ["uvx", "--from", project_root, "pywrangler", "sync"]
462
431
 
463
- result = subprocess.run(sync_cmd, capture_output=True, text=True)
464
- print(f"\nCommand output:\n{result.stdout}")
465
- if result.stderr:
466
- print(f"Command errors:\n{result.stderr}")
467
-
468
- finally:
469
- # Change back to the original directory
470
- os.chdir(original_dir)
432
+ result = subprocess.run(sync_cmd, capture_output=True, text=True, cwd=subdir)
433
+ print(f"\nCommand output:\n{result.stdout}")
434
+ if result.stderr:
435
+ print(f"Command errors:\n{result.stderr}")
471
436
 
472
437
  # Check that the command succeeded
473
438
  assert result.returncode == 0, (
@@ -496,49 +461,45 @@ def test_sync_recreates_venv_on_python_version_mismatch(clean_test_dir):
496
461
  create_test_pyproject([])
497
462
  create_test_wrangler_jsonc()
498
463
 
499
- original_dir = os.getcwd()
500
- try:
501
- os.chdir(clean_test_dir)
502
- project_root = Path(original_dir)
503
- sync_cmd = ["uvx", "--from", str(project_root), "pywrangler", "sync"]
504
- venv_path = clean_test_dir / ".venv-workers"
505
-
506
- # First run: Create venv with Python 3.12
507
- print("\nRunning sync to create venv with Python 3.12...")
508
- env = os.environ.copy()
509
- env["_PYWRANGLER_PYTHON_VERSION"] = "3.12"
510
- result1 = subprocess.run(sync_cmd, capture_output=True, text=True, env=env)
511
-
512
- assert result1.returncode == 0, (
513
- f"First sync failed: {result1.stdout}\n{result1.stderr}"
514
- )
515
- assert venv_path.exists(), "Venv was not created on the first run."
516
- initial_mtime = venv_path.stat().st_mtime
464
+ project_root = Path.cwd().resolve()
517
465
 
518
- # Second run: Recreate venv with Python 3.13
519
- print("\nRunning sync to recreate venv with Python 3.13...")
520
- env["_PYWRANGLER_PYTHON_VERSION"] = "3.13"
521
- result2 = subprocess.run(sync_cmd, capture_output=True, text=True, env=env)
466
+ sync_cmd = ["uvx", "--from", project_root, "pywrangler", "sync"]
467
+ venv_path = clean_test_dir / ".venv-workers"
522
468
 
523
- assert result2.returncode == 0, (
524
- f"Second sync failed: {result2.stdout}\n{result2.stderr}"
525
- )
526
- assert venv_path.exists(), "Venv was not recreated."
527
- final_mtime = venv_path.stat().st_mtime
469
+ # First run: Create venv with Python 3.12
470
+ print("\nRunning sync to create venv with Python 3.12...")
471
+ env = os.environ | {"_PYWRANGLER_PYTHON_VERSION": "3.12"}
472
+ result1 = subprocess.run(
473
+ sync_cmd, capture_output=True, text=True, env=env, cwd=clean_test_dir
474
+ )
528
475
 
529
- # Check that the venv was actually modified
530
- assert final_mtime > initial_mtime, "Venv modification time did not change."
476
+ assert result1.returncode == 0, (
477
+ f"First sync failed: {result1.stdout}\n{result1.stderr}"
478
+ )
479
+ assert venv_path.exists(), "Venv was not created on the first run."
480
+ initial_mtime = venv_path.stat().st_mtime
481
+
482
+ # Second run: Recreate venv with Python 3.13
483
+ print("\nRunning sync to recreate venv with Python 3.13...")
484
+ env = os.environ | {"_PYWRANGLER_PYTHON_VERSION": "3.13"}
485
+ result2 = subprocess.run(
486
+ sync_cmd, capture_output=True, text=True, env=env, cwd=clean_test_dir
487
+ )
531
488
 
532
- # Verify the python version in the new venv is 3.13.
533
- python_exe = venv_path / (
534
- "Scripts/python.exe" if os.name == "nt" else "bin/python"
535
- )
536
- version_result = subprocess.run(
537
- [str(python_exe), "--version"], capture_output=True, text=True
538
- )
539
- assert "3.13" in version_result.stdout, (
540
- f"Python version is not 3.13: {version_result.stdout}"
541
- )
489
+ assert result2.returncode == 0, (
490
+ f"Second sync failed: {result2.stdout}\n{result2.stderr}"
491
+ )
492
+ assert venv_path.exists(), "Venv was not recreated."
493
+ final_mtime = venv_path.stat().st_mtime
494
+
495
+ # Check that the venv was actually modified
496
+ assert final_mtime > initial_mtime, "Venv modification time did not change."
542
497
 
543
- finally:
544
- os.chdir(original_dir)
498
+ # Verify the python version in the new venv is 3.13.
499
+ python_exe = venv_path / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
500
+ version_result = subprocess.run(
501
+ [python_exe, "--version"], capture_output=True, text=True, cwd=clean_test_dir
502
+ )
503
+ assert "3.13" in version_result.stdout, (
504
+ f"Python version is not 3.13: {version_result.stdout}"
505
+ )
@@ -40,6 +40,15 @@ wheels = [
40
40
  { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 },
41
41
  ]
42
42
 
43
+ [[package]]
44
+ name = "cfgv"
45
+ version = "3.4.0"
46
+ source = { registry = "https://pypi.org/simple" }
47
+ sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 }
48
+ wheels = [
49
+ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 },
50
+ ]
51
+
43
52
  [[package]]
44
53
  name = "click"
45
54
  version = "8.1.8"
@@ -79,6 +88,15 @@ wheels = [
79
88
  { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
80
89
  ]
81
90
 
91
+ [[package]]
92
+ name = "distlib"
93
+ version = "0.4.0"
94
+ source = { registry = "https://pypi.org/simple" }
95
+ sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605 }
96
+ wheels = [
97
+ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047 },
98
+ ]
99
+
82
100
  [[package]]
83
101
  name = "exceptiongroup"
84
102
  version = "1.3.0"
@@ -91,6 +109,24 @@ wheels = [
91
109
  { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 },
92
110
  ]
93
111
 
112
+ [[package]]
113
+ name = "filelock"
114
+ version = "3.19.1"
115
+ source = { registry = "https://pypi.org/simple" }
116
+ sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687 }
117
+ wheels = [
118
+ { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988 },
119
+ ]
120
+
121
+ [[package]]
122
+ name = "identify"
123
+ version = "2.6.14"
124
+ source = { registry = "https://pypi.org/simple" }
125
+ sdist = { url = "https://files.pythonhosted.org/packages/52/c4/62963f25a678f6a050fb0505a65e9e726996171e6dbe1547f79619eefb15/identify-2.6.14.tar.gz", hash = "sha256:663494103b4f717cb26921c52f8751363dc89db64364cd836a9bf1535f53cd6a", size = 99283 }
126
+ wheels = [
127
+ { url = "https://files.pythonhosted.org/packages/e5/ae/2ad30f4652712c82f1c23423d79136fbce338932ad166d70c1efb86a5998/identify-2.6.14-py2.py3-none-any.whl", hash = "sha256:11a073da82212c6646b1f39bb20d4483bfb9543bd5566fec60053c4bb309bf2e", size = 99172 },
128
+ ]
129
+
94
130
  [[package]]
95
131
  name = "iniconfig"
96
132
  version = "2.1.0"
@@ -169,6 +205,15 @@ wheels = [
169
205
  { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
170
206
  ]
171
207
 
208
+ [[package]]
209
+ name = "nodeenv"
210
+ version = "1.9.1"
211
+ source = { registry = "https://pypi.org/simple" }
212
+ sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 }
213
+ wheels = [
214
+ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 },
215
+ ]
216
+
172
217
  [[package]]
173
218
  name = "packaging"
174
219
  version = "25.0"
@@ -205,6 +250,22 @@ wheels = [
205
250
  { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 },
206
251
  ]
207
252
 
253
+ [[package]]
254
+ name = "pre-commit"
255
+ version = "4.3.0"
256
+ source = { registry = "https://pypi.org/simple" }
257
+ dependencies = [
258
+ { name = "cfgv" },
259
+ { name = "identify" },
260
+ { name = "nodeenv" },
261
+ { name = "pyyaml" },
262
+ { name = "virtualenv" },
263
+ ]
264
+ sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792 }
265
+ wheels = [
266
+ { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965 },
267
+ ]
268
+
208
269
  [[package]]
209
270
  name = "pygments"
210
271
  version = "2.19.1"
@@ -263,6 +324,50 @@ wheels = [
263
324
  { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
264
325
  ]
265
326
 
327
+ [[package]]
328
+ name = "pyyaml"
329
+ version = "6.0.2"
330
+ source = { registry = "https://pypi.org/simple" }
331
+ sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 }
332
+ wheels = [
333
+ { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 },
334
+ { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 },
335
+ { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 },
336
+ { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 },
337
+ { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 },
338
+ { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 },
339
+ { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 },
340
+ { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 },
341
+ { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 },
342
+ { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 },
343
+ { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 },
344
+ { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 },
345
+ { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 },
346
+ { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 },
347
+ { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 },
348
+ { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 },
349
+ { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 },
350
+ { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 },
351
+ { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 },
352
+ { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 },
353
+ { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 },
354
+ { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 },
355
+ { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 },
356
+ { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 },
357
+ { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 },
358
+ { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 },
359
+ { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 },
360
+ { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 },
361
+ { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 },
362
+ { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 },
363
+ { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 },
364
+ { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 },
365
+ { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 },
366
+ { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 },
367
+ { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 },
368
+ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
369
+ ]
370
+
266
371
  [[package]]
267
372
  name = "rich"
268
373
  version = "14.0.0"
@@ -400,9 +505,24 @@ wheels = [
400
505
  { url = "https://files.pythonhosted.org/packages/12/64/af4aa07bc1c525b1fefd1686d31a43a74eac51e74046755ffdca4502784d/uv-0.5.31-py3-none-win_arm64.whl", hash = "sha256:51ceab5a128dd22bcd62489107563e10084e13ed9c15107193c2d7d1139979f4", size = 15776619 },
401
506
  ]
402
507
 
508
+ [[package]]
509
+ name = "virtualenv"
510
+ version = "20.34.0"
511
+ source = { registry = "https://pypi.org/simple" }
512
+ dependencies = [
513
+ { name = "distlib" },
514
+ { name = "filelock" },
515
+ { name = "platformdirs" },
516
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
517
+ ]
518
+ sdist = { url = "https://files.pythonhosted.org/packages/1c/14/37fcdba2808a6c615681cd216fecae00413c9dab44fb2e57805ecf3eaee3/virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a", size = 6003808 }
519
+ wheels = [
520
+ { url = "https://files.pythonhosted.org/packages/76/06/04c8e804f813cf972e3262f3f8584c232de64f0cde9f703b46cf53a45090/virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", size = 5983279 },
521
+ ]
522
+
403
523
  [[package]]
404
524
  name = "workers-py"
405
- version = "1.1.7"
525
+ version = "1.2.0"
406
526
  source = { editable = "." }
407
527
  dependencies = [
408
528
  { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
@@ -423,6 +543,7 @@ dev = [
423
543
  { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
424
544
  { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
425
545
  { name = "mypy" },
546
+ { name = "pre-commit" },
426
547
  { name = "pytest" },
427
548
  { name = "ruff" },
428
549
  ]
@@ -440,6 +561,7 @@ dev = [
440
561
  { name = "black", specifier = ">=23.0.0" },
441
562
  { name = "click", specifier = ">=8.0.0" },
442
563
  { name = "mypy", specifier = ">=1.0.0" },
564
+ { name = "pre-commit", specifier = ">=4.3.0" },
443
565
  { name = "pytest", specifier = ">=7.0.0" },
444
566
  { name = "ruff", specifier = ">=0.1.0" },
445
567
  ]
@@ -1 +0,0 @@
1
-
File without changes
File without changes
File without changes