gazebo 0.1.0__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.
- gazebo-0.1.0/.github/dependabot.yml +11 -0
- gazebo-0.1.0/.github/workflows/ci.yml +43 -0
- gazebo-0.1.0/.github/workflows/docs.yml +73 -0
- gazebo-0.1.0/.github/workflows/release.yml +47 -0
- gazebo-0.1.0/.gitignore +127 -0
- gazebo-0.1.0/.pre-commit-config.yaml +58 -0
- gazebo-0.1.0/CHANGELOG.md +27 -0
- gazebo-0.1.0/CLAUDE.md +173 -0
- gazebo-0.1.0/CONTRIBUTING.md +130 -0
- gazebo-0.1.0/LICENSE +21 -0
- gazebo-0.1.0/PKG-INFO +239 -0
- gazebo-0.1.0/README.md +225 -0
- gazebo-0.1.0/docs/core/collections.md +45 -0
- gazebo-0.1.0/docs/core/constants.md +34 -0
- gazebo-0.1.0/docs/core/context.md +63 -0
- gazebo-0.1.0/docs/core/index.md +33 -0
- gazebo-0.1.0/docs/core/links.md +50 -0
- gazebo-0.1.0/docs/core/ogc.md +34 -0
- gazebo-0.1.0/docs/core/problems.md +43 -0
- gazebo-0.1.0/docs/di/index.md +52 -0
- gazebo-0.1.0/docs/di/providers.md +57 -0
- gazebo-0.1.0/docs/di/qualifiers-overrides.md +34 -0
- gazebo-0.1.0/docs/di/scopes.md +64 -0
- gazebo-0.1.0/docs/example.md +61 -0
- gazebo-0.1.0/docs/fastapi/app.md +78 -0
- gazebo-0.1.0/docs/fastapi/index.md +43 -0
- gazebo-0.1.0/docs/fastapi/proxy.md +64 -0
- gazebo-0.1.0/docs/fastapi/routers.md +55 -0
- gazebo-0.1.0/docs/getting-started.md +55 -0
- gazebo-0.1.0/docs/index.md +51 -0
- gazebo-0.1.0/docs/reference.md +31 -0
- gazebo-0.1.0/examples/garden/README.md +96 -0
- gazebo-0.1.0/examples/garden/garden/__init__.py +1 -0
- gazebo-0.1.0/examples/garden/garden/__main__.py +8 -0
- gazebo-0.1.0/examples/garden/garden/api.py +106 -0
- gazebo-0.1.0/examples/garden/garden/app.py +95 -0
- gazebo-0.1.0/examples/garden/garden/models.py +38 -0
- gazebo-0.1.0/examples/garden/garden/resources.py +164 -0
- gazebo-0.1.0/examples/garden/pyproject.toml +43 -0
- gazebo-0.1.0/examples/garden/tests/test_app.py +110 -0
- gazebo-0.1.0/pyproject.toml +133 -0
- gazebo-0.1.0/src/gazebo/__init__.py +28 -0
- gazebo-0.1.0/src/gazebo/__version__.py +24 -0
- gazebo-0.1.0/src/gazebo/asgi.py +146 -0
- gazebo-0.1.0/src/gazebo/collection.py +48 -0
- gazebo-0.1.0/src/gazebo/context.py +107 -0
- gazebo-0.1.0/src/gazebo/di/__init__.py +41 -0
- gazebo-0.1.0/src/gazebo/di/container.py +366 -0
- gazebo-0.1.0/src/gazebo/di/providers.py +193 -0
- gazebo-0.1.0/src/gazebo/ext/__init__.py +1 -0
- gazebo-0.1.0/src/gazebo/ext/fastapi.py +508 -0
- gazebo-0.1.0/src/gazebo/link.py +143 -0
- gazebo-0.1.0/src/gazebo/ogc.py +64 -0
- gazebo-0.1.0/src/gazebo/pagination.py +65 -0
- gazebo-0.1.0/src/gazebo/problems.py +64 -0
- gazebo-0.1.0/src/gazebo/py.typed +0 -0
- gazebo-0.1.0/src/gazebo/rels.py +47 -0
- gazebo-0.1.0/src/gazebo/tags.py +32 -0
- gazebo-0.1.0/tests/conftest.py +24 -0
- gazebo-0.1.0/tests/examples/__init__.py +7 -0
- gazebo-0.1.0/tests/examples/app.py +89 -0
- gazebo-0.1.0/tests/examples/collections.py +46 -0
- gazebo-0.1.0/tests/examples/constants.py +28 -0
- gazebo-0.1.0/tests/examples/context.py +50 -0
- gazebo-0.1.0/tests/examples/getting_started.py +67 -0
- gazebo-0.1.0/tests/examples/links.py +54 -0
- gazebo-0.1.0/tests/examples/ogc.py +46 -0
- gazebo-0.1.0/tests/examples/problems.py +27 -0
- gazebo-0.1.0/tests/examples/providers.py +87 -0
- gazebo-0.1.0/tests/examples/proxy.py +74 -0
- gazebo-0.1.0/tests/examples/qualifiers_overrides.py +65 -0
- gazebo-0.1.0/tests/examples/routers.py +95 -0
- gazebo-0.1.0/tests/examples/scopes.py +43 -0
- gazebo-0.1.0/tests/test_asgi.py +87 -0
- gazebo-0.1.0/tests/test_collection.py +50 -0
- gazebo-0.1.0/tests/test_core_ogc.py +91 -0
- gazebo-0.1.0/tests/test_di.py +206 -0
- gazebo-0.1.0/tests/test_examples.py +23 -0
- gazebo-0.1.0/tests/test_fastapi.py +285 -0
- gazebo-0.1.0/tests/test_link.py +56 -0
- gazebo-0.1.0/uv.lock +1284 -0
- gazebo-0.1.0/zensical.toml +110 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: "Continuous integration"
|
|
2
|
+
|
|
3
|
+
concurrency:
|
|
4
|
+
group: ${{ github.ref }}
|
|
5
|
+
cancel-in-progress: false
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches:
|
|
10
|
+
- main
|
|
11
|
+
pull_request:
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
ci:
|
|
15
|
+
name: Continuous integration
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
strategy:
|
|
18
|
+
matrix:
|
|
19
|
+
python-version:
|
|
20
|
+
- "3.12"
|
|
21
|
+
- "3.13"
|
|
22
|
+
- "3.14"
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
25
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
- name: Sync
|
|
29
|
+
run: uv sync --locked --all-extras --all-packages --no-editable
|
|
30
|
+
- name: Pre-Commit Hooks
|
|
31
|
+
run: uv run pre-commit run --all-files
|
|
32
|
+
- name: Test
|
|
33
|
+
run: uv run pytest
|
|
34
|
+
- name: Test example
|
|
35
|
+
run: uv run --no-sync pytest
|
|
36
|
+
working-directory: examples/garden
|
|
37
|
+
- name: "Upload coverage to Codecov"
|
|
38
|
+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
39
|
+
env:
|
|
40
|
+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
41
|
+
with:
|
|
42
|
+
fail_ci_if_error: false
|
|
43
|
+
verbose: true
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
name: Documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
release:
|
|
9
|
+
types:
|
|
10
|
+
- published
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
# Allow only one concurrent deployment, to avoid mike/gh-pages races.
|
|
14
|
+
concurrency:
|
|
15
|
+
group: docs-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: false
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: write
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
# On pull requests, just confirm the docs build cleanly (no deploy).
|
|
23
|
+
build:
|
|
24
|
+
if: github.event_name == 'pull_request'
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
28
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.14"
|
|
31
|
+
- name: Sync
|
|
32
|
+
run: uv sync --locked --all-extras --group docs
|
|
33
|
+
- name: Build (strict)
|
|
34
|
+
run: uv run zensical build --strict --clean
|
|
35
|
+
|
|
36
|
+
deploy:
|
|
37
|
+
if: github.event_name != 'pull_request'
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
41
|
+
with:
|
|
42
|
+
fetch-depth: 0 # full history so mike can update the gh-pages branch
|
|
43
|
+
|
|
44
|
+
- name: Configure git
|
|
45
|
+
run: |
|
|
46
|
+
git config user.name "github-actions[bot]"
|
|
47
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
48
|
+
|
|
49
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
50
|
+
with:
|
|
51
|
+
python-version: "3.14"
|
|
52
|
+
|
|
53
|
+
- name: Sync
|
|
54
|
+
run: uv sync --locked --all-extras --group docs
|
|
55
|
+
|
|
56
|
+
# mike (squidfunk's Zensical-aware fork) builds via `zensical build` and
|
|
57
|
+
# commits each version to the gh-pages branch.
|
|
58
|
+
- name: Deploy dev version
|
|
59
|
+
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
|
60
|
+
run: uv run mike deploy --push --update-aliases dev latest
|
|
61
|
+
|
|
62
|
+
- name: Deploy release version
|
|
63
|
+
if: github.event_name == 'release' && !github.event.release.prerelease
|
|
64
|
+
run: |
|
|
65
|
+
VERSION="${{ github.event.release.tag_name }}"
|
|
66
|
+
uv run mike deploy --push --update-aliases "$VERSION" stable
|
|
67
|
+
uv run mike set-default --push stable
|
|
68
|
+
|
|
69
|
+
- name: Deploy pre-release version
|
|
70
|
+
if: github.event_name == 'release' && github.event.release.prerelease
|
|
71
|
+
run: |
|
|
72
|
+
VERSION="${{ github.event.release.tag_name }}"
|
|
73
|
+
uv run mike deploy --push "$VERSION"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Build and release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
branches:
|
|
9
|
+
- main
|
|
10
|
+
release:
|
|
11
|
+
types:
|
|
12
|
+
- published
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build-package:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
19
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
20
|
+
- name: Build package
|
|
21
|
+
run: uv build
|
|
22
|
+
- name: Upload artifact
|
|
23
|
+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
24
|
+
if: startsWith(github.ref, 'refs/tags')
|
|
25
|
+
with:
|
|
26
|
+
name: dist-${{ github.ref_name }}
|
|
27
|
+
path: dist/
|
|
28
|
+
overwrite: true
|
|
29
|
+
if-no-files-found: error
|
|
30
|
+
|
|
31
|
+
release-package:
|
|
32
|
+
if: startsWith(github.ref, 'refs/tags')
|
|
33
|
+
needs: build-package
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
environment:
|
|
36
|
+
name: pypi
|
|
37
|
+
url: https://pypi.org/p/gazebo
|
|
38
|
+
permissions:
|
|
39
|
+
id-token: write
|
|
40
|
+
steps:
|
|
41
|
+
- name: Download artifact
|
|
42
|
+
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v5.0.0
|
|
43
|
+
with:
|
|
44
|
+
name: dist-${{ github.ref_name }}
|
|
45
|
+
path: dist/
|
|
46
|
+
- name: Upload release
|
|
47
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
gazebo-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
__version__.py
|
|
2
|
+
|
|
3
|
+
# Byte-compiled / optimized / DLL files
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[cod]
|
|
6
|
+
*$py.class
|
|
7
|
+
|
|
8
|
+
# C extensions
|
|
9
|
+
*.so
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging
|
|
12
|
+
.Python
|
|
13
|
+
build/
|
|
14
|
+
develop-eggs/
|
|
15
|
+
dist/
|
|
16
|
+
downloads/
|
|
17
|
+
eggs/
|
|
18
|
+
.eggs/
|
|
19
|
+
lib/
|
|
20
|
+
lib64/
|
|
21
|
+
parts/
|
|
22
|
+
sdist/
|
|
23
|
+
var/
|
|
24
|
+
wheels/
|
|
25
|
+
share/python-wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
MANIFEST
|
|
30
|
+
|
|
31
|
+
# PyInstaller
|
|
32
|
+
# Usually these files are written by a python script from a template
|
|
33
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
34
|
+
*.manifest
|
|
35
|
+
*.spec
|
|
36
|
+
|
|
37
|
+
# Installer logs
|
|
38
|
+
pip-log.txt
|
|
39
|
+
pip-delete-this-directory.txt
|
|
40
|
+
|
|
41
|
+
# Unit test / coverage reports
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
*.py,cover
|
|
52
|
+
.hypothesis/
|
|
53
|
+
.pytest_cache/
|
|
54
|
+
cover/
|
|
55
|
+
|
|
56
|
+
# Translations
|
|
57
|
+
*.mo
|
|
58
|
+
*.pot
|
|
59
|
+
|
|
60
|
+
# Scrapy stuff:
|
|
61
|
+
.scrapy
|
|
62
|
+
|
|
63
|
+
# Sphinx documentation
|
|
64
|
+
docs/_build/
|
|
65
|
+
|
|
66
|
+
# PyBuilder
|
|
67
|
+
.pybuilder/
|
|
68
|
+
target/
|
|
69
|
+
|
|
70
|
+
# Jupyter Notebook
|
|
71
|
+
.ipynb_checkpoints
|
|
72
|
+
|
|
73
|
+
# IPython
|
|
74
|
+
profile_default/
|
|
75
|
+
ipython_config.py
|
|
76
|
+
|
|
77
|
+
# pyenv
|
|
78
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
79
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
80
|
+
.python-version
|
|
81
|
+
|
|
82
|
+
# pdm
|
|
83
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
84
|
+
#pdm.lock
|
|
85
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
86
|
+
# in version control.
|
|
87
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
88
|
+
.pdm.toml
|
|
89
|
+
.pdm-python
|
|
90
|
+
.pdm-build/
|
|
91
|
+
|
|
92
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
93
|
+
__pypackages__/
|
|
94
|
+
|
|
95
|
+
# Environments
|
|
96
|
+
.env
|
|
97
|
+
.env.*
|
|
98
|
+
.venv
|
|
99
|
+
env/
|
|
100
|
+
venv/
|
|
101
|
+
ENV/
|
|
102
|
+
env.bak/
|
|
103
|
+
venv.bak/
|
|
104
|
+
|
|
105
|
+
# Spyder project settings
|
|
106
|
+
.spyderproject
|
|
107
|
+
.spyproject
|
|
108
|
+
|
|
109
|
+
# Rope project settings
|
|
110
|
+
.ropeproject
|
|
111
|
+
|
|
112
|
+
# mkdocs documentation
|
|
113
|
+
/site
|
|
114
|
+
|
|
115
|
+
# mypy
|
|
116
|
+
.mypy_cache/
|
|
117
|
+
.dmypy.json
|
|
118
|
+
dmypy.json
|
|
119
|
+
|
|
120
|
+
# Pyre type checker
|
|
121
|
+
.pyre/
|
|
122
|
+
|
|
123
|
+
# pytype static type analyzer
|
|
124
|
+
.pytype/
|
|
125
|
+
|
|
126
|
+
# Cython debug symbols
|
|
127
|
+
cython_debug/
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: local
|
|
3
|
+
hooks:
|
|
4
|
+
- id: ruff_check
|
|
5
|
+
name: ruff check
|
|
6
|
+
entry: ruff check --force-exclude
|
|
7
|
+
language: python
|
|
8
|
+
'types_or': [python, pyi]
|
|
9
|
+
args: [--fix, --exit-non-zero-on-fix]
|
|
10
|
+
require_serial: true
|
|
11
|
+
- id: ruff_format
|
|
12
|
+
name: ruff format
|
|
13
|
+
entry: ruff format --force-exclude
|
|
14
|
+
language: python
|
|
15
|
+
'types_or': [python, pyi]
|
|
16
|
+
args: []
|
|
17
|
+
require_serial: true
|
|
18
|
+
- id: check-added-large-files
|
|
19
|
+
name: Check for added large files
|
|
20
|
+
entry: check-added-large-files
|
|
21
|
+
language: system
|
|
22
|
+
- id: check-toml
|
|
23
|
+
name: Check Toml
|
|
24
|
+
entry: check-toml
|
|
25
|
+
language: system
|
|
26
|
+
types: [toml]
|
|
27
|
+
- id: check-yaml
|
|
28
|
+
name: Check Yaml
|
|
29
|
+
entry: check-yaml
|
|
30
|
+
language: system
|
|
31
|
+
types: [yaml]
|
|
32
|
+
- id: end-of-file-fixer
|
|
33
|
+
name: Fix End of Files
|
|
34
|
+
entry: end-of-file-fixer
|
|
35
|
+
language: system
|
|
36
|
+
types: [text]
|
|
37
|
+
stages: [pre-commit, pre-push, manual]
|
|
38
|
+
- id: trailing-whitespace
|
|
39
|
+
name: Trim Trailing Whitespace
|
|
40
|
+
entry: trailing-whitespace-fixer
|
|
41
|
+
language: system
|
|
42
|
+
types: [text]
|
|
43
|
+
stages: [pre-commit, pre-push, manual]
|
|
44
|
+
- id: mypy
|
|
45
|
+
name: mypy
|
|
46
|
+
entry: mypy
|
|
47
|
+
language: python
|
|
48
|
+
'types_or': [python, pyi]
|
|
49
|
+
args: []
|
|
50
|
+
require_serial: true
|
|
51
|
+
- id: pyright
|
|
52
|
+
name: pyright
|
|
53
|
+
entry: pyright
|
|
54
|
+
language: system
|
|
55
|
+
'types_or': [python, pyi]
|
|
56
|
+
pass_filenames: false
|
|
57
|
+
args: [src, tests]
|
|
58
|
+
require_serial: true
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
### Deprecated
|
|
15
|
+
|
|
16
|
+
### Removed
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
### Security
|
|
21
|
+
|
|
22
|
+
## [0.1.0] - XXXX-XX-XX
|
|
23
|
+
|
|
24
|
+
Initial release 🎉
|
|
25
|
+
|
|
26
|
+
[unreleased]: https://github.com/jkeifer/gazebo/compare/v0.1.0...HEAD
|
|
27
|
+
[0.1.0]: https://github.com/jkeifer/gazebo/releases/tag/v0.1.0
|
gazebo-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
gazebo packages the recurring machinery of OGC-style REST APIs (deferred links,
|
|
8
|
+
collection envelopes, RFC 7807 problems, proxy-aware URLs, a typed DI container, and
|
|
9
|
+
FastAPI glue) so it isn't re-implemented per project. The core depends only
|
|
10
|
+
on `pydantic`; framework integration is opt-in via extras. Requires Python 3.12+.
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
The project is `uv`-managed and is a `uv` workspace whose only member is
|
|
15
|
+
`examples/garden`. Run everything through `uv`.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
uv sync --all-extras --all-packages # install (CI also uses --locked --no-editable)
|
|
19
|
+
uv run pytest # run the library test suite (tests/)
|
|
20
|
+
uv run pytest tests/test_link.py::test_name # a single test
|
|
21
|
+
uv run pre-commit run --all-files # ruff check+format, mypy, pyright, file hygiene
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`uv run pytest` always reports coverage on the `gazebo` package (`addopts=--cov=gazebo`
|
|
25
|
+
in `pyproject.toml`) and treats warnings as errors (`filterwarnings = ['error']`).
|
|
26
|
+
|
|
27
|
+
The example app is its **own** project under `examples/garden` with its own test suite
|
|
28
|
+
and entry point; run them from that directory:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
cd examples/garden
|
|
32
|
+
uv run garden # serve Gazebo Gardens on http://127.0.0.1:8000
|
|
33
|
+
uv run pytest # the example's tests
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Type checking is enforced by **both** mypy and pyright (pyright runs over `src` and
|
|
37
|
+
`tests`); both run in pre-commit and CI must be green on Python 3.12–3.14.
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
The codebase is strictly layered and **dependencies only ever point downward**. The
|
|
42
|
+
two load-bearing ideas are *deferred links* and a *scoped DI container*; understand
|
|
43
|
+
those two seams and the rest follows.
|
|
44
|
+
|
|
45
|
+
### Layers (`src/gazebo/`)
|
|
46
|
+
|
|
47
|
+
1. **Core** (`context.py`, `link.py`, `collection.py`, `pagination.py`, `rels.py`,
|
|
48
|
+
`problems.py`, `ogc.py`) — pydantic + stdlib only. **Never imports a web
|
|
49
|
+
framework.** Pure models and the context seam.
|
|
50
|
+
2. **DI core** (`di/`) — stdlib only, no web framework. A standalone, extraction-ready
|
|
51
|
+
container behind a `Providers` interface.
|
|
52
|
+
3. **Pure ASGI** (`asgi.py`) — proxy-header middleware and context-setting middleware;
|
|
53
|
+
no framework import.
|
|
54
|
+
4. **Framework glue** (`ext/fastapi.py`) — the only module that imports `fastapi`.
|
|
55
|
+
Wires the DI container and the link context into a real app. Importing it requires
|
|
56
|
+
the `gazebo[fastapi]` extra.
|
|
57
|
+
|
|
58
|
+
When adding code, respect the layer: anything a lower layer needs must not import
|
|
59
|
+
upward, and the core must stay framework-free.
|
|
60
|
+
|
|
61
|
+
### Deferred links (the central trick)
|
|
62
|
+
|
|
63
|
+
A `Link.href` may be a plain URL **or** a callable taking a `RequestContext` and
|
|
64
|
+
returning a URL. Callables are resolved at **JSON serialization time**, so links (and
|
|
65
|
+
whole `LinkedCollection`s) are fully constructible in business logic with no request in
|
|
66
|
+
hand.
|
|
67
|
+
|
|
68
|
+
- `gazebo/context.py` defines the `RequestContext` Protocol (the minimal surface link
|
|
69
|
+
factories need: `base_url`, `url`, `query_params`, `url_for`) and delivers it
|
|
70
|
+
ambiently via the `link_context` ContextVar.
|
|
71
|
+
- The framework glue sets that ContextVar per request (`use_context`). For manual dumps
|
|
72
|
+
/ tests there is a fallback: `model_dump(context={'request': ctx})`, resolved by
|
|
73
|
+
`resolve_context`.
|
|
74
|
+
- `Link` factories (`self_link`, `root_link`, `to_route`) build callable hrefs that
|
|
75
|
+
call back into the context — they stay framework-agnostic.
|
|
76
|
+
|
|
77
|
+
A consequence: a callable-href link only serializes correctly inside an active request
|
|
78
|
+
(or with an explicit dump context). Serializing one with no context raises a clear
|
|
79
|
+
`ValueError` by design.
|
|
80
|
+
|
|
81
|
+
### DI container (`di/`)
|
|
82
|
+
|
|
83
|
+
- `providers.py` — registration. A **recipe** is a callable that builds a value, keyed
|
|
84
|
+
by the type it produces; it may be colocated as a `__provide__` classmethod on the
|
|
85
|
+
type, or supplied standalone for external types. **Scope is a wiring decision bound
|
|
86
|
+
at registration, never a property of the type** (`providers.app(T)` /
|
|
87
|
+
`providers.request(T)`). Recipes may be sync/async functions, (async) generators, or
|
|
88
|
+
(async) context managers; generators are auto-wrapped as CMs. `Qualify` disambiguates
|
|
89
|
+
duplicate types; `Overrides` is a typed replacement layer (the test-override
|
|
90
|
+
mechanism — by parameter, never by mutating a global).
|
|
91
|
+
- `container.py` — the resolution engine. Resolves a recipe's dependencies by the
|
|
92
|
+
**types of its parameters**; a parameter typed as a scope's *root* (e.g. the request
|
|
93
|
+
object) receives that root. Each entered scope owns a resolution cache and an
|
|
94
|
+
`AsyncExitStack` for teardown. Errors are specific: `UnresolvedDependencyError`,
|
|
95
|
+
`ScopeMismatchError`, `CircularDependencyError`.
|
|
96
|
+
|
|
97
|
+
### How the glue ties it together (`ext/fastapi.py`)
|
|
98
|
+
|
|
99
|
+
`GazeboApp` enters the **app** scope in its lifespan and opens a **request** scope per
|
|
100
|
+
request, publishing the link `RequestContext` for that request. Routes opt into
|
|
101
|
+
bare-type injection by living on a `GazeboRouter` (or the app directly): any parameter
|
|
102
|
+
whose type carries `__provide__`, or is marked `Annotated[T, Inject]`, is resolved from
|
|
103
|
+
the per-request scope by rewriting the route signature into FastAPI `Depends`.
|
|
104
|
+
|
|
105
|
+
`GazeboApp` + `GazeboRouter` are an **intended pair**. Putting an injectable-typed route
|
|
106
|
+
on a plain `APIRouter` fails loudly at startup (naming the route) rather than silently
|
|
107
|
+
treating the parameter as a request body. To add gazebo behavior to an app you didn't
|
|
108
|
+
construct, use `upgrade(app, providers)` instead of subclassing; to mount a `GazeboApp`
|
|
109
|
+
under a root app, forward its lifespan with `forward_lifespans`.
|
|
110
|
+
|
|
111
|
+
## Docs
|
|
112
|
+
|
|
113
|
+
- `working-docs/` — design specs and drafts: `design.md` (the OGC/web shapes),
|
|
114
|
+
`design-di.md` (the injection system), `roadmap.md` (post-v1 feature backlog). These
|
|
115
|
+
are the authoritative rationale for why things are shaped as they are; read the
|
|
116
|
+
relevant one before reworking a subsystem.
|
|
117
|
+
- `docs/` — the published documentation site (zensical/mkdocs, versioned with mike).
|
|
118
|
+
- `examples/garden/` — a complete standalone OGC-style API that exercises every feature;
|
|
119
|
+
the best end-to-end reference for how the pieces fit.
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
uv run --group docs zensical build --strict --clean # build (CI gate; fails on issues)
|
|
123
|
+
uv run --group docs zensical serve # live preview while writing
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Documentation style & guidelines
|
|
127
|
+
|
|
128
|
+
- **Split by document type; never duplicate across the boundary.** *Reference* (what
|
|
129
|
+
each symbol is — signatures, params) lives in **docstrings** and is autogenerated into
|
|
130
|
+
`docs/reference.md` via mkdocstrings. *Explanation/how-to* (why you'd reach for it, how
|
|
131
|
+
pieces combine) lives in **handwritten Markdown**. Keep docstrings Google-style and
|
|
132
|
+
current — they are the single source of truth for the reference layer.
|
|
133
|
+
- **Narrative pages must not restate the API.** No retyped signatures or param tables in
|
|
134
|
+
prose; link into the reference anchor instead (e.g. `reference.md#gazebo.link`, or a
|
|
135
|
+
per-symbol anchor like `#gazebo.link.Link.self_link`). Each page ends with a
|
|
136
|
+
**Reference** link.
|
|
137
|
+
- **Structure is layered by architecture** (Core → DI → FastAPI integration), one page
|
|
138
|
+
per module, mirroring `src/gazebo/`. The nav lives in `zensical.toml`; update it when
|
|
139
|
+
adding a page.
|
|
140
|
+
- **Page shape:** open with a one-line *why* blockquote, lead with the rationale (the
|
|
141
|
+
*why*) before the *how*, then concept sections. Prefer small, self-contained,
|
|
142
|
+
copy-pasteable snippets over one large example; the garden example is the
|
|
143
|
+
"see it all together" reference.
|
|
144
|
+
- **All code snippets are tested.** Example code lives in `tests/examples/<page>.py` as
|
|
145
|
+
runnable modules with module-level `assert`s; `tests/test_examples.py` executes each
|
|
146
|
+
via `runpy`, so a broken snippet fails CI. Docs **include** the clean region with
|
|
147
|
+
pymdownx.snippets — ` ```python\n--8<-- "tests/examples/links.py:self_link"\n``` ` —
|
|
148
|
+
rather than pasting code, so what readers see is exactly what runs. Wrap the rendered
|
|
149
|
+
region in `# --8<-- [start:name]` / `# --8<-- [end:name]` markers and keep the driving
|
|
150
|
+
`assert`/`TestClient` code *outside* the markers so it doesn't render. `check_paths`
|
|
151
|
+
is on, so a bad include path or region name fails the strict build.
|
|
152
|
+
|
|
153
|
+
## Landing a feature
|
|
154
|
+
|
|
155
|
+
A new feature or behavior change is not complete until all three of these land with it:
|
|
156
|
+
|
|
157
|
+
1. **Tests** in `tests/` covering the new behavior (coverage and warnings-as-errors are
|
|
158
|
+
enforced; CI runs the suite on Python 3.12–3.14).
|
|
159
|
+
2. **Use in the garden example** — `examples/garden` is meant to exercise *every*
|
|
160
|
+
feature, so wire the new capability into the example app and its tests. CI runs the
|
|
161
|
+
garden suite separately, so this is load-bearing, not decorative.
|
|
162
|
+
3. **Documentation** — update the relevant page under `docs/` (and the design spec in
|
|
163
|
+
`working-docs/` if the feature changes a subsystem's rationale or shape).
|
|
164
|
+
|
|
165
|
+
## Conventions
|
|
166
|
+
|
|
167
|
+
- Ruff is configured with single quotes and a 99-char line length, with a broad lint
|
|
168
|
+
rule set (see `[tool.ruff.lint]` in `pyproject.toml`). Match the surrounding style.
|
|
169
|
+
- Every module starts with a docstring explaining its role in the layering; keep that
|
|
170
|
+
up to date when a module's responsibility shifts.
|
|
171
|
+
</content>
|
|
172
|
+
</invoke>
|
|
173
|
+
</invoke>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
gazebo packages the recurring machinery of OGC-style REST APIs (deferred links,
|
|
4
|
+
collection envelopes, RFC 7807 problems, proxy-aware URLs, a typed DI container,
|
|
5
|
+
and FastAPI glue). The core depends only on `pydantic`; framework integration is
|
|
6
|
+
opt-in via extras. **Requires Python 3.12+.**
|
|
7
|
+
|
|
8
|
+
See [`CLAUDE.md`](CLAUDE.md) for a deeper tour of the architecture and layering.
|
|
9
|
+
|
|
10
|
+
## Project Setup
|
|
11
|
+
|
|
12
|
+
This project uses [uv](https://docs.astral.sh/uv) for project management. On a
|
|
13
|
+
Mac it can be installed globally with `brew install uv`. `uv` manages its own
|
|
14
|
+
virtual environment, so there is no separate venv step.
|
|
15
|
+
|
|
16
|
+
The project is a `uv` workspace whose only member is `examples/garden`. Sync
|
|
17
|
+
everything (all extras and all workspace packages) with:
|
|
18
|
+
|
|
19
|
+
```commandline
|
|
20
|
+
uv sync --all-extras --all-packages
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
CI installs with `--locked --no-editable` in addition to the flags above.
|
|
24
|
+
|
|
25
|
+
## Pre-commit
|
|
26
|
+
|
|
27
|
+
Install the pre-commit hooks so they run on every `git commit`:
|
|
28
|
+
|
|
29
|
+
```commandline
|
|
30
|
+
uv run pre-commit install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
You can also run them explicitly against all files:
|
|
34
|
+
|
|
35
|
+
```commandline
|
|
36
|
+
uv run pre-commit run --all-files
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The hooks cover `ruff` (lint + format), `mypy`, `pyright`, and a set of
|
|
40
|
+
file-hygiene checks. The same hooks run in CI, so it's worth keeping them green
|
|
41
|
+
locally.
|
|
42
|
+
|
|
43
|
+
If for some reason you need to commit code that does not pass the pre-commit
|
|
44
|
+
checks, this can be done with:
|
|
45
|
+
|
|
46
|
+
```commandline
|
|
47
|
+
git commit -m "message" --no-verify
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Type Checking
|
|
51
|
+
|
|
52
|
+
Type checking is enforced by **both** mypy and pyright (pyright runs over `src`
|
|
53
|
+
and `tests`). Both run in pre-commit and must be green in CI on Python
|
|
54
|
+
3.12–3.14.
|
|
55
|
+
|
|
56
|
+
## Testing
|
|
57
|
+
|
|
58
|
+
Tests are run with `pytest`. Put test modules and resources in the `tests/`
|
|
59
|
+
directory.
|
|
60
|
+
|
|
61
|
+
```commandline
|
|
62
|
+
uv run pytest
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Run a single test by node id:
|
|
66
|
+
|
|
67
|
+
```commandline
|
|
68
|
+
uv run pytest tests/test_link.py::test_name
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`uv run pytest` always reports coverage on the `gazebo` package
|
|
72
|
+
(`addopts=--cov=gazebo` in `pyproject.toml`) and treats warnings as errors
|
|
73
|
+
(`filterwarnings = ['error']`), so a stray warning fails the suite.
|
|
74
|
+
|
|
75
|
+
### Tested documentation snippets
|
|
76
|
+
|
|
77
|
+
All code snippets in the docs are tested. Example code lives in
|
|
78
|
+
`tests/examples/<page>.py` as runnable modules with module-level `assert`s, and
|
|
79
|
+
`tests/test_examples.py` executes each via `runpy`. The docs **include** the
|
|
80
|
+
clean region with `pymdownx.snippets` rather than pasting code, so a broken
|
|
81
|
+
snippet fails CI. See the documentation guidelines in `CLAUDE.md` for the marker
|
|
82
|
+
conventions.
|
|
83
|
+
|
|
84
|
+
## The Garden Example
|
|
85
|
+
|
|
86
|
+
`examples/garden` is a complete, standalone OGC-style API that is meant to
|
|
87
|
+
exercise *every* feature — it is the best end-to-end reference and is **load
|
|
88
|
+
bearing**: CI runs its suite separately. It is its own project with its own
|
|
89
|
+
entry point, so run it from that directory:
|
|
90
|
+
|
|
91
|
+
```commandline
|
|
92
|
+
cd examples/garden
|
|
93
|
+
uv run garden # serve Gazebo Gardens on http://127.0.0.1:8000
|
|
94
|
+
uv run pytest # the example's tests
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Documentation
|
|
98
|
+
|
|
99
|
+
The published docs site is built with zensical/mkdocs (versioned with `mike`).
|
|
100
|
+
|
|
101
|
+
```commandline
|
|
102
|
+
uv run --group docs zensical serve # live preview while writing
|
|
103
|
+
uv run --group docs zensical build --strict --clean # build (CI gate; fails on issues)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
*Reference* docs (signatures, params) are autogenerated from Google-style
|
|
107
|
+
docstrings into `docs/reference.md` via mkdocstrings — keep docstrings as the
|
|
108
|
+
single source of truth. *Explanation/how-to* lives in handwritten Markdown under
|
|
109
|
+
`docs/`, one page per module, mirroring `src/gazebo/`. The nav lives in
|
|
110
|
+
`zensical.toml`; update it when adding a page. See `CLAUDE.md` for the full
|
|
111
|
+
documentation style guide.
|
|
112
|
+
|
|
113
|
+
## Modifying Dependencies
|
|
114
|
+
|
|
115
|
+
With `uv`, add a dependency by running `uv add` (add `--dev` for a dev
|
|
116
|
+
dependency). Upgrade dependencies with `uv sync --upgrade`, optionally passing a
|
|
117
|
+
package name to upgrade just one; by default it upgrades everything.
|
|
118
|
+
|
|
119
|
+
## Landing a Feature
|
|
120
|
+
|
|
121
|
+
A new feature or behavior change is not complete until all three of these land
|
|
122
|
+
with it:
|
|
123
|
+
|
|
124
|
+
1. **Tests** in `tests/` covering the new behavior (coverage and
|
|
125
|
+
warnings-as-errors are enforced; CI runs the suite on Python 3.12–3.14).
|
|
126
|
+
2. **Use in the garden example** — wire the new capability into
|
|
127
|
+
`examples/garden` and its tests.
|
|
128
|
+
3. **Documentation** — update the relevant page under `docs/` (and the design
|
|
129
|
+
spec in `working-docs/` if the feature changes a subsystem's rationale or
|
|
130
|
+
shape).
|