Skip to content

Commit be80cf8

Browse files
feat: add Python SDK and CLI
1 parent 99fce04 commit be80cf8

34 files changed

Lines changed: 10711 additions & 1 deletion

.github/workflows/ci.yml

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
11+
concurrency:
12+
group: ci-${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
test-package:
17+
name: ${{ matrix.os }} / Python ${{ matrix.python-version }}
18+
runs-on: ${{ matrix.os }}
19+
timeout-minutes: 30
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
os:
24+
- ubuntu-latest
25+
- macos-15
26+
- windows-latest
27+
python-version:
28+
- "3.10"
29+
- "3.14"
30+
31+
env:
32+
PIP_DISABLE_PIP_VERSION_CHECK: "1"
33+
PYTHONUTF8: "1"
34+
35+
steps:
36+
- name: Check out repository
37+
uses: actions/checkout@v7
38+
39+
- name: Set up Python ${{ matrix.python-version }}
40+
uses: actions/setup-python@v7
41+
with:
42+
python-version: ${{ matrix.python-version }}
43+
cache: pip
44+
cache-dependency-path: pyproject.toml
45+
46+
- name: Install package and development tools
47+
run: python -m pip install -e ".[dev]"
48+
49+
- name: Check formatting
50+
run: python -m ruff format --check src tests
51+
52+
- name: Lint
53+
run: python -m ruff check src tests
54+
55+
- name: Type-check
56+
run: python -m mypy
57+
58+
- name: Test
59+
run: python -m pytest --cov=annotateit_ai --cov-report=term-missing
60+
61+
- name: Build source and wheel distributions
62+
run: python -m build
63+
64+
- name: Check distribution metadata
65+
run: python -m twine check dist/*
66+
67+
- name: Create isolated wheel smoke-test environment
68+
run: python -m venv .wheel-smoke
69+
70+
- name: Smoke-test wheel on Windows
71+
if: runner.os == 'Windows'
72+
shell: pwsh
73+
run: |
74+
$wheels = @(Get-ChildItem -LiteralPath dist -Filter *.whl)
75+
if ($wheels.Count -ne 1) { throw "Expected exactly one wheel, found $($wheels.Count)." }
76+
.\.wheel-smoke\Scripts\python.exe -m pip install $wheels[0].FullName
77+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
78+
.\.wheel-smoke\Scripts\annotateit.exe --help
79+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
80+
81+
- name: Smoke-test wheel on macOS and Ubuntu
82+
if: runner.os != 'Windows'
83+
shell: bash
84+
run: |
85+
shopt -s nullglob
86+
wheels=(dist/*.whl)
87+
if (( ${#wheels[@]} != 1 )); then
88+
echo "Expected exactly one wheel, found ${#wheels[@]}." >&2
89+
exit 1
90+
fi
91+
.wheel-smoke/bin/python -m pip install "${wheels[0]}"
92+
.wheel-smoke/bin/annotateit --help

.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Bytecode and interpreter caches
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# Build and packaging output
7+
build/
8+
dist/
9+
*.egg-info/
10+
*.egg
11+
12+
# Test, coverage, lint, and type-check caches
13+
.coverage
14+
.coverage.*
15+
coverage.xml
16+
htmlcov/
17+
.pytest_cache/
18+
.mypy_cache/
19+
.ruff_cache/
20+
.tox/
21+
.nox/
22+
23+
# Virtual environments
24+
.venv/
25+
venv/
26+
env/
27+
28+
# Local configuration and credentials
29+
.env
30+
.env.*
31+
!.env.example
32+
33+
# Editors and operating systems
34+
.idea/
35+
.vscode/
36+
.DS_Store
37+
Thumbs.db

README.md

Lines changed: 198 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,198 @@
1-
# annotateit-python
1+
# AnnotateIt Python SDK and CLI
2+
3+
`annotateit-ai` is the typed Python SDK and command-line client for the local
4+
AnnotateIt REST API. The project is currently alpha software and targets Python
5+
3.10 or newer.
6+
7+
Three names are intentionally different:
8+
9+
| Purpose | Name |
10+
| --- | --- |
11+
| PyPI distribution | `annotateit-ai` |
12+
| Python package | `annotateit_ai` |
13+
| CLI command | `annotateit` |
14+
15+
The `annotateit` distribution name on PyPI is already owned by another project,
16+
so installs and dependency declarations must use `annotateit-ai`.
17+
18+
## Install
19+
20+
After the first PyPI release, the recommended CLI installation will be:
21+
22+
```console
23+
pipx install annotateit-ai
24+
```
25+
26+
For use as a library:
27+
28+
```console
29+
python -m pip install annotateit-ai
30+
```
31+
32+
To install the current checkout instead:
33+
34+
### Windows PowerShell
35+
36+
```powershell
37+
cd C:\repos\annotateit-python
38+
py -3.14 -m venv .venv
39+
.\.venv\Scripts\Activate.ps1
40+
python -m pip install --upgrade pip
41+
python -m pip install -e .
42+
annotateit --help
43+
```
44+
45+
If PowerShell blocks the activation script, either adjust the execution policy
46+
for the current process or call `.\.venv\Scripts\python.exe` directly.
47+
48+
### macOS zsh or bash
49+
50+
```bash
51+
cd /path/to/annotateit-python
52+
python3.14 -m venv .venv
53+
source .venv/bin/activate
54+
python -m pip install --upgrade pip
55+
python -m pip install -e .
56+
annotateit --help
57+
```
58+
59+
Python 3.10 or newer is supported. CI exercises the minimum version and the
60+
current stable Python 3.14 on Windows, macOS, and Ubuntu.
61+
62+
## Connect to AnnotateIt
63+
64+
The default endpoint is `http://127.0.0.1:8420/api/v1`. A bare port, host URL,
65+
or complete `/api/v1` URL can be supplied through `ANNOTATEIT_URL` or `--url`.
66+
Authenticated commands also need `ANNOTATEIT_TOKEN` or `--token`.
67+
68+
PowerShell:
69+
70+
```powershell
71+
$env:ANNOTATEIT_URL = "8420"
72+
$env:ANNOTATEIT_TOKEN = "replace-with-your-token"
73+
annotateit doctor
74+
annotateit projects list
75+
```
76+
77+
macOS:
78+
79+
```bash
80+
export ANNOTATEIT_URL="8420"
81+
export ANNOTATEIT_TOKEN="replace-with-your-token"
82+
annotateit doctor
83+
annotateit projects list
84+
```
85+
86+
Command-line values take precedence over environment variables. Avoid putting a
87+
token directly on a shared machine's command line because it can be retained in
88+
shell history or visible to other processes.
89+
90+
## CLI
91+
92+
Commands follow the resource layout of the API:
93+
94+
```console
95+
annotateit status
96+
annotateit product info
97+
annotateit projects list
98+
annotateit datasets list <project-id>
99+
annotateit media list <project-id> <dataset-id>
100+
annotateit annotations get <project-id> <dataset-id> <media-id>
101+
annotateit versions list <project-id> <dataset-id>
102+
annotateit split show <project-id> <dataset-id>
103+
annotateit quality show <project-id> <dataset-id>
104+
annotateit openapi show
105+
```
106+
107+
Use `annotateit --help` or append `--help` to a command for its exact arguments.
108+
The CLI also covers tracks, frame annotations, imports, exports, project
109+
activity, split planning, and quality scans.
110+
111+
Useful global options are:
112+
113+
- `--url` and `--token` override the matching environment variables.
114+
- `--json` emits machine-readable JSON.
115+
- `--no-version-check` skips the API compatibility preflight when connecting to
116+
an older server.
117+
118+
Lists that expose server cursors are fetched across pages automatically. Use the
119+
command-specific page-size and maximum-item options to cap large results.
120+
121+
Commands that delete or replace data require `--yes`. Downloads and generated
122+
files refuse to replace an existing destination unless `--force` is present.
123+
JSON request bodies accept `--from path.json`; use `--from -` to read UTF-8 JSON
124+
from standard input. Downloads are written through a temporary file and moved
125+
into place only after a successful response.
126+
127+
Examples:
128+
129+
```console
130+
annotateit projects create --name "Road signs" --task-type Detection
131+
annotateit media upload <project-id> <dataset-id> ./frame.jpg
132+
annotateit export <project-id> <dataset-id> --format coco --out dataset-coco.zip
133+
annotateit annotations replace <project-id> <dataset-id> <media-id> --from annotations.json --yes
134+
annotateit openapi show --out annotateit-openapi.json
135+
```
136+
137+
## Python SDK
138+
139+
The client can use the same environment variables as the CLI:
140+
141+
```python
142+
from annotateit_ai import Client
143+
144+
with Client() as client:
145+
response = client.call("listProjects")
146+
print(response)
147+
```
148+
149+
Typed resource helpers are also available on `client.projects`,
150+
`client.datasets`, `client.media`, `client.annotations`, `client.tracks`,
151+
`client.versions`, `client.splits`, `client.quality`, and `client.system`.
152+
153+
For explicit configuration:
154+
155+
```python
156+
from annotateit_ai import Client
157+
158+
with Client("http://127.0.0.1:8420", "replace-with-your-token") as client:
159+
project = client.projects.get("project-id")
160+
```
161+
162+
## Development and CI
163+
164+
Install the development tools with `python -m pip install -e ".[dev]"`.
165+
The local preflight creates isolated temporary environments, runs the same
166+
quality gates as CI, builds both distributions, checks their metadata, installs
167+
the wheel, and smoke-tests the console entry point.
168+
169+
PowerShell:
170+
171+
```powershell
172+
.\scripts\preflight.ps1
173+
```
174+
175+
macOS or Linux:
176+
177+
```bash
178+
bash scripts/preflight.sh
179+
```
180+
181+
Pass a Python executable to select a version:
182+
183+
```powershell
184+
.\scripts\preflight.ps1 -Python C:\Python310\python.exe
185+
```
186+
187+
```bash
188+
bash scripts/preflight.sh python3.10
189+
```
190+
191+
GitHub Actions runs the same lint, formatting, strict type checking, tests,
192+
package build, `twine check`, and wheel smoke test for Python 3.10 and 3.14 on
193+
`windows-latest`, `macos-15`, and `ubuntu-latest`.
194+
195+
## License
196+
197+
License metadata is intentionally omitted until the repository owner selects a
198+
license. Do not assume an open-source license from the package's public source.

pyproject.toml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
[build-system]
2+
requires = ["setuptools>=75"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "annotateit-ai"
7+
version = "0.1.0"
8+
description = "Python SDK and command-line client for the AnnotateIt local REST API"
9+
readme = "README.md"
10+
requires-python = ">=3.10"
11+
authors = [{ name = "AnnotateIt AI" }]
12+
keywords = ["annotation", "computer-vision", "dataset", "sdk"]
13+
classifiers = [
14+
"Development Status :: 3 - Alpha",
15+
"Intended Audience :: Developers",
16+
"Programming Language :: Python :: 3",
17+
"Programming Language :: Python :: 3.10",
18+
"Programming Language :: Python :: 3.11",
19+
"Programming Language :: Python :: 3.12",
20+
"Programming Language :: Python :: 3.13",
21+
"Programming Language :: Python :: 3.14",
22+
"Typing :: Typed",
23+
]
24+
dependencies = ["httpx>=0.27,<1"]
25+
26+
[project.scripts]
27+
annotateit = "annotateit_ai.cli:main"
28+
29+
[project.optional-dependencies]
30+
dev = [
31+
"build>=1.2",
32+
"mypy>=1.13",
33+
"pytest>=8",
34+
"pytest-cov>=5",
35+
"ruff>=0.8",
36+
"twine>=6",
37+
]
38+
39+
[tool.setuptools.packages.find]
40+
where = ["src"]
41+
42+
[tool.setuptools.package-data]
43+
annotateit_ai = ["py.typed", "openapi/*.json"]
44+
45+
[tool.pytest.ini_options]
46+
addopts = "-ra --strict-config --strict-markers"
47+
testpaths = ["tests"]
48+
49+
[tool.ruff]
50+
target-version = "py310"
51+
line-length = 120
52+
src = ["src", "tests"]
53+
54+
[tool.ruff.lint]
55+
select = ["B", "E", "F", "I", "RUF", "UP"]
56+
57+
[tool.ruff.lint.per-file-ignores]
58+
"tests/**" = ["S101"]
59+
60+
[tool.mypy]
61+
python_version = "3.10"
62+
strict = true
63+
packages = ["annotateit_ai"]
64+
mypy_path = "src"

0 commit comments

Comments
 (0)