apheris-foundry-sdk 0.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.
- apheris_foundry_sdk-0.2.0/CHANGELOG.md +29 -0
- apheris_foundry_sdk-0.2.0/LICENSE +51 -0
- apheris_foundry_sdk-0.2.0/MANIFEST.in +35 -0
- apheris_foundry_sdk-0.2.0/PKG-INFO +57 -0
- apheris_foundry_sdk-0.2.0/README.md +39 -0
- apheris_foundry_sdk-0.2.0/docs/usage.md +114 -0
- apheris_foundry_sdk-0.2.0/pyproject.toml +68 -0
- apheris_foundry_sdk-0.2.0/setup.cfg +4 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/__init__.py +43 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/cli/__init__.py +12 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/cli/app.py +175 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/cli/config.py +65 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/cli/contract.py +81 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/cli/entrypoint.py +47 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/cli/format.py +39 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/cli/output.py +90 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/cli/runner.py +171 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/cli/types.py +87 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/constants.py +45 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/describe.py +63 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/errors.py +100 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/examples/__init__.py +0 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/examples/basic_module.py +42 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/io.py +30 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/params/__init__.py +13 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/params/describe.py +29 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/params/introspect.py +75 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/params/schema.py +98 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/params/validate.py +63 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/provenance.py +69 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/py.typed +0 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/registry.py +81 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/task.py +226 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/text.py +24 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk/version.py +12 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk.egg-info/PKG-INFO +57 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk.egg-info/SOURCES.txt +38 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk.egg-info/dependency_links.txt +1 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk.egg-info/requires.txt +3 -0
- apheris_foundry_sdk-0.2.0/src/apheris_foundry_sdk.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
## v0.2.0 (2026-07-16)
|
|
2
|
+
|
|
3
|
+
### Feat
|
|
4
|
+
|
|
5
|
+
- add SDK PyPI publishing safeguards (#41) [50c9c](https://github.com//apheris/apheris-foundry-sdk/commit/50c9c693c1388d7e1df67ecfe3a493b488ed7470)
|
|
6
|
+
- accept Path/FilePath/DirectoryPath and their list[] forms as input (#39) [da412](https://github.com//apheris/apheris-foundry-sdk/commit/da412350e7d23db54319effde7863c86bb9ff022)
|
|
7
|
+
- **[EN-6143](https://apheris.atlassian.net/browse/EN-6143)**: add netflow compatibility skill and harness pack (#32) [d028e](https://github.com//apheris/apheris-foundry-sdk/commit/d028efa92f56152dc1096aaaa16cc6f97830f343)
|
|
8
|
+
|
|
9
|
+
## v0.1.1 (2026-07-15)
|
|
10
|
+
|
|
11
|
+
### Fix
|
|
12
|
+
|
|
13
|
+
- raise pydantic floor to >=2.4.0 to clear regex-DoS advisory (#35) [e5280](https://github.com//apheris/apheris-foundry-sdk/commit/e528051b3b33a401392d1a8d994344050b5031ac)
|
|
14
|
+
|
|
15
|
+
## v0.1.0 (2026-07-15)
|
|
16
|
+
|
|
17
|
+
### Feat
|
|
18
|
+
|
|
19
|
+
- --overwrite deletes and recreates the output directory (#31) [b1088](https://github.com//apheris/apheris-foundry-sdk/commit/b1088a3767fe7cf2d57f36bdc594d1087e97cbbc)
|
|
20
|
+
- tiered input provenance (none/metadata/content) (#23) [9f463](https://github.com//apheris/apheris-foundry-sdk/commit/9f463359856608f2adcd272e761bda6a27475ed4)
|
|
21
|
+
- error.json, cancellation, --dry-run, and --log-level (#22) [8c9b7](https://github.com//apheris/apheris-foundry-sdk/commit/8c9b77fbac51f057572fa904cd9f5e266d62a05a)
|
|
22
|
+
- write config.json and result.json artifacts (#21) [eb1c1](https://github.com//apheris/apheris-foundry-sdk/commit/eb1c1985aa5b2522638d1f26736fbad1f4ce0573)
|
|
23
|
+
- describe --json self-description (#20) [b6e0a](https://github.com//apheris/apheris-foundry-sdk/commit/b6e0a654ec883b02f81d279343210588d7bc621f)
|
|
24
|
+
- --config flag merged into task parameters (#19) [7da3e](https://github.com//apheris/apheris-foundry-sdk/commit/7da3e28ac376d84eaa264c35a964b42b3ba7421f)
|
|
25
|
+
- nested params as dotted flags, JSON flags for complex fields (#18) [711ca](https://github.com//apheris/apheris-foundry-sdk/commit/711ca1c91355b2ba4c9ef7510d4a61b149a84490)
|
|
26
|
+
- built-in --input and --output directory flags (#17) [1c5d5](https://github.com//apheris/apheris-foundry-sdk/commit/1c5d55edabf835da6021b6ddc69ac43ce6b5631f)
|
|
27
|
+
- typed task parameters from any pydantic model (#16) [55c86](https://github.com//apheris/apheris-foundry-sdk/commit/55c86c3083bce2fb89883642924c8a000837352b)
|
|
28
|
+
- add base CLI prototype (#15) [fb1c4](https://github.com//apheris/apheris-foundry-sdk/commit/fb1c4df29e251a4a746ec418a6ad63c24223e0f8)
|
|
29
|
+
- EN-6069 migrate apheris-foundry-setup skill (+ k8s orchestrator mapping fixes) (#8) [59d9f](https://github.com//apheris/apheris-foundry-sdk/commit/59d9f53d225ff3c19e2b9a5236778fff4cc1c90d)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Apheris Foundry Software License
|
|
2
|
+
Copyright © 2025-2026 apheris AI GmbH, Kolonnenstr. 8, 10827 Berlin, Germany ("Licensor" or "Apheris"). All rights reserved.
|
|
3
|
+
Definitions:
|
|
4
|
+
"Software" means the Apheris Foundry software made available by Licensor to Licensee, in whole or in part and in any form of delivery, including the Foundry modules and container (e.g. Docker) images, the Foundry command-line interface (CLI), Python packages and wheel (.whl) distributions, workflow definitions and configuration files, the Foundry Hub orchestration service, and any accompanying libraries, scripts, skills, and documentation, together with any updates, patches, and new versions provided by Licensor.
|
|
5
|
+
"Commercial Agreement" means the order form, master subscription or services agreement, evaluation or design-partner agreement, memorandum of understanding, or other written agreement between Licensor and Licensee that governs Licensee's access to and use of the Software.
|
|
6
|
+
"Component" means any separately delivered part of the Software, for example an individual module image, the CLI, or a wheel file.
|
|
7
|
+
License Grant:
|
|
8
|
+
Subject to Licensee's compliance with this License and the Commercial Agreement, Licensor grants Licensee a non-exclusive, non-transferable, and non-sublicensable right to install, run, and use the Software for Licensee's internal business purposes, solely to the extent, for the term, in the scope, and against the fees (if any) set out in the Commercial Agreement.
|
|
9
|
+
Where the Commercial Agreement so provides, use may be granted free of charge, for example for evaluation, design-partner, or limited-time access. Free-of-charge use is not the default and applies only where expressly stated in writing by Licensor.
|
|
10
|
+
If no Commercial Agreement is in place, Licensee's use is limited to internal evaluation of Software lawfully obtained from Licensor, on a non-production and revocable basis, until a Commercial Agreement is concluded or access is withdrawn.
|
|
11
|
+
Restrictions:
|
|
12
|
+
Except as expressly permitted in the Commercial Agreement, Licensee shall not:
|
|
13
|
+
Redistribute, publish, sublicense, sell, rent, lease, or otherwise make the Software or any Component available to any third party, in whole or in part, with or without modification;
|
|
14
|
+
Pledge or encumber the rights granted under this License, or make them subject to any rights of third parties;
|
|
15
|
+
Reverse engineer, decompile, disassemble, or otherwise attempt to derive the source code, model weights, or underlying structure of the Software, except to the extent this restriction cannot be excluded under applicable mandatory law;
|
|
16
|
+
Use the Software, its outputs, or any model weights to train, fine-tune, distil, or develop any model, product, or service that competes with Licensor's products or services, or attempt model extraction, model inversion, or membership-inference attacks against the Software or its models;
|
|
17
|
+
Modify, adapt, translate, or create derivative works of the Software, other than configuration expressly contemplated by the Software or permitted in the Commercial Agreement;
|
|
18
|
+
Remove, obscure, or alter any copyright, license, or other proprietary notice in or on the Software.
|
|
19
|
+
Licensee must reproduce and retain all copyright and ownership notices in all permitted copies or substantial portions of the Software. Licensee acknowledges that breach of these restrictions may cause Licensor irreparable harm for which monetary damages would be inadequate, and that Licensor is entitled to seek injunctive or other equitable relief in addition to any other remedy.
|
|
20
|
+
Notices and Delivery of this License:
|
|
21
|
+
This License governs each Component whether it is delivered together with the Component, embedded within it, or referenced by it. Licensor may include this License, or a short notice referring to it, within each Component, for example as a LICENSE file inside a container image or wheel package, as CLI output, or as accompanying documentation. The absence of a License file next to a given Component does not waive or limit this License.
|
|
22
|
+
Third-Party and Open-Source Components:
|
|
23
|
+
The Software may include third-party or open-source components licensed under their own terms. Those terms are set out in the applicable NOTICE or third-party license files delivered with the Software and govern those components. Nothing in this License limits Licensee's rights, or extends Licensor's rights, under those third-party terms.
|
|
24
|
+
Research and Non-Clinical Use:
|
|
25
|
+
The Software and its outputs are provided for internal research and development only. They are not a medical device and are not intended for diagnosis, treatment, or any use that requires regulatory clearance or approval. Outputs are predictions that may be inaccurate or incomplete, and Licensee is solely responsible for independently validating them before relying on them. Licensee is responsible for controlling how its users, and any agents or automated tools it operates, interact with the Software and with Licensee's own data, including any sensitive sequences or other confidential inputs.
|
|
26
|
+
Models, Weights, and Outputs:
|
|
27
|
+
Rights in any machine-learning models, model weights, and outputs made available through or produced using the Software are governed by the Commercial Agreement. Except as expressly stated there, this License grants no rights in any models, weights, or outputs.
|
|
28
|
+
Feedback:
|
|
29
|
+
Any suggestions, ideas, or other feedback that Licensee provides regarding the Software belong exclusively to Licensor. Licensee assigns to Licensor all rights in such feedback to the extent assignable and, to the extent any right cannot be assigned, grants Licensor a perpetual, irrevocable, worldwide, royalty-free license to use it without restriction and without obligation to Licensee.
|
|
30
|
+
Confidentiality:
|
|
31
|
+
The Software, including its non-public components, structure, and performance characteristics, is confidential information of Licensor. Licensee shall protect it with at least reasonable care, shall not disclose it to third parties, and shall use it only as permitted under this License and the Commercial Agreement. This obligation does not apply to information that is or becomes public through no fault of Licensee, or that Licensee is required to disclose by law or court order, provided that Licensee gives Licensor reasonable prior notice where lawful.
|
|
32
|
+
Compliance, Export Control, and Data Protection:
|
|
33
|
+
Licensee shall comply with all applicable export control and sanctions laws and shall not export, re-export, or use the Software in violation of them. Licensee represents that any data it inputs into the Software has been lawfully obtained and that its use of such data, including any personal data under the EU General Data Protection Regulation and other applicable data protection law, complies with applicable law.
|
|
34
|
+
No Transfer of Ownership:
|
|
35
|
+
This License does not convey any ownership in the Software. The Software, and all intellectual property rights in it, remain the sole and exclusive property of Licensor and its licensors. All rights not expressly granted are reserved.
|
|
36
|
+
Assignment:
|
|
37
|
+
Licensor may assign or transfer this License, in whole or in part, including in connection with a merger, acquisition, financing, or sale of assets. Licensee may not assign or transfer this License or any rights under it, whether by operation of law, change of control, or otherwise, without Licensor's prior written consent. Any purported assignment in violation of this section is void.
|
|
38
|
+
Term and Termination:
|
|
39
|
+
This License is effective until terminated and, where use is tied to a Commercial Agreement, runs co-terminously with that agreement. During the term of an active Commercial Agreement, Licensor will not terminate this License for convenience. Either party may terminate for cause on 30 days' written notice if the other materially breaches this License or the Commercial Agreement and fails to cure the breach within that period. Where no Commercial Agreement is in place, or for free-of-charge or evaluation use, either party may terminate at any time and Licensor may withdraw access at any time. On termination, all rights granted to Licensee end immediately, and Licensee shall cease all use of the Software and delete or destroy all copies in its possession or control, except for copies retained in routine backups or as required by applicable law, which remain subject to this License.
|
|
40
|
+
Disclaimer and Liability:
|
|
41
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL LICENSOR OR ITS AFFILIATES BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, EXCEPT IN CASE OF INTENT OR GROSS NEGLIGENCE OR IN CASE OF HARM TO LIFE, BODY, OR HEALTH.
|
|
42
|
+
In addition, the Licensor is liable for breaches of cardinal obligations, that is, essential duties whose fulfilment enables proper performance of this License and on which Licensee may reasonably rely. In cases of ordinary negligence, such liability is limited to foreseeable, typical damages at the time of conclusion. Indirect or consequential damages are compensable only if foreseeable when using the Software as intended. Any monetary limitation or cap on liability is as set out in the Commercial Agreement; where use is free of charge and no Commercial Agreement applies, Licensor's aggregate liability is limited to the maximum extent permitted by applicable law. Mandatory statutory liability, for example under product liability law, remains unaffected.
|
|
43
|
+
Survival:
|
|
44
|
+
Termination does not affect any provision that by its nature should survive, including Restrictions, Research and Non-Clinical Use, Models, Weights, and Outputs, Feedback, Confidentiality, Compliance, Export Control, and Data Protection, No Transfer of Ownership, Assignment, Disclaimer and Liability, Precedence, and Governing Law.
|
|
45
|
+
Precedence:
|
|
46
|
+
If there is a conflict between this License and the Commercial Agreement, the Commercial Agreement prevails to the extent of the conflict.
|
|
47
|
+
Severability:
|
|
48
|
+
If a provision of this License should be held invalid, this shall not affect the validity of the remaining provisions.
|
|
49
|
+
Governing Law:
|
|
50
|
+
This License shall be governed by and construed in accordance with the laws of the Federal Republic of Germany, without regard to its conflict of law principles.
|
|
51
|
+
For licensing inquiries, please contact support@apheris.com.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
include CHANGELOG.md
|
|
2
|
+
include LICENSE
|
|
3
|
+
include README.md
|
|
4
|
+
include pyproject.toml
|
|
5
|
+
|
|
6
|
+
recursive-include docs *.md
|
|
7
|
+
recursive-include src/apheris_foundry_sdk *.py
|
|
8
|
+
recursive-include src/apheris_foundry_sdk py.typed
|
|
9
|
+
|
|
10
|
+
prune .agents
|
|
11
|
+
prune .codex
|
|
12
|
+
prune .github
|
|
13
|
+
prune customer-packs
|
|
14
|
+
prune scripts
|
|
15
|
+
prune skills
|
|
16
|
+
prune tests
|
|
17
|
+
prune coverage_html_report
|
|
18
|
+
|
|
19
|
+
exclude .coverage
|
|
20
|
+
exclude .coverage_threshold
|
|
21
|
+
exclude .coveragerc
|
|
22
|
+
exclude .cz.yaml
|
|
23
|
+
exclude .editorconfig
|
|
24
|
+
exclude .flake8
|
|
25
|
+
exclude .gitignore
|
|
26
|
+
exclude .pre-commit-config.yaml
|
|
27
|
+
exclude .python-version
|
|
28
|
+
exclude coverage.xml
|
|
29
|
+
exclude pixi.lock
|
|
30
|
+
exclude pixi.toml
|
|
31
|
+
|
|
32
|
+
global-exclude *.py[cod]
|
|
33
|
+
global-exclude __pycache__
|
|
34
|
+
global-exclude scm_file_list.json
|
|
35
|
+
global-exclude scm_version.json
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apheris-foundry-sdk
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Reference Python SDK for Apheris Foundry per-module CLIs.
|
|
5
|
+
Author: Apheris
|
|
6
|
+
License-Expression: LicenseRef-Apheris-Foundry-Software-License
|
|
7
|
+
Project-URL: License, https://www.apheris.com/docs/hub/hub-license.html
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: click>=8.1
|
|
15
|
+
Requires-Dist: pydantic<3,>=2.4.0
|
|
16
|
+
Requires-Dist: pyyaml>=6
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# Apheris Foundry SDK
|
|
20
|
+
|
|
21
|
+
Reference Python SDK for building Apheris Foundry per-module CLIs.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
Declare a task — a plain function plus a pydantic config model — and foundry
|
|
26
|
+
builds a typed CLI for it. See the [usage guide](docs/usage.md) to get started.
|
|
27
|
+
|
|
28
|
+
## Development
|
|
29
|
+
|
|
30
|
+
This project uses [pixi](https://pixi.sh) to manage the toolchain and environments.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pixi run install-dev # install the git hooks (pre-commit + commit-msg)
|
|
34
|
+
pixi run test # run the test suite
|
|
35
|
+
pixi run lint # check formatting and lint with ruff
|
|
36
|
+
pixi run fix # auto-fix formatting and lint issues
|
|
37
|
+
pixi run build # build the wheel and sdist
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Commits follow [Conventional Commits](https://www.conventionalcommits.org/); the
|
|
41
|
+
commit-msg hook enforces this via commitizen.
|
|
42
|
+
|
|
43
|
+
## Project structure
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
src/apheris_foundry_sdk/ # SDK package
|
|
47
|
+
tests/ # test suite
|
|
48
|
+
docs/ # usage guide
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
This package is licensed under the [Apheris Foundry Software License](https://www.apheris.com/docs/hub/hub-license.html). The full license text is also included in every published distribution as `LICENSE`.
|
|
54
|
+
|
|
55
|
+
## Support
|
|
56
|
+
|
|
57
|
+
Contact [support@apheris.com](mailto:support@apheris.com) for access and deployment assistance.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Apheris Foundry SDK
|
|
2
|
+
|
|
3
|
+
Reference Python SDK for building Apheris Foundry per-module CLIs.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Declare a task — a plain function plus a pydantic config model — and foundry
|
|
8
|
+
builds a typed CLI for it. See the [usage guide](docs/usage.md) to get started.
|
|
9
|
+
|
|
10
|
+
## Development
|
|
11
|
+
|
|
12
|
+
This project uses [pixi](https://pixi.sh) to manage the toolchain and environments.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pixi run install-dev # install the git hooks (pre-commit + commit-msg)
|
|
16
|
+
pixi run test # run the test suite
|
|
17
|
+
pixi run lint # check formatting and lint with ruff
|
|
18
|
+
pixi run fix # auto-fix formatting and lint issues
|
|
19
|
+
pixi run build # build the wheel and sdist
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Commits follow [Conventional Commits](https://www.conventionalcommits.org/); the
|
|
23
|
+
commit-msg hook enforces this via commitizen.
|
|
24
|
+
|
|
25
|
+
## Project structure
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
src/apheris_foundry_sdk/ # SDK package
|
|
29
|
+
tests/ # test suite
|
|
30
|
+
docs/ # usage guide
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## License
|
|
34
|
+
|
|
35
|
+
This package is licensed under the [Apheris Foundry Software License](https://www.apheris.com/docs/hub/hub-license.html). The full license text is also included in every published distribution as `LICENSE`.
|
|
36
|
+
|
|
37
|
+
## Support
|
|
38
|
+
|
|
39
|
+
Contact [support@apheris.com](mailto:support@apheris.com) for access and deployment assistance.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Usage guide
|
|
2
|
+
|
|
3
|
+
Build a per-module CLI by declaring tasks. A task is a plain function plus a
|
|
4
|
+
pydantic config model — no foundry base class.
|
|
5
|
+
|
|
6
|
+
## Define a task
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
from pydantic import BaseModel, DirectoryPath, Field
|
|
10
|
+
from apheris_foundry_sdk import run_foundry_cli, task
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PredictConfig(BaseModel):
|
|
14
|
+
model: str = Field("default", description="Model identifier")
|
|
15
|
+
precision: str = "fp32"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PredictResult(BaseModel):
|
|
19
|
+
model: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@task("predict", description="Run a prediction")
|
|
23
|
+
def predict(input: list[DirectoryPath], output: DirectoryPath, config: PredictConfig) -> PredictResult:
|
|
24
|
+
return PredictResult(model=config.model)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main() -> int:
|
|
28
|
+
return run_foundry_cli(module_name="apheris-example", module_version="0.1.0")
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Every task takes three arguments and returns a model — the signature is checked
|
|
32
|
+
when the task is declared:
|
|
33
|
+
|
|
34
|
+
- `input` — annotate it with the shape you want; `--input` follows:
|
|
35
|
+
|
|
36
|
+
| Annotation | `--input` | task receives |
|
|
37
|
+
|------------|-----------|---------------|
|
|
38
|
+
| `DirectoryPath` | one directory | a `Path` |
|
|
39
|
+
| `FilePath` | one file | a `Path` |
|
|
40
|
+
| `Path` | one file or directory | a `Path` |
|
|
41
|
+
| `list[DirectoryPath]` | repeatable directories | a `list[Path]` |
|
|
42
|
+
| `list[FilePath]` | repeatable files | a `list[Path]` |
|
|
43
|
+
| `list[Path]` | repeatable files or directories | a `list[Path]` |
|
|
44
|
+
|
|
45
|
+
The kind (`FilePath`/`DirectoryPath`/`Path`) maps to what `--input` accepts;
|
|
46
|
+
the `list[...]` form makes it repeatable. Anything else (a union, a `tuple`)
|
|
47
|
+
is rejected at declaration. click validates each path exists and is the right
|
|
48
|
+
kind — a file given to a directory-only task (or vice versa) is a precondition
|
|
49
|
+
error (exit 4). A single-input task rejects a repeated `--input` as a usage
|
|
50
|
+
error, rather than silently keeping the last.
|
|
51
|
+
- `output: DirectoryPath` — the `--output` directory (foundry creates it).
|
|
52
|
+
- `config: <BaseModel>` — your config model; each field becomes a flag.
|
|
53
|
+
- returns a pydantic `BaseModel` (written to `result.json`), or `None` for a
|
|
54
|
+
side-effect-only task.
|
|
55
|
+
|
|
56
|
+
## Run it
|
|
57
|
+
|
|
58
|
+
```console
|
|
59
|
+
$ apheris-example predict --input ./in --output ./run --model demo
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- Config fields become flags: `--model`, `--precision`, etc. Nested models use
|
|
63
|
+
dotted flags (`--optimizer.lr`); complex fields take inline JSON.
|
|
64
|
+
- `--config` sets values in bulk from a JSON/YAML file or inline JSON; explicit flags win.
|
|
65
|
+
- `--overwrite` deletes and recreates a non-empty output directory (only one this tool
|
|
66
|
+
created), so the artifacts reflect the latest run alone.
|
|
67
|
+
- `--dry-run` validates and writes `config.json`, without running the task.
|
|
68
|
+
- `--log-level debug` prints a stack trace on failure.
|
|
69
|
+
|
|
70
|
+
## Inspect a task
|
|
71
|
+
|
|
72
|
+
```console
|
|
73
|
+
$ apheris-example describe # module + its tasks
|
|
74
|
+
$ apheris-example predict describe # one task; add --json for machine output
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`describe` needs no `--input`/`--output` — only running the task does.
|
|
78
|
+
|
|
79
|
+
## Artifacts
|
|
80
|
+
|
|
81
|
+
Each run writes to the output directory:
|
|
82
|
+
|
|
83
|
+
- `config.json` — the resolved config.
|
|
84
|
+
- `result.json` — the returned model (dumped to JSON) plus a provenance block.
|
|
85
|
+
- `error.json` — written instead when the task fails.
|
|
86
|
+
|
|
87
|
+
## Input provenance
|
|
88
|
+
|
|
89
|
+
`result.json` records where each input came from. How much is recorded is a per-task
|
|
90
|
+
choice, declared at `@task` — hashing every byte on every run is costly for large
|
|
91
|
+
input roots, so it's opt-in:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
@task("predict", input_provenance="metadata")
|
|
95
|
+
def predict(...): ...
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| Tier | Records | Cost |
|
|
99
|
+
|------|---------|------|
|
|
100
|
+
| `none` (default) | path only | no traversal |
|
|
101
|
+
| `metadata` | digest over each file's path/size/mtime | stat only |
|
|
102
|
+
| `content` | digest over each file's bytes | reads everything |
|
|
103
|
+
|
|
104
|
+
## Exit codes
|
|
105
|
+
|
|
106
|
+
| Code | Category | Meaning |
|
|
107
|
+
|------|----------|---------|
|
|
108
|
+
| 0 | success | ran successfully |
|
|
109
|
+
| 1 | internal | unexpected/uncaught error |
|
|
110
|
+
| 2 | usage | bad invocation or config |
|
|
111
|
+
| 3 | input-data | invalid input data |
|
|
112
|
+
| 4 | precondition | a required resource is missing |
|
|
113
|
+
| 5 | output | could not write output |
|
|
114
|
+
| 130 | cancelled | interrupted (SIGINT/SIGTERM) |
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=80", "wheel", "setuptools_scm[toml]>=8"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "apheris-foundry-sdk"
|
|
7
|
+
description = "Reference Python SDK for Apheris Foundry per-module CLIs."
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
authors = [{ name = "Apheris" }]
|
|
11
|
+
license = "LicenseRef-Apheris-Foundry-Software-License"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
dependencies = ["click>=8.1", "pydantic>=2.4.0,<3", "pyyaml>=6"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
]
|
|
19
|
+
dynamic = ["version"]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
License = "https://www.apheris.com/docs/hub/hub-license.html"
|
|
23
|
+
|
|
24
|
+
[tool.setuptools_scm]
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.package-data]
|
|
27
|
+
apheris_foundry_sdk = ["py.typed"]
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.package-dir]
|
|
30
|
+
"" = "src"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["src"]
|
|
34
|
+
include = ["apheris_foundry_sdk*"]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
testpaths = ["tests"]
|
|
38
|
+
|
|
39
|
+
[tool.ruff]
|
|
40
|
+
line-length = 120
|
|
41
|
+
target-version = "py311"
|
|
42
|
+
|
|
43
|
+
[tool.ruff.lint]
|
|
44
|
+
select = ["ALL"]
|
|
45
|
+
ignore = [
|
|
46
|
+
"A001",
|
|
47
|
+
"A002",
|
|
48
|
+
"ANN401",
|
|
49
|
+
"COM812",
|
|
50
|
+
"S311",
|
|
51
|
+
"PLR0912",
|
|
52
|
+
"PLR0913",
|
|
53
|
+
"PLR0915",
|
|
54
|
+
"C901",
|
|
55
|
+
"D",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint.per-file-ignores]
|
|
59
|
+
"tests/*" = [
|
|
60
|
+
"S101", # Allow asserts in tests
|
|
61
|
+
"FBT001", # Boolean traps are not relevant here
|
|
62
|
+
"ANN", # Test methods and local fixtures don't need full annotations
|
|
63
|
+
"ARG001", # Test task fixtures keep SDK-required signatures
|
|
64
|
+
"PLR2004", # Asserting against literal exit codes/values is the point in tests
|
|
65
|
+
]
|
|
66
|
+
"customer-packs/foundry-module-local-harness/tasks/example-module-mock/run.py" = [
|
|
67
|
+
"INP001", # Standalone container entrypoint, not a package module
|
|
68
|
+
]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Apheris Foundry SDK: reference toolkit for per-module CLIs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from apheris_foundry_sdk.cli import (
|
|
6
|
+
FoundryGroup,
|
|
7
|
+
build_click_app,
|
|
8
|
+
build_task_command,
|
|
9
|
+
run_foundry_cli,
|
|
10
|
+
)
|
|
11
|
+
from apheris_foundry_sdk.constants import ModuleInfo
|
|
12
|
+
from apheris_foundry_sdk.errors import (
|
|
13
|
+
CancelledError,
|
|
14
|
+
FoundryError,
|
|
15
|
+
InputDataError,
|
|
16
|
+
InternalError,
|
|
17
|
+
OutputError,
|
|
18
|
+
PreconditionError,
|
|
19
|
+
UsageError,
|
|
20
|
+
)
|
|
21
|
+
from apheris_foundry_sdk.registry import Registry, default_registry, tasks
|
|
22
|
+
from apheris_foundry_sdk.task import task
|
|
23
|
+
from apheris_foundry_sdk.version import __version__
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"CancelledError",
|
|
27
|
+
"FoundryError",
|
|
28
|
+
"FoundryGroup",
|
|
29
|
+
"InputDataError",
|
|
30
|
+
"InternalError",
|
|
31
|
+
"ModuleInfo",
|
|
32
|
+
"OutputError",
|
|
33
|
+
"PreconditionError",
|
|
34
|
+
"Registry",
|
|
35
|
+
"UsageError",
|
|
36
|
+
"__version__",
|
|
37
|
+
"build_click_app",
|
|
38
|
+
"build_task_command",
|
|
39
|
+
"default_registry",
|
|
40
|
+
"run_foundry_cli",
|
|
41
|
+
"task",
|
|
42
|
+
"tasks",
|
|
43
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .app import build_click_app, build_task_command
|
|
4
|
+
from .contract import FoundryGroup
|
|
5
|
+
from .entrypoint import run_foundry_cli
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"FoundryGroup",
|
|
9
|
+
"build_click_app",
|
|
10
|
+
"build_task_command",
|
|
11
|
+
"run_foundry_cli",
|
|
12
|
+
]
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from apheris_foundry_sdk.constants import ModuleInfo
|
|
9
|
+
from apheris_foundry_sdk.describe import describe_module, describe_task
|
|
10
|
+
|
|
11
|
+
from .contract import FoundryGroup
|
|
12
|
+
from .format import format_module, format_task
|
|
13
|
+
from .runner import run_task
|
|
14
|
+
from .types import JSON, ExplicitBool, build_path, click_type_for
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from apheris_foundry_sdk.registry import Registry, TaskSpec
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_click_app(module: ModuleInfo, registry: Registry) -> click.Group:
|
|
21
|
+
"""Build the branded ``apheris-<module>`` command group.
|
|
22
|
+
|
|
23
|
+
Wires up the group-level ``--version``/``--help``, a module-level ``describe``,
|
|
24
|
+
and each task's pre-built sub-command (each of which also has ``describe``). The
|
|
25
|
+
module is stored on the click context so ``describe`` can read its name.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
@click.group(cls=FoundryGroup, name=module.name, context_settings={"help_option_names": ["--help"]})
|
|
29
|
+
@click.version_option(
|
|
30
|
+
version=f"{module.version} cli-contract {module.cli_contract_version}",
|
|
31
|
+
prog_name=module.name,
|
|
32
|
+
message="%(prog)s %(version)s",
|
|
33
|
+
)
|
|
34
|
+
@click.pass_context
|
|
35
|
+
def app(ctx: click.Context) -> None:
|
|
36
|
+
ctx.obj = module
|
|
37
|
+
|
|
38
|
+
@app.command(name="describe")
|
|
39
|
+
@click.option("--json", "as_json", is_flag=True, help="Emit the machine-readable JSON description.")
|
|
40
|
+
def module_describe(as_json: bool) -> None: # noqa: FBT001 - Click injects flags positionally.
|
|
41
|
+
payload = describe_module(registry, module.name, module.version, module.cli_contract_version)
|
|
42
|
+
click.echo(json.dumps(payload, indent=2, sort_keys=True) if as_json else format_module(payload))
|
|
43
|
+
|
|
44
|
+
for spec in registry.tasks().values():
|
|
45
|
+
app.add_command(spec.command)
|
|
46
|
+
return app
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def build_task_command(spec: TaskSpec) -> FoundryGroup:
|
|
50
|
+
"""Build the task's command (a :class:`FoundryGroup`).
|
|
51
|
+
|
|
52
|
+
Built once at registration and stored on the spec. It is a group (not a bare
|
|
53
|
+
command) so it can carry its own ``describe`` sub-command while still running
|
|
54
|
+
the task itself via ``invoke_without_command``. As a :class:`FoundryGroup` it
|
|
55
|
+
carries the error contract whether run standalone or under the module group.
|
|
56
|
+
"""
|
|
57
|
+
params: list[click.Parameter] = [*_foundry_options(spec), *_param_options(spec)]
|
|
58
|
+
|
|
59
|
+
@click.pass_context
|
|
60
|
+
def callback(ctx: click.Context, **kwargs: Any) -> int | None:
|
|
61
|
+
if ctx.invoked_subcommand is not None:
|
|
62
|
+
return None
|
|
63
|
+
return run_task(spec, module_from_context(ctx, spec), kwargs)
|
|
64
|
+
|
|
65
|
+
group = FoundryGroup(
|
|
66
|
+
spec.name,
|
|
67
|
+
params=params,
|
|
68
|
+
callback=callback,
|
|
69
|
+
help=spec.description,
|
|
70
|
+
invoke_without_command=True,
|
|
71
|
+
context_settings={"help_option_names": ["--help"]},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@group.command(name="describe")
|
|
75
|
+
@click.option("--json", "as_json", is_flag=True, help="Emit the machine-readable JSON description.")
|
|
76
|
+
@click.pass_context
|
|
77
|
+
def task_describe(ctx: click.Context, as_json: bool) -> None: # noqa: FBT001 - Click injects flags positionally.
|
|
78
|
+
payload = describe_task(spec, module_from_context(ctx, spec).name)
|
|
79
|
+
click.echo(json.dumps(payload, indent=2, sort_keys=True) if as_json else format_task(payload))
|
|
80
|
+
|
|
81
|
+
return group
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def module_from_context(ctx: click.Context, spec: TaskSpec) -> ModuleInfo:
|
|
85
|
+
"""The module a task runs under, taken from the click context.
|
|
86
|
+
|
|
87
|
+
``build_click_app`` stores the real :class:`ModuleInfo` on the context. A task
|
|
88
|
+
command run on its own (no module group above it) has none, so fall back to a
|
|
89
|
+
module named after the task itself.
|
|
90
|
+
"""
|
|
91
|
+
root = ctx.find_root()
|
|
92
|
+
if isinstance(root.obj, ModuleInfo):
|
|
93
|
+
return root.obj
|
|
94
|
+
return ModuleInfo(name=spec.name, version="0.0.0")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _at_most_once(_ctx: click.Context, _param: click.Parameter, values: tuple[Any, ...]) -> Any:
|
|
98
|
+
"""Unwrap a repeatable flag to a single value, rejecting more than one."""
|
|
99
|
+
if len(values) > 1:
|
|
100
|
+
msg = "--input may only be given once for this task"
|
|
101
|
+
raise click.UsageError(msg)
|
|
102
|
+
return values[0] if values else None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _foundry_options(spec: TaskSpec) -> list[click.Parameter]:
|
|
106
|
+
"""The built-in flags: ``--input`` (file or directory, repeatable for a list), ``--output``, ``--config``."""
|
|
107
|
+
noun = spec.input.noun
|
|
108
|
+
return [
|
|
109
|
+
# --input/--output are required, but not via click's required=True: that fires
|
|
110
|
+
# during parsing even for `<task> describe`, which needs neither. The runner
|
|
111
|
+
# enforces presence only when the task actually runs.
|
|
112
|
+
click.Option(
|
|
113
|
+
["--input", "input"],
|
|
114
|
+
type=build_path(is_file=spec.input.is_file, is_dir=spec.input.is_dir, must_exist=True),
|
|
115
|
+
multiple=True,
|
|
116
|
+
callback=_at_most_once if not spec.input.multiple else None,
|
|
117
|
+
default=(),
|
|
118
|
+
help=f"Input {noun} (repeatable)." if spec.input.multiple else f"Input {noun}.",
|
|
119
|
+
),
|
|
120
|
+
click.Option(
|
|
121
|
+
["--output", "output"],
|
|
122
|
+
# Output dir need not exist (the runner creates it); must be writable.
|
|
123
|
+
type=build_path(is_file=False, is_dir=True, must_exist=False, writable=True),
|
|
124
|
+
default=None,
|
|
125
|
+
help="Output directory (required).",
|
|
126
|
+
),
|
|
127
|
+
click.Option(
|
|
128
|
+
["--overwrite"],
|
|
129
|
+
type=ExplicitBool(),
|
|
130
|
+
default=None,
|
|
131
|
+
required=False,
|
|
132
|
+
help="Delete and recreate a non-empty output directory (only if this tool created it).",
|
|
133
|
+
),
|
|
134
|
+
click.Option(
|
|
135
|
+
["--dry-run", "dry_run"],
|
|
136
|
+
type=ExplicitBool(),
|
|
137
|
+
default=None,
|
|
138
|
+
required=False,
|
|
139
|
+
help="Validate inputs and config and write config.json, without running the task.",
|
|
140
|
+
),
|
|
141
|
+
click.Option(
|
|
142
|
+
["--log-level", "log_level"],
|
|
143
|
+
type=click.Choice(["info", "debug"]),
|
|
144
|
+
default="info",
|
|
145
|
+
required=False,
|
|
146
|
+
help="Log level; 'debug' prints a stack trace on failure ('info' is the default).",
|
|
147
|
+
),
|
|
148
|
+
click.Option(
|
|
149
|
+
["--config"],
|
|
150
|
+
type=str,
|
|
151
|
+
default=None,
|
|
152
|
+
required=False,
|
|
153
|
+
help="Parameter values as inline JSON or a path to a JSON file; flags override config.",
|
|
154
|
+
),
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _param_options(spec: TaskSpec) -> list[click.Parameter]:
|
|
159
|
+
"""One flag per config field, from the params introspected at registration."""
|
|
160
|
+
options: list[click.Parameter] = []
|
|
161
|
+
for param in spec.params.values():
|
|
162
|
+
# Scalar leaves get a typed flag (repeatable for a list); complex-but-JSON-able
|
|
163
|
+
# leaves (dict, list of models, unions) get a single inline-JSON flag.
|
|
164
|
+
multiple = param.scalar and param.repeatable
|
|
165
|
+
options.append(
|
|
166
|
+
click.Option(
|
|
167
|
+
[param.flag, param.dest],
|
|
168
|
+
type=click_type_for(param.element) if param.scalar else JSON(),
|
|
169
|
+
multiple=multiple,
|
|
170
|
+
default=() if multiple else None,
|
|
171
|
+
required=False,
|
|
172
|
+
help=param.description or ("" if param.scalar else "Inline JSON value."),
|
|
173
|
+
),
|
|
174
|
+
)
|
|
175
|
+
return options
|