Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

[*.{yml,yaml,md,toml}]
indent_size = 2

[Makefile]
indent_style = tab
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BILD_API_KEY=YOUR_JWT_TOKEN
25 changes: 18 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
branches: [main]

jobs:
test:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -15,13 +15,24 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: "3.12"

- name: Install deps
- name: Install
run: |
python -m pip install --upgrade pip
pip install requests
pip install -e ".[dev]"

- name: Run tests
run: |
python -m unittest discover -s tests -p 'test_*.py' -v
- name: Format
run: ruff format --check .

- name: Lint
run: ruff check .

- name: Typecheck
run: mypy bild

- name: Harness
run: python tools/check.py

- name: Tests
run: pytest tests -q
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
/__pycache__/
.venv/
venv/
.env
__pycache__/
*.pyc
*.egg-info/
.mypy_cache/
.ruff_cache/
.pytest_cache/
dist/
build/
13 changes: 13 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.10
hooks:
- id: ruff
- id: ruff-format
- repo: local
hooks:
- id: harness
name: harness linters
entry: python tools/check.py
language: system
pass_filenames: false
68 changes: 68 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Agent map

Python SDK for the [Bild External API](https://bildexternalapi.portledocs.com/).
Read this file first, then open only the docs you need for the task.

## What this is

`bild` is a source-install client for `https://api.getbild.com`.
Callers use `BildClient` and `client.api.<group>` resource methods.
The package is not on PyPI yet.

## Start here

| If you need | Open |
| --- | --- |
| Product intent | [docs/PRODUCT.md](docs/PRODUCT.md) |
| Architecture / layers | [ARCHITECTURE.md](ARCHITECTURE.md) |
| Coding rules | [docs/CONVENTIONS.md](docs/CONVENTIONS.md) |
| Design history | [docs/design-docs/index.md](docs/design-docs/index.md) |
| Active work | [docs/exec-plans/active/](docs/exec-plans/active/) |
| Tech debt | [docs/exec-plans/tech-debt-tracker.md](docs/exec-plans/tech-debt-tracker.md) |
| Quality grades | [docs/QUALITY_SCORE.md](docs/QUALITY_SCORE.md) |
| Security | [docs/SECURITY.md](docs/SECURITY.md) |
| Reliability | [docs/RELIABILITY.md](docs/RELIABILITY.md) |
| Full catalog | [docs/INDEX.md](docs/INDEX.md) |

## Layout

- `bild/` — SDK package (transport, errors, resource APIs)
- `tests/` — unit tests always; live API tests only if `BILD_API_KEY` is set
- `docs/` — system of record (do not put long guidance in this file)
- `tools/` — harness linters and `tools/check.py`
- `.github/workflows/ci.yml` — format, lint, typecheck, harness, tests

## Commands

```bash
python -m pip install -e ".[dev]" # or: uv pip install -e ".[dev]"
python tools/check.py --all
```

Or one at a time: `ruff format .` · `ruff check .` · `mypy bild` · `python tools/check.py` · `pytest tests -q`

Live tests are read-only and skip unless `BILD_API_KEY` is set (or present in `.env`).

## Invariants (mechanically enforced)

1. Do not set `Content-Type` on the shared session. Bild treats that header as "this request has a JSON body"; GET/DELETE then 500.
2. Public exports stay `{BildClient, BildAPIError, BildAuthError}`.
3. Resource classes are named `*API` and attached on `_Resources`.
4. Optional JSON fields go through `_omit_none`.
5. Never commit `.env` or real tokens. Use `.env.example`.
6. Live tests must not write or delete.
7. Keep this file under 130 lines. Put detail in `docs/`.
8. After any change, run `python tools/check.py --all` and follow each `REMEDIATION:` line.

## How to change the SDK

1. Read `ARCHITECTURE.md` and the matching file under `docs/design-docs/`.
2. Add or update the method on the correct `*API` class.
3. Add a route assertion in `tests/test_client_routes.py`.
4. If the change is user-facing, update `README.md` and `docs/product-specs/python-sdk.md`.
5. Do not invent endpoints. Confirm against the Bild External API reference.

## When something fails

Harness linters print `REMEDIATION:` lines. Follow those before improvising.
If a rule is wrong, update the linter and the doc that states the rule in the same change.
78 changes: 78 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Architecture

Top-level map of the Bild Python SDK. Layer rules are enforced by
`tools/linters/architecture.py` and `tests/test_architecture.py`.

## Purpose

Wrap the Bild External HTTP API in a small, typed-enough Python client so
scripts and apps can list projects, manage files, and call other documented
endpoints without assembling URLs and auth headers by hand.

## Layers (dependency only flows downward)

```
bild/__init__.py public surface
bild/client.py transport + resource APIs
bild/errors.py exception types (no client import)
requests / stdlib
```

| Layer | Module | May import | Must not import |
| --- | --- | --- | --- |
| Public surface | `bild/__init__.py` | `client`, `errors` | `requests` directly |
| Transport + resources | `bild/client.py` | `errors`, `requests`, stdlib | nothing outside `bild` except `requests` |
| Errors | `bild/errors.py` | stdlib only | `bild.client`, `requests` |

New modules under `bild/` are allowed only if they fit this layering and are
wired into the public surface or a resource class. Do not add a second HTTP
client.

## Runtime shape

```
BildClient
token, base_url, timeout, session
request / get / post / put / delete
resolve_branch_id / resolve_file_version
api: _Resources
users, projects, project_users, branches, commits, files,
uploads, checkouts, shared_links, metadata, feedback,
packages, revisions, approvals, boms, search, webhooks
```

Each `*API` class holds a `client: BildClient` and only issues HTTP via
`self.client`. Resource classes do not call `requests` themselves.

## HTTP contract

- Default host: `https://api.getbild.com`
- Auth: `Authorization: Bearer <token>` on every request
- `Accept: application/json` is set on the session
- `Content-Type` is **not** set on the session. `requests` adds it only when
`json=` is passed. See [docs/design-docs/http-client.md](docs/design-docs/http-client.md).
- 401/403 → `BildAuthError`; other non-OK → `BildAPIError`

## Tests

| Suite | Role |
| --- | --- |
| `tests/test_auth.py` | token required, bearer header, no Content-Type, 401/403 |
| `tests/test_client_routes.py` | every resource method hits the expected path/method |
| `tests/test_import.py` | package import smoke |
| `tests/test_live_api.py` | read-only calls against the real API when a token is present |
| `tests/test_architecture.py` | layer and public-surface invariants |
| `tests/test_docs.py` | knowledge-base files exist and stay linked |

## Known structural debt

`bild/client.py` currently holds transport helpers and every resource class.
Splitting resources into `bild/resources/` is tracked in
[docs/exec-plans/tech-debt-tracker.md](docs/exec-plans/tech-debt-tracker.md).
Until that lands, file-size limits treat `client.py` as one module.
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.PHONY: install format lint typecheck harness test check

install:
python -m pip install -e ".[dev]"

format:
ruff format .

lint:
ruff check .

typecheck:
mypy bild

harness:
python tools/check.py

test:
pytest tests -q

check:
python tools/check.py --all
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ The client sends that token on every request as:
Authorization: Bearer <your_token>
```

Set it in the environment:
Copy `.env.example` to `.env` and set the token (`.env` is gitignored):

```bash
BILD_API_KEY=YOUR_JWT_TOKEN
```

`BildClient()` loads `.env` automatically. You can also set the variable in the shell:

```bash
export BILD_API_KEY="YOUR_JWT_TOKEN"
Expand Down Expand Up @@ -97,7 +103,7 @@ print(files)
```python
result = client.api.files.export_universal(
project_id="project-id",
branch_id=None, # auto-resolves main/default branch
branch_id=None, # auto-resolves main/default branch
file_id="file-id",
output_format="stl",
)
Expand Down Expand Up @@ -155,10 +161,7 @@ These map to the groups in the [Bild External API reference](https://bildexterna
## Advanced: custom base URL

```python
client = BildClient(
token="YOUR_JWT_TOKEN",
base_url="https://api.getbild.com"
)
client = BildClient(token="YOUR_JWT_TOKEN", base_url="https://api.getbild.com")
```

## Escape hatch for unwrapped endpoints
Expand All @@ -168,10 +171,13 @@ raw = client.get("projects")
print(raw)
```

## Tests
## Tests and development

```bash
python -m unittest discover -s tests -p "test_*.py" -v
python -m pip install -e ".[dev]"
python tools/check.py --all
```

If `BILD_API_KEY` is set, a live auth smoke test also runs against `GET /users`.
That runs format check, ruff, mypy, harness linters, and pytest. Agents should start at [AGENTS.md](AGENTS.md); the knowledge base lives in [docs/INDEX.md](docs/INDEX.md).

If `BILD_API_KEY` is set (or present in `.env`), live read-only tests also run against the real API (`users`, `projects`, `files`, `search`, and the other list/get groups). Write and delete calls are not exercised.
Loading
Loading