dyno-phi 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/.gitignore +4 -1
- dyno_phi-0.3.2/CHANGELOG.md +134 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/PKG-INFO +41 -19
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/README.md +36 -14
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/pyproject.toml +5 -5
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/__init__.py +2 -0
- dyno_phi-0.3.2/src/phi/_version.py +1 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/async_client.py +39 -6
- dyno_phi-0.3.2/src/phi/cli/commands/audit.py +133 -0
- dyno_phi-0.3.2/src/phi/cli/commands/auth.py +283 -0
- dyno_phi-0.3.2/src/phi/cli/commands/billing.py +145 -0
- dyno_phi-0.3.2/src/phi/cli/commands/doctor.py +177 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/jobs.py +31 -7
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/models.py +18 -2
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/pipeline.py +7 -19
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/quota.py +4 -1
- dyno_phi-0.3.2/src/phi/cli/commands/skills.py +251 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/document_sync.py +16 -5
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/errors.py +3 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/main.py +8 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/state.py +5 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/client.py +33 -3
- dyno_phi-0.3.2/src/phi/config.py +291 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/errors.py +5 -1
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/graph.py +26 -28
- dyno_phi-0.3.2/src/phi/models/__init__.py +16 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/_generated.py +2 -0
- dyno_phi-0.3.2/src/phi/models/audit.py +43 -0
- dyno_phi-0.3.2/src/phi/models/billing.py +92 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/job_types.py +5 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/jobs.py +17 -0
- dyno_phi-0.3.2/src/phi/models/quota.py +52 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/runs.py +45 -2
- dyno_phi-0.3.2/src/phi/oauth.py +670 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/profiles.py +38 -2
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/__init__.py +0 -16
- dyno_phi-0.3.2/src/phi/resources/audit.py +292 -0
- dyno_phi-0.3.2/src/phi/resources/billing.py +113 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/jobs.py +45 -1
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/quota.py +35 -6
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/runs.py +5 -4
- dyno_phi-0.3.2/tests/cli/test_audit.py +125 -0
- dyno_phi-0.3.2/tests/cli/test_auth.py +502 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_cli_scaffold.py +3 -0
- dyno_phi-0.3.2/tests/cli/test_doctor.py +190 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_document_sync.py +59 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_jobs.py +73 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_json_contract.py +40 -5
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_models_cmd.py +33 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_pipeline.py +19 -7
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_quota.py +11 -1
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_runs.py +3 -1
- dyno_phi-0.3.2/tests/cli/test_skills.py +175 -0
- dyno_phi-0.3.2/tests/test_audit.py +108 -0
- dyno_phi-0.3.2/tests/test_billing.py +87 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_errors.py +14 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_graph.py +0 -4
- dyno_phi-0.3.2/tests/test_oauth.py +657 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_packaging.py +31 -31
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_quota.py +24 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_runs.py +15 -9
- dyno_phi-0.3.0/CHANGELOG.md +0 -53
- dyno_phi-0.3.0/src/phi/_version.py +0 -1
- dyno_phi-0.3.0/src/phi/cli/commands/auth.py +0 -166
- dyno_phi-0.3.0/src/phi/config.py +0 -100
- dyno_phi-0.3.0/src/phi/models/__init__.py +0 -7
- dyno_phi-0.3.0/src/phi/models/quota.py +0 -25
- dyno_phi-0.3.0/tests/cli/test_auth.py +0 -268
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/examples/README.md +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/examples/async_concurrent.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/examples/compose_dag.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/examples/download_results.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/examples/pipelines/fold_and_analyze.yaml +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/examples/pipelines/mpnn_esmfold_alphafold.yaml +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/examples/quickstart/README.md +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/examples/quickstart/trp_cage.fasta +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/examples/run_pipeline.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/examples/submit_fold.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/_schema_validation.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/_transport.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/__init__.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/__main__.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/__init__.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/artifacts.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/datasets.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/documents.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/export.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/logs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/projects.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/quickstart.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/runs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/commands/use.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/completion.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/params.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/cli/render.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/files.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/artifacts.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/common.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/datasets.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/designs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/documents.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/imports.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/logs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/projects.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/run_admin.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/sequences.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/models/tool_configs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/py.typed +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/artifacts.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/datasets.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/designs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/documents.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/files.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/imports.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/job_types.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/logs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/projects.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/run_admin.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/sequences.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/tool_configs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/resources/uploads.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/schema_cache.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/src/phi/waiters.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/__init__.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/__init__.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/conftest.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_artifacts.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_completion.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_datasets.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_documents.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_export.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_logs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_params.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_projects.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/cli/test_quickstart.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/conftest.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/live/__init__.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/live/conftest.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/live/test_live_artifacts_logs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/live/test_live_auth_discovery.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/live/test_live_datasets_export.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/live/test_live_errors.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/live/test_live_jobs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/live/test_live_pipelines.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/live/test_live_quickstart_sdk.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_artifacts.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_config.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_datasets.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_designs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_docs_examples.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_documents.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_files.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_imports.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_job_types.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_jobs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_logs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_parity.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_projects.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_run_admin.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_schema_validation.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_sequences.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_tool_configs.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_transport.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_uploads.py +0 -0
- {dyno_phi-0.3.0 → dyno_phi-0.3.2}/tests/test_waiters.py +0 -0
|
@@ -92,6 +92,7 @@ cloudbuild/*.log
|
|
|
92
92
|
*.pdb
|
|
93
93
|
!tests/fixtures/metrics/ipsae_nonzero_multisubchain.pdb
|
|
94
94
|
!examples/bindcraft2/*.pdb
|
|
95
|
+
!experiments/campaigns/y4_agent_designs/assets/*.pdb
|
|
95
96
|
*.cif
|
|
96
97
|
!examples/bindcraft2/*.cif
|
|
97
98
|
*.csv
|
|
@@ -122,4 +123,6 @@ experiments/cxcr3_vhh_design/data/scored/refold_manifest*.tsv
|
|
|
122
123
|
# Agent-development guide is account/app-specific (DynoPhiAgent). Other
|
|
123
124
|
# contributors run different agents/apps, so keep it local-only, not in the
|
|
124
125
|
# shared repo.
|
|
125
|
-
docs/agent-development.md
|
|
126
|
+
docs/agent-development.md
|
|
127
|
+
# The tag ruleset the docs tell an admin to apply; *.json is ignored above.
|
|
128
|
+
!docs/github-rulesets/tags.json
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
|
|
4
|
+
package follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The
|
|
5
|
+
version is read from `src/phi/_version.py`; a release is the tag `client-v<version>`
|
|
6
|
+
on `dynotx/phi-api`, which `.github/workflows/publish-client.yml` builds and publishes.
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.3.2] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- `phi auth whoami`'s `credits_available`, `phi quota`'s available line and the
|
|
15
|
+
`insufficient credits` message count purchased credits as well as the
|
|
16
|
+
allowance (`total_available`, falling back to `credits_available` on an older
|
|
17
|
+
server). `Quota.spendable` is that number; `phi quota` adds an allowance line.
|
|
18
|
+
- `runs.estimate` sends `params` and `inputs`, and the estimate breakdown is a
|
|
19
|
+
list of `{stage, unit, count, cents}`. `phi pipeline estimate` asks
|
|
20
|
+
`POST /v1/jobs/estimate` for the price a submit would reserve.
|
|
21
|
+
- `phi auth login` no longer prompts for a base URL and a key by default; that
|
|
22
|
+
is `phi auth login --interactive --key`.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- `JobTypeSchema.typical_seconds`, `PipelineTypeResponse.typical_seconds`,
|
|
27
|
+
`JobSubmitResponse.estimated_seconds` and `Job.typical_seconds`: how long a
|
|
28
|
+
job type usually takes from submit to completion, from the API's completed
|
|
29
|
+
runs. `phi models list` shows it as a Time column ("about 4 min", "40 s") and
|
|
30
|
+
`phi jobs wait` and `phi jobs submit --wait` count the elapsed time against
|
|
31
|
+
it ("[30s of about 240s] RUNNING fold").
|
|
32
|
+
- `Job`, `JobStage`, `RunResponse`, `StageResponse` and `DagNode` read the
|
|
33
|
+
optional `error_code`, `exit_code` and `recommended_action` a failed run
|
|
34
|
+
carries; `phi jobs wait`, `phi pipeline run` and `phi quickstart` name the
|
|
35
|
+
code before the error when a failing job has one.
|
|
36
|
+
- `phi.billing`: `get()` (the balance, auto-reload, the load presets and packs),
|
|
37
|
+
`load(amount_cents)` or `load(pack=...)` (starts a load and returns the URL
|
|
38
|
+
to pay at), `loads()`, `auto_reload(...)` and `portal()`; `phi billing`,
|
|
39
|
+
`phi billing load <dollars>`, `phi billing loads`, `phi billing auto-reload
|
|
40
|
+
--on|--off [--threshold] [--amount] [--cap]` and `phi billing portal`. A
|
|
41
|
+
`Load` carries its `provider_ref`.
|
|
42
|
+
- `Quota` carries `purchased_balance`, `total_available`, `plan` and
|
|
43
|
+
`plan_source`, and `LedgerEntry` its `pool`; `phi quota` prints the plan and
|
|
44
|
+
the purchased balance. An older server that omits them still parses.
|
|
45
|
+
- `Billing.payment_method` (brand, last four and expiry of the saved card) and
|
|
46
|
+
`LedgerEntry.run_name`, `pipeline_type` and `note`, as the server sends them
|
|
47
|
+
from 0.2.1; `phi billing` prints the card. `quota.history(reason=[...])` keeps
|
|
48
|
+
the reasons asked for, and `quota.months()` is the ledger by calendar month.
|
|
49
|
+
- `phi skills list`, `phi skills show <name>` and `phi skills install [name]...
|
|
50
|
+
[--global] [--agent claude-code|cursor|codex|all]`: the agent skills Phi
|
|
51
|
+
publishes from `dynotx/phi-skills`, read from the site's index
|
|
52
|
+
(`https://phi.dynotx.com/skills.json`, or `PHI_SKILLS_INDEX`), installed
|
|
53
|
+
through `npx skills add` when `npx` is on `PATH` and otherwise written as
|
|
54
|
+
`SKILL.md` files into the agents' skill directories. `phi doctor` says how
|
|
55
|
+
many are installed and where.
|
|
56
|
+
- `phi docs sync` publishes a checkout that has no retrieval guard when every
|
|
57
|
+
document declares `access: general` in its frontmatter (`dynotx/phi-skills`);
|
|
58
|
+
a document that does not is named in the refusal.
|
|
59
|
+
- `phi auth login` signs in through the browser: Clerk's OAuth with PKCE and a
|
|
60
|
+
loopback redirect on a random port, the organization chosen at sign-in, the
|
|
61
|
+
access and refresh tokens saved in the profile (`auth = "oauth"`) and
|
|
62
|
+
refreshed when within sixty seconds of expiry or once when the API refuses the
|
|
63
|
+
token; a refresh the server declines ends with "Signed out. Run phi auth
|
|
64
|
+
login." and exit code 1. `--device` is the device authorization grant for a
|
|
65
|
+
machine with no browser; `--key [KEY]` is the key path, non-interactive when
|
|
66
|
+
the key or `PHI_API_KEY` is given. The base URL is `--api`, then the profile's,
|
|
67
|
+
then production; `--interactive` prompts. The CLI's OAuth client id is the
|
|
68
|
+
metadata document the API serves at `/.well-known/phi-cli-client.json`.
|
|
69
|
+
- `phi auth logout` revokes the refresh token at Clerk; `phi auth whoami` and
|
|
70
|
+
`phi auth profiles` say how a profile is authenticated and when its token
|
|
71
|
+
expires. `phi doctor` checks the base URL, the credential, the installed
|
|
72
|
+
version against PyPI and whether another `phi` is first on `PATH`.
|
|
73
|
+
- `PhiClient` and `AsyncPhiClient` take `user_agent=`; the CLI sends
|
|
74
|
+
`phi-cli/<version>`. `PhiSignedOutError` is exported.
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
|
|
78
|
+
- `Billing.presets` and `Billing.packs` use independent default lists for each
|
|
79
|
+
model instance.
|
|
80
|
+
|
|
81
|
+
## [0.3.1] - 2026-10-17
|
|
82
|
+
|
|
83
|
+
### Changed
|
|
84
|
+
|
|
85
|
+
- The CLI is installed with the package: `pip install dyno-phi` puts `phi` on the
|
|
86
|
+
path. The `[cli]` extra is kept as a no-op alias, so `dyno-phi[cli]` still
|
|
87
|
+
installs.
|
|
88
|
+
|
|
89
|
+
## [0.3.0] - 2026-10-17
|
|
90
|
+
|
|
91
|
+
The first release of the Phi SDK and CLI on PyPI, published from `clients/phi` in
|
|
92
|
+
`dynotx/phi-api`. It was distributed as `phi-client` 0.0.1 from this repository
|
|
93
|
+
over git during the internal preview.
|
|
94
|
+
|
|
95
|
+
`dyno-phi` 0.2.x on PyPI was an earlier, unrelated client of a retired API. 0.3.0
|
|
96
|
+
replaces it under the same name; nothing from 0.2.x carries over.
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
|
|
100
|
+
- `PhiClient` and `AsyncPhiClient` with the same resources and signatures:
|
|
101
|
+
`projects`, `datasets`, `sequences`, `designs`, `jobs`, `job_types`, `runs`,
|
|
102
|
+
`run_admin`, `artifacts`, `files`, `uploads`, `logs`, `quota`, `tool_configs`,
|
|
103
|
+
`imports`, `documents`.
|
|
104
|
+
- Typed request and response models, local JSON Schema validation of `params` and
|
|
105
|
+
`inputs` for every job type (`client.job_types.validate_params`), waiters for
|
|
106
|
+
jobs and runs, and a typed error taxonomy (`PhiError` and its subclasses).
|
|
107
|
+
- The `phi` CLI behind the `[cli]` extra, every command taking `--json`:
|
|
108
|
+
`auth`, `projects`, `datasets`, `docs`, `models`, `jobs`, `runs`, `pipeline`,
|
|
109
|
+
`artifacts`, `export`, `logs`, `quota`, `use`, `quickstart`.
|
|
110
|
+
- Profiles in `~/.config/phi/config.toml`, selected with `--profile` or
|
|
111
|
+
`PHI_PROFILE`.
|
|
112
|
+
- A Claude Code skill (`skills/phi/SKILL.md`) that drives the CLI.
|
|
113
|
+
|
|
114
|
+
### Changed from `dyno-phi` 0.2.x
|
|
115
|
+
|
|
116
|
+
- Environment: `PHI_API` is the base URL and `PHI_KEY` or `PHI_API_KEY` the key
|
|
117
|
+
(`DYNO_API_BASE_URL` and `DYNO_API_KEY` are not read). `PHI_ORG` names the
|
|
118
|
+
organization for a static key; a Clerk key carries its own.
|
|
119
|
+
- Command tree: `phi <group> <command>` as listed above. `phi upload`, `phi filter`,
|
|
120
|
+
`phi research`, `phi notes`, `phi tutorial`, the filter presets and the
|
|
121
|
+
per-model commands (`phi esmfold`, `phi alphafold`, ...) are gone; a model runs
|
|
122
|
+
as `phi jobs submit <JOB_TYPE>` and a chain of models as `phi runs submit` or
|
|
123
|
+
`phi pipeline run`. `phi quickstart` is the walkthrough.
|
|
124
|
+
- State: `.phi/state.json` holds the active project and dataset and the last job
|
|
125
|
+
and run, nothing else; the API key is never written there.
|
|
126
|
+
- Python 3.12 or newer. The SDK depends on `httpx` and `pydantic` alone; the CLI's
|
|
127
|
+
dependencies install only with the `[cli]` extra.
|
|
128
|
+
- Web app: results open at `design.dynotx.com`; the dashboard URLs the old client
|
|
129
|
+
printed no longer exist.
|
|
130
|
+
|
|
131
|
+
Versioning note: the client stays below 1.0 while the API it speaks to is itself
|
|
132
|
+
pre-1.0 (0.1.x) and the request shapes can still change between releases. 0.3.0
|
|
133
|
+
is the first release on this line only because 0.2.x was already taken on PyPI
|
|
134
|
+
by the earlier client; it is not a continuation of it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dyno-phi
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Phi — Python SDK and CLI for the Phi protein design API
|
|
5
5
|
Project-URL: Homepage, https://design.dynotx.com
|
|
6
6
|
Project-URL: Documentation, https://design.dynotx.com/docs
|
|
@@ -20,21 +20,21 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
20
20
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
21
21
|
Classifier: Typing :: Typed
|
|
22
22
|
Requires-Python: >=3.12
|
|
23
|
+
Requires-Dist: click>=8.1
|
|
23
24
|
Requires-Dist: httpx>=0.27
|
|
24
25
|
Requires-Dist: pydantic>=2.9
|
|
26
|
+
Requires-Dist: pyyaml>=6
|
|
27
|
+
Requires-Dist: rich>=13
|
|
28
|
+
Requires-Dist: typer>=0.12
|
|
25
29
|
Provides-Extra: cli
|
|
26
|
-
Requires-Dist: click>=8.1; extra == 'cli'
|
|
27
|
-
Requires-Dist: pyyaml>=6; extra == 'cli'
|
|
28
|
-
Requires-Dist: rich>=13; extra == 'cli'
|
|
29
|
-
Requires-Dist: typer>=0.12; extra == 'cli'
|
|
30
30
|
Description-Content-Type: text/markdown
|
|
31
31
|
|
|
32
32
|
# dyno-phi
|
|
33
33
|
|
|
34
34
|
Python SDK and CLI for the [Phi](https://design.dynotx.com) protein design API.
|
|
35
35
|
The `phi` package exposes synchronous (`PhiClient`) and asynchronous
|
|
36
|
-
(`AsyncPhiClient`) clients
|
|
37
|
-
|
|
36
|
+
(`AsyncPhiClient`) clients and the `phi` CLI, installed together. It is
|
|
37
|
+
published to PyPI from `clients/phi` in
|
|
38
38
|
[`dynotx/phi-api`](https://github.com/dynotx/phi-api).
|
|
39
39
|
|
|
40
40
|
## Install
|
|
@@ -42,16 +42,17 @@ stays light. It is published to PyPI from `clients/phi` in
|
|
|
42
42
|
The CLI as an isolated tool on your `PATH`:
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
uv tool install
|
|
45
|
+
uv tool install dyno-phi
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
`uv tool` provisions its own Python, which covers the client's **Python 3.12+**
|
|
49
49
|
requirement on stock macOS, where the system Python is 3.9. Re-run with
|
|
50
|
-
`--upgrade` to upgrade. `pip install
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
`--upgrade` to upgrade. `pip install dyno-phi` into a Python 3.12+ of your own
|
|
51
|
+
does the same. From a `phi-api` checkout, `just install-cli` runs the first
|
|
52
|
+
form and `just install-cli-dev` installs the working tree.
|
|
53
53
|
|
|
54
|
-
The SDK inside an existing project
|
|
54
|
+
The SDK inside an existing project (the CLI comes with it; the `[cli]` extra
|
|
55
|
+
from 0.3.0 still installs and adds nothing):
|
|
55
56
|
|
|
56
57
|
```bash
|
|
57
58
|
uv add dyno-phi # or: pip install dyno-phi
|
|
@@ -60,7 +61,7 @@ uv add dyno-phi # or: pip install dyno-phi
|
|
|
60
61
|
Check the install with `phi --version`. `dyno-phi` 0.2.x was an earlier,
|
|
61
62
|
unrelated client of a retired API; if `phi --version` prints `0.2.x`, that
|
|
62
63
|
client is first on your `PATH` (`which -a phi` lists every match) and
|
|
63
|
-
`pip install --upgrade
|
|
64
|
+
`pip install --upgrade dyno-phi` in the Python that owns it replaces it.
|
|
64
65
|
Coming from 0.2.x: the key is `PHI_API_KEY` (or `PHI_KEY`) and the base URL
|
|
65
66
|
`PHI_API`, the commands are `phi <group> <command>` as listed under
|
|
66
67
|
[CLI](#cli), and Python 3.12+ is required; see
|
|
@@ -79,13 +80,31 @@ path=("$HOME/.local/bin" $path)
|
|
|
79
80
|
|
|
80
81
|
## Authentication
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
Sign in through your browser:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
phi auth login
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
It opens Dyno's sign-in, you pick the organization, and the CLI saves the sign-in
|
|
90
|
+
to `~/.config/phi/config.toml`, refreshing it as it runs out. On a machine with
|
|
91
|
+
no browser, `phi auth login --device` prints a URL and a code to enter on another
|
|
92
|
+
device. `phi auth whoami` prints who you are and which organization and
|
|
93
|
+
environment you are on; `phi doctor` checks the install when something is off.
|
|
94
|
+
|
|
95
|
+
An agent, CI or a service account uses an API key from
|
|
96
|
+
[design.dynotx.com](https://design.dynotx.com) instead, either saved as a profile
|
|
97
|
+
or read from the environment:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
phi auth login --key ak_... # saved to the profile
|
|
101
|
+
export PHI_API_KEY=ak_... # or per process
|
|
102
|
+
```
|
|
84
103
|
|
|
85
104
|
| Variable | Purpose |
|
|
86
105
|
|---|---|
|
|
87
|
-
| `PHI_API` | Base URL of the API. Its host selects the environment class
|
|
88
|
-
| `PHI_KEY` / `PHI_API_KEY` | API key. |
|
|
106
|
+
| `PHI_API` | Base URL of the API. Its host selects the environment class: `hosted`, `staging`, or `local`. |
|
|
107
|
+
| `PHI_KEY` / `PHI_API_KEY` | API key. Wins over a stored sign-in. |
|
|
89
108
|
| `PHI_ORG` | Organization ID. Required with a static key. |
|
|
90
109
|
|
|
91
110
|
```bash
|
|
@@ -98,7 +117,8 @@ With a Clerk key (`ak_...`) the tenant comes from the key, so `PHI_ORG` is
|
|
|
98
117
|
ignored; it is required only for a static key.
|
|
99
118
|
|
|
100
119
|
Pass `base_url=`, `api_key=`, and `org=` to the client constructor to override
|
|
101
|
-
the environment.
|
|
120
|
+
the environment. `api_key=` takes a sign-in's access token as readily as a key.
|
|
121
|
+
The three ways and when to use each: [`docs/sdk-and-cli.md`](https://github.com/dynotx/phi-api/blob/main/docs/sdk-and-cli.md#authentication).
|
|
102
122
|
|
|
103
123
|
## 60-second quickstart
|
|
104
124
|
|
|
@@ -180,7 +200,7 @@ Every command reads the same environment and accepts `--json` for scripting.
|
|
|
180
200
|
|
|
181
201
|
| Group | Commands |
|
|
182
202
|
|---|---|
|
|
183
|
-
| `auth` | `login`, `whoami`, `logout`, `profiles` |
|
|
203
|
+
| `auth` | `login [--device] [--key [KEY]]`, `whoami`, `logout`, `profiles` |
|
|
184
204
|
| `projects` | `list`, `create`, `show`, `update`, `archive` |
|
|
185
205
|
| `datasets` | `list`, `create`, `show`, `update`, `delete`, `copy`, `export`, `sequences` |
|
|
186
206
|
| `docs` | `list`, `read`, `search`, `add`, `update`, `history`, `propose`, `activate`, `sync` |
|
|
@@ -192,8 +212,10 @@ Every command reads the same environment and accepts `--json` for scripting.
|
|
|
192
212
|
| `export` | `sequences`, `designs`, `structure`, `pae` |
|
|
193
213
|
| `logs` | `show`, `download`, `settings` |
|
|
194
214
|
| `quota` | (summary), `history` |
|
|
215
|
+
| `billing` | (summary), `load <dollars>`, `loads`, `auto-reload`, `portal` |
|
|
195
216
|
| `use` | Set the active project |
|
|
196
217
|
| `quickstart` | Run the end-to-end ESMFOLD example |
|
|
218
|
+
| `doctor` | Check the base URL, the credential, the version and the `PATH` |
|
|
197
219
|
|
|
198
220
|
`phi docs sync <dir> --dry-run` safely plans publication from a clean knowledge
|
|
199
221
|
checkout. Without `--dry-run` it publishes only Git-tracked files in the
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Python SDK and CLI for the [Phi](https://design.dynotx.com) protein design API.
|
|
4
4
|
The `phi` package exposes synchronous (`PhiClient`) and asynchronous
|
|
5
|
-
(`AsyncPhiClient`) clients
|
|
6
|
-
|
|
5
|
+
(`AsyncPhiClient`) clients and the `phi` CLI, installed together. It is
|
|
6
|
+
published to PyPI from `clients/phi` in
|
|
7
7
|
[`dynotx/phi-api`](https://github.com/dynotx/phi-api).
|
|
8
8
|
|
|
9
9
|
## Install
|
|
@@ -11,16 +11,17 @@ stays light. It is published to PyPI from `clients/phi` in
|
|
|
11
11
|
The CLI as an isolated tool on your `PATH`:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
uv tool install
|
|
14
|
+
uv tool install dyno-phi
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
`uv tool` provisions its own Python, which covers the client's **Python 3.12+**
|
|
18
18
|
requirement on stock macOS, where the system Python is 3.9. Re-run with
|
|
19
|
-
`--upgrade` to upgrade. `pip install
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
`--upgrade` to upgrade. `pip install dyno-phi` into a Python 3.12+ of your own
|
|
20
|
+
does the same. From a `phi-api` checkout, `just install-cli` runs the first
|
|
21
|
+
form and `just install-cli-dev` installs the working tree.
|
|
22
22
|
|
|
23
|
-
The SDK inside an existing project
|
|
23
|
+
The SDK inside an existing project (the CLI comes with it; the `[cli]` extra
|
|
24
|
+
from 0.3.0 still installs and adds nothing):
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
27
|
uv add dyno-phi # or: pip install dyno-phi
|
|
@@ -29,7 +30,7 @@ uv add dyno-phi # or: pip install dyno-phi
|
|
|
29
30
|
Check the install with `phi --version`. `dyno-phi` 0.2.x was an earlier,
|
|
30
31
|
unrelated client of a retired API; if `phi --version` prints `0.2.x`, that
|
|
31
32
|
client is first on your `PATH` (`which -a phi` lists every match) and
|
|
32
|
-
`pip install --upgrade
|
|
33
|
+
`pip install --upgrade dyno-phi` in the Python that owns it replaces it.
|
|
33
34
|
Coming from 0.2.x: the key is `PHI_API_KEY` (or `PHI_KEY`) and the base URL
|
|
34
35
|
`PHI_API`, the commands are `phi <group> <command>` as listed under
|
|
35
36
|
[CLI](#cli), and Python 3.12+ is required; see
|
|
@@ -48,13 +49,31 @@ path=("$HOME/.local/bin" $path)
|
|
|
48
49
|
|
|
49
50
|
## Authentication
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
Sign in through your browser:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
phi auth login
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
It opens Dyno's sign-in, you pick the organization, and the CLI saves the sign-in
|
|
59
|
+
to `~/.config/phi/config.toml`, refreshing it as it runs out. On a machine with
|
|
60
|
+
no browser, `phi auth login --device` prints a URL and a code to enter on another
|
|
61
|
+
device. `phi auth whoami` prints who you are and which organization and
|
|
62
|
+
environment you are on; `phi doctor` checks the install when something is off.
|
|
63
|
+
|
|
64
|
+
An agent, CI or a service account uses an API key from
|
|
65
|
+
[design.dynotx.com](https://design.dynotx.com) instead, either saved as a profile
|
|
66
|
+
or read from the environment:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
phi auth login --key ak_... # saved to the profile
|
|
70
|
+
export PHI_API_KEY=ak_... # or per process
|
|
71
|
+
```
|
|
53
72
|
|
|
54
73
|
| Variable | Purpose |
|
|
55
74
|
|---|---|
|
|
56
|
-
| `PHI_API` | Base URL of the API. Its host selects the environment class
|
|
57
|
-
| `PHI_KEY` / `PHI_API_KEY` | API key. |
|
|
75
|
+
| `PHI_API` | Base URL of the API. Its host selects the environment class: `hosted`, `staging`, or `local`. |
|
|
76
|
+
| `PHI_KEY` / `PHI_API_KEY` | API key. Wins over a stored sign-in. |
|
|
58
77
|
| `PHI_ORG` | Organization ID. Required with a static key. |
|
|
59
78
|
|
|
60
79
|
```bash
|
|
@@ -67,7 +86,8 @@ With a Clerk key (`ak_...`) the tenant comes from the key, so `PHI_ORG` is
|
|
|
67
86
|
ignored; it is required only for a static key.
|
|
68
87
|
|
|
69
88
|
Pass `base_url=`, `api_key=`, and `org=` to the client constructor to override
|
|
70
|
-
the environment.
|
|
89
|
+
the environment. `api_key=` takes a sign-in's access token as readily as a key.
|
|
90
|
+
The three ways and when to use each: [`docs/sdk-and-cli.md`](https://github.com/dynotx/phi-api/blob/main/docs/sdk-and-cli.md#authentication).
|
|
71
91
|
|
|
72
92
|
## 60-second quickstart
|
|
73
93
|
|
|
@@ -149,7 +169,7 @@ Every command reads the same environment and accepts `--json` for scripting.
|
|
|
149
169
|
|
|
150
170
|
| Group | Commands |
|
|
151
171
|
|---|---|
|
|
152
|
-
| `auth` | `login`, `whoami`, `logout`, `profiles` |
|
|
172
|
+
| `auth` | `login [--device] [--key [KEY]]`, `whoami`, `logout`, `profiles` |
|
|
153
173
|
| `projects` | `list`, `create`, `show`, `update`, `archive` |
|
|
154
174
|
| `datasets` | `list`, `create`, `show`, `update`, `delete`, `copy`, `export`, `sequences` |
|
|
155
175
|
| `docs` | `list`, `read`, `search`, `add`, `update`, `history`, `propose`, `activate`, `sync` |
|
|
@@ -161,8 +181,10 @@ Every command reads the same environment and accepts `--json` for scripting.
|
|
|
161
181
|
| `export` | `sequences`, `designs`, `structure`, `pae` |
|
|
162
182
|
| `logs` | `show`, `download`, `settings` |
|
|
163
183
|
| `quota` | (summary), `history` |
|
|
184
|
+
| `billing` | (summary), `load <dollars>`, `loads`, `auto-reload`, `portal` |
|
|
164
185
|
| `use` | Set the active project |
|
|
165
186
|
| `quickstart` | Run the end-to-end ESMFOLD example |
|
|
187
|
+
| `doctor` | Check the base URL, the credential, the version and the `PATH` |
|
|
166
188
|
|
|
167
189
|
`phi docs sync <dir> --dry-run` safely plans publication from a clean knowledge
|
|
168
190
|
checkout. Without `--dry-run` it publishes only Git-tracked files in the
|
|
@@ -33,10 +33,6 @@ classifiers = [
|
|
|
33
33
|
dependencies = [
|
|
34
34
|
"httpx>=0.27",
|
|
35
35
|
"pydantic>=2.9",
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
[project.optional-dependencies]
|
|
39
|
-
cli = [
|
|
40
36
|
"typer>=0.12",
|
|
41
37
|
# Typer vendors Click from 0.27 on, so it is no longer a transitive
|
|
42
38
|
# dependency. The CLI imports click directly for UsageError and confirm.
|
|
@@ -45,6 +41,10 @@ cli = [
|
|
|
45
41
|
"pyyaml>=6",
|
|
46
42
|
]
|
|
47
43
|
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
# The CLI ships with the package since 0.3.1; `dyno-phi[cli]` stays installable.
|
|
46
|
+
cli = []
|
|
47
|
+
|
|
48
48
|
[project.scripts]
|
|
49
49
|
phi = "phi.cli:main"
|
|
50
50
|
|
|
@@ -57,7 +57,7 @@ Issues = "https://github.com/dynotx/phi-api/issues"
|
|
|
57
57
|
|
|
58
58
|
[dependency-groups]
|
|
59
59
|
dev = [
|
|
60
|
-
"dyno-phi
|
|
60
|
+
"dyno-phi",
|
|
61
61
|
"pytest>=8.3.0",
|
|
62
62
|
"pytest-asyncio>=0.24.0",
|
|
63
63
|
"respx>=0.21.0",
|
|
@@ -18,6 +18,7 @@ from .errors import (
|
|
|
18
18
|
)
|
|
19
19
|
from .graph import Node, PipelineGraph, PortRef
|
|
20
20
|
from .models import JobTypeSchema
|
|
21
|
+
from .oauth import PhiSignedOutError
|
|
21
22
|
|
|
22
23
|
__all__ = [
|
|
23
24
|
"AsyncPhiClient",
|
|
@@ -37,5 +38,6 @@ __all__ = [
|
|
|
37
38
|
"PhiInsufficientCreditsError",
|
|
38
39
|
"PhiRateLimitError",
|
|
39
40
|
"PhiServerError",
|
|
41
|
+
"PhiSignedOutError",
|
|
40
42
|
"__version__",
|
|
41
43
|
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.2"
|
|
@@ -13,8 +13,11 @@ from ._transport import (
|
|
|
13
13
|
async_send_with_retry,
|
|
14
14
|
parse_response,
|
|
15
15
|
)
|
|
16
|
+
from ._version import __version__
|
|
16
17
|
from .config import Profile
|
|
17
18
|
from .resources.artifacts import AsyncArtifactsResource
|
|
19
|
+
from .resources.audit import AsyncAuditResource
|
|
20
|
+
from .resources.billing import AsyncBillingResource
|
|
18
21
|
from .resources.datasets import AsyncDatasetsResource
|
|
19
22
|
from .resources.designs import AsyncDesignsResource
|
|
20
23
|
from .resources.documents import AsyncDocumentsResource
|
|
@@ -43,16 +46,24 @@ class AsyncPhiClient:
|
|
|
43
46
|
timeout: float = 30.0,
|
|
44
47
|
max_retries: int = 3,
|
|
45
48
|
http_client: httpx.AsyncClient | None = None,
|
|
49
|
+
user_agent: str | None = None,
|
|
46
50
|
) -> None:
|
|
51
|
+
"""`api_key` is an API key or the access token of a sign-in; with neither, the
|
|
52
|
+
profile's stored credential is used and refreshed as it runs out."""
|
|
47
53
|
self.profile = Profile(base_url=base_url, api_key=api_key, org=org, profile=profile)
|
|
48
54
|
self.retry_policy = RetryPolicy(max_retries=max_retries)
|
|
49
55
|
self._http = http_client or httpx.AsyncClient(
|
|
50
56
|
base_url=self.profile.base_url,
|
|
51
|
-
headers=
|
|
57
|
+
headers={
|
|
58
|
+
**self.profile.auth_headers(),
|
|
59
|
+
"User-Agent": user_agent or f"dyno-phi/{__version__}",
|
|
60
|
+
},
|
|
52
61
|
timeout=timeout,
|
|
53
62
|
)
|
|
54
63
|
|
|
55
64
|
self.quota = AsyncQuotaResource(self)
|
|
65
|
+
self.audit = AsyncAuditResource(self)
|
|
66
|
+
self.billing = AsyncBillingResource(self)
|
|
56
67
|
self.projects = AsyncProjectsResource(self)
|
|
57
68
|
self.datasets = AsyncDatasetsResource(self)
|
|
58
69
|
self.jobs = AsyncJobsResource(self)
|
|
@@ -78,7 +89,15 @@ class AsyncPhiClient:
|
|
|
78
89
|
return body
|
|
79
90
|
|
|
80
91
|
async def send_with_headers(self, spec: RequestSpec) -> tuple[Any, httpx.Headers]:
|
|
81
|
-
|
|
92
|
+
await self._refresh_if_expiring()
|
|
93
|
+
response = await self._send(spec)
|
|
94
|
+
# A token the server refuses is refreshed once; a second refusal is the answer.
|
|
95
|
+
if response.status_code == 401 and await self._refresh_token():
|
|
96
|
+
response = await self._send(spec)
|
|
97
|
+
return parse_response(response, method=spec.method, path=spec.path), response.headers
|
|
98
|
+
|
|
99
|
+
async def _send(self, spec: RequestSpec) -> httpx.Response:
|
|
100
|
+
return await async_send_with_retry(
|
|
82
101
|
lambda: self._http.request(
|
|
83
102
|
spec.method,
|
|
84
103
|
spec.path,
|
|
@@ -90,10 +109,23 @@ class AsyncPhiClient:
|
|
|
90
109
|
path=spec.path,
|
|
91
110
|
policy=self.retry_policy,
|
|
92
111
|
)
|
|
93
|
-
return parse_response(response, method=spec.method, path=spec.path), response.headers
|
|
94
112
|
|
|
95
|
-
def
|
|
96
|
-
|
|
113
|
+
async def _refresh_if_expiring(self) -> None:
|
|
114
|
+
if self.profile.needs_refresh():
|
|
115
|
+
await self._refresh_token()
|
|
116
|
+
|
|
117
|
+
async def _refresh_token(self) -> bool:
|
|
118
|
+
"""Refresh a stored sign-in and carry the new token on every later request."""
|
|
119
|
+
if not await self.profile.arefresh():
|
|
120
|
+
return False
|
|
121
|
+
self._http.headers["X-API-Key"] = self.profile.api_key
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
async def stream_lines(self, spec: RequestSpec) -> AsyncIterator[str]:
|
|
125
|
+
# The refresh is the iterator's first step, so it is awaited on the loop
|
|
126
|
+
# rather than run blocking before the iterator is handed back.
|
|
127
|
+
await self._refresh_if_expiring()
|
|
128
|
+
async for line in async_iter_lines_with_retry(
|
|
97
129
|
lambda: self._http.stream(
|
|
98
130
|
spec.method,
|
|
99
131
|
spec.path,
|
|
@@ -104,7 +136,8 @@ class AsyncPhiClient:
|
|
|
104
136
|
method=spec.method,
|
|
105
137
|
path=spec.path,
|
|
106
138
|
policy=self.retry_policy,
|
|
107
|
-
)
|
|
139
|
+
):
|
|
140
|
+
yield line
|
|
108
141
|
|
|
109
142
|
async def aclose(self) -> None:
|
|
110
143
|
await self._http.aclose()
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from .. import render
|
|
11
|
+
from ..state import CliState
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(no_args_is_help=True, help="Inspect and export organization audit events.")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AuditFormat(StrEnum):
|
|
17
|
+
csv = "csv"
|
|
18
|
+
jsonl = "jsonl"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _datetime(value: str | None, option: str) -> datetime | None:
|
|
22
|
+
if value is None:
|
|
23
|
+
return None
|
|
24
|
+
return _required_datetime(value, option)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _required_datetime(value: str, option: str) -> datetime:
|
|
28
|
+
try:
|
|
29
|
+
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
30
|
+
except ValueError as exc:
|
|
31
|
+
raise typer.BadParameter("must be an ISO 8601 timestamp", param_hint=option) from exc
|
|
32
|
+
if parsed.tzinfo is None or parsed.utcoffset() is None:
|
|
33
|
+
raise typer.BadParameter("must include a timezone", param_hint=option)
|
|
34
|
+
return parsed
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.command("list")
|
|
38
|
+
def list_events(
|
|
39
|
+
ctx: typer.Context,
|
|
40
|
+
limit: int = typer.Option(50, "--limit", min=1, max=200),
|
|
41
|
+
from_: str | None = typer.Option(None, "--from"),
|
|
42
|
+
to: str | None = typer.Option(None, "--to"),
|
|
43
|
+
action: list[str] | None = typer.Option(None, "--action"),
|
|
44
|
+
category: list[str] | None = typer.Option(None, "--category"),
|
|
45
|
+
actor: str | None = typer.Option(None, "--actor"),
|
|
46
|
+
subject_type: str | None = typer.Option(None, "--subject-type"),
|
|
47
|
+
subject_id: str | None = typer.Option(None, "--subject-id"),
|
|
48
|
+
source: list[str] | None = typer.Option(None, "--source"),
|
|
49
|
+
outcome: list[str] | None = typer.Option(None, "--outcome"),
|
|
50
|
+
text: str | None = typer.Option(None, "--text"),
|
|
51
|
+
) -> None:
|
|
52
|
+
state: CliState = ctx.obj
|
|
53
|
+
events = state.client.audit.list(
|
|
54
|
+
page_size=limit,
|
|
55
|
+
from_=_datetime(from_, "--from"),
|
|
56
|
+
to=_datetime(to, "--to"),
|
|
57
|
+
action=action,
|
|
58
|
+
category=category,
|
|
59
|
+
actor=actor,
|
|
60
|
+
subject_type=subject_type,
|
|
61
|
+
subject_id=subject_id,
|
|
62
|
+
source=source,
|
|
63
|
+
outcome=outcome,
|
|
64
|
+
text=text,
|
|
65
|
+
)
|
|
66
|
+
if state.json_output:
|
|
67
|
+
render.print_json([event.model_dump(mode="json") for event in events])
|
|
68
|
+
return
|
|
69
|
+
render.table(
|
|
70
|
+
["id", "occurred_at", "action", "actor", "subject", "outcome", "source"],
|
|
71
|
+
[
|
|
72
|
+
(
|
|
73
|
+
event.id,
|
|
74
|
+
event.occurred_at.isoformat(),
|
|
75
|
+
event.action,
|
|
76
|
+
event.actor.label,
|
|
77
|
+
event.subject.label or event.subject.id or event.subject.type,
|
|
78
|
+
event.outcome,
|
|
79
|
+
event.source,
|
|
80
|
+
)
|
|
81
|
+
for event in events
|
|
82
|
+
],
|
|
83
|
+
title="audit events",
|
|
84
|
+
no_color=state.no_color,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.command("show")
|
|
89
|
+
def show_event(
|
|
90
|
+
ctx: typer.Context,
|
|
91
|
+
event_id: int = typer.Argument(..., min=1),
|
|
92
|
+
) -> None:
|
|
93
|
+
state: CliState = ctx.obj
|
|
94
|
+
render.model(state, state.client.audit.show(event_id), title="audit event")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.command("export")
|
|
98
|
+
def export_events(
|
|
99
|
+
ctx: typer.Context,
|
|
100
|
+
from_: str = typer.Option(..., "--from"),
|
|
101
|
+
to: str = typer.Option(..., "--to"),
|
|
102
|
+
format: AuditFormat = typer.Option(AuditFormat.csv, "--format"),
|
|
103
|
+
out: str = typer.Option("-", "--out", help="Output file, or - for stdout."),
|
|
104
|
+
action: list[str] | None = typer.Option(None, "--action"),
|
|
105
|
+
category: list[str] | None = typer.Option(None, "--category"),
|
|
106
|
+
actor: str | None = typer.Option(None, "--actor"),
|
|
107
|
+
subject_type: str | None = typer.Option(None, "--subject-type"),
|
|
108
|
+
subject_id: str | None = typer.Option(None, "--subject-id"),
|
|
109
|
+
source: list[str] | None = typer.Option(None, "--source"),
|
|
110
|
+
outcome: list[str] | None = typer.Option(None, "--outcome"),
|
|
111
|
+
text: str | None = typer.Option(None, "--text"),
|
|
112
|
+
) -> None:
|
|
113
|
+
state: CliState = ctx.obj
|
|
114
|
+
lines = state.client.audit.export(
|
|
115
|
+
format=format.value,
|
|
116
|
+
from_=_required_datetime(from_, "--from"),
|
|
117
|
+
to=_required_datetime(to, "--to"),
|
|
118
|
+
action=action,
|
|
119
|
+
category=category,
|
|
120
|
+
actor=actor,
|
|
121
|
+
subject_type=subject_type,
|
|
122
|
+
subject_id=subject_id,
|
|
123
|
+
source=source,
|
|
124
|
+
outcome=outcome,
|
|
125
|
+
text=text,
|
|
126
|
+
)
|
|
127
|
+
if out == "-":
|
|
128
|
+
for line in lines:
|
|
129
|
+
sys.stdout.write(line + "\n")
|
|
130
|
+
return
|
|
131
|
+
with Path(out).open("w") as handle:
|
|
132
|
+
for line in lines:
|
|
133
|
+
handle.write(line + "\n")
|