1- # subfork
1+ <div class =" gridline-home-hero " >
2+ <a href =" https://subfork.com " >
3+ <img src="assets/subfork-banner.png" alt="Subfork" width="100%">
4+ </a >
5+ <p >Create, publish, and run graphs from Python.</p >
6+ </div >
27
3- Python API client for Subfork graph authoring, publication and execution.
4- ** Alpha scaffold; not yet published to PyPI.** Distribution and import name: ` subfork ` .
5- BYO workers belong in the separate worker package, not this client.
8+ # Subfork Python
69
7- ## Development install
10+ The Python client for [ Subfork] ( https://subfork.com ) . Discover nodes, build reusable
11+ graphs, publish versions, and run them from your scripts, applications, or agents.
812
9- Requires Python 3.8+ and a modern pip (21.3+ for editable installs).
10- Use a supported interpreter explicitly; the system ` python3 ` may be older.
13+ ## Install
1114
12- ``` bash
13- python3 -m venv .venv
14- source .venv/bin/activate
15- python -m pip install --upgrade pip
16- python -m pip install -e ' .[dev]'
17- pytest
18- python -m build
19- python -m twine check dist/*
20- ```
21-
22- If pip reports that editable mode requires ` setup.py ` , check ` python --version `
23- and ` python -m pip --version ` . Upgrade pip inside the active environment:
24- ` python -m pip install --upgrade pip ` . Python 3.8 is supported; pip will select
25- the latest pip and dependencies compatible with that interpreter. A ` setup.py `
26- compatibility shim is not needed.
27-
28- With [ uv] ( https://docs.astral.sh/uv/ ) , a supported Python can also be provisioned
29- without changing the system Python. Preserve an existing environment by using a
30- new directory:
15+ Requires Python 3.8+.
3116
3217``` bash
33- uv venv --python 3.12 .venv312
34- source .venv312/bin/activate
35- uv pip install -e ' .[dev]'
18+ pip install subfork
3619```
3720
38- Licensed under [ BSD-3-Clause] ( LICENSE ) . CI builds and checks distributions but does
39- not publish them.
40-
41- ## Usage
21+ ## Connect
4222
43- Create an API key under Account → API keys on a deployment with API-key support.
44- Set ` SUBFORK_API_KEY ` in your process environment; never put it in source files ,
45- URLs, graph definitions, notebooks committed to git, or agent transcripts .
23+ Create a key under ** Account → API keys** in Subfork and set ` SUBFORK_API_KEY ` in
24+ your environment. Grant the permissions your application needs: read, write, run ,
25+ and/or publish. Keep the key out of source files and graph definitions .
4626
4727``` python
4828from subfork import Subfork
4929
50- with Subfork() as client: # Reads SUBFORK_API_KEY; defaults to https://subfork.com
51- nodes = client.nodes.list(include_all_versions = True )
30+ with Subfork() as client:
31+ nodes = client.nodes.list()
5232 graphs = client.graphs.list()
5333```
5434
55- For a local deployment:
35+ The client reads ` SUBFORK_API_KEY ` and connects to ` https://subfork.com ` by default.
36+ Responses are dictionaries and lists matching the API's JSON responses.
37+
38+ For another deployment, pass its origin without ` /api/v1 ` :
5639
5740``` python
5841with Subfork(base_url = " http://subfork.localhost" ) as client:
5942 nodes = client.nodes.list()
6043```
6144
62- ` base_url ` is an origin, without ` /api/v1 ` . HTTPS is required except for loopback
63- and ` .localhost ` hosts. Credentials are sent in the Authorization bearer header.
64- Redirects are not followed. Use a context manager or call ` client.close() ` .
45+ ## Create and run a graph
6546
66- ## Author, publish, execute
67-
68- The following deliberately creates and publishes a public graph and consumes
69- execution quota. Grant read/write/run/publish scopes only when needed.
70- Responses are dictionaries preserving the server's JSON shape.
47+ This example creates a text-producing graph, publishes ` v1 ` , and runs that version.
48+ It requires read, write, publish, and run permissions. Graphs are public, and runs
49+ use your account's execution quota.
7150
7251``` python
7352from subfork import Subfork
@@ -76,71 +55,63 @@ name = "Python greeting"
7655definition = {
7756 " name" : name,
7857 " nodes" : [{
79- " node_instance_id" : " hello" , " node_id" : " n_text_value" ,
80- " node_version" : " 1.0.0" , " title" : " Hello" ,
58+ " node_instance_id" : " hello" ,
59+ " node_id" : " n_text_value" ,
60+ " node_version" : " 1.0.0" ,
61+ " title" : " Hello" ,
8162 " params" : {" text" : " Hello from Python" },
8263 }],
8364 " edges" : [],
84- " graph_outputs" : {" text" : {" node_instance_id" : " hello" , " output_name" : " text" }},
65+ " graph_outputs" : {
66+ " text" : {" node_instance_id" : " hello" , " output_name" : " text" },
67+ },
8568}
69+
8670with Subfork() as client:
8771 client.graphs.validate(definition)
8872 graph = client.graphs.create(name = name, definition = definition)
8973 client.graphs.publish(graph[" id" ], version = " v1" )
74+
9075 execution = client.graphs.execute(graph[" id" ], version = " v1" )
9176 result = client.executions.wait(execution[" id" ], timeout = 120 )
9277 print (result[" status" ], result.get(" outputs" ))
9378```
9479
95- Discover published blocks with ` graphs.published() ` and retrieve their exact
96- interfaces with ` graphs.published_version(graph_id, version) ` . Compose their
97- returned node IDs with ` composite_graph ` parameters and explicit pinned versions.
98- ` graphs.interface(graph_id) ` previews the server-inferred interface.
99- ` graphs.update() ` replaces the submitted draft fields, including description;
100- pass the description to preserve it. Existing server ownership and quotas apply.
80+ To test a draft before publishing, call ` graphs.execute(graph_id) ` without a
81+ version. To update a draft, use ` graphs.update() ` with its name, definition, and
82+ description; omitting the description clears it.
10183
102- ## Errors and limits
84+ ## Reuse building blocks
10385
104- Catch ` AuthenticationError ` , ` PermissionDeniedError ` , ` ValidationError ` ,
105- ` RateLimitError ` , or the base ` APIError ` ; HTTP errors expose ` status_code ` and
106- ` retry_after ` . Messages intentionally do not echo raw response bodies or secrets.
107- ` TransportError ` means the outcome of a write may be unknown. There are no automatic
108- retries: inspect remote state before retrying create, publish or execute operations.
86+ Explore published graphs and inspect their versioned interfaces:
10987
110- ` executions.wait() ` polls until a terminal status and returns failed executions
111- as results too. ` ExecutionTimeout ` does ** not ** cancel the remote execution;
112- use ` executions.cancel(id) ` explicitly. Polling uses a monotonic deadline and caps
113- individual HTTP timeout settings by remaining time. HTTPX timeouts apply per I/O
114- phase, not a strict wall-clock deadline for the entire network exchange.
88+ ``` python
89+ with Subfork() as client:
90+ blocks = client.graphs.published()
91+ block = client.graphs.published_version(graph_id, " v1 " )
92+ ```
11593
116- The first scaffold is synchronous. Async support, rich response models, artifact
117- streaming, idempotent write retries and worker support are not included. Mock HTTP
118- tests do not certify any particular server deployment .
94+ Replace ` graph_id ` with the ID of a graph you want to use. Published graph versions
95+ can be composed into larger graphs using the returned composite node manifest.
96+ Pin child versions so later publications do not change your graph's behavior .
11997
120- ## Code quality
98+ ## Handle failures
12199
122- Install ` .[dev] ` to get the pinned development tools. Black 24.8.0, isort 5.13.2,
123- and Flake8 7.1.1 follow the conventions used in the sibling envstack and pyseq
124- projects: 100-column formatting, isort's Black profile, and a Python 3.8 target.
125- The tool versions also support running locally under Python 3.8.
100+ HTTP failures raise ` APIError ` subclasses, including ` AuthenticationError ` ,
101+ ` PermissionDeniedError ` , ` ValidationError ` , and ` RateLimitError ` . These expose
102+ ` status_code ` and an optional ` retry_after ` header.
126103
127- ``` bash
128- make format # Sort imports and apply Black to src/ and tests/
129- make lint # Check formatting, imports, Flake8 and Google-style docstrings
130- make typecheck # Check annotated library code with mypy
131- make test # Run the HTTP contract tests
132- make check # Run lint, type checking and tests
133- make build # Build distributions and check their metadata
134- ```
104+ ` executions.wait() ` returns failed and canceled runs as well as successful ones,
105+ so check the returned status. An ` ExecutionTimeout ` stops polling but does not
106+ cancel the remote run; use ` executions.cancel(execution_id) ` to request cancellation.
107+
108+ The client does not automatically retry requests. After a ` TransportError ` , check
109+ remote state before repeating a create, publish, or execute request.
110+
111+ ## Contributing
112+
113+ See [ CONTRIBUTING.md] ( CONTRIBUTING.md ) for development and code-quality checks.
114+
115+ ## License
135116
136- Activate your environment first, or specify it explicitly, for example:
137- ` make check PYTHON=.venv/bin/python ` . The CI lint job uses the same commands.
138- EditorConfig defines whitespace and newline conventions for supporting editors.
139-
140- Add annotations and docstrings to new functions, methods and classes, including
141- constructors and test helpers. Public docstrings should explain side effects,
142- permissions, return values and failure behavior where useful. JSON dictionaries
143- retain ` Any ` values because node parameters and server response fields are dynamic;
144- this release does not pretend to provide complete generated response models.
145- Mypy checks library signatures and bodies; Flake8 and formatting cover both the
146- library and tests. Runtime tests continue to cover Python 3.8 in CI.
117+ [ BSD-3-Clause] ( LICENSE ) .
0 commit comments