Skip to content

Commit d7112cf

Browse files
committed
feat(python-sdk): add understand-quickly Python SDK + PyPI publish workflow
1 parent c5640e1 commit d7112cf

17 files changed

Lines changed: 1900 additions & 0 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: publish-pysdk
2+
3+
# Triggered when a GitHub Release is published with a tag like
4+
# `pysdk-vX.Y.Z`. Using a published-release trigger so the release
5+
# notes are already in place when artifacts upload, and so the
6+
# sdist/wheel attach to the release page itself.
7+
8+
on:
9+
release:
10+
types: [published]
11+
workflow_dispatch:
12+
inputs:
13+
ref:
14+
description: 'Tag or branch to build (must match pysdk-v*)'
15+
required: false
16+
default: ''
17+
18+
permissions:
19+
contents: write # upload assets onto the GitHub release
20+
id-token: write # reserved if we ever switch to trusted-publishing
21+
22+
jobs:
23+
build-and-publish:
24+
name: Build + publish python-sdk
25+
runs-on: ubuntu-latest
26+
if: ${{ github.event_name != 'release' || startsWith(github.event.release.tag_name, 'pysdk-v') }}
27+
28+
defaults:
29+
run:
30+
working-directory: python-sdk
31+
32+
steps:
33+
- uses: actions/checkout@v4
34+
with:
35+
ref: ${{ github.event.inputs.ref || github.ref }}
36+
37+
- name: Set up Python
38+
uses: actions/setup-python@v5
39+
with:
40+
python-version: '3.12'
41+
42+
- name: Install build tooling
43+
run: python -m pip install --upgrade pip hatchling build twine
44+
45+
- name: Build sdist + wheel
46+
run: python -m build .
47+
48+
- name: Twine check
49+
run: python -m twine check dist/*
50+
51+
- name: Upload artifacts to workflow
52+
uses: actions/upload-artifact@v4
53+
with:
54+
name: python-sdk-dist
55+
path: python-sdk/dist/*
56+
if-no-files-found: error
57+
58+
- name: Attach wheel + sdist to GitHub release
59+
if: github.event_name == 'release'
60+
uses: softprops/action-gh-release@v2
61+
with:
62+
files: python-sdk/dist/*
63+
64+
- name: Publish to PyPI (skip cleanly if PYPI_API_TOKEN unset)
65+
env:
66+
TWINE_USERNAME: __token__
67+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
68+
run: |
69+
if [ -z "${TWINE_PASSWORD}" ]; then
70+
echo "::warning::PYPI_API_TOKEN secret is not set; skipping PyPI publish. Set PYPI_API_TOKEN on the repo to enable publishing."
71+
exit 0
72+
fi
73+
python -m twine upload --skip-existing dist/*

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ Pick the path that fits:
6868

6969
Tools: `list_repos`, `get_graph`, `search_concepts`. See [`mcp/README.md`](mcp/README.md).
7070

71+
### I'm a Python developer
72+
73+
```bash
74+
pip install understand-quickly
75+
```
76+
77+
```python
78+
from understand_quickly import Registry
79+
print(Registry().list(status="ok"))
80+
```
81+
82+
See [`python-sdk/README.md`](python-sdk/README.md).
83+
7184
### I'm a developer / contributor
7285

7386
```bash

python-sdk/.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.egg-info/
6+
.pytest_cache/
7+
.mypy_cache/
8+
.ruff_cache/
9+
10+
# Virtual envs
11+
.venv/
12+
venv/
13+
env/
14+
15+
# Build artifacts
16+
build/
17+
dist/
18+
*.whl
19+
*.tar.gz

python-sdk/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Alex Macdonald-Smith <Alex.Mac@LoopTech.AI>
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.

python-sdk/README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# understand-quickly (Python SDK)
2+
3+
A thin Python client for the [understand-quickly](https://looptech-ai.github.io/understand-quickly/) registry of code-knowledge graphs.
4+
5+
```bash
6+
pip install understand-quickly
7+
```
8+
9+
- **Sync client** (zero runtime deps beyond `httpx`): `Registry`
10+
- **Async client** (using `httpx.AsyncClient`): `AsyncRegistry`
11+
- **CLI**: `python -m understand_quickly` or `understand-quickly`
12+
- 60-second in-memory TTL cache by default.
13+
14+
## Quick examples
15+
16+
```python
17+
from understand_quickly import Registry
18+
19+
reg = Registry() # default registry: looptech-ai.github.io/understand-quickly
20+
21+
# 1. List all healthy entries.
22+
entries = reg.list(status="ok")
23+
24+
# 2. Filter by format.
25+
ua = reg.list(format="understand-anything@1")
26+
27+
# 3. Resolve an entry id to its graph body.
28+
graph = reg.get_graph("Lum1104/Understand-Anything")
29+
30+
# 4. Map a GitHub URL back to its registry entry.
31+
hit = reg.find_graph_for_repo("https://github.com/owner/repo")
32+
33+
# 5. Cross-graph concept search (uses stats.json + entry metadata).
34+
results = reg.search("auth", scope="all")
35+
```
36+
37+
## Async equivalent
38+
39+
```python
40+
import asyncio
41+
from understand_quickly import AsyncRegistry
42+
43+
async def main() -> None:
44+
async with AsyncRegistry() as reg:
45+
entries = await reg.list(status="ok")
46+
graph = await reg.get_graph(entries[0]["id"])
47+
print(len(graph.get("nodes", [])))
48+
49+
asyncio.run(main())
50+
```
51+
52+
## CLI
53+
54+
```
55+
understand-quickly list [--status ok] [--format understand-anything@1] [--owner OWNER] [--tag TAG]
56+
understand-quickly get-graph <entry_id>
57+
understand-quickly find <github_url_or_owner/repo>
58+
understand-quickly search <query> [--scope all|entries|concepts]
59+
understand-quickly stats
60+
```
61+
62+
JSON is emitted by default. Add `--pretty` for indented JSON (and a compact table for `list`). Both `python -m understand_quickly ...` and `understand-quickly ...` work after install.
63+
64+
Exit codes: `0` success, `1` not-found (`get-graph`, `find`), `2` HTTP/parse error, `64` usage error.
65+
66+
## Environment variables
67+
68+
| Name | Purpose | Default |
69+
| --- | --- | --- |
70+
| `UNDERSTAND_QUICKLY_REGISTRY` | Override the registry base URL. | `https://looptech-ai.github.io/understand-quickly/` |
71+
72+
The base URL can also be passed explicitly: `Registry("https://my-mirror.example/")`.
73+
74+
## Public types
75+
76+
`understand_quickly.types` exports `TypedDict` shapes for the documents this SDK fetches:
77+
78+
- `Entry` — one registry entry (matches `registry.json`).
79+
- `Stats`, `StatsTotals`, `StatsKind`, `StatsLanguage`, `StatsConcept``stats.json` shape.
80+
- `WellKnown`, `WellKnownRepo``.well-known/repos.json` shape.
81+
- `SearchHit` — single result from `Registry.search()`.
82+
- `Graph` — opaque dict, shape depends on `entry["format"]`.
83+
84+
Everything is `total=False` so you can rely on `entry.get("status")` returning safely.
85+
86+
## Development
87+
88+
```bash
89+
cd python-sdk
90+
python -m pip install -e ".[dev]"
91+
pytest -q
92+
python -m build .
93+
```
94+
95+
## Release notes
96+
97+
See the project [release notes](https://github.com/looptech-ai/understand-quickly/releases) on GitHub. Python SDK versions are tagged `pysdk-vX.Y.Z`.
98+
99+
## License
100+
101+
[MIT](LICENSE) — Copyright (c) 2026 Alex Macdonald-Smith.

python-sdk/pyproject.toml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
[build-system]
2+
requires = ["hatchling>=1.18"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "understand-quickly"
7+
version = "0.1.0"
8+
description = "Thin Python SDK for the understand-quickly registry of code-knowledge graphs."
9+
readme = "README.md"
10+
license = { file = "LICENSE" }
11+
requires-python = ">=3.10"
12+
authors = [
13+
{ name = "Alex Macdonald-Smith", email = "Alex.Mac@LoopTech.AI" },
14+
]
15+
keywords = [
16+
"code-knowledge-graph",
17+
"registry",
18+
"ai-agents",
19+
"mcp",
20+
"understand-quickly",
21+
]
22+
classifiers = [
23+
"Development Status :: 4 - Beta",
24+
"Intended Audience :: Developers",
25+
"License :: OSI Approved :: MIT License",
26+
"Operating System :: OS Independent",
27+
"Programming Language :: Python :: 3",
28+
"Programming Language :: Python :: 3.10",
29+
"Programming Language :: Python :: 3.11",
30+
"Programming Language :: Python :: 3.12",
31+
"Programming Language :: Python :: 3.13",
32+
"Topic :: Software Development :: Libraries :: Python Modules",
33+
]
34+
dependencies = [
35+
"httpx>=0.25",
36+
]
37+
38+
[project.urls]
39+
Homepage = "https://looptech-ai.github.io/understand-quickly/"
40+
Repository = "https://github.com/looptech-ai/understand-quickly"
41+
Issues = "https://github.com/looptech-ai/understand-quickly/issues"
42+
Releases = "https://github.com/looptech-ai/understand-quickly/releases"
43+
44+
[project.optional-dependencies]
45+
dev = [
46+
"pytest>=7",
47+
"pytest-asyncio>=0.21",
48+
"build>=1.0",
49+
"twine>=4.0",
50+
]
51+
52+
[project.scripts]
53+
understand-quickly = "understand_quickly.cli:main"
54+
55+
[tool.hatch.build.targets.wheel]
56+
packages = ["understand_quickly"]
57+
58+
[tool.hatch.build.targets.sdist]
59+
include = [
60+
"understand_quickly",
61+
"tests",
62+
"README.md",
63+
"LICENSE",
64+
"pyproject.toml",
65+
]
66+
67+
[tool.pytest.ini_options]
68+
testpaths = ["tests"]
69+
asyncio_mode = "auto"

python-sdk/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)