shiftgate 0.1.2__tar.gz → 0.1.4__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.
- {shiftgate-0.1.2 → shiftgate-0.1.4}/PKG-INFO +1 -1
- {shiftgate-0.1.2 → shiftgate-0.1.4}/pyproject.toml +17 -1
- shiftgate-0.1.4/shiftgate/data/__init__.py +1 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/registry/task_registry.py +33 -13
- shiftgate-0.1.4/tests/test_packaging.py +178 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/tests/test_registry.py +23 -0
- shiftgate-0.1.2/.github/workflows/release.yml +0 -59
- {shiftgate-0.1.2 → shiftgate-0.1.4}/.gitignore +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/README.md +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/__init__.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/cli.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4/shiftgate}/data/default_tasks.json +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/feedback/__init__.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/feedback/loop.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/registry/__init__.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/registry/adapter_registry.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/registry/schemas.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/router/__init__.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/router/embedder.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/router/matcher.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/router/router.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/runtime/__init__.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/runtime/backend.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/utils/__init__.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/shiftgate/utils/display.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/tests/__init__.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/tests/test_feedback.py +0 -0
- {shiftgate-0.1.2 → shiftgate-0.1.4}/tests/test_router.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shiftgate
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Intelligent routing layer that automatically selects the right LoRA adapter for each task in your local agent loop.
|
|
5
5
|
Project-URL: Homepage, https://github.com/shiftgate-ai/shiftgate
|
|
6
6
|
Project-URL: Repository, https://github.com/shiftgate-ai/shiftgate
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "shiftgate"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "Intelligent routing layer that automatically selects the right LoRA adapter for each task in your local agent loop."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -47,9 +47,25 @@ Homepage = "https://github.com/shiftgate-ai/shiftgate"
|
|
|
47
47
|
Repository = "https://github.com/shiftgate-ai/shiftgate"
|
|
48
48
|
Issues = "https://github.com/shiftgate-ai/shiftgate/issues"
|
|
49
49
|
|
|
50
|
+
# Wheel: ship the whole importable package. Non-.py data files inside the
|
|
51
|
+
# package directory (e.g. shiftgate/data/default_tasks.json) are included
|
|
52
|
+
# automatically because they live under the packaged directory.
|
|
50
53
|
[tool.hatch.build.targets.wheel]
|
|
51
54
|
packages = ["shiftgate"]
|
|
52
55
|
|
|
56
|
+
# Sdist: must contain the FULL source tree. The wheel published to PyPI is
|
|
57
|
+
# built from the sdist, so a restrictive sdist `include` here silently strips
|
|
58
|
+
# the .py modules out of the resulting wheel (the 0.1.3 regression). Include
|
|
59
|
+
# the entire package plus tests so `uv build` / `python -m build` reproduce a
|
|
60
|
+
# complete wheel.
|
|
61
|
+
[tool.hatch.build.targets.sdist]
|
|
62
|
+
include = [
|
|
63
|
+
"/shiftgate",
|
|
64
|
+
"/tests",
|
|
65
|
+
"/README.md",
|
|
66
|
+
"/pyproject.toml",
|
|
67
|
+
]
|
|
68
|
+
|
|
53
69
|
[tool.pytest.ini_options]
|
|
54
70
|
asyncio_mode = "auto"
|
|
55
71
|
testpaths = ["tests"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Bundled package data for shiftgate."""
|
|
@@ -3,7 +3,7 @@ Task registry: load, persist, and manage TaskCluster definitions.
|
|
|
3
3
|
|
|
4
4
|
The registry reads from (in priority order):
|
|
5
5
|
1. ``~/.shiftgate/tasks.json`` — user-edited / previously saved
|
|
6
|
-
2.
|
|
6
|
+
2. ``shiftgate.data/default_tasks.json`` — bundled defaults (via importlib.resources)
|
|
7
7
|
|
|
8
8
|
On first run (``shiftgate init``) the ``compute_embeddings`` method is called
|
|
9
9
|
to populate ``embedding_centroid`` for every cluster and cache them to
|
|
@@ -12,6 +12,7 @@ to populate ``embedding_centroid`` for every cluster and cache them to
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
+
import importlib.resources
|
|
15
16
|
import json
|
|
16
17
|
import logging
|
|
17
18
|
from pathlib import Path
|
|
@@ -31,8 +32,24 @@ _SHIFTGATE_DIR = Path.home() / ".shiftgate"
|
|
|
31
32
|
_USER_TASKS_PATH = _SHIFTGATE_DIR / "tasks.json"
|
|
32
33
|
_CACHE_PATH = _SHIFTGATE_DIR / "embeddings_cache.npy"
|
|
33
34
|
|
|
34
|
-
#
|
|
35
|
-
|
|
35
|
+
# Legacy dev-checkout path kept for backwards compatibility with source trees
|
|
36
|
+
# that still ship repo-root ``data/default_tasks.json``.
|
|
37
|
+
_LEGACY_DEFAULT_TASKS_PATH = Path(__file__).parent.parent.parent / "data" / "default_tasks.json"
|
|
38
|
+
_BUNDLED_DEFAULT_TASKS_LABEL = "shiftgate.data/default_tasks.json"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _read_bundled_default_tasks() -> str:
|
|
42
|
+
"""Return the bundled default task registry JSON from the installed package."""
|
|
43
|
+
try:
|
|
44
|
+
resource = importlib.resources.files("shiftgate.data") / "default_tasks.json"
|
|
45
|
+
return resource.read_text(encoding="utf-8")
|
|
46
|
+
except (FileNotFoundError, ModuleNotFoundError, TypeError):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
if _LEGACY_DEFAULT_TASKS_PATH.exists():
|
|
50
|
+
return _LEGACY_DEFAULT_TASKS_PATH.read_text(encoding="utf-8")
|
|
51
|
+
|
|
52
|
+
raise FileNotFoundError(_BUNDLED_DEFAULT_TASKS_LABEL)
|
|
36
53
|
|
|
37
54
|
|
|
38
55
|
class TaskRegistry:
|
|
@@ -58,22 +75,25 @@ class TaskRegistry:
|
|
|
58
75
|
"""Load the task registry from disk.
|
|
59
76
|
|
|
60
77
|
Prefers the user's ``~/.shiftgate/tasks.json`` and falls back to the
|
|
61
|
-
bundled ``data/default_tasks.json`` if the user file does not exist.
|
|
78
|
+
bundled ``shiftgate.data/default_tasks.json`` if the user file does not exist.
|
|
62
79
|
"""
|
|
63
80
|
if _USER_TASKS_PATH.exists():
|
|
64
81
|
source = _USER_TASKS_PATH
|
|
65
|
-
|
|
66
|
-
source = _DEFAULT_TASKS_PATH
|
|
82
|
+
raw = json.loads(source.read_text(encoding="utf-8"))
|
|
67
83
|
else:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
try:
|
|
85
|
+
raw_text = _read_bundled_default_tasks()
|
|
86
|
+
except FileNotFoundError:
|
|
87
|
+
raise FileNotFoundError(
|
|
88
|
+
f"No task registry found. Expected one of:\n"
|
|
89
|
+
f" {_USER_TASKS_PATH}\n"
|
|
90
|
+
f" {_BUNDLED_DEFAULT_TASKS_LABEL}\n"
|
|
91
|
+
"Run `shiftgate init` to set up the default registry."
|
|
92
|
+
) from None
|
|
93
|
+
source = Path(_BUNDLED_DEFAULT_TASKS_LABEL)
|
|
94
|
+
raw = json.loads(raw_text)
|
|
74
95
|
|
|
75
96
|
logger.debug("Loading task registry from %s", source)
|
|
76
|
-
raw = json.loads(source.read_text(encoding="utf-8"))
|
|
77
97
|
tasks = [TaskCluster.model_validate(t) for t in raw]
|
|
78
98
|
instance = cls(tasks, source_path=source)
|
|
79
99
|
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Packaging tests — guard against the 0.1.3 regression where the published wheel
|
|
3
|
+
shipped without its Python modules (``ModuleNotFoundError: shiftgate.cli``).
|
|
4
|
+
|
|
5
|
+
Why these tests build the *full* pipeline
|
|
6
|
+
-----------------------------------------
|
|
7
|
+
PyPI (and ``uv build`` / ``python -m build`` with no flags) builds the sdist
|
|
8
|
+
first, then builds the wheel **from the extracted sdist**. A wheel built
|
|
9
|
+
directly from the source tree can look complete while the real published wheel
|
|
10
|
+
(built from a too-restrictive sdist) is missing modules. So the regression
|
|
11
|
+
guard below uses the default ``uv build`` (sdist + wheel-from-sdist), exactly
|
|
12
|
+
like a release, and inspects the resulting wheel.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
import tarfile
|
|
23
|
+
import zipfile
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
import pytest
|
|
27
|
+
|
|
28
|
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
29
|
+
|
|
30
|
+
# Skip the whole module gracefully if `uv` isn't on PATH (e.g. minimal CI image).
|
|
31
|
+
_UV = shutil.which("uv")
|
|
32
|
+
pytestmark = pytest.mark.skipif(_UV is None, reason="uv is required to build the package")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _run_build(*args: str, out_dir: Path) -> None:
|
|
36
|
+
"""Build into ``out_dir``. With no extra args, builds sdist then wheel-from-sdist."""
|
|
37
|
+
subprocess.run(
|
|
38
|
+
[_UV, "build", *args, "-o", str(out_dir)],
|
|
39
|
+
cwd=PROJECT_ROOT,
|
|
40
|
+
check=True,
|
|
41
|
+
capture_output=True,
|
|
42
|
+
text=True,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.fixture(scope="module")
|
|
47
|
+
def full_dist(tmp_path_factory: pytest.TempPathFactory) -> dict[str, Path]:
|
|
48
|
+
"""Build the sdist and a wheel-from-sdist once, like a real release.
|
|
49
|
+
|
|
50
|
+
Returns a dict with ``wheel`` and ``sdist`` paths.
|
|
51
|
+
"""
|
|
52
|
+
dist_dir = tmp_path_factory.mktemp("dist")
|
|
53
|
+
_run_build(out_dir=dist_dir) # no flags → sdist + wheel built from that sdist
|
|
54
|
+
|
|
55
|
+
wheels = list(dist_dir.glob("*.whl"))
|
|
56
|
+
sdists = list(dist_dir.glob("*.tar.gz"))
|
|
57
|
+
assert len(wheels) == 1, f"expected one wheel, got {wheels}"
|
|
58
|
+
assert len(sdists) == 1, f"expected one sdist, got {sdists}"
|
|
59
|
+
return {"wheel": wheels[0], "sdist": sdists[0]}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _wheel_names(wheel: Path) -> list[str]:
|
|
63
|
+
with zipfile.ZipFile(wheel) as archive:
|
|
64
|
+
return archive.namelist()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _sdist_names(sdist: Path) -> list[str]:
|
|
68
|
+
with tarfile.open(sdist, "r:gz") as archive:
|
|
69
|
+
return archive.getnames()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Wheel content tests
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
class TestWheelContents:
|
|
77
|
+
def test_wheel_contains_cli_module(self, full_dist: dict[str, Path]) -> None:
|
|
78
|
+
"""The 0.1.3 regression guard: shiftgate/cli.py must be in the wheel."""
|
|
79
|
+
names = _wheel_names(full_dist["wheel"])
|
|
80
|
+
assert "shiftgate/cli.py" in names, (
|
|
81
|
+
"shiftgate/cli.py missing from wheel — this is the 0.1.3 packaging bug. "
|
|
82
|
+
f"Wheel contained: {names}"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def test_wheel_contains_all_subpackages(self, full_dist: dict[str, Path]) -> None:
|
|
86
|
+
"""Every shiftgate subpackage module should ship in the wheel."""
|
|
87
|
+
names = set(_wheel_names(full_dist["wheel"]))
|
|
88
|
+
expected = {
|
|
89
|
+
"shiftgate/__init__.py",
|
|
90
|
+
"shiftgate/cli.py",
|
|
91
|
+
"shiftgate/registry/schemas.py",
|
|
92
|
+
"shiftgate/registry/adapter_registry.py",
|
|
93
|
+
"shiftgate/registry/task_registry.py",
|
|
94
|
+
"shiftgate/router/router.py",
|
|
95
|
+
"shiftgate/router/matcher.py",
|
|
96
|
+
"shiftgate/router/embedder.py",
|
|
97
|
+
"shiftgate/runtime/backend.py",
|
|
98
|
+
"shiftgate/feedback/loop.py",
|
|
99
|
+
"shiftgate/utils/display.py",
|
|
100
|
+
}
|
|
101
|
+
missing = expected - names
|
|
102
|
+
assert not missing, f"wheel is missing modules: {sorted(missing)}"
|
|
103
|
+
|
|
104
|
+
def test_wheel_contains_default_tasks_json(self, full_dist: dict[str, Path]) -> None:
|
|
105
|
+
names = _wheel_names(full_dist["wheel"])
|
|
106
|
+
assert "shiftgate/data/default_tasks.json" in names
|
|
107
|
+
|
|
108
|
+
def test_wheel_default_tasks_json_is_valid(self, full_dist: dict[str, Path]) -> None:
|
|
109
|
+
with zipfile.ZipFile(full_dist["wheel"]) as archive:
|
|
110
|
+
raw = archive.read("shiftgate/data/default_tasks.json")
|
|
111
|
+
tasks = json.loads(raw)
|
|
112
|
+
assert isinstance(tasks, list)
|
|
113
|
+
assert len(tasks) == 10
|
|
114
|
+
assert tasks[0]["id"]
|
|
115
|
+
|
|
116
|
+
def test_wheel_is_not_truncated(self, full_dist: dict[str, Path]) -> None:
|
|
117
|
+
"""A healthy wheel is well above the ~8 KB broken build."""
|
|
118
|
+
size_kb = full_dist["wheel"].stat().st_size / 1024
|
|
119
|
+
assert size_kb > 15, f"wheel is suspiciously small ({size_kb:.1f} KB)"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
# Sdist content tests
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
class TestSdistContents:
|
|
127
|
+
def test_sdist_contains_cli_module(self, full_dist: dict[str, Path]) -> None:
|
|
128
|
+
names = _sdist_names(full_dist["sdist"])
|
|
129
|
+
assert any(n.endswith("shiftgate/cli.py") for n in names), (
|
|
130
|
+
"shiftgate/cli.py missing from sdist — wheels built from it will be broken."
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def test_sdist_contains_default_tasks_json(self, full_dist: dict[str, Path]) -> None:
|
|
134
|
+
names = _sdist_names(full_dist["sdist"])
|
|
135
|
+
assert any(n.endswith("shiftgate/data/default_tasks.json") for n in names)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
# Integration test: install the wheel into a fresh venv and run the CLI
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
class TestWheelInstall:
|
|
143
|
+
def test_installed_wheel_cli_help_runs(
|
|
144
|
+
self, full_dist: dict[str, Path], tmp_path: Path
|
|
145
|
+
) -> None:
|
|
146
|
+
"""Install the wheel into a clean venv and run `shiftgate --help`.
|
|
147
|
+
|
|
148
|
+
Verifies the console-script entry point resolves and ``shiftgate.cli``
|
|
149
|
+
imports cleanly from the installed package (exit code 0).
|
|
150
|
+
"""
|
|
151
|
+
venv_dir = tmp_path / "venv"
|
|
152
|
+
subprocess.run([_UV, "venv", str(venv_dir)], check=True, capture_output=True, text=True)
|
|
153
|
+
|
|
154
|
+
# Install the freshly built wheel (deps resolved from uv's cache).
|
|
155
|
+
subprocess.run(
|
|
156
|
+
[_UV, "pip", "install", "--python", str(venv_dir), str(full_dist["wheel"])],
|
|
157
|
+
check=True,
|
|
158
|
+
capture_output=True,
|
|
159
|
+
text=True,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Locate the installed console script (cross-platform).
|
|
163
|
+
if os.name == "nt":
|
|
164
|
+
script = venv_dir / "Scripts" / "shiftgate.exe"
|
|
165
|
+
else:
|
|
166
|
+
script = venv_dir / "bin" / "shiftgate"
|
|
167
|
+
assert script.exists(), f"console script not installed at {script}"
|
|
168
|
+
|
|
169
|
+
result = subprocess.run(
|
|
170
|
+
[str(script), "--help"],
|
|
171
|
+
capture_output=True,
|
|
172
|
+
text=True,
|
|
173
|
+
)
|
|
174
|
+
assert result.returncode == 0, (
|
|
175
|
+
f"`shiftgate --help` failed (rc={result.returncode}).\n"
|
|
176
|
+
f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}"
|
|
177
|
+
)
|
|
178
|
+
assert "shiftgate" in result.stdout.lower()
|
|
@@ -319,6 +319,29 @@ class TestTaskRegistry:
|
|
|
319
319
|
assert len(reg) == 1
|
|
320
320
|
assert reg.get_task("test_task").name == "Updated Task"
|
|
321
321
|
|
|
322
|
+
def test_load_bundled_defaults_when_no_user_registry(self, tmp_shiftgate):
|
|
323
|
+
"""Packaged defaults load via importlib when ~/.shiftgate/tasks.json is absent."""
|
|
324
|
+
reg = TaskRegistry.load()
|
|
325
|
+
assert len(reg) == 10
|
|
326
|
+
assert reg.get_task("code_python") is not None
|
|
327
|
+
assert reg.get_task("code_sql") is not None
|
|
328
|
+
|
|
329
|
+
def test_user_registry_takes_priority_over_bundled_defaults(self, tmp_shiftgate, sample_task):
|
|
330
|
+
"""Existing user registries continue to win over bundled defaults."""
|
|
331
|
+
import shiftgate.registry.task_registry as tr_mod
|
|
332
|
+
|
|
333
|
+
user_path = tmp_shiftgate / "tasks.json"
|
|
334
|
+
user_path.write_text(
|
|
335
|
+
json.dumps([sample_task.model_dump()]),
|
|
336
|
+
encoding="utf-8",
|
|
337
|
+
)
|
|
338
|
+
assert tr_mod._USER_TASKS_PATH == user_path
|
|
339
|
+
|
|
340
|
+
reg = TaskRegistry.load()
|
|
341
|
+
assert len(reg) == 1
|
|
342
|
+
assert reg.get_task("test_task") is not None
|
|
343
|
+
assert reg.get_task("code_python") is None
|
|
344
|
+
|
|
322
345
|
|
|
323
346
|
# ---------------------------------------------------------------------------
|
|
324
347
|
# _auto_link_adapter helper tests
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
name: Release and Publish
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- 'v*' # Triggers the pipeline whenever a version tag is pushed, e.g., v0.1.0
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
validate:
|
|
10
|
-
name: Validate and Test Code
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
steps:
|
|
13
|
-
- name: Check out repository
|
|
14
|
-
uses: actions/checkout@v4
|
|
15
|
-
|
|
16
|
-
- name: Install uv
|
|
17
|
-
uses: astral-sh/setup-uv@v5
|
|
18
|
-
with:
|
|
19
|
-
enable-cache: true
|
|
20
|
-
|
|
21
|
-
- name: Set up Python
|
|
22
|
-
uses: actions/setup-python@v5
|
|
23
|
-
with:
|
|
24
|
-
python-version-file: "pyproject.toml"
|
|
25
|
-
|
|
26
|
-
- name: Install dependencies & run tests
|
|
27
|
-
run: |
|
|
28
|
-
uv sync --all-extras --dev
|
|
29
|
-
uv run pytest
|
|
30
|
-
|
|
31
|
-
publish:
|
|
32
|
-
name: Build and Publish to PyPI
|
|
33
|
-
needs: validate
|
|
34
|
-
runs-on: ubuntu-latest
|
|
35
|
-
|
|
36
|
-
# Define the target environment configured on PyPI
|
|
37
|
-
environment:
|
|
38
|
-
name: pypi
|
|
39
|
-
url: https://pypi.org/p/shiftgate
|
|
40
|
-
|
|
41
|
-
# CRITICAL: This gives the job permission to request an OIDC id-token from GitHub
|
|
42
|
-
permissions:
|
|
43
|
-
id-token: write
|
|
44
|
-
contents: read
|
|
45
|
-
|
|
46
|
-
steps:
|
|
47
|
-
- name: Check out repository
|
|
48
|
-
uses: actions/checkout@v4
|
|
49
|
-
|
|
50
|
-
- name: Install uv
|
|
51
|
-
uses: astral-sh/setup-uv@v5
|
|
52
|
-
with:
|
|
53
|
-
enable-cache: true
|
|
54
|
-
|
|
55
|
-
- name: Build distribution packages
|
|
56
|
-
run: uv build
|
|
57
|
-
|
|
58
|
-
- name: Publish to PyPI via Trusted Publishing
|
|
59
|
-
run: uv publish --trusted-publishing always
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|