frootai-orchard 1.0.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.
- frootai_orchard-1.0.0/.gitignore +95 -0
- frootai_orchard-1.0.0/CHANGELOG.md +40 -0
- frootai_orchard-1.0.0/PKG-INFO +102 -0
- frootai_orchard-1.0.0/README.md +79 -0
- frootai_orchard-1.0.0/frootai_orchard/__init__.py +73 -0
- frootai_orchard-1.0.0/frootai_orchard/_deprecate.py +130 -0
- frootai_orchard-1.0.0/frootai_orchard/_orchard_types.py +123 -0
- frootai_orchard-1.0.0/frootai_orchard/auth.py +278 -0
- frootai_orchard-1.0.0/frootai_orchard/cache.py +268 -0
- frootai_orchard-1.0.0/frootai_orchard/client.py +368 -0
- frootai_orchard-1.0.0/frootai_orchard/cursor.py +205 -0
- frootai_orchard-1.0.0/frootai_orchard/errors.py +276 -0
- frootai_orchard-1.0.0/frootai_orchard/filter_lang.py +283 -0
- frootai_orchard-1.0.0/frootai_orchard/http_client.py +347 -0
- frootai_orchard-1.0.0/pyproject.toml +44 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
website/build/
|
|
3
|
+
website/.docusaurus/
|
|
4
|
+
website/node_modules/
|
|
5
|
+
npm-mcp/node_modules/
|
|
6
|
+
.DS_Store
|
|
7
|
+
*.log
|
|
8
|
+
.env
|
|
9
|
+
|
|
10
|
+
# [M8.18] Foundry deployment config — real values (subscription IDs, etc.)
|
|
11
|
+
# stay local; only the .example template is committed.
|
|
12
|
+
.frootai/foundry-config.json
|
|
13
|
+
|
|
14
|
+
# [M9.11] Per-tenant ACA deployment params — same rationale.
|
|
15
|
+
infra/hosted-mcp/aca.parameters.*.json
|
|
16
|
+
!infra/hosted-mcp/aca.parameters.example.json
|
|
17
|
+
# Pulumi state + tenant stacks
|
|
18
|
+
infra/hosted-mcp/pulumi/Pulumi.*.yaml
|
|
19
|
+
!infra/hosted-mcp/pulumi/Pulumi.yaml
|
|
20
|
+
infra/hosted-mcp/pulumi/node_modules/
|
|
21
|
+
infra/hosted-mcp/pulumi/bin/
|
|
22
|
+
|
|
23
|
+
# [M9.14 / M9.15 / M9.16] k6 load-test results (per-run outputs, not committed).
|
|
24
|
+
load-tests/hosted-mcp/last-run.json
|
|
25
|
+
load-tests/hosted-mcp/last-run-federated.json
|
|
26
|
+
load-tests/hosted-mcp/last-run-autoscale.json
|
|
27
|
+
|
|
28
|
+
# [M9.20] Alpha-tenant onboarding — real configs hold subscription IDs.
|
|
29
|
+
scripts/alpha-tenants.json
|
|
30
|
+
scripts/alpha-tenants/
|
|
31
|
+
|
|
32
|
+
# Test artefact + intermediate report directories — not source code.
|
|
33
|
+
tmp/
|
|
34
|
+
|
|
35
|
+
# Release-gate tarballs and AFCLI-T021 platform evidence are regenerated per run.
|
|
36
|
+
artifacts/
|
|
37
|
+
|
|
38
|
+
# Internal development plans — NOT pushed to remote
|
|
39
|
+
.internal/
|
|
40
|
+
|
|
41
|
+
# Website lives in separate private repo (frootai/frootai.dev)
|
|
42
|
+
website/
|
|
43
|
+
website-v2/
|
|
44
|
+
website-v3/
|
|
45
|
+
website-v4/
|
|
46
|
+
website-v5/
|
|
47
|
+
website-v6/
|
|
48
|
+
website-implementation-plan.md
|
|
49
|
+
|
|
50
|
+
__pycache__/
|
|
51
|
+
*.pyc
|
|
52
|
+
|
|
53
|
+
npm-sdk/node_modules/
|
|
54
|
+
vscode-extension/esbuild.config.d.mts
|
|
55
|
+
|
|
56
|
+
# FAI Factory — temp files (catalog is committed)
|
|
57
|
+
.factory/harvest.json
|
|
58
|
+
.factory/fai-catalog.prev.json
|
|
59
|
+
.factory/changes.json
|
|
60
|
+
.factory/logs/
|
|
61
|
+
|
|
62
|
+
# Build artifacts (Phase 1 OT-3, May 3, 2026)
|
|
63
|
+
# vsix bundles are produced by vsce-publish.yml; never commit
|
|
64
|
+
*.vsix
|
|
65
|
+
**/*.vsix
|
|
66
|
+
# functions deploy.zip is produced by deploy-chatbot.yml; never commit
|
|
67
|
+
|
|
68
|
+
# [X6.4] Cosign bundle-signing key material — NEVER commit the private key.
|
|
69
|
+
# Generated by scripts/marketplace/sign/gen-key.mjs during the founder-only
|
|
70
|
+
# key ceremony (docs/security/cosign-key-ceremony.md); stored in Vault.
|
|
71
|
+
cosign.key
|
|
72
|
+
*.key
|
|
73
|
+
**/cosign.key
|
|
74
|
+
functions/deploy.zip
|
|
75
|
+
**/deploy.zip
|
|
76
|
+
# Python build wheels & sdists are produced by pypi-publish.yml; never commit (Phase 2 cleanup, May 3, 2026)
|
|
77
|
+
python-sdk/dist/
|
|
78
|
+
python-mcp/dist/
|
|
79
|
+
**/*.whl
|
|
80
|
+
**/*.egg-info/
|
|
81
|
+
.pytest_cache/
|
|
82
|
+
**/.pytest_cache/
|
|
83
|
+
|
|
84
|
+
# V0.3 avm-cache: per-module deep-fetched AVM source files (regenerable via npm run verified:catalog --deep-fetch)
|
|
85
|
+
data/avm-cache/
|
|
86
|
+
|
|
87
|
+
# [P1.1] Live telemetry ingest (real tenant data) — never commit; seed is the committed fallback
|
|
88
|
+
observability/*.live.ndjson
|
|
89
|
+
|
|
90
|
+
# 'public/' was an accidental submodule gitlink (no .gitmodules) that broke the
|
|
91
|
+
# Cloudflare Workers Build clone (--recurse-submodules). Untracked + ignored.
|
|
92
|
+
/public/
|
|
93
|
+
|
|
94
|
+
# harvest service corpus (13MB build artifact, bundled into image not git)
|
|
95
|
+
aca/frootai-harvest/corpus/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `frootai-orchard` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-05-26 (Phase A6.9–A6.16)
|
|
9
|
+
|
|
10
|
+
> **Released under tag `orchard-v1.0.0`** (Phase A10 — General Availability). v1.0 additions:
|
|
11
|
+
>
|
|
12
|
+
> - **STABILITY.md** — explicit public-export list (`__all__` is the source of truth) + v1.x semver contract at `frootai-core/python-sdk/orchard/STABILITY.md` (inherits from repo-root `STABILITY.md`).
|
|
13
|
+
> - **v1.0 semver promise** — every public symbol frozen until v2.0; 6-month minimum deprecation window.
|
|
14
|
+
> - **Deprecation framework** at `frootai_orchard._deprecate` (`emit_deprecation`, `format_deprecation_line`); byte-equivalent with the npm peer at `@frootai/orchard-types/lib/deprecate`; opt-out via `FROOTAI_SUPPRESS_DEPRECATIONS=1`.
|
|
15
|
+
|
|
16
|
+
> **Released under tag `orchard-v0.6.0-sdk`** (Phase A6 close — [retro](https://github.com/frootai/frootai-core/blob/main/planning/retros/orchard-phase6.md)). Ships with:
|
|
17
|
+
>
|
|
18
|
+
> - **PyPI trusted publishing** via OIDC (PEP 740 attestations — NO `PYPI_TOKEN` secret used). Audit via the PyPI JSON API: `https://pypi.org/pypi/frootai-orchard/1.0.0/json` → `urls[].provenance`
|
|
19
|
+
> - **CycloneDX 1.5 SBOM** uploaded as 90-day workflow artifact (`orchard-sboms/orchard-py.sbom.json` — zero runtime deps so the SBOM lists only the root component)
|
|
20
|
+
> - **Cross-SDK conformance certificate** uploaded as 90-day workflow artifact (SHA-256 manifest fingerprint, identical byte-for-byte contracts as `@frootai/orchard` v1.0.0)
|
|
21
|
+
> - **Wheel size ≤ 100 KB** enforced as a publish gate (currently ~50 KB)
|
|
22
|
+
|
|
23
|
+
Initial public release of the Python SDK. Byte-equal contract with the npm
|
|
24
|
+
`@frootai/orchard` v1.0.0 SDK.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- `create_client(**opts)` — public client factory
|
|
29
|
+
- `client.list(variety=, where=, limit=)` — iterator over catalog fruits
|
|
30
|
+
- `client.search(query, where=, limit=)` — client-side scored search
|
|
31
|
+
- `client.show(id)` — single fruit detail with index-scan fallback
|
|
32
|
+
- `client.bushel.list/add/remove` — authenticated bushel sync (Team tier+)
|
|
33
|
+
- `client.whoami()` — never-raises auth probe
|
|
34
|
+
- `client.cursor.encode/decode` — opaque HMAC-signed pagination cursors
|
|
35
|
+
- 10-class typed error hierarchy (`FrootaiOrchardError` + subclasses)
|
|
36
|
+
- Filter expression language (`field:value AND/OR/NOT (...)`) byte-equal with npm
|
|
37
|
+
- 3-tier cache (LRU 1k entries → disk `~/.frootai/cache/sdk/` 1h → network) with stale-while-error
|
|
38
|
+
- Decorrelated-jitter retry + per-host circuit breaker + Retry-After honoring
|
|
39
|
+
- Shared `~/.frootai/.token` auth file with the `frootai` CLI
|
|
40
|
+
- `frootai_orchard._orchard_types` byte-equal mirror of `@frootai/orchard-types` v1.0.0
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: frootai-orchard
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Programmatic SDK for the FrootAI Orchard catalog — type-safe list/search/show/bushel surface for partner apps, CI pipelines, and BI integrations. Reads the same ~/.frootai/.token + ~/.frootai/cache as the frootai CLI for transparent shared auth.
|
|
5
|
+
Project-URL: Homepage, https://frootai.dev/orchard
|
|
6
|
+
Project-URL: Repository, https://github.com/frootai/frootai-core
|
|
7
|
+
Project-URL: Issues, https://github.com/frootai/frootai-core/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/frootai/frootai-core/blob/main/python-sdk/orchard/CHANGELOG.md
|
|
9
|
+
Author-email: FrootAI <hello@frootai.dev>
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: accelerators,ai-agents,catalog,frootai,orchard,sdk,solution-plays
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# frootai-orchard
|
|
25
|
+
|
|
26
|
+
Programmatic Python SDK for the FrootAI Orchard catalog — type-safe `list` / `search` / `show` / `bushel` surface for partner apps, CI pipelines, and BI integrations.
|
|
27
|
+
|
|
28
|
+
Byte-equal contract with the npm `@frootai/orchard` v1.0.0 SDK: same enums, same filter expression language, same HMAC-signed pagination cursors, same retry semantics, same shared `~/.frootai/.token` auth file. A query that returns 12 results in JS returns the same 12 results in Python.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install frootai-orchard
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Zero runtime dependencies. Python 3.10+. Stdlib only (`urllib`, `hmac`, `base64`, `json`, `hashlib`, `pathlib`).
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from frootai_orchard import create_client
|
|
42
|
+
|
|
43
|
+
client = create_client()
|
|
44
|
+
|
|
45
|
+
# Anonymous catalog reads — no auth required
|
|
46
|
+
for fruit in client.list(variety="azure", where="ripeness:Mature OR ripeness:Bearing"):
|
|
47
|
+
print(fruit["name"], "—", fruit.get("tagline", ""))
|
|
48
|
+
|
|
49
|
+
# Search with scoring
|
|
50
|
+
for hit in client.search("rag", limit=10):
|
|
51
|
+
print(f'{hit["_score"]:>2} {hit["name"]}')
|
|
52
|
+
|
|
53
|
+
# Single fruit detail
|
|
54
|
+
detail = client.show("ms-azure-openai-rag")
|
|
55
|
+
|
|
56
|
+
# Authenticated — bushel sync (Team tier+)
|
|
57
|
+
client.bushel.add("ms-azure-openai-rag")
|
|
58
|
+
print(client.bushel.list()["ids"])
|
|
59
|
+
|
|
60
|
+
# Never-raises auth probe
|
|
61
|
+
print(client.whoami())
|
|
62
|
+
# → {"signed_in": True, "anonymous": False, "redacted": "…a3f9", ...}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Auth priority
|
|
66
|
+
|
|
67
|
+
1. `token=` keyword argument
|
|
68
|
+
2. `FROOTAI_TOKEN` environment variable
|
|
69
|
+
3. `~/.frootai/.token` shared file (same as the `frootai` CLI)
|
|
70
|
+
|
|
71
|
+
A `frootai login` in your terminal transparently authorizes Python SDK calls.
|
|
72
|
+
|
|
73
|
+
## Typed errors
|
|
74
|
+
|
|
75
|
+
Every public surface raises only `FrootaiOrchardError` subclasses — partners catch by type, never by string match:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from frootai_orchard import errors as fai_err
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
client.bushel.add("some-fruit")
|
|
82
|
+
except fai_err.NotSignedInError:
|
|
83
|
+
print("Run `frootai login` first")
|
|
84
|
+
except fai_err.EntitlementRequiredError as e:
|
|
85
|
+
print(f'Upgrade required: {e.context["entitlement"]}')
|
|
86
|
+
except fai_err.RateLimitError as e:
|
|
87
|
+
print(f"Slow down — retry after {e.retry_after_ms} ms")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Filter expression language
|
|
91
|
+
|
|
92
|
+
Identical grammar to the npm SDK:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
client.list(where="tier_class:paid AND variety:azure")
|
|
96
|
+
client.list(where="ripeness:Mature OR (variety:gcp AND category:rag)")
|
|
97
|
+
client.list(where='NOT category:eval')
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frootai-orchard
|
|
2
|
+
|
|
3
|
+
Programmatic Python SDK for the FrootAI Orchard catalog — type-safe `list` / `search` / `show` / `bushel` surface for partner apps, CI pipelines, and BI integrations.
|
|
4
|
+
|
|
5
|
+
Byte-equal contract with the npm `@frootai/orchard` v1.0.0 SDK: same enums, same filter expression language, same HMAC-signed pagination cursors, same retry semantics, same shared `~/.frootai/.token` auth file. A query that returns 12 results in JS returns the same 12 results in Python.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install frootai-orchard
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Zero runtime dependencies. Python 3.10+. Stdlib only (`urllib`, `hmac`, `base64`, `json`, `hashlib`, `pathlib`).
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from frootai_orchard import create_client
|
|
19
|
+
|
|
20
|
+
client = create_client()
|
|
21
|
+
|
|
22
|
+
# Anonymous catalog reads — no auth required
|
|
23
|
+
for fruit in client.list(variety="azure", where="ripeness:Mature OR ripeness:Bearing"):
|
|
24
|
+
print(fruit["name"], "—", fruit.get("tagline", ""))
|
|
25
|
+
|
|
26
|
+
# Search with scoring
|
|
27
|
+
for hit in client.search("rag", limit=10):
|
|
28
|
+
print(f'{hit["_score"]:>2} {hit["name"]}')
|
|
29
|
+
|
|
30
|
+
# Single fruit detail
|
|
31
|
+
detail = client.show("ms-azure-openai-rag")
|
|
32
|
+
|
|
33
|
+
# Authenticated — bushel sync (Team tier+)
|
|
34
|
+
client.bushel.add("ms-azure-openai-rag")
|
|
35
|
+
print(client.bushel.list()["ids"])
|
|
36
|
+
|
|
37
|
+
# Never-raises auth probe
|
|
38
|
+
print(client.whoami())
|
|
39
|
+
# → {"signed_in": True, "anonymous": False, "redacted": "…a3f9", ...}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Auth priority
|
|
43
|
+
|
|
44
|
+
1. `token=` keyword argument
|
|
45
|
+
2. `FROOTAI_TOKEN` environment variable
|
|
46
|
+
3. `~/.frootai/.token` shared file (same as the `frootai` CLI)
|
|
47
|
+
|
|
48
|
+
A `frootai login` in your terminal transparently authorizes Python SDK calls.
|
|
49
|
+
|
|
50
|
+
## Typed errors
|
|
51
|
+
|
|
52
|
+
Every public surface raises only `FrootaiOrchardError` subclasses — partners catch by type, never by string match:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from frootai_orchard import errors as fai_err
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
client.bushel.add("some-fruit")
|
|
59
|
+
except fai_err.NotSignedInError:
|
|
60
|
+
print("Run `frootai login` first")
|
|
61
|
+
except fai_err.EntitlementRequiredError as e:
|
|
62
|
+
print(f'Upgrade required: {e.context["entitlement"]}')
|
|
63
|
+
except fai_err.RateLimitError as e:
|
|
64
|
+
print(f"Slow down — retry after {e.retry_after_ms} ms")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Filter expression language
|
|
68
|
+
|
|
69
|
+
Identical grammar to the npm SDK:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
client.list(where="tier_class:paid AND variety:azure")
|
|
73
|
+
client.list(where="ripeness:Mature OR (variety:gcp AND category:rag)")
|
|
74
|
+
client.list(where='NOT category:eval')
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
frootai-orchard — public Python SDK for the FrootAI Orchard catalog.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from frootai_orchard import create_client
|
|
6
|
+
client = create_client()
|
|
7
|
+
for fruit in client.list(variety="azure"):
|
|
8
|
+
print(fruit["name"])
|
|
9
|
+
|
|
10
|
+
Shared auth with the `frootai` CLI: reads `~/.frootai/.token` so a
|
|
11
|
+
`frootai login` in your terminal transparently authorizes SDK calls.
|
|
12
|
+
Override priority: `token=` arg > `FROOTAI_TOKEN` env > `~/.frootai/.token`.
|
|
13
|
+
|
|
14
|
+
Mirror of the npm `@frootai/orchard` v1.0.0 SDK. Cross-language enum
|
|
15
|
+
byte-equality enforced by the cross-SDK conformance test suite.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from . import _orchard_types
|
|
19
|
+
from . import auth, cache, cursor, errors, filter_lang, http_client
|
|
20
|
+
from .client import create_client
|
|
21
|
+
from .cursor import build_cursor_codec
|
|
22
|
+
from .errors import (
|
|
23
|
+
AuthError,
|
|
24
|
+
ConflictError,
|
|
25
|
+
EntitlementRequiredError,
|
|
26
|
+
ERROR_CODES,
|
|
27
|
+
FrootaiOrchardError,
|
|
28
|
+
NetworkError,
|
|
29
|
+
NotFoundError,
|
|
30
|
+
NotSignedInError,
|
|
31
|
+
RateLimitError,
|
|
32
|
+
ServerError,
|
|
33
|
+
TokenExpiredError,
|
|
34
|
+
ValidationError,
|
|
35
|
+
)
|
|
36
|
+
from .filter_lang import build_predicate as build_filter_predicate
|
|
37
|
+
from .filter_lang import parse as parse_filter
|
|
38
|
+
|
|
39
|
+
__version__ = "1.0.0"
|
|
40
|
+
VERSION = __version__
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Factory
|
|
44
|
+
"create_client",
|
|
45
|
+
# Error types (for isinstance checks at the call site)
|
|
46
|
+
"FrootaiOrchardError",
|
|
47
|
+
"AuthError",
|
|
48
|
+
"NotSignedInError",
|
|
49
|
+
"TokenExpiredError",
|
|
50
|
+
"EntitlementRequiredError",
|
|
51
|
+
"RateLimitError",
|
|
52
|
+
"NetworkError",
|
|
53
|
+
"ValidationError",
|
|
54
|
+
"NotFoundError",
|
|
55
|
+
"ConflictError",
|
|
56
|
+
"ServerError",
|
|
57
|
+
"ERROR_CODES",
|
|
58
|
+
# Filter language (partners can use this directly to inspect ASTs)
|
|
59
|
+
"parse_filter",
|
|
60
|
+
"build_filter_predicate",
|
|
61
|
+
# Cursor codec (partners building their own pagination shell)
|
|
62
|
+
"build_cursor_codec",
|
|
63
|
+
# Version
|
|
64
|
+
"VERSION",
|
|
65
|
+
"__version__",
|
|
66
|
+
# Submodules
|
|
67
|
+
"errors",
|
|
68
|
+
"auth",
|
|
69
|
+
"http_client",
|
|
70
|
+
"filter_lang",
|
|
71
|
+
"cursor",
|
|
72
|
+
"cache",
|
|
73
|
+
]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""A10.3 — Deprecation framework (warn-once-per-process), Python mirror of orchard-types/lib/deprecate.js.
|
|
2
|
+
|
|
3
|
+
Byte-equivalent behaviour with the npm peer:
|
|
4
|
+
* WARN ONCE PER PROCESS PER SYMBOL.
|
|
5
|
+
* NEVER raise — emit to ``sys.stderr`` (never stdout).
|
|
6
|
+
* Opt-out via ``FROOTAI_SUPPRESS_DEPRECATIONS=1``.
|
|
7
|
+
* Machine-readable single-line payload:
|
|
8
|
+
``[FROOTAI_DEPRECATED] symbol="x" since="1.x.y" removed_in="2.0.0" message="..."``
|
|
9
|
+
|
|
10
|
+
Doctrine:
|
|
11
|
+
* Stdlib only — no third-party deps.
|
|
12
|
+
* Pure ``format_deprecation_line`` exported for tests.
|
|
13
|
+
* Test injection via keyword args (``emitted``, ``write``, ``env``).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
22
|
+
from typing import Callable, Mapping, MutableSet, Optional
|
|
23
|
+
|
|
24
|
+
DEPRECATION_ENV_SUPPRESS = "FROOTAI_SUPPRESS_DEPRECATIONS"
|
|
25
|
+
DEPRECATION_PREFIX = "[FROOTAI_DEPRECATED]"
|
|
26
|
+
_SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$")
|
|
27
|
+
|
|
28
|
+
# Per-process suppression set (mirrors the JS module-scope Set).
|
|
29
|
+
_EMITTED_SYMBOLS: MutableSet[str] = set()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def format_deprecation_line(
|
|
33
|
+
*,
|
|
34
|
+
symbol: str,
|
|
35
|
+
since: Optional[str] = None,
|
|
36
|
+
removed_in: Optional[str] = None,
|
|
37
|
+
message: Optional[str] = None,
|
|
38
|
+
replacement: Optional[str] = None,
|
|
39
|
+
) -> str:
|
|
40
|
+
"""Pure: build the canonical one-line deprecation message."""
|
|
41
|
+
if not isinstance(symbol, str) or not symbol:
|
|
42
|
+
raise TypeError("format_deprecation_line requires non-empty symbol")
|
|
43
|
+
parts = [
|
|
44
|
+
DEPRECATION_PREFIX,
|
|
45
|
+
f"symbol={json.dumps(symbol)}",
|
|
46
|
+
]
|
|
47
|
+
if isinstance(since, str) and _SEMVER_RE.match(since):
|
|
48
|
+
parts.append(f"since={json.dumps(since)}")
|
|
49
|
+
if isinstance(removed_in, str) and _SEMVER_RE.match(removed_in):
|
|
50
|
+
parts.append(f"removed_in={json.dumps(removed_in)}")
|
|
51
|
+
if isinstance(replacement, str) and replacement:
|
|
52
|
+
parts.append(f"replacement={json.dumps(replacement)}")
|
|
53
|
+
if isinstance(message, str) and message:
|
|
54
|
+
parts.append(f"message={json.dumps(message)}")
|
|
55
|
+
return " ".join(parts)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def emit_deprecation(
|
|
59
|
+
*,
|
|
60
|
+
symbol: str,
|
|
61
|
+
since: Optional[str] = None,
|
|
62
|
+
removed_in: Optional[str] = None,
|
|
63
|
+
message: Optional[str] = None,
|
|
64
|
+
replacement: Optional[str] = None,
|
|
65
|
+
emitted: Optional[MutableSet[str]] = None,
|
|
66
|
+
write: Optional[Callable[[str], None]] = None,
|
|
67
|
+
env: Optional[Mapping[str, str]] = None,
|
|
68
|
+
) -> dict:
|
|
69
|
+
"""Emit a deprecation warning ONCE per process per symbol.
|
|
70
|
+
|
|
71
|
+
Returns ``{"emitted": bool, "line": str|None, "reason"?: str}``.
|
|
72
|
+
"""
|
|
73
|
+
if not isinstance(symbol, str) or not symbol:
|
|
74
|
+
raise TypeError("emit_deprecation requires non-empty symbol")
|
|
75
|
+
|
|
76
|
+
effective_env = env if env is not None else os.environ
|
|
77
|
+
if effective_env.get(DEPRECATION_ENV_SUPPRESS) == "1":
|
|
78
|
+
return {"emitted": False, "line": None, "reason": "suppressed_by_env"}
|
|
79
|
+
|
|
80
|
+
effective_set = emitted if emitted is not None else _EMITTED_SYMBOLS
|
|
81
|
+
if symbol in effective_set:
|
|
82
|
+
return {"emitted": False, "line": None, "reason": "already_emitted_this_process"}
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
line = format_deprecation_line(
|
|
86
|
+
symbol=symbol,
|
|
87
|
+
since=since,
|
|
88
|
+
removed_in=removed_in,
|
|
89
|
+
message=message,
|
|
90
|
+
replacement=replacement,
|
|
91
|
+
)
|
|
92
|
+
except Exception as err: # pragma: no cover - defensive; mirrors npm impl
|
|
93
|
+
line = (
|
|
94
|
+
f"{DEPRECATION_PREFIX} symbol={json.dumps(symbol)} "
|
|
95
|
+
f"message={json.dumps('format_error: ' + str(err))}"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def _default_write(s: str) -> None:
|
|
99
|
+
try:
|
|
100
|
+
sys.stderr.write(s)
|
|
101
|
+
except Exception: # pragma: no cover - never propagate
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
writer = write if callable(write) else _default_write
|
|
105
|
+
try:
|
|
106
|
+
writer(line + "\n")
|
|
107
|
+
except Exception: # pragma: no cover - never propagate
|
|
108
|
+
pass
|
|
109
|
+
effective_set.add(symbol)
|
|
110
|
+
return {"emitted": True, "line": line}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _reset_emitted_symbols_for_test() -> None:
|
|
114
|
+
"""TEST UTILITY — clear the per-process set. NEVER call from production."""
|
|
115
|
+
_EMITTED_SYMBOLS.clear()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _get_emitted_symbols_for_test() -> set:
|
|
119
|
+
"""TEST UTILITY — defensive copy of warned-symbol set."""
|
|
120
|
+
return set(_EMITTED_SYMBOLS)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
__all__ = [
|
|
124
|
+
"DEPRECATION_ENV_SUPPRESS",
|
|
125
|
+
"DEPRECATION_PREFIX",
|
|
126
|
+
"format_deprecation_line",
|
|
127
|
+
"emit_deprecation",
|
|
128
|
+
"_reset_emitted_symbols_for_test",
|
|
129
|
+
"_get_emitted_symbols_for_test",
|
|
130
|
+
]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
frootai-orchard — byte-equal enum mirror of @frootai/orchard-types v1.0.0.
|
|
3
|
+
|
|
4
|
+
This module is **byte-for-byte identical** to:
|
|
5
|
+
frootai-core/orchard-types/index.js
|
|
6
|
+
|
|
7
|
+
Drift is prevented by the cross-language enum test in
|
|
8
|
+
scripts/orchard/test/sdk_orchard_py_test.py which loads the JS module via
|
|
9
|
+
subprocess and asserts byte-equality with this module.
|
|
10
|
+
|
|
11
|
+
Why a separate Python file instead of pip-installing a py-types package:
|
|
12
|
+
- Zero runtime dependencies (matches @frootai/orchard peer-dep model)
|
|
13
|
+
- Drift detection runs against the JS source of truth on every regression
|
|
14
|
+
- Partners get the enums without an extra install
|
|
15
|
+
|
|
16
|
+
Doctrine:
|
|
17
|
+
- Every enum is a tuple (immutable at runtime — closest Python equivalent
|
|
18
|
+
to Object.freeze on an Array).
|
|
19
|
+
- TIER_RANK is a dict but exposed via MappingProxyType for read-only view.
|
|
20
|
+
- No exported function has side effects.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from types import MappingProxyType
|
|
24
|
+
from typing import Mapping, Sequence
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Accelerator manifest enums
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
VARIETY_ENUM: Sequence[str] = ("azure", "gcp", "aws", "oss", "hybrid")
|
|
31
|
+
OWNER_TYPE_ENUM: Sequence[str] = ("first_party", "community", "partner", "frootai")
|
|
32
|
+
FROOT_LAYER_ENUM: Sequence[str] = ("F", "R", "O1", "O2", "T")
|
|
33
|
+
RIPENESS_ENUM: Sequence[str] = ("Seedling", "Sapling", "Bearing", "Mature")
|
|
34
|
+
SEASON_ENUM: Sequence[str] = ("Spring", "Summer", "Autumn", "Winter")
|
|
35
|
+
CATEGORY_ENUM: Sequence[str] = (
|
|
36
|
+
"rag", "agent", "multi-agent", "voice", "vision", "chat", "doc", "code",
|
|
37
|
+
"infra", "mcp", "security", "search", "data", "eval", "edge",
|
|
38
|
+
"industry-healthcare", "industry-finance", "industry-retail", "industry-public-sector",
|
|
39
|
+
)
|
|
40
|
+
TRUST_BADGE_ENUM: Sequence[str] = (
|
|
41
|
+
"microsoft_official", "google_official", "aws_official",
|
|
42
|
+
"azd_template",
|
|
43
|
+
"frootai_compatible_full", "frootai_compatible_partial", "frootai_pollinated",
|
|
44
|
+
"eval_proven", "safety_layer", "production_ready",
|
|
45
|
+
)
|
|
46
|
+
ORIGIN_ENUM: Sequence[str] = ("harvested", "cultivated", "first_party")
|
|
47
|
+
POLLINATION_RELATION_ENUM: Sequence[str] = (
|
|
48
|
+
"baseline", "extends_to", "alternative", "uses_pattern", "provides_infra",
|
|
49
|
+
)
|
|
50
|
+
POLLINATION_SOURCE_ENUM: Sequence[str] = ("auto", "manual", "community_pr")
|
|
51
|
+
NO_PLAY_MATCH_TAG: str = "no-play-match"
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# Telemetry enums
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
EVENT_ENUM: Sequence[str] = (
|
|
58
|
+
"subcommand_invoked",
|
|
59
|
+
"install_succeeded",
|
|
60
|
+
"upgrade_to_play_attempted",
|
|
61
|
+
"tool_invoked",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
ALLOWED_PROP_KEYS: Sequence[str] = (
|
|
65
|
+
"cmd",
|
|
66
|
+
"variety",
|
|
67
|
+
"paid",
|
|
68
|
+
"dry_run",
|
|
69
|
+
"success",
|
|
70
|
+
"exit_code",
|
|
71
|
+
"has_json",
|
|
72
|
+
"tier_class",
|
|
73
|
+
"hooks_count",
|
|
74
|
+
"error_code",
|
|
75
|
+
"ms_elapsed",
|
|
76
|
+
"client",
|
|
77
|
+
"has_args",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# Entitlement / tier enums
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
TIER_ENUM: Sequence[str] = ("free", "pro", "team", "business", "enterprise")
|
|
85
|
+
|
|
86
|
+
TIER_RANK: Mapping[str, int] = MappingProxyType({
|
|
87
|
+
"free": 0,
|
|
88
|
+
"pro": 1,
|
|
89
|
+
"team": 2,
|
|
90
|
+
"business": 3,
|
|
91
|
+
"enterprise": 4,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
KNOWN_ENTITLEMENTS: Sequence[str] = (
|
|
95
|
+
"upgrade-to-play",
|
|
96
|
+
"bushel-sync",
|
|
97
|
+
"telemetry-dash",
|
|
98
|
+
"sso-saml",
|
|
99
|
+
"audit-export",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
# Pure helpers — no IO, no dependencies
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def is_tier_at_least(tier: str, required: str) -> bool:
|
|
108
|
+
"""Returns True if `tier` is at least `required` per TIER_RANK ordering."""
|
|
109
|
+
a = TIER_RANK.get(tier)
|
|
110
|
+
b = TIER_RANK.get(required)
|
|
111
|
+
if not isinstance(a, int) or not isinstance(b, int):
|
|
112
|
+
return False
|
|
113
|
+
return a >= b
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def is_valid_enum(value: str, enum_seq: Sequence[str]) -> bool:
|
|
117
|
+
"""Returns True if `value` is a valid member of `enum_seq`."""
|
|
118
|
+
if not isinstance(value, str):
|
|
119
|
+
return False
|
|
120
|
+
return value in enum_seq
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
ORCHARD_TYPES_VERSION: int = 1
|