jero 0.0.2__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.
- jero-0.0.2/.github/actions/setup-python-env/action.yml +30 -0
- jero-0.0.2/.github/workflows/main.yml +70 -0
- jero-0.0.2/.github/workflows/on-release-main.yml +69 -0
- jero-0.0.2/.github/workflows/validate-codecov-config.yml +15 -0
- jero-0.0.2/.gitignore +211 -0
- jero-0.0.2/.pre-commit-config.yaml +41 -0
- jero-0.0.2/AGENTS.md +128 -0
- jero-0.0.2/CONTRIBUTING.md +126 -0
- jero-0.0.2/LICENSE +21 -0
- jero-0.0.2/PKG-INFO +128 -0
- jero-0.0.2/README.md +103 -0
- jero-0.0.2/Taskfile.yml +90 -0
- jero-0.0.2/bugs/README.md +19 -0
- jero-0.0.2/bugs/server-side-decode-returns-422.md +106 -0
- jero-0.0.2/codecov.yaml +9 -0
- jero-0.0.2/docs/index.md +8 -0
- jero-0.0.2/docs/modules.md +3 -0
- jero-0.0.2/jero/__init__.py +39 -0
- jero-0.0.2/jero/core.py +1518 -0
- jero-0.0.2/jero/forms.py +21 -0
- jero-0.0.2/jero/streaming.py +52 -0
- jero-0.0.2/jero/testing.py +722 -0
- jero-0.0.2/plans/form-typed-headers.md +146 -0
- jero-0.0.2/plans/forms.md +272 -0
- jero-0.0.2/plans/raw-headers-source.md +148 -0
- jero-0.0.2/plans/str-text-responses.md +93 -0
- jero-0.0.2/plans/streaming.md +207 -0
- jero-0.0.2/pyproject.toml +159 -0
- jero-0.0.2/style-guide.md +487 -0
- jero-0.0.2/tests/__init__.py +0 -0
- jero-0.0.2/tests/conftest.py +33 -0
- jero-0.0.2/tests/demo_app.py +224 -0
- jero-0.0.2/tests/test_auth.py +20 -0
- jero-0.0.2/tests/test_binding.py +125 -0
- jero-0.0.2/tests/test_endpoint.py +50 -0
- jero-0.0.2/tests/test_factory_harness.py +88 -0
- jero-0.0.2/tests/test_forms.py +420 -0
- jero-0.0.2/tests/test_resource.py +79 -0
- jero-0.0.2/tests/test_responses.py +75 -0
- jero-0.0.2/tests/test_routing.py +47 -0
- jero-0.0.2/tests/test_streaming.py +257 -0
- jero-0.0.2/tests/test_wiring.py +108 -0
- jero-0.0.2/uv.lock +838 -0
- jero-0.0.2/zensical.toml +82 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: "Setup Python Environment"
|
|
2
|
+
description: "Set up Python environment for the given Python version"
|
|
3
|
+
|
|
4
|
+
inputs:
|
|
5
|
+
python-version:
|
|
6
|
+
description: "Python version to use"
|
|
7
|
+
required: true
|
|
8
|
+
default: "3.14"
|
|
9
|
+
uv-version:
|
|
10
|
+
description: "uv version to use"
|
|
11
|
+
required: true
|
|
12
|
+
default: "0.10.12"
|
|
13
|
+
|
|
14
|
+
runs:
|
|
15
|
+
using: "composite"
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ inputs.python-version }}
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v6
|
|
23
|
+
with:
|
|
24
|
+
version: ${{ inputs.uv-version }}
|
|
25
|
+
enable-cache: 'true'
|
|
26
|
+
cache-suffix: ${{ matrix.python-version }}
|
|
27
|
+
|
|
28
|
+
- name: Install Python dependencies
|
|
29
|
+
run: uv sync --frozen
|
|
30
|
+
shell: bash
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
name: Main
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
types: [opened, synchronize, reopened, ready_for_review]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
quality:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- name: Check out
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: actions/cache@v4
|
|
18
|
+
with:
|
|
19
|
+
path: ~/.cache/pre-commit
|
|
20
|
+
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
|
21
|
+
|
|
22
|
+
- name: Set up the environment
|
|
23
|
+
uses: ./.github/actions/setup-python-env
|
|
24
|
+
|
|
25
|
+
- name: Run checks
|
|
26
|
+
run: |
|
|
27
|
+
uv lock --locked
|
|
28
|
+
uv run prek run -a
|
|
29
|
+
|
|
30
|
+
tests-and-type-check:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
strategy:
|
|
33
|
+
matrix:
|
|
34
|
+
python-version: ["3.14"]
|
|
35
|
+
fail-fast: false
|
|
36
|
+
defaults:
|
|
37
|
+
run:
|
|
38
|
+
shell: bash
|
|
39
|
+
steps:
|
|
40
|
+
- name: Check out
|
|
41
|
+
uses: actions/checkout@v4
|
|
42
|
+
|
|
43
|
+
- name: Set up the environment
|
|
44
|
+
uses: ./.github/actions/setup-python-env
|
|
45
|
+
with:
|
|
46
|
+
python-version: ${{ matrix.python-version }}
|
|
47
|
+
|
|
48
|
+
- name: Run tests
|
|
49
|
+
run: uv run python -m pytest tests --cov --cov-config=pyproject.toml --cov-report=xml
|
|
50
|
+
|
|
51
|
+
- name: Check typing
|
|
52
|
+
run: uv run pyright
|
|
53
|
+
|
|
54
|
+
- name: Upload coverage reports to Codecov with GitHub Action on Python 3.14
|
|
55
|
+
uses: codecov/codecov-action@v4
|
|
56
|
+
if: ${{ matrix.python-version == '3.14' }}
|
|
57
|
+
env:
|
|
58
|
+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
59
|
+
|
|
60
|
+
check-docs:
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
steps:
|
|
63
|
+
- name: Check out
|
|
64
|
+
uses: actions/checkout@v4
|
|
65
|
+
|
|
66
|
+
- name: Set up the environment
|
|
67
|
+
uses: ./.github/actions/setup-python-env
|
|
68
|
+
|
|
69
|
+
- name: Check if documentation can be built
|
|
70
|
+
run: uv run zensical build -s
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: release-main
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
# Trusted Publishing (OIDC) — no PyPI token needed. The environment name
|
|
12
|
+
# must match the one configured on the PyPI publisher ("pypi").
|
|
13
|
+
environment:
|
|
14
|
+
name: pypi
|
|
15
|
+
url: https://pypi.org/p/jero
|
|
16
|
+
permissions:
|
|
17
|
+
id-token: write
|
|
18
|
+
steps:
|
|
19
|
+
- name: Check out
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up the environment
|
|
23
|
+
uses: ./.github/actions/setup-python-env
|
|
24
|
+
|
|
25
|
+
# pyproject.toml is the single source of truth for the version; the release
|
|
26
|
+
# tag must match it exactly, or the release fails here (no silent drift).
|
|
27
|
+
- name: Verify the release tag matches the pyproject version
|
|
28
|
+
run: |
|
|
29
|
+
version="$(python -c 'import tomllib, pathlib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
|
|
30
|
+
tag="${{ github.event.release.tag_name }}"
|
|
31
|
+
if [ "$version" != "$tag" ]; then
|
|
32
|
+
echo "::error::release tag '$tag' does not match pyproject version '$version' — bump 'version' in pyproject.toml to match the tag"
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
- name: Build package
|
|
37
|
+
run: uv build
|
|
38
|
+
|
|
39
|
+
- name: Publish package
|
|
40
|
+
run: uv publish
|
|
41
|
+
|
|
42
|
+
deploy-docs:
|
|
43
|
+
needs: publish
|
|
44
|
+
permissions:
|
|
45
|
+
contents: read
|
|
46
|
+
pages: write
|
|
47
|
+
id-token: write
|
|
48
|
+
environment:
|
|
49
|
+
name: github-pages
|
|
50
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
steps:
|
|
53
|
+
- name: Check out
|
|
54
|
+
uses: actions/checkout@v4
|
|
55
|
+
|
|
56
|
+
- name: Set up the environment
|
|
57
|
+
uses: ./.github/actions/setup-python-env
|
|
58
|
+
|
|
59
|
+
- name: Build documentation
|
|
60
|
+
run: uv run zensical build --clean
|
|
61
|
+
|
|
62
|
+
- name: Upload artifact
|
|
63
|
+
uses: actions/upload-pages-artifact@v4
|
|
64
|
+
with:
|
|
65
|
+
path: site
|
|
66
|
+
|
|
67
|
+
- name: Deploy to GitHub Pages
|
|
68
|
+
id: deployment
|
|
69
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name: validate-codecov-config
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
paths: [codecov.yaml]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
validate-codecov-config:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- name: Validate codecov configuration
|
|
15
|
+
run: curl -sSL --fail-with-body --data-binary @codecov.yaml https://codecov.io/validate
|
jero-0.0.2/.gitignore
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
docs/source
|
|
2
|
+
|
|
3
|
+
# From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore
|
|
4
|
+
|
|
5
|
+
# Byte-compiled / optimized / DLL files
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[codz]
|
|
8
|
+
*$py.class
|
|
9
|
+
|
|
10
|
+
# C extensions
|
|
11
|
+
*.so
|
|
12
|
+
|
|
13
|
+
# Distribution / packaging
|
|
14
|
+
.Python
|
|
15
|
+
build/
|
|
16
|
+
develop-eggs/
|
|
17
|
+
dist/
|
|
18
|
+
downloads/
|
|
19
|
+
eggs/
|
|
20
|
+
.eggs/
|
|
21
|
+
lib/
|
|
22
|
+
lib64/
|
|
23
|
+
parts/
|
|
24
|
+
sdist/
|
|
25
|
+
var/
|
|
26
|
+
wheels/
|
|
27
|
+
share/python-wheels/
|
|
28
|
+
*.egg-info/
|
|
29
|
+
.installed.cfg
|
|
30
|
+
*.egg
|
|
31
|
+
MANIFEST
|
|
32
|
+
|
|
33
|
+
# PyInstaller
|
|
34
|
+
# Usually these files are written by a python script from a template
|
|
35
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
36
|
+
*.manifest
|
|
37
|
+
*.spec
|
|
38
|
+
|
|
39
|
+
# Installer logs
|
|
40
|
+
pip-log.txt
|
|
41
|
+
pip-delete-this-directory.txt
|
|
42
|
+
|
|
43
|
+
# Unit test / coverage reports
|
|
44
|
+
htmlcov/
|
|
45
|
+
.tox/
|
|
46
|
+
.nox/
|
|
47
|
+
.coverage
|
|
48
|
+
.coverage.*
|
|
49
|
+
.cache
|
|
50
|
+
nosetests.xml
|
|
51
|
+
coverage.xml
|
|
52
|
+
*.cover
|
|
53
|
+
*.py.cover
|
|
54
|
+
.hypothesis/
|
|
55
|
+
.pytest_cache/
|
|
56
|
+
cover/
|
|
57
|
+
|
|
58
|
+
# Translations
|
|
59
|
+
*.mo
|
|
60
|
+
*.pot
|
|
61
|
+
|
|
62
|
+
# Django stuff:
|
|
63
|
+
*.log
|
|
64
|
+
local_settings.py
|
|
65
|
+
db.sqlite3
|
|
66
|
+
db.sqlite3-journal
|
|
67
|
+
|
|
68
|
+
# Flask stuff:
|
|
69
|
+
instance/
|
|
70
|
+
.webassets-cache
|
|
71
|
+
|
|
72
|
+
# Scrapy stuff:
|
|
73
|
+
.scrapy
|
|
74
|
+
|
|
75
|
+
# Sphinx documentation
|
|
76
|
+
docs/_build/
|
|
77
|
+
|
|
78
|
+
# PyBuilder
|
|
79
|
+
.pybuilder/
|
|
80
|
+
target/
|
|
81
|
+
|
|
82
|
+
# Jupyter Notebook
|
|
83
|
+
.ipynb_checkpoints
|
|
84
|
+
|
|
85
|
+
# IPython
|
|
86
|
+
profile_default/
|
|
87
|
+
ipython_config.py
|
|
88
|
+
|
|
89
|
+
# pyenv
|
|
90
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
91
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
92
|
+
# .python-version
|
|
93
|
+
|
|
94
|
+
# pipenv
|
|
95
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
96
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
97
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
98
|
+
# install all needed dependencies.
|
|
99
|
+
#Pipfile.lock
|
|
100
|
+
|
|
101
|
+
# UV
|
|
102
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
103
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
104
|
+
# commonly ignored for libraries.
|
|
105
|
+
#uv.lock
|
|
106
|
+
|
|
107
|
+
# poetry
|
|
108
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
109
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
110
|
+
# commonly ignored for libraries.
|
|
111
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
112
|
+
#poetry.lock
|
|
113
|
+
#poetry.toml
|
|
114
|
+
|
|
115
|
+
# pdm
|
|
116
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
117
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
118
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
119
|
+
#pdm.lock
|
|
120
|
+
#pdm.toml
|
|
121
|
+
.pdm-python
|
|
122
|
+
.pdm-build/
|
|
123
|
+
|
|
124
|
+
# pixi
|
|
125
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
126
|
+
#pixi.lock
|
|
127
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
128
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
129
|
+
.pixi
|
|
130
|
+
|
|
131
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
132
|
+
__pypackages__/
|
|
133
|
+
|
|
134
|
+
# Celery stuff
|
|
135
|
+
celerybeat-schedule
|
|
136
|
+
celerybeat.pid
|
|
137
|
+
|
|
138
|
+
# SageMath parsed files
|
|
139
|
+
*.sage.py
|
|
140
|
+
|
|
141
|
+
# Environments
|
|
142
|
+
.env
|
|
143
|
+
.envrc
|
|
144
|
+
.venv
|
|
145
|
+
env/
|
|
146
|
+
venv/
|
|
147
|
+
ENV/
|
|
148
|
+
env.bak/
|
|
149
|
+
venv.bak/
|
|
150
|
+
|
|
151
|
+
# Spyder project settings
|
|
152
|
+
.spyderproject
|
|
153
|
+
.spyproject
|
|
154
|
+
|
|
155
|
+
# Rope project settings
|
|
156
|
+
.ropeproject
|
|
157
|
+
|
|
158
|
+
# mkdocs/zensical documentation
|
|
159
|
+
/site
|
|
160
|
+
|
|
161
|
+
# mypy
|
|
162
|
+
.mypy_cache/
|
|
163
|
+
.dmypy.json
|
|
164
|
+
dmypy.json
|
|
165
|
+
|
|
166
|
+
# Pyre type checker
|
|
167
|
+
.pyre/
|
|
168
|
+
|
|
169
|
+
# pytype static type analyzer
|
|
170
|
+
.pytype/
|
|
171
|
+
|
|
172
|
+
# Cython debug symbols
|
|
173
|
+
cython_debug/
|
|
174
|
+
|
|
175
|
+
# PyCharm
|
|
176
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
177
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
178
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
179
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
180
|
+
#.idea/
|
|
181
|
+
|
|
182
|
+
# Abstra
|
|
183
|
+
# Abstra is an AI-powered process automation framework.
|
|
184
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
185
|
+
# Learn more at https://abstra.io/docs
|
|
186
|
+
.abstra/
|
|
187
|
+
|
|
188
|
+
# Visual Studio Code
|
|
189
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
190
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
191
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
192
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
193
|
+
# .vscode/
|
|
194
|
+
|
|
195
|
+
# Ruff stuff:
|
|
196
|
+
.ruff_cache/
|
|
197
|
+
|
|
198
|
+
# PyPI configuration file
|
|
199
|
+
.pypirc
|
|
200
|
+
|
|
201
|
+
# Cursor
|
|
202
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
203
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
204
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
205
|
+
.cursorignore
|
|
206
|
+
.cursorindexingignore
|
|
207
|
+
|
|
208
|
+
# Marimo
|
|
209
|
+
marimo/_static/
|
|
210
|
+
marimo/_lsp/
|
|
211
|
+
__marimo__/
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: "v6.0.0"
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-case-conflict
|
|
6
|
+
- id: check-merge-conflict
|
|
7
|
+
- id: check-toml
|
|
8
|
+
- id: check-yaml
|
|
9
|
+
- id: check-json
|
|
10
|
+
- id: pretty-format-json
|
|
11
|
+
args: [--autofix, --no-sort-keys]
|
|
12
|
+
- id: end-of-file-fixer
|
|
13
|
+
- id: trailing-whitespace
|
|
14
|
+
|
|
15
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
16
|
+
rev: "v0.15.15"
|
|
17
|
+
hooks:
|
|
18
|
+
- id: ruff-check
|
|
19
|
+
args: [--exit-non-zero-on-fix]
|
|
20
|
+
- id: ruff-format
|
|
21
|
+
|
|
22
|
+
- repo: local
|
|
23
|
+
hooks:
|
|
24
|
+
- id: pyright
|
|
25
|
+
name: pyright
|
|
26
|
+
entry: uv run pyright
|
|
27
|
+
language: system
|
|
28
|
+
types: [python]
|
|
29
|
+
pass_filenames: false
|
|
30
|
+
|
|
31
|
+
- id: deptry
|
|
32
|
+
name: deptry
|
|
33
|
+
entry: uv run deptry .
|
|
34
|
+
language: system
|
|
35
|
+
pass_filenames: false
|
|
36
|
+
|
|
37
|
+
- id: pylint
|
|
38
|
+
name: pylint
|
|
39
|
+
entry: uv run pylint
|
|
40
|
+
language: system
|
|
41
|
+
types: [python]
|
jero-0.0.2/AGENTS.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
## jero
|
|
4
|
+
|
|
5
|
+
An opinionated, msgspec-first ASGI web framework (Python 3.14). The goal is a
|
|
6
|
+
framework that is **both** very fast **and** a joy to build on — achieved by
|
|
7
|
+
being aggressively opinionated rather than flexible.
|
|
8
|
+
|
|
9
|
+
## Design philosophy (three pillars, all non-negotiable)
|
|
10
|
+
|
|
11
|
+
1. **Speed.** All introspection happens once, at wiring time; the per-request
|
|
12
|
+
path is just dict lookup → msgspec decode → call → encode, nothing else.
|
|
13
|
+
Benchmarks co-lead blacksheep and well outpace FastAPI. Never add per-request
|
|
14
|
+
work — resolve it at startup.
|
|
15
|
+
|
|
16
|
+
2. **Opinionated, scaffolded DX.** There is *one blessed way* to do each thing,
|
|
17
|
+
and the framework encodes the expertise so the developer can't get it wrong
|
|
18
|
+
(lifecycle, REST semantics, validation, dependency wiring). Contracts are
|
|
19
|
+
checked at startup and fail loud with a precise `WiringError` — never
|
|
20
|
+
silently at runtime. The aim: there's a framework answer to "how do I do X?"
|
|
21
|
+
so that question never reaches code review.
|
|
22
|
+
|
|
23
|
+
3. **Strict, expressive typing — not optional.** Everything is fully, statically
|
|
24
|
+
typed under pyright-strict. If you don't like typing, this is not your
|
|
25
|
+
framework. Types are not decoration — they *are* the contract (the binding,
|
|
26
|
+
the `WiringError`s) and, soon, the source of the OpenAPI spec (see roadmap).
|
|
27
|
+
Every design decision must produce precise static types; prefer rich,
|
|
28
|
+
self-documenting generics (`-> NDJSONStreamingResponse[Movie]`,
|
|
29
|
+
`BaseApp[Factory]`) over loose annotations. A feature that can't be expressed
|
|
30
|
+
in exact static types isn't done. **Never** reach for `Any` or untyped
|
|
31
|
+
`dict`/`bytes` I/O to dodge a type — it punches a hole in both the contract
|
|
32
|
+
and the generated docs. Hold this standard in every future design session.
|
|
33
|
+
|
|
34
|
+
These pull against each other constantly; keep all three in mind on every change.
|
|
35
|
+
|
|
36
|
+
## Working on jero
|
|
37
|
+
|
|
38
|
+
- **Read `style-guide.md` before writing code** — project conventions beyond what
|
|
39
|
+
ruff/pyright enforce (dataclasses, no globals, member ordering, no nested
|
|
40
|
+
funcs/classes, etc.).
|
|
41
|
+
- pyright **strict** and ruff must stay clean; tests must pass. `task pyright`,
|
|
42
|
+
`task ruff`, `task test` — or run everything at once with `task check`.
|
|
43
|
+
- **Never suppress a lint/type error to make it pass — always fix the code.**
|
|
44
|
+
Adding *any* ignore/disable — `# pylint: disable=…`, `# noqa`, `# type: ignore`,
|
|
45
|
+
`# pyright: ignore`, a `disable`/`ignore`/`per-file-ignores` entry in config, a
|
|
46
|
+
`deptry` ignore, etc. — is **forbidden without explicit human approval first**.
|
|
47
|
+
Ask, with the specific rule and why a code fix won't do; apply it only once the
|
|
48
|
+
human says yes. This applies to *every* tool, every time — no exceptions, no
|
|
49
|
+
"obviously fine" cases.
|
|
50
|
+
- For framework-level / design changes, **discuss the design first** — don't just
|
|
51
|
+
implement. Give options + a recommendation, then build once decided.
|
|
52
|
+
- **Testing stance:** tests run only through the public boundary — `TestClient`
|
|
53
|
+
against demo apps in `tests/`. **Do not unit-test `jero/` internals directly**;
|
|
54
|
+
they're covered transitively. This is deliberate (style-guide rule 7, and it
|
|
55
|
+
lets the internals be refactored freely — which they are, often). Don't "fix"
|
|
56
|
+
the absence of internal tests. Revisit only once the internals stabilize
|
|
57
|
+
(approaching a stable release), and even then only a thin layer for the
|
|
58
|
+
intricate *pure* helpers (`_parse_template`/`_route_segments`, the
|
|
59
|
+
`__orig_bases__` factory-type extraction, SSE/NDJSON formatting).
|
|
60
|
+
|
|
61
|
+
## The contract (how apps are built)
|
|
62
|
+
|
|
63
|
+
- **`Resource`** — a class with CRUD methods: `create` / `read_one` / `read_many`
|
|
64
|
+
/ `update` / `partial_update` / `delete` → POST / GET(item) / GET(collection)
|
|
65
|
+
/ PUT / PATCH / DELETE.
|
|
66
|
+
- **`Endpoint`** — bare verbs (`get`/`post`/…) for non-resource routes (health,
|
|
67
|
+
webhooks, actions). One path per Endpoint.
|
|
68
|
+
- Handler args bind **by name**, each a msgspec Struct: `json`, `content` (raw
|
|
69
|
+
bytes, exclusive with `json`), `params` (query), `path` (URL template slots),
|
|
70
|
+
`headers`, `user` (auth result). Return a Struct, `list[Struct]`, `bytes`, or a
|
|
71
|
+
`BytesResponse`/`JSONResponse`/streaming response.
|
|
72
|
+
- **A JSON body is always a Struct — never a raw `dict`.** The
|
|
73
|
+
`@api.get(...) → return {"a": 1}` idiom is gone: a `dict`/blob return is a
|
|
74
|
+
`WiringError` at startup. JSON in and out is a typed Struct, every time — that's
|
|
75
|
+
what gives it validation *and* a schema for the OpenAPI spec. No exceptions.
|
|
76
|
+
- **Auth**: an object with `authenticate(headers: Struct) -> UserStruct`; the
|
|
77
|
+
user type is checked against handlers at startup.
|
|
78
|
+
- **Wiring / DI**: there is **no DI container** — and that's deliberate, not a
|
|
79
|
+
gap. You hand-wire classes in `_wire` (subclass `BaseApp[Factory]`, linear
|
|
80
|
+
async, no yield); a dependency is just a constructor argument. The one thing
|
|
81
|
+
the language doesn't give you free — lifecycle — is what the framework adds:
|
|
82
|
+
open resources with `self._aenter` / `self._enter` (the app owns two exit
|
|
83
|
+
stacks, closed in reverse at shutdown, even on partial failure), and a
|
|
84
|
+
`BaseFactory` (stacks injected) groups construction. Past that there's nothing
|
|
85
|
+
to "resolve." Per-request resources are an `async with` inside the handler.
|
|
86
|
+
Do **not** add an injection/resolver system.
|
|
87
|
+
- REST error semantics throughout (404/400/422/401/405, auto HEAD + OPTIONS);
|
|
88
|
+
camelCase on the wire via msgspec `rename`.
|
|
89
|
+
- **Naming convention**: foundations you extend once are `Base*` (`BaseApp`,
|
|
90
|
+
`BaseFactory`); the request vocabulary you implement many specific subclasses of
|
|
91
|
+
stays plain (`Resource`, `Endpoint`).
|
|
92
|
+
|
|
93
|
+
## Layout
|
|
94
|
+
|
|
95
|
+
- `jero/core.py` — the framework. `jero/testing.py` — sync in-process `TestClient`
|
|
96
|
+
+ `FactoryHarness`. `jero/forms.py` / `jero/streaming.py` — form parts and
|
|
97
|
+
streaming response types.
|
|
98
|
+
- Runtime deps are intentionally sparse: `msgspec` for typed validation/JSON and
|
|
99
|
+
`python-multipart` for buffered `multipart/form-data` parsing.
|
|
100
|
+
- `tests/` — pytest suite driven through `TestClient` against demo apps in
|
|
101
|
+
`tests/demo_app.py`.
|
|
102
|
+
- `plans/` — design plans for not-yet-built features (e.g. `streaming.md`,
|
|
103
|
+
`forms.md`), staged for review before implementation.
|
|
104
|
+
- `bugs/` — one markdown note per bug, tracked in `bugs/README.md` (the manifest).
|
|
105
|
+
**Never delete a fixed bug note** — flip its row to `Done` in the manifest and
|
|
106
|
+
update the Open/Done counts. A fix isn't done until it has a regression test.
|
|
107
|
+
- Demo apps and the competitor/benchmark harness live in a **separate repo**, not here.
|
|
108
|
+
|
|
109
|
+
## Status & sharp edges
|
|
110
|
+
|
|
111
|
+
- **Built**: routing + path-param templates, Resource/Endpoint, all binding
|
|
112
|
+
sources, auth, REST semantics, response kinds, `BaseApp`/`BaseFactory`
|
|
113
|
+
lifecycle, `TestClient`, the test suite.
|
|
114
|
+
- **Performance (validated natively)**: on the authed write path
|
|
115
|
+
(`POST /movies` — bearer auth + JSON decode + encode + 201, C=200), jero ≈
|
|
116
|
+
blacksheep (~43k req/s, a tie), ~2× litestar, ~3× robyn, ~6× idiomatic FastAPI.
|
|
117
|
+
Tight unimodal latency — trustworthy. (The benchmark harness lives in a
|
|
118
|
+
separate repo; run natively rather than under emulation for real figures.)
|
|
119
|
+
- **Unbuilt**: custom status codes, `Location` header on 201. Minor polish: the
|
|
120
|
+
factory's `es`/`aes` stack injection matches by name with no startup check — a
|
|
121
|
+
`WiringError` on an unrecognized param would close that.
|
|
122
|
+
- **Roadmap**: auto-generated **OpenAPI spec + live, hosted docs**. This is the
|
|
123
|
+
reason every endpoint must be statically typed end to end — the schema is
|
|
124
|
+
*derived from the types* (Struct sources, typed returns including generics like
|
|
125
|
+
`NDJSONStreamingResponse[Movie]`), with no runtime guessing. Any feature that
|
|
126
|
+
escapes static typing won't appear in the spec, so don't add one.
|
|
127
|
+
- **Untested**: no non-trivial real app has been built on it yet — that's where
|
|
128
|
+
the opinions (pagination, streaming, cross-cutting concerns) get stress-tested.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Contributing to `jero`
|
|
2
|
+
|
|
3
|
+
Contributions are welcome, and they are greatly appreciated!
|
|
4
|
+
Every little bit helps, and credit will always be given.
|
|
5
|
+
|
|
6
|
+
You can contribute in many ways:
|
|
7
|
+
|
|
8
|
+
# Types of Contributions
|
|
9
|
+
|
|
10
|
+
## Report Bugs
|
|
11
|
+
|
|
12
|
+
Report bugs at https://github.com/RogerThomas/jero/issues
|
|
13
|
+
|
|
14
|
+
If you are reporting a bug, please include:
|
|
15
|
+
|
|
16
|
+
- Your operating system name and version.
|
|
17
|
+
- Any details about your local setup that might be helpful in troubleshooting.
|
|
18
|
+
- Detailed steps to reproduce the bug.
|
|
19
|
+
|
|
20
|
+
## Fix Bugs
|
|
21
|
+
|
|
22
|
+
Look through the GitHub issues for bugs.
|
|
23
|
+
Anything tagged with "bug" and "help wanted" is open to whoever wants to implement a fix for it.
|
|
24
|
+
|
|
25
|
+
## Implement Features
|
|
26
|
+
|
|
27
|
+
Look through the GitHub issues for features.
|
|
28
|
+
Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it.
|
|
29
|
+
|
|
30
|
+
## Write Documentation
|
|
31
|
+
|
|
32
|
+
jero could always use more documentation, whether as part of the official docs, in docstrings, or even on the web in blog posts, articles, and such.
|
|
33
|
+
|
|
34
|
+
## Submit Feedback
|
|
35
|
+
|
|
36
|
+
The best way to send feedback is to file an issue at https://github.com/RogerThomas/jero/issues.
|
|
37
|
+
|
|
38
|
+
If you are proposing a new feature:
|
|
39
|
+
|
|
40
|
+
- Explain in detail how it would work.
|
|
41
|
+
- Keep the scope as narrow as possible, to make it easier to implement.
|
|
42
|
+
- Remember that this is a volunteer-driven project, and that contributions
|
|
43
|
+
are welcome :)
|
|
44
|
+
|
|
45
|
+
# Get Started!
|
|
46
|
+
|
|
47
|
+
Ready to contribute? Here's how to set up `jero` for local development.
|
|
48
|
+
Please note this documentation assumes you already have `uv` and `Git` installed and ready to go.
|
|
49
|
+
|
|
50
|
+
1. Fork the `jero` repo on GitHub.
|
|
51
|
+
|
|
52
|
+
2. Clone your fork locally:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cd <directory_in_which_repo_should_be_created>
|
|
56
|
+
git clone git@github.com:YOUR_NAME/jero.git
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
3. Now we need to install the environment. Navigate into the directory
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cd jero
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then, install and activate the environment with:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv sync
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
4. Install pre-commit to run linters/formatters at commit time:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
uv run pre-commit install
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
5. Create a branch for local development:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git checkout -b name-of-your-bugfix-or-feature
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Now you can make your changes locally.
|
|
84
|
+
|
|
85
|
+
6. Don't forget to add test cases for your added functionality to the `tests` directory.
|
|
86
|
+
|
|
87
|
+
7. When you're done making changes, check that your changes pass the formatting tests.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
make check
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Now, validate that all unit tests are passing:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
make test
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
9. Before raising a pull request you should also run tox.
|
|
100
|
+
This will run the tests across different versions of Python:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
tox
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This requires you to have multiple versions of python installed.
|
|
107
|
+
This step is also triggered in the CI/CD pipeline, so you could also choose to skip this step locally.
|
|
108
|
+
|
|
109
|
+
10. Commit your changes and push your branch to GitHub:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
git add .
|
|
113
|
+
git commit -m "Your detailed description of your changes."
|
|
114
|
+
git push origin name-of-your-bugfix-or-feature
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
11. Submit a pull request through the GitHub website.
|
|
118
|
+
|
|
119
|
+
# Pull Request Guidelines
|
|
120
|
+
|
|
121
|
+
Before you submit a pull request, check that it meets these guidelines:
|
|
122
|
+
|
|
123
|
+
1. The pull request should include tests.
|
|
124
|
+
|
|
125
|
+
2. If the pull request adds functionality, the docs should be updated.
|
|
126
|
+
Put your new functionality into a function with a docstring, and add the feature to the list in `README.md`.
|