coloph-toolset 0.1.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.
@@ -0,0 +1,30 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ permissions:
4
+ contents: read
5
+ jobs:
6
+ minimum-dependencies:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v4
10
+ - uses: astral-sh/setup-uv@v6
11
+ with:
12
+ python-version: '3.12'
13
+ - run: uv run --isolated --python 3.12 --with pydantic==2.12.0 --with annotated-types==0.7.0 python -m pytest
14
+ contracts:
15
+ strategy:
16
+ matrix:
17
+ python: ['3.11', '3.12', '3.13', '3.14']
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: astral-sh/setup-uv@v6
22
+ with:
23
+ python-version: ${{ matrix.python }}
24
+ - run: uv sync --locked
25
+ - run: uv run pytest
26
+ - run: uv run mypy
27
+ - run: uv run python -m ruff check .
28
+ - run: uv run python -m ruff format --check .
29
+ - run: uv build
30
+ - run: uv run python scripts/smoke_wheel.py
@@ -0,0 +1,30 @@
1
+ name: Release
2
+ on:
3
+ push:
4
+ tags: ['v*']
5
+ permissions:
6
+ contents: write
7
+ id-token: write
8
+ jobs:
9
+ release:
10
+ environment:
11
+ name: pypi
12
+ url: https://pypi.org/p/coloph-toolset
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v6
17
+ with:
18
+ python-version: '3.12'
19
+ - run: uv sync --locked
20
+ - run: uv run pytest
21
+ - run: uv run mypy
22
+ - run: uv run python -m ruff check .
23
+ - run: uv run python -m ruff format --check .
24
+ - run: uv build
25
+ - run: uv run python scripts/smoke_wheel.py
26
+ - uses: pypa/gh-action-pypi-publish@release/v1
27
+ - run: uv run python scripts/release.py
28
+ env:
29
+ GH_TOKEN: ${{ github.token }}
30
+ RELEASE_TAG: ${{ github.ref_name }}
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ *.egg-info/
9
+ build/
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Add lazy function declarations and parameter inspection.
6
+ - Generate JSON Schema and argument models from the same retained annotations.
7
+ - Preserve supported constraints, nullable required inputs, defaults, and hidden argument projections.
8
+ - Reject unsupported signatures and metadata before application dispatch.
9
+ - Add the standalone shipping-quote example and clean wheel smoke coverage.
10
+
11
+ Execution remains application-owned in this release.
@@ -0,0 +1,36 @@
1
+ # Contributing
2
+
3
+ ## Local checks
4
+
5
+ ```sh
6
+ uv sync --locked
7
+ uv run pytest
8
+ uv run mypy
9
+ uv run python -m ruff check .
10
+ uv run python -m ruff format --check .
11
+ uv build
12
+ uv run python scripts/smoke_wheel.py
13
+ ```
14
+
15
+ Keep fixes and tests within the current milestone. Add contract tests for changes in public behavior.
16
+ Preserve every project under `examples/`. Each project needs source, a README, dependency metadata, and automated smoke coverage.
17
+ Examples must use public imports and work without a Coloph checkout or private services.
18
+
19
+ ## Release
20
+
21
+ This package initially distributes wheels and source archives through GitHub Releases. It does not require a PyPI publishing credential.
22
+
23
+ 1. Update the version and changelog.
24
+ 2. Run all local checks and the clean wheel smoke.
25
+ 3. Review and commit the changes.
26
+ 4. Push the reviewed commit to the public default branch.
27
+ 5. Wait for CI on the exact release commit.
28
+ 6. Create an immutable `vX.Y.Z` tag on that commit.
29
+ 7. Push the tag. The release workflow reruns tests and publishes the wheel and source archive.
30
+ 8. Validate the published artifacts in a clean environment.
31
+
32
+ The package version, tag, and release notes must match. Never move an existing release tag.
33
+ Coloph can adopt a release only after the public default branch and release contain its tested commit.
34
+ Record the release and Coloph dependency pin in the owning Coloph issue.
35
+
36
+ When PyPI distribution is introduced, the release process must publish the same tested artifacts and version there.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 golergka
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,108 @@
1
+ Metadata-Version: 2.5
2
+ Name: coloph-toolset
3
+ Version: 0.1.0
4
+ Summary: Declare Python tools once, export their schemas, and validate their arguments.
5
+ Project-URL: Repository, https://github.com/golergka/coloph-toolset
6
+ Project-URL: Issues, https://github.com/golergka/coloph-toolset/issues
7
+ Project-URL: Changelog, https://github.com/golergka/coloph-toolset/blob/main/CHANGELOG.md
8
+ Author: golergka
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Typing :: Typed
14
+ Requires-Python: >=3.11
15
+ Requires-Dist: annotated-types<1,>=0.7
16
+ Requires-Dist: pydantic<3,>=2.12
17
+ Description-Content-Type: text/markdown
18
+
19
+ # coloph-toolset
20
+
21
+ Declare Python tools once. Export their JSON Schema and validate arguments before your application calls the function.
22
+
23
+ Version 0.1 provides declarations and validation. It does not manage execution, transactions, authorization, or model providers.
24
+ Later releases add catalogs and adapters. The [roadmap](docs/roadmap.md) describes that sequence.
25
+
26
+ ## Install
27
+
28
+ ```sh
29
+ uv add coloph-toolset
30
+ ```
31
+
32
+ The package requires Python 3.11 or later and Pydantic 2.12 or later within major version 2.
33
+ No Coloph installation, database, credentials, HTTP framework, or agent framework is required.
34
+
35
+ ## Declare and validate
36
+
37
+ ```python
38
+ from typing import Annotated
39
+ from pydantic import Field, ValidationError
40
+ from coloph_toolset import tool, tool_for
41
+
42
+
43
+ @tool()
44
+ def shipping_quote(
45
+ ctx,
46
+ quantity: Annotated[int, "Number of parcels", Field(gt=0)],
47
+ destination: Annotated[str | None, "Country code, or null for collection"],
48
+ insured: Annotated[bool, "Include insurance"] = False,
49
+ ) -> dict[str, object]:
50
+ return {"quantity": quantity, "destination": destination, "insured": insured}
51
+
52
+
53
+ declaration = tool_for(shipping_quote)
54
+ schema = declaration.json_schema()
55
+ arguments = declaration.validate_arguments({"quantity": "2", "destination": None})
56
+ result = shipping_quote(None, **arguments)
57
+
58
+ try:
59
+ declaration.validate_arguments({"quantity": 0, "destination": None})
60
+ except ValidationError as error:
61
+ print(error.errors(include_url=False))
62
+ ```
63
+
64
+ Validation never calls the function. Calling the Python function directly does not apply validation.
65
+ The application owns execution and must use validated arguments at its dispatch boundary.
66
+
67
+ ## Context and restricted arguments
68
+
69
+ The declaration expects a required first parameter named `ctx`.
70
+ Its type is application-owned and its annotation is not evaluated. Context never appears in the argument schema.
71
+
72
+ ```python
73
+ @tool(model_hidden_args=("internal",))
74
+ def inspect_order(ctx, order: str, internal: bool = False):
75
+ return ctx.lookup(order, internal=internal)
76
+
77
+
78
+ public = tool_for(inspect_order)
79
+ public.validate_arguments({"order": "demo"})
80
+ # An external "internal" argument raises ValidationError.
81
+ operator_schema = public.json_schema(include_hidden=True)
82
+ ```
83
+
84
+ Hidden arguments require valid defaults. Only trusted application code can select `include_hidden=True`.
85
+ Argument projection is not an authorization system.
86
+
87
+ ## Contracts and examples
88
+
89
+ - [Argument contract and API](docs/contracts.md)
90
+ - [Standalone shipping-quote project](examples/01-tool-declarations/README.md)
91
+ - [Development and release procedure](CONTRIBUTING.md)
92
+ - [Changes](CHANGELOG.md)
93
+
94
+ Every milestone adds a project under `examples/`. CI runs all preserved examples against the current library.
95
+
96
+ ## Development
97
+
98
+ ```sh
99
+ uv sync --locked
100
+ uv run pytest
101
+ uv run mypy
102
+ uv run python -m ruff check .
103
+ uv run python -m ruff format --check .
104
+ uv build
105
+ uv run python scripts/smoke_wheel.py
106
+ ```
107
+
108
+ MIT licensed.
@@ -0,0 +1,90 @@
1
+ # coloph-toolset
2
+
3
+ Declare Python tools once. Export their JSON Schema and validate arguments before your application calls the function.
4
+
5
+ Version 0.1 provides declarations and validation. It does not manage execution, transactions, authorization, or model providers.
6
+ Later releases add catalogs and adapters. The [roadmap](docs/roadmap.md) describes that sequence.
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ uv add coloph-toolset
12
+ ```
13
+
14
+ The package requires Python 3.11 or later and Pydantic 2.12 or later within major version 2.
15
+ No Coloph installation, database, credentials, HTTP framework, or agent framework is required.
16
+
17
+ ## Declare and validate
18
+
19
+ ```python
20
+ from typing import Annotated
21
+ from pydantic import Field, ValidationError
22
+ from coloph_toolset import tool, tool_for
23
+
24
+
25
+ @tool()
26
+ def shipping_quote(
27
+ ctx,
28
+ quantity: Annotated[int, "Number of parcels", Field(gt=0)],
29
+ destination: Annotated[str | None, "Country code, or null for collection"],
30
+ insured: Annotated[bool, "Include insurance"] = False,
31
+ ) -> dict[str, object]:
32
+ return {"quantity": quantity, "destination": destination, "insured": insured}
33
+
34
+
35
+ declaration = tool_for(shipping_quote)
36
+ schema = declaration.json_schema()
37
+ arguments = declaration.validate_arguments({"quantity": "2", "destination": None})
38
+ result = shipping_quote(None, **arguments)
39
+
40
+ try:
41
+ declaration.validate_arguments({"quantity": 0, "destination": None})
42
+ except ValidationError as error:
43
+ print(error.errors(include_url=False))
44
+ ```
45
+
46
+ Validation never calls the function. Calling the Python function directly does not apply validation.
47
+ The application owns execution and must use validated arguments at its dispatch boundary.
48
+
49
+ ## Context and restricted arguments
50
+
51
+ The declaration expects a required first parameter named `ctx`.
52
+ Its type is application-owned and its annotation is not evaluated. Context never appears in the argument schema.
53
+
54
+ ```python
55
+ @tool(model_hidden_args=("internal",))
56
+ def inspect_order(ctx, order: str, internal: bool = False):
57
+ return ctx.lookup(order, internal=internal)
58
+
59
+
60
+ public = tool_for(inspect_order)
61
+ public.validate_arguments({"order": "demo"})
62
+ # An external "internal" argument raises ValidationError.
63
+ operator_schema = public.json_schema(include_hidden=True)
64
+ ```
65
+
66
+ Hidden arguments require valid defaults. Only trusted application code can select `include_hidden=True`.
67
+ Argument projection is not an authorization system.
68
+
69
+ ## Contracts and examples
70
+
71
+ - [Argument contract and API](docs/contracts.md)
72
+ - [Standalone shipping-quote project](examples/01-tool-declarations/README.md)
73
+ - [Development and release procedure](CONTRIBUTING.md)
74
+ - [Changes](CHANGELOG.md)
75
+
76
+ Every milestone adds a project under `examples/`. CI runs all preserved examples against the current library.
77
+
78
+ ## Development
79
+
80
+ ```sh
81
+ uv sync --locked
82
+ uv run pytest
83
+ uv run mypy
84
+ uv run python -m ruff check .
85
+ uv run python -m ruff format --check .
86
+ uv build
87
+ uv run python scripts/smoke_wheel.py
88
+ ```
89
+
90
+ MIT licensed.
@@ -0,0 +1,81 @@
1
+ # Argument contract
2
+
3
+ ## Declaration lifetime
4
+
5
+ `@tool()` attaches a `Tool` at `fn.tool` and returns the original function.
6
+ `tool_for(fn)` reads that declaration. `Tool(fn, ...)` also works without a decorator for application integration.
7
+
8
+ Signature structure is validated at construction. Parameter annotations are resolved lazily on first inspection.
9
+ Schema construction validates defaults and rejects unsupported metadata. A malformed declaration raises `DeclarationError`, the exported name for `TypeError`.
10
+ Applications must build their argument models during startup before they accept calls.
11
+
12
+ Annotations are trusted developer code. String annotations resolve in the original function's global namespace, including through `functools.wraps`.
13
+ Function-local forward names must be available in that namespace or use evaluated annotations.
14
+ Context and return annotations are not resolved. Schema inspection never calls the body.
15
+ Do not mutate a function's signature, annotations, defaults, or declaration after registration.
16
+
17
+ ## Supported signatures and types
18
+
19
+ - Plain synchronous functions with named external arguments. Positional-only external arguments and variadic arguments are rejected.
20
+ - A required injected context named `ctx` as the first positional parameter.
21
+ - `bool`, `int`, `float`, and `str`.
22
+ - Homogeneous string or integer `Literal` choices.
23
+ - Lists of supported scalar or literal values. Nested lists and nullable list elements are not supported in 0.1.
24
+ - Nullable forms of those types, including a nullable list.
25
+ - `Annotated` descriptions and the constraint metadata listed next.
26
+
27
+ Async functions, generators, bound methods, arbitrary callable objects, unconstrained `Any`, mixed unions, and nested models are not supported in 0.1.
28
+ These declarations fail explicitly. Async execution belongs to a later milestone.
29
+
30
+ ## Metadata
31
+
32
+ One string in `Annotated` supplies the argument description.
33
+ Supported Pydantic `Field` options are `description`, `title`, `examples`, `gt`, `ge`, `lt`, `le`, `multiple_of`, `min_length`, `max_length`, `pattern`, and `strict`.
34
+ Direct `annotated_types.Gt`, `Ge`, `Lt`, `Le`, `MultipleOf`, `MinLen`, and `MaxLen` metadata also works, as does `pydantic.Strict`.
35
+ Constraints must apply to the annotated type. Duplicate descriptions and unsupported metadata raise `DeclarationError`.
36
+ Literal choices cannot carry additional value constraints in 0.1. Their containing list can carry list constraints.
37
+
38
+ Function signatures own defaults. `Field` defaults, default factories, aliases, serializers, validators, schema overrides, and exclusion controls are rejected.
39
+ This keeps validation, parameter names, and exported schemas aligned.
40
+
41
+ Applications can pass exact marker classes through `metadata_types=(MyMarker,)`.
42
+ The declaration retains these markers in `ToolParam.metadata` and removes them from the validation annotation.
43
+ Markers are descriptive data only. They cannot implement Pydantic validators or change JSON Schema through this interface.
44
+
45
+ ## Validation and defaults
46
+
47
+ `Tool.validate_arguments(object, include_hidden=False)` returns normalized values in a new dictionary.
48
+ It raises `pydantic.ValidationError` for invalid input. `error.errors()` provides field locations, codes, messages, and input values.
49
+ Applications own error presentation and any redaction of submitted values.
50
+
51
+ Pydantic 2's default coercion rules apply. Numeric strings can become numbers. Accepted boolean strings can become booleans.
52
+ Strings do not automatically accept numbers. `Field(strict=True)` disables coercion for its annotated type.
53
+ This policy is identical through `validate_arguments()` and the generated argument model.
54
+
55
+ Omission uses the declared default. Explicit null requires a nullable annotation.
56
+ A nullable argument without a default is still required. Unknown arguments are forbidden.
57
+ Defaults are validated even for hidden arguments. Validation supplies independent list values, so one call cannot mutate another call's validated defaults.
58
+
59
+ Hidden arguments are absent from the default schema and rejected as external input.
60
+ `include_hidden=True` creates the full contract for trusted application callers.
61
+ The restricted result contains only visible arguments. The application owns how it supplies private values during execution.
62
+
63
+ ## Public inspection API
64
+
65
+ - `Tool.params`: ordered `ToolParam` records with the validation annotation, default, description, scalar type, choices, repeated/nullable flags, and application metadata.
66
+ - `ToolParam.required`: whether the signature has no default.
67
+ - `Tool.description` and `Tool.summary`: full docstring and first line, or an empty string.
68
+ - `Tool.argument_model(include_hidden=False, name=None, descriptions=None)`: a Pydantic model for that projection.
69
+ - `Tool.json_schema(include_hidden=False)`: a fresh validation schema.
70
+ - `annotation_with_description(fn, parameter, description)`: a description override that retains the original annotation for adapter signatures.
71
+
72
+ Description overrides accept real parameter names only. They do not change validation.
73
+ Treat generated model classes and parameter metadata as read-only.
74
+
75
+ ## Boundaries
76
+
77
+ Importing `coloph_toolset` does not import Pydantic until annotation/schema work needs it.
78
+ The package does not replace stdout or read environment files.
79
+ It has no KB names, reference registry, credentials, database connections, workflow state, or agent framework dependency.
80
+
81
+ The [Pydantic field contract](https://docs.pydantic.dev/latest/concepts/fields/) describes the underlying constraint and default behavior.
@@ -0,0 +1,19 @@
1
+ # Extraction milestones
2
+
3
+ The extraction plan tracks six sequential releases.
4
+
5
+ 1. Declarations, canonical argument validation, and the shipping-quote example.
6
+ 2. Catalogs, lazy trees, CLI generation, and a task CLI example.
7
+ 3. Invocation, typed context, results, sync/async execution, and a runtime example.
8
+ 4. An optional HTTP adapter and a standalone service example.
9
+ 5. An optional Pydantic AI adapter and a deterministic agent example.
10
+ 6. Hierarchical discovery, documentation gates, terminal controls, and a hierarchical agent example.
11
+
12
+ Each milestone adds a project under `examples/` and preserves every previous project.
13
+ CI runs all examples against the current package. Pins to obsolete releases do not replace current-version coverage.
14
+
15
+ Version 0.1 does not promise CLI or HTTP parity in Coloph before those adapters migrate.
16
+ Coloph retains reference resolution, authorization, workflow policy, resource management, and output behavior until their respective extraction milestones.
17
+
18
+ The 0.x public API can change in a minor release. Release notes must describe required migrations.
19
+ Patch releases preserve documented contracts. The package must remain useful at every milestone.
@@ -0,0 +1,25 @@
1
+ # Shipping quote
2
+
3
+ This standalone project uses only the public package and its Pydantic dependency.
4
+ It exports a schema, normalizes valid arguments, calls a function, and displays structured validation errors.
5
+
6
+ From this directory:
7
+
8
+ ```sh
9
+ uv run shipping_quote.py
10
+ ```
11
+
12
+ The project installs the public wheel. No Coloph source, database, credentials, or environment file is needed.
13
+ The valid example returns a quote of 1,000 cents for two parcels.
14
+ The rejected examples show a quantity constraint, a missing nullable argument, and an attempt to supply a hidden argument.
15
+
16
+ To use a locally built current wheel:
17
+
18
+ ```sh
19
+ uv venv .venv
20
+ uv pip install --python .venv/bin/python ../../dist/coloph_toolset-0.1.0-py3-none-any.whl
21
+ uv run --no-project --python .venv/bin/python shipping_quote.py
22
+ ```
23
+
24
+ The repository's `scripts/smoke_wheel.py` installs the current wheel into a temporary environment and runs this project outside the checkout.
25
+ Repository CI also runs this project's tests against the current library, so the release URL cannot hide a compatibility regression.
@@ -0,0 +1,8 @@
1
+ [project]
2
+ name = "toolset-shipping-quote-example"
3
+ version = "0.1.0"
4
+ requires-python = ">=3.11"
5
+ dependencies = ["coloph-toolset"]
6
+
7
+ [dependency-groups]
8
+ dev = ["pytest>=8,<10"]
@@ -0,0 +1,57 @@
1
+ """A standalone tool declaration with schema export and input validation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Annotated, Literal
7
+
8
+ from pydantic import Field, ValidationError
9
+
10
+ from coloph_toolset import tool, tool_for
11
+
12
+
13
+ @tool(model_hidden_args=("account_discount",))
14
+ def shipping_quote(
15
+ ctx,
16
+ quantity: Annotated[int, "Number of parcels", Field(gt=0, le=100)],
17
+ destination: Annotated[str | None, "Country code, or null for collection"],
18
+ service: Annotated[Literal["standard", "express"], "Delivery service"] = "standard",
19
+ insured: Annotated[bool, "Include insurance"] = False,
20
+ account_discount: Annotated[bool, "Private account discount"] = False,
21
+ ) -> dict[str, object]:
22
+ """Calculate a shipping quote without external services."""
23
+ unit_cost = 0 if destination is None else (1200 if service == "express" else 500)
24
+ total = quantity * unit_cost + (200 if insured else 0)
25
+ if account_discount:
26
+ total = total * 9 // 10
27
+ return {"amount_cents": total, "currency": "USD", "parcels": quantity}
28
+
29
+
30
+ def main() -> None:
31
+ declaration = tool_for(shipping_quote)
32
+ arguments = declaration.validate_arguments({"quantity": "2", "destination": "AR"})
33
+ rejected = []
34
+ for value in (
35
+ {"quantity": 0, "destination": "AR"},
36
+ {"quantity": 2},
37
+ {"quantity": 2, "destination": None, "account_discount": True},
38
+ ):
39
+ try:
40
+ declaration.validate_arguments(value)
41
+ except ValidationError as error:
42
+ rejected.append(error.errors(include_url=False))
43
+ print(
44
+ json.dumps(
45
+ {
46
+ "schema": declaration.json_schema(),
47
+ "arguments": arguments,
48
+ "quote": shipping_quote(None, **arguments),
49
+ "rejected": rejected,
50
+ },
51
+ indent=2,
52
+ )
53
+ )
54
+
55
+
56
+ if __name__ == "__main__":
57
+ main()
@@ -0,0 +1,36 @@
1
+ """Executable example contract, included in the cumulative example test suite."""
2
+
3
+ import importlib.util
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+ from pydantic import ValidationError
9
+
10
+ from coloph_toolset import tool_for
11
+
12
+ spec = importlib.util.spec_from_file_location("shipping_quote", Path(__file__).with_name("shipping_quote.py"))
13
+ assert spec and spec.loader
14
+ module = importlib.util.module_from_spec(spec)
15
+ spec.loader.exec_module(module)
16
+
17
+
18
+ def test_quote_and_rejections(capsys):
19
+ module.main()
20
+ output = json.loads(capsys.readouterr().out)
21
+ assert output["quote"]["amount_cents"] == 1000
22
+ assert output["arguments"]["quantity"] == 2
23
+ assert [errors[0]["type"] for errors in output["rejected"]] == [
24
+ "greater_than",
25
+ "missing",
26
+ "extra_forbidden",
27
+ ]
28
+ assert "account_discount" not in output["schema"]["properties"]
29
+
30
+
31
+ def test_collection_and_insurance():
32
+ declaration = tool_for(module.shipping_quote)
33
+ values = declaration.validate_arguments({"quantity": 1, "destination": None, "insured": True})
34
+ assert module.shipping_quote(None, **values)["amount_cents"] == 200
35
+ with pytest.raises(ValidationError):
36
+ declaration.validate_arguments({"quantity": 101, "destination": "AR"})
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27,<2"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "coloph-toolset"
7
+ version = "0.1.0"
8
+ description = "Declare Python tools once, export their schemas, and validate their arguments."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [{name = "golergka"}]
13
+ dependencies = ["pydantic>=2.12,<3", "annotated-types>=0.7,<1"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Programming Language :: Python :: 3",
17
+ "Typing :: Typed",
18
+ ]
19
+
20
+ [project.urls]
21
+ Repository = "https://github.com/golergka/coloph-toolset"
22
+ Issues = "https://github.com/golergka/coloph-toolset/issues"
23
+ Changelog = "https://github.com/golergka/coloph-toolset/blob/main/CHANGELOG.md"
24
+
25
+ [dependency-groups]
26
+ dev = ["pytest>=8,<10", "ruff>=0.11,<1", "mypy>=1.15,<2", "build>=1.2,<2"]
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["src/coloph_toolset"]
30
+
31
+ [tool.ruff]
32
+ line-length = 120
33
+ target-version = "py311"
34
+
35
+ [tool.ruff.lint]
36
+ select = ["E", "F", "I"]
37
+
38
+ [tool.mypy]
39
+ python_version = "3.11"
40
+ strict = true
41
+ files = ["src/coloph_toolset"]
42
+
43
+ [tool.pytest.ini_options]
44
+ testpaths = ["tests", "examples"]
45
+ addopts = "-ra"
@@ -0,0 +1,33 @@
1
+ """Publish tested artifacts only when the tag matches package metadata."""
2
+
3
+ import os
4
+ import subprocess
5
+ import tomllib
6
+ from pathlib import Path
7
+
8
+ root = Path(__file__).resolve().parents[1]
9
+ version = tomllib.loads((root / "pyproject.toml").read_text())["project"]["version"]
10
+ tag = os.environ["RELEASE_TAG"]
11
+ if tag != f"v{version}":
12
+ raise SystemExit(f"Tag {tag!r} does not match package version {version!r}")
13
+ artifacts = sorted((root / "dist").iterdir())
14
+ if {file.name for file in artifacts} != {
15
+ f"coloph_toolset-{version}-py3-none-any.whl",
16
+ f"coloph_toolset-{version}.tar.gz",
17
+ }:
18
+ raise SystemExit("The release must contain exactly the matching wheel and source archive")
19
+ subprocess.run(
20
+ [
21
+ "gh",
22
+ "release",
23
+ "create",
24
+ tag,
25
+ *map(str, artifacts),
26
+ "--verify-tag",
27
+ "--title",
28
+ f"coloph-toolset {version}",
29
+ "--notes-file",
30
+ str(root / "CHANGELOG.md"),
31
+ ],
32
+ check=True,
33
+ )