oceanum-prax-sdk 0.3.2__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.
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env python3
2
+ """Validate exported release provenance and plan an immutable PyPI upload."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import hashlib
8
+ import json
9
+ import os
10
+ import re
11
+ import shutil
12
+ import sys
13
+ import time
14
+ import tomllib
15
+ import urllib.error
16
+ import urllib.request
17
+ from pathlib import Path, PurePosixPath
18
+
19
+ PYPI_JSON = "https://pypi.org/pypi/{package}/{version}/json"
20
+ PACKAGE_NAMES = {"sdk": "oceanum-prax-sdk", "cli": "oceanum-prax-cli"}
21
+
22
+
23
+ def validate_snapshot(
24
+ snapshot_path: Path, expected_package: str
25
+ ) -> tuple[bool, str | None]:
26
+ root = snapshot_path.resolve().parent
27
+ snapshot = json.loads(snapshot_path.read_text(encoding="utf-8"))
28
+ package = snapshot.get("package")
29
+ if package not in PACKAGE_NAMES or PACKAGE_NAMES[package] != expected_package:
30
+ raise ValueError("Snapshot package does not match the expected distribution")
31
+ if not re.fullmatch(r"[0-9a-f]{40}", snapshot.get("source_commit", "")):
32
+ raise ValueError("Snapshot source_commit must be a full Git SHA")
33
+ tooling_commit = snapshot.get("tooling_commit", snapshot["source_commit"])
34
+ if not isinstance(tooling_commit, str) or not re.fullmatch(
35
+ r"[0-9a-f]{40}", tooling_commit
36
+ ):
37
+ raise ValueError("Snapshot tooling_commit must be a full Git SHA")
38
+ files = snapshot.get("files")
39
+ if not isinstance(files, dict) or not files:
40
+ raise ValueError("Snapshot must include its exported file hashes")
41
+ for relative, expected_hash in files.items():
42
+ path = PurePosixPath(relative)
43
+ if path.is_absolute() or ".." in path.parts:
44
+ raise ValueError(f"Unsafe exported path in snapshot: {relative}")
45
+ file_path = root.joinpath(*path.parts)
46
+ if file_path.is_symlink() or not file_path.is_file():
47
+ raise ValueError(f"Snapshot file is missing: {relative}")
48
+ actual_hash = hashlib.sha256(file_path.read_bytes()).hexdigest()
49
+ if actual_hash != expected_hash:
50
+ raise ValueError(f"Snapshot file hash mismatch: {relative}")
51
+ tooling_files = snapshot.get("tooling_files", {})
52
+ if not isinstance(tooling_files, dict) or any(
53
+ files.get(relative) != expected_hash
54
+ for relative, expected_hash in tooling_files.items()
55
+ ):
56
+ raise ValueError("Snapshot tooling file hashes do not match exported files")
57
+ expected_files = set(files) | {"SOURCE_SNAPSHOT.json"}
58
+ actual_files = {
59
+ path.relative_to(root).as_posix()
60
+ for path in root.rglob("*")
61
+ if path.is_file()
62
+ and ".git" not in path.relative_to(root).parts
63
+ and ".venv" not in path.relative_to(root).parts
64
+ and "__pycache__" not in path.relative_to(root).parts
65
+ }
66
+ if actual_files != expected_files:
67
+ raise ValueError("Mirror contains files outside the validated snapshot")
68
+ project = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))[
69
+ "project"
70
+ ]
71
+ if project.get("name") != expected_package:
72
+ raise ValueError("pyproject distribution name does not match snapshot")
73
+
74
+ release_tag = snapshot.get("release_tag")
75
+ release_version = snapshot.get("release_version")
76
+ if release_tag is None and release_version is None:
77
+ if project.get("version") != "0.0.0":
78
+ raise ValueError("Non-release snapshot must retain the development version")
79
+ return False, None
80
+ if (
81
+ not isinstance(release_tag, str)
82
+ or not re.fullmatch(r"v[0-9]+\.[0-9]+\.[0-9]+", release_tag)
83
+ or release_version != release_tag[1:]
84
+ or project.get("version") != release_version
85
+ ):
86
+ raise ValueError("Snapshot release tag, version, and package metadata disagree")
87
+ return True, release_version
88
+
89
+
90
+ def artifact_hashes(directory: Path) -> dict[str, str]:
91
+ artifacts = sorted(
92
+ path
93
+ for path in directory.iterdir()
94
+ if path.name.endswith(".whl") or path.name.endswith(".tar.gz")
95
+ )
96
+ if len(artifacts) != 2 or {path.suffix for path in artifacts} != {".whl", ".gz"}:
97
+ raise ValueError(f"Expected exactly one wheel and one sdist in {directory}")
98
+ return {
99
+ path.name: hashlib.sha256(path.read_bytes()).hexdigest() for path in artifacts
100
+ }
101
+
102
+
103
+ def release_json(package: str, version: str) -> dict | None:
104
+ request = urllib.request.Request(
105
+ PYPI_JSON.format(package=package, version=version),
106
+ headers={"Accept": "application/json"},
107
+ )
108
+ try:
109
+ with urllib.request.urlopen(request, timeout=20) as response:
110
+ return json.load(response)
111
+ except urllib.error.HTTPError as error:
112
+ if error.code == 404:
113
+ return None
114
+ raise
115
+
116
+
117
+ def files_to_publish(package: str, version: str, directory: Path) -> list[str]:
118
+ local = artifact_hashes(directory)
119
+ published = release_json(package, version)
120
+ if published is None:
121
+ return sorted(local)
122
+ remote = {
123
+ file["filename"]: file["digests"]["sha256"]
124
+ for file in published.get("urls", [])
125
+ }
126
+ if not set(remote).issubset(local) or any(
127
+ local[filename] != digest for filename, digest in remote.items()
128
+ ):
129
+ raise ValueError(
130
+ f"PyPI already has conflicting files for {package} {version}; "
131
+ "refusing to overwrite"
132
+ )
133
+ return sorted(set(local) - set(remote))
134
+
135
+
136
+ def should_publish(package: str, version: str, directory: Path) -> bool:
137
+ return bool(files_to_publish(package, version, directory))
138
+
139
+
140
+ def wait_for_sdk(version: str, *, attempts: int = 30, delay: float = 10) -> None:
141
+ for attempt in range(attempts):
142
+ release = release_json(PACKAGE_NAMES["sdk"], version)
143
+ urls = release.get("urls", []) if release else []
144
+ has_wheel = any(item.get("packagetype") == "bdist_wheel" for item in urls)
145
+ has_sdist = any(item.get("packagetype") == "sdist" for item in urls)
146
+ if has_wheel and has_sdist:
147
+ return
148
+ if attempt + 1 < attempts:
149
+ time.sleep(delay)
150
+ raise ValueError(f"Matching SDK {version} is not fully available on PyPI")
151
+
152
+
153
+ def write_output(name: str, value: str) -> None:
154
+ output = os.environ.get("GITHUB_OUTPUT")
155
+ if output:
156
+ with open(output, "a", encoding="utf-8") as stream:
157
+ stream.write(f"{name}={value}\n")
158
+
159
+
160
+ def main() -> int:
161
+ parser = argparse.ArgumentParser(description=__doc__)
162
+ parser.add_argument("--snapshot", type=Path, required=True)
163
+ parser.add_argument("--expected-package", required=True)
164
+ parser.add_argument("--validate-only", action="store_true")
165
+ parser.add_argument("--distribution-dir", type=Path)
166
+ parser.add_argument("--publish-dir", type=Path)
167
+ parser.add_argument("--require-sdk", action="store_true")
168
+ args = parser.parse_args()
169
+ try:
170
+ is_release, version = validate_snapshot(args.snapshot, args.expected_package)
171
+ write_output("is_release", str(is_release).lower())
172
+ if args.validate_only or not is_release:
173
+ print(f"is_release={str(is_release).lower()}")
174
+ return 0
175
+ if args.distribution_dir is None:
176
+ raise ValueError("--distribution-dir is required for publishing preflight")
177
+ if args.require_sdk:
178
+ assert version is not None
179
+ wait_for_sdk(version)
180
+ assert version is not None
181
+ files = files_to_publish(args.expected_package, version, args.distribution_dir)
182
+ publish = bool(files)
183
+ if args.publish_dir:
184
+ args.publish_dir.mkdir(parents=True, exist_ok=True)
185
+ for filename in files:
186
+ shutil.copy2(
187
+ args.distribution_dir / filename, args.publish_dir / filename
188
+ )
189
+ write_output("should_publish", str(publish).lower())
190
+ print(f"should_publish={str(publish).lower()}")
191
+ return 0
192
+ except (OSError, ValueError, json.JSONDecodeError, KeyError) as error:
193
+ print(f"PyPI release preflight failed: {error}", file=sys.stderr)
194
+ return 1
195
+
196
+
197
+ if __name__ == "__main__":
198
+ raise SystemExit(main())
@@ -0,0 +1,62 @@
1
+ name: Publish SDK to PyPI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "SOURCE_SNAPSHOT.json"
8
+ - "LICENSE"
9
+ - "pyproject.toml"
10
+ - "src/**"
11
+ - ".github/workflows/publish.yml"
12
+ - ".github/scripts/check_pypi_release.py"
13
+ workflow_dispatch:
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ concurrency:
19
+ group: pypi-${{ github.repository }}
20
+ cancel-in-progress: false
21
+
22
+ jobs:
23
+ publish:
24
+ if: github.ref == 'refs/heads/main'
25
+ runs-on: ubuntu-latest
26
+ environment:
27
+ name: pypi
28
+ url: https://pypi.org/project/oceanum-prax-sdk/
29
+ permissions:
30
+ contents: read
31
+ id-token: write
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+
35
+ - name: Set up uv
36
+ uses: astral-sh/setup-uv@v5
37
+ with:
38
+ python-version: "3.12"
39
+ enable-cache: false
40
+
41
+ - id: release
42
+ name: Validate release snapshot provenance
43
+ run: python .github/scripts/check_pypi_release.py --snapshot SOURCE_SNAPSHOT.json --expected-package oceanum-prax-sdk --validate-only
44
+
45
+ - name: Build and validate distributions
46
+ if: steps.release.outputs.is_release == 'true'
47
+ run: |
48
+ mkdir -p "$RUNNER_TEMP/package-source"
49
+ git archive HEAD | tar -x -C "$RUNNER_TEMP/package-source"
50
+ uv build --no-sources "$RUNNER_TEMP/package-source" --out-dir "$RUNNER_TEMP/dist"
51
+ uvx --from twine twine check "$RUNNER_TEMP"/dist/*
52
+
53
+ - id: preflight
54
+ name: Prepare only missing immutable PyPI files
55
+ if: steps.release.outputs.is_release == 'true'
56
+ run: python .github/scripts/check_pypi_release.py --snapshot SOURCE_SNAPSHOT.json --expected-package oceanum-prax-sdk --distribution-dir "$RUNNER_TEMP/dist" --publish-dir "$RUNNER_TEMP/dist-to-upload"
57
+
58
+ - name: Publish to PyPI
59
+ if: steps.preflight.outputs.should_publish == 'true'
60
+ uses: pypa/gh-action-pypi-publish@release/v1
61
+ with:
62
+ packages-dir: ${{ runner.temp }}/dist-to-upload
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022, Oceanum Developers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.5
2
+ Name: oceanum-prax-sdk
3
+ Version: 0.3.2
4
+ Summary: Typed clients and data models for PRAX platform
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Keywords: api,client,prax,pydantic
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: httpx>=0.27.0
13
+ Requires-Dist: pydantic-settings>=2.0
14
+ Requires-Dist: pydantic<3.0,>=2.0
15
+ Requires-Dist: pyyaml>=6.0
16
+ Provides-Extra: dev
17
+ Requires-Dist: datamodel-code-generator>=0.25.0; extra == 'dev'
18
+ Requires-Dist: mypy>=1.11.0; extra == 'dev'
19
+ Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
20
+ Requires-Dist: ruff>=0.6.0; extra == 'dev'
21
+ Requires-Dist: types-pyyaml>=6.0.12; extra == 'dev'
22
+ Provides-Extra: test
23
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
24
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
25
+ Requires-Dist: pytest-timeout>=2.3.1; extra == 'test'
26
+ Requires-Dist: pytest>=8.0.0; extra == 'test'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # prax (PRAX SDK)
30
+
31
+ Typed HTTP clients, API models, and authored input helpers for PRAX applications.
32
+ The Python distribution and import name are **`prax`**.
33
+
34
+ ## Working with PRAX
35
+
36
+ Use the SDK to:
37
+
38
+ - Read exact service-instance health, metrics and usage, and stream logs with
39
+ `PraxAPIClient`.
40
+ - Read and compare authored ProjectSpec revisions with bounded history operations.
41
+ - Validate and manipulate pipeline/stage inputs with `prax.spec` and
42
+ `prax.project_spec_workflow`.
43
+ - Work with typed request/response data through `prax.models`.
44
+
45
+ The asynchronous client supports context-manager cleanup, request timeouts, and
46
+ bounded pagination. Authorize requests with an API token for your environment;
47
+ resource access follows your account's permissions.
48
+
49
+ ## Example
50
+
51
+ Set `PRAX_API_URL` to your PRAX endpoint and `PRAX_API_TOKEN` to your API token.
52
+ Substitute a project name and revision number accessible to your account.
53
+
54
+ <!-- sdk-http-example:start -->
55
+ ```python
56
+ import asyncio
57
+ import os
58
+
59
+ from prax import PraxAPIClient
60
+
61
+
62
+ async def main():
63
+ async with PraxAPIClient(
64
+ os.environ["PRAX_API_URL"], token=os.environ["PRAX_API_TOKEN"]
65
+ ) as client:
66
+ revision = await client.get_project_spec_revision("forecast-project", 1)
67
+ print(revision.model_dump_json())
68
+
69
+
70
+ if __name__ == "__main__":
71
+ asyncio.run(main())
72
+ ```
73
+ <!-- sdk-http-example:end -->
74
+
75
+ ## Following service-instance logs
76
+
77
+ Use the complete project, stage, service and stage-incarnation identity returned
78
+ by the API. Open logs with `async with`, not `await`: the context owns the response
79
+ and closes it on completion, an early exit, error or cancellation.
80
+
81
+ ```python
82
+ async with client.stream_service_instance_logs(
83
+ project_ref="forecast-project",
84
+ stage_ref="production",
85
+ service_name="api",
86
+ stage_incarnation=stage_incarnation,
87
+ org="oceanum",
88
+ follow=True,
89
+ tail=100,
90
+ ) as response:
91
+ async for line in response.aiter_lines():
92
+ print(line)
93
+ ```
94
+
95
+ Read the response only inside this context. Follow has no idle read deadline;
96
+ connection, write and pool timeouts remain those of the client. Snapshot requests
97
+ retain the normal read timeout. Injected/shared HTTP clients remain caller-owned.
98
+ No status lookup or repeated reconnection is needed to receive a live line.
99
+
100
+ ## Errors
101
+
102
+ Client operations raise `PraxAPIError`. Use its `.status`, `.code`, and `.reason`
103
+ attributes to handle errors programmatically.
104
+
105
+ Catch this exception around awaited client operations to distinguish API responses
106
+ from transport failures and decide whether to retry.
107
+
108
+ ## Requirements
109
+
110
+ Python **3.11+** is required. Runtime dependencies are Pydantic, Pydantic Settings,
111
+ HTTPX, and PyYAML; version bounds are declared in `pyproject.toml`.
112
+
113
+ ## Versions
114
+
115
+ The SDK and CLI share the same release version. Each CLI release depends on the
116
+ matching `prax` SDK release. To inspect your installed SDK version:
117
+
118
+ ```python
119
+ from importlib.metadata import version
120
+
121
+ print(version("oceanum-prax-sdk"))
122
+ ```
123
+
124
+ ## See also
125
+
126
+ - [PRAX command-line interface](https://github.com/oceanum/prax-cli)
@@ -0,0 +1,98 @@
1
+ # prax (PRAX SDK)
2
+
3
+ Typed HTTP clients, API models, and authored input helpers for PRAX applications.
4
+ The Python distribution and import name are **`prax`**.
5
+
6
+ ## Working with PRAX
7
+
8
+ Use the SDK to:
9
+
10
+ - Read exact service-instance health, metrics and usage, and stream logs with
11
+ `PraxAPIClient`.
12
+ - Read and compare authored ProjectSpec revisions with bounded history operations.
13
+ - Validate and manipulate pipeline/stage inputs with `prax.spec` and
14
+ `prax.project_spec_workflow`.
15
+ - Work with typed request/response data through `prax.models`.
16
+
17
+ The asynchronous client supports context-manager cleanup, request timeouts, and
18
+ bounded pagination. Authorize requests with an API token for your environment;
19
+ resource access follows your account's permissions.
20
+
21
+ ## Example
22
+
23
+ Set `PRAX_API_URL` to your PRAX endpoint and `PRAX_API_TOKEN` to your API token.
24
+ Substitute a project name and revision number accessible to your account.
25
+
26
+ <!-- sdk-http-example:start -->
27
+ ```python
28
+ import asyncio
29
+ import os
30
+
31
+ from prax import PraxAPIClient
32
+
33
+
34
+ async def main():
35
+ async with PraxAPIClient(
36
+ os.environ["PRAX_API_URL"], token=os.environ["PRAX_API_TOKEN"]
37
+ ) as client:
38
+ revision = await client.get_project_spec_revision("forecast-project", 1)
39
+ print(revision.model_dump_json())
40
+
41
+
42
+ if __name__ == "__main__":
43
+ asyncio.run(main())
44
+ ```
45
+ <!-- sdk-http-example:end -->
46
+
47
+ ## Following service-instance logs
48
+
49
+ Use the complete project, stage, service and stage-incarnation identity returned
50
+ by the API. Open logs with `async with`, not `await`: the context owns the response
51
+ and closes it on completion, an early exit, error or cancellation.
52
+
53
+ ```python
54
+ async with client.stream_service_instance_logs(
55
+ project_ref="forecast-project",
56
+ stage_ref="production",
57
+ service_name="api",
58
+ stage_incarnation=stage_incarnation,
59
+ org="oceanum",
60
+ follow=True,
61
+ tail=100,
62
+ ) as response:
63
+ async for line in response.aiter_lines():
64
+ print(line)
65
+ ```
66
+
67
+ Read the response only inside this context. Follow has no idle read deadline;
68
+ connection, write and pool timeouts remain those of the client. Snapshot requests
69
+ retain the normal read timeout. Injected/shared HTTP clients remain caller-owned.
70
+ No status lookup or repeated reconnection is needed to receive a live line.
71
+
72
+ ## Errors
73
+
74
+ Client operations raise `PraxAPIError`. Use its `.status`, `.code`, and `.reason`
75
+ attributes to handle errors programmatically.
76
+
77
+ Catch this exception around awaited client operations to distinguish API responses
78
+ from transport failures and decide whether to retry.
79
+
80
+ ## Requirements
81
+
82
+ Python **3.11+** is required. Runtime dependencies are Pydantic, Pydantic Settings,
83
+ HTTPX, and PyYAML; version bounds are declared in `pyproject.toml`.
84
+
85
+ ## Versions
86
+
87
+ The SDK and CLI share the same release version. Each CLI release depends on the
88
+ matching `prax` SDK release. To inspect your installed SDK version:
89
+
90
+ ```python
91
+ from importlib.metadata import version
92
+
93
+ print(version("oceanum-prax-sdk"))
94
+ ```
95
+
96
+ ## See also
97
+
98
+ - [PRAX command-line interface](https://github.com/oceanum/prax-cli)
@@ -0,0 +1,60 @@
1
+ {
2
+ "files": {
3
+ ".github/scripts/check_pypi_release.py": "87c9e2d2834541e9fbf631d2b281514952fcd96abf9a2937eb6d007a93c063aa",
4
+ ".github/workflows/publish.yml": "236641898fe29afb92a512999b1881a986d060f10f4d85ebf59d344e51f4d3bb",
5
+ "LICENSE": "cc79a4f8cc49e9db712786cf7df8a962f1da42ea390be5fecededd9eafaaf3bb",
6
+ "README.md": "25e9f0767400767cb6b0bccc44011cae03d3abd78c3769ec4a887faced5eed55",
7
+ "pyproject.toml": "d8ef2786a4d8d37372fd1ce940437b79ba04507cc0c87f83aec35ae2a2c81d52",
8
+ "src/prax/__init__.py": "cd41a84b73cc1cf0e65dc7904fde540e443d22502d3a34675170bfeded4e081e",
9
+ "src/prax/_version.py": "8f5323cc90d87b05740ede2dea97a2e4f8d09c9a956aae3d945e182b8c79e327",
10
+ "src/prax/api.py": "c2d51329286e5aa66dbe06e2f06dcf6665da4f48220b0c211c85b10ebc96603a",
11
+ "src/prax/files/__init__.py": "3333a4e8da259746c5ee671d066156296165d40c0c601f32a7ae8f3004271d01",
12
+ "src/prax/files/base.py": "4fcce9d2c44babfacbdd338a30210b8181fe7e6f8d0aa8bd561f90f13bb47469",
13
+ "src/prax/models/__init__.py": "2836f33525efeeca58839e3de431c4eb4ee3d2f76f6e284c7f5b3c0548007283",
14
+ "src/prax/project_spec_workflow.py": "9b3d4218f2e7c34c5573e4f27497ff6a4b41e5ea168fd823e8a9d9780be11f22",
15
+ "src/prax/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
16
+ "src/prax/settings.py": "8051b865132b9bd63ed2fed1956cc21a06ace4e49526c5d06acd32ce7f31cdbf",
17
+ "src/prax/spec/__init__.py": "8e4809da07c289b64c19fa712ac278d5b3a912ea2729ba4b324e1ec2b942071e",
18
+ "src/prax/spec/_base_types.py": "98f4151503e40c3f8e1cdf9a546c728ebf527db0f1438cb5874972f259d0a617",
19
+ "src/prax/spec/_workflow.py": "42a4861ac84a293417f4111ab0d188e40f50a959dc1bae33662be2b02dc52f58",
20
+ "src/prax/spec/pipeline.py": "4dc8d660d8e695906897973c9bb5ab3a9e1a81899db104f74eceb2a4eec7ea15",
21
+ "src/prax/spec/resources.py": "e659a7c2f9d443eb330a9537dd2ec7455fa479f863c1b69af2c590548db44c11",
22
+ "src/prax/spec/secret_refs.py": "00e0b8f9f9a4da4d8d4405bc9b94eddaf287883d4b103381145a927b9ff0c3ee",
23
+ "src/prax/spec/source_refs.py": "1fe0dcd6c60cfc618bdb86fa5241ea67849813d808462dac4ede1bed6ed1e185",
24
+ "src/prax/spec/stage.py": "8f77d8fe58a0f2b98336efd9826920e9ad59ded76bd9baf88a5d59eead3a2532",
25
+ "src/prax/utils.py": "81185a7529283b19999da234e8246f761fceaf027e46f9a139f7d638b5e14494"
26
+ },
27
+ "package": "sdk",
28
+ "release_tag": "v0.3.2",
29
+ "release_version": "0.3.2",
30
+ "source_commit": "b40191a0a15e5459c7e5b672a41cb80326e33c1b",
31
+ "source_files": {
32
+ ".github/scripts/check_pypi_release.py": "49454c3b95615cba02c209d6ed63e2f27bb5507553f987b0bffcd095a7ad7f76",
33
+ ".github/workflows/publish.yml": "236641898fe29afb92a512999b1881a986d060f10f4d85ebf59d344e51f4d3bb",
34
+ "LICENSE": "cc79a4f8cc49e9db712786cf7df8a962f1da42ea390be5fecededd9eafaaf3bb",
35
+ "README.md": "25e9f0767400767cb6b0bccc44011cae03d3abd78c3769ec4a887faced5eed55",
36
+ "pyproject.toml": "cce473f073295243a84873b14a09a8bf477d5d3b5a462b27d6f80ff64c186740",
37
+ "src/prax/__init__.py": "cd41a84b73cc1cf0e65dc7904fde540e443d22502d3a34675170bfeded4e081e",
38
+ "src/prax/_version.py": "8f5323cc90d87b05740ede2dea97a2e4f8d09c9a956aae3d945e182b8c79e327",
39
+ "src/prax/api.py": "c2d51329286e5aa66dbe06e2f06dcf6665da4f48220b0c211c85b10ebc96603a",
40
+ "src/prax/files/__init__.py": "3333a4e8da259746c5ee671d066156296165d40c0c601f32a7ae8f3004271d01",
41
+ "src/prax/files/base.py": "4fcce9d2c44babfacbdd338a30210b8181fe7e6f8d0aa8bd561f90f13bb47469",
42
+ "src/prax/models/__init__.py": "2836f33525efeeca58839e3de431c4eb4ee3d2f76f6e284c7f5b3c0548007283",
43
+ "src/prax/project_spec_workflow.py": "9b3d4218f2e7c34c5573e4f27497ff6a4b41e5ea168fd823e8a9d9780be11f22",
44
+ "src/prax/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
45
+ "src/prax/settings.py": "8051b865132b9bd63ed2fed1956cc21a06ace4e49526c5d06acd32ce7f31cdbf",
46
+ "src/prax/spec/__init__.py": "8e4809da07c289b64c19fa712ac278d5b3a912ea2729ba4b324e1ec2b942071e",
47
+ "src/prax/spec/_base_types.py": "98f4151503e40c3f8e1cdf9a546c728ebf527db0f1438cb5874972f259d0a617",
48
+ "src/prax/spec/_workflow.py": "42a4861ac84a293417f4111ab0d188e40f50a959dc1bae33662be2b02dc52f58",
49
+ "src/prax/spec/pipeline.py": "4dc8d660d8e695906897973c9bb5ab3a9e1a81899db104f74eceb2a4eec7ea15",
50
+ "src/prax/spec/resources.py": "e659a7c2f9d443eb330a9537dd2ec7455fa479f863c1b69af2c590548db44c11",
51
+ "src/prax/spec/secret_refs.py": "00e0b8f9f9a4da4d8d4405bc9b94eddaf287883d4b103381145a927b9ff0c3ee",
52
+ "src/prax/spec/source_refs.py": "1fe0dcd6c60cfc618bdb86fa5241ea67849813d808462dac4ede1bed6ed1e185",
53
+ "src/prax/spec/stage.py": "8f77d8fe58a0f2b98336efd9826920e9ad59ded76bd9baf88a5d59eead3a2532",
54
+ "src/prax/utils.py": "81185a7529283b19999da234e8246f761fceaf027e46f9a139f7d638b5e14494"
55
+ },
56
+ "tooling_commit": "4277b49c22b7d99b6ce4b8ec7afb253a7e843c50",
57
+ "tooling_files": {
58
+ ".github/scripts/check_pypi_release.py": "87c9e2d2834541e9fbf631d2b281514952fcd96abf9a2937eb6d007a93c063aa"
59
+ }
60
+ }
@@ -0,0 +1,120 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "oceanum-prax-sdk"
7
+ # Development placeholder; release exports are stamped from the monorepo tag.
8
+ version = "0.3.2"
9
+ description = "Typed clients and data models for PRAX platform"
10
+ readme = "README.md"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ requires-python = ">=3.11"
14
+ keywords = ["prax", "pydantic", "client", "api"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Development Status :: 3 - Alpha",
19
+ ]
20
+
21
+ dependencies = [
22
+ "pydantic>=2.0,<3.0",
23
+ "pydantic-settings>=2.0",
24
+ "httpx>=0.27.0",
25
+ "PyYAML>=6.0",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ test = [
30
+ "pytest>=8.0.0",
31
+ "pytest-cov>=4.1.0",
32
+ "pytest-asyncio>=0.23.0",
33
+ "pytest-timeout>=2.3.1",
34
+ ]
35
+ dev = [
36
+ "ruff>=0.6.0",
37
+ "mypy>=1.11.0",
38
+ "pre-commit>=3.5.0",
39
+ "types-PyYAML>=6.0.12",
40
+ "datamodel-code-generator>=0.25.0",
41
+ ]
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/prax"]
45
+
46
+ [tool.pytest.ini_options]
47
+ minversion = "8.0"
48
+ addopts = [
49
+ "-ra",
50
+ "-q",
51
+ "--strict-markers",
52
+ "--strict-config",
53
+ "--tb=short",
54
+ ]
55
+ testpaths = ["tests"]
56
+ python_files = ["test_*.py", "*_test.py"]
57
+ python_classes = ["Test*"]
58
+ python_functions = ["test_*"]
59
+
60
+ [tool.ruff]
61
+ target-version = "py311"
62
+ line-length = 100
63
+ exclude = [
64
+ ".git",
65
+ "build",
66
+ "dist",
67
+ ".venv",
68
+ "__pycache__",
69
+ ]
70
+
71
+ [tool.ruff.lint]
72
+ select = [
73
+ "E", # pycodestyle errors
74
+ "W", # pycodestyle warnings
75
+ "F", # pyflakes
76
+ "I", # isort
77
+ "N", # pep8-naming
78
+ "UP", # pyupgrade
79
+ "B", # flake8-bugbear
80
+ "C4", # flake8-comprehensions
81
+ "SIM", # flake8-simplify
82
+ "RET", # flake8-return
83
+ "ARG", # flake8-unused-arguments
84
+ "PTH", # flake8-use-pathlib
85
+ "PL", # pylint
86
+ "RUF", # Ruff-specific
87
+ ]
88
+ ignore = [
89
+ "E501", # Line too long (handled by formatter)
90
+ "B008", # Function call in defaults (Pydantic pattern)
91
+ "PLR0913", # Too many arguments
92
+ "ARG001", # Unused function argument
93
+ "ARG002", # Unused method argument
94
+ "PLW1641", # Object does not implement __hash__ (Pydantic models)
95
+ ]
96
+
97
+ [tool.ruff.lint.per-file-ignores]
98
+ "tests/**/*.py" = ["F401", "F403"]
99
+
100
+ [tool.ruff.lint.isort]
101
+ known-first-party = ["prax"]
102
+ section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
103
+
104
+ [tool.ruff.format]
105
+ quote-style = "double"
106
+ indent-style = "space"
107
+ line-ending = "auto"
108
+
109
+ [tool.mypy]
110
+ python_version = "3.12"
111
+ exclude = ["src/prax/models/"]
112
+ warn_return_any = true
113
+ warn_unused_configs = true
114
+ disallow_untyped_defs = false
115
+ check_untyped_defs = true
116
+ warn_redundant_casts = true
117
+ warn_unused_ignores = true
118
+ warn_no_return = true
119
+ strict_optional = true
120
+ pretty = true