Contributing
Architecture
graph LR
U[User] -->|typer / CLI| S[stare]
S -->|HTTPS httpx| G[ATLAS Glance API]
S -->|browser + local server| K[CERN Keycloak PKCE]
The library uses httpx for HTTP, pydantic for all data models, implements OAuth2 PKCE natively, and typer + rich for the CLI.
Development setup
git clone https://github.com/kratsg/stare
cd stare
pixi install
pixi run pre-commit-install
pixi run stare auth login
Pointing at staging
Use the staging pixi environment to point all requests at the Glance staging
server:
This sets STARE_BASE_URL, STARE_CA_BUNDLE=CERN, and
STARE_EXCHANGE_AUDIENCE to the staging values automatically.
Build and test commands
pixi run test # quick tests (no live CERN needed)
pixi run test-slow # all tests including live integration (requires stare login)
pixi run lint # pre-commit + pylint
pixi run build # build sdist + wheel
pixi run build-check # verify the built distributions with twine
pixi run docs-serve # build and serve docs locally
Adding a new endpoint
When the Glance/Fence API adds a new endpoint:
- Add or update the pydantic model in
src/stare/models/ - Write a failing test in
tests/test_models.py(TDD) - Make the test pass
- Add a resource accessor method in
src/stare/client.py - Write a failing test in
tests/test_client.py - Make it pass
- Add a CLI command in
src/stare/cli.py - Write a failing test in
tests/test_cli.py - Make it pass
- Add a live integration test in
tests/integration/test_live.py(runs withpixi run test-slow) - Add representative fixture JSON under
tests/fixtures/and load it intests/test_client.py(model tests intests/test_models.pykeep hand-built dicts inline, not fixture files) - Run
pixi run testto confirm everything is green
Model conventions
All pydantic models inherit from _Base (defined in
src/stare/models/common.py), which provides:
alias_generator=to_camel— automatically mapssnake_casefields tocamelCaseAPI keys (so explicitField(alias=...)is only needed for non-standard names)populate_by_name=True— allows access by both alias and Python attribute namemodel_validate()override — wrapsValidationErrorinResponseParseErrorwith a human-readable message
Use None defaults for all optional fields.
Example:
from stare.models.common import _Base
from pydantic import Field
class MyModel(_Base):
# alias "referenceCode" is auto-generated by to_camel
reference_code: str
# alias "shortTitle" is auto-generated by to_camel
short_title: str | None = None
# use Field(alias=...) only when the API key differs from to_camel(field_name)
some_special_key: str | None = Field(default=None, alias="s_pecial_Key")
Regenerating the client scaffolding
When the OpenAPI spec at
https://atlas-glance.cern.ch/atlas/analysis/api/docs/api.yml is updated,
regenerate the reference scaffolding and translate the relevant parts into
src/stare/.
uvx openapi-python-client generate \
--url https://atlas-glance.cern.ch/atlas/analysis/api/docs/api.yml \
--output-path _generated --overwrite
# _generated/ is gitignored — reference only, never shipped
Translate generated attrs-based models into pydantic BaseModel subclasses,
map verbose generated names (SearchAnalysisResponseResultsItemPhase0) to short
domain names (Phase0), and add Field(alias=...) for camelCase keys.
Tests
Tests live in tests/. The test layout mirrors the source:
| File | Tests |
|---|---|
tests/test_settings.py |
StareSettings env-var overrides |
tests/test_exceptions.py |
Exception hierarchy and ApiError fields |
tests/test_models.py |
Pydantic model parsing from hand-built dicts (no fixture JSON — endpoints are planned) |
tests/test_auth.py |
TokenManager login/logout/refresh |
tests/test_client.py |
Glance client + resource accessors (respx mocks) |
tests/test_cli.py |
typer CLI via CliRunner |
tests/integration/test_live.py |
Live CERN endpoints (requires stare login, run with --runslow) |
Key fixtures in tests/conftest.py:
test_settings—StareSettingspointing athttps://test-glance.example.com/apiwith auth endpoints athttps://auth.example.com/and caching disabledstored_token_path— temp file with a pre-written valid tokentmp_token_path— temp file path (does not exist yet)