API Reference¶
The full public surface, grouped by area. Everything here is importable from jero
(test helpers from jero.testing).
App & wiring¶
Bases: ABC
Subclass and override wire to open resources and include resources/endpoints.
The app owns the two exit stacks. Parameterize with a factory class —
class MyApp(BaseApp[MyFactory]) — and the app builds it at construction,
injecting the stacks the factory's __init__ names (es for the
ExitStack, aes for the AsyncExitStack). The built factory is then
self._factory (typed as MyFactory) inside wire, and any resource
it registers on those stacks is closed at shutdown.
Pass factory= to supply a prebuilt factory instead of building one — the
seam for tests, which inject a create_autospec stand-in
(MyApp(factory=mock_factory)) so the real services are never constructed.
Reverse-routed Location / Link URLs are relative unless the environment sets
JERO_BASE_URL (a static public origin) or JERO_TRUST_FORWARDED (rebuild the
origin per request from X-Forwarded-*); see :func:_forwarded_config_from_env.
Source code in jero/core.py
3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 | |
_factory
property
¶
The built factory, read inside wire. Set once at construction (read-only).
_aenter(cm)
async
¶
Open an async context manager, closed at shutdown in reverse order.
Source code in jero/core.py
3415 3416 3417 | |
_create_background_tasks(*, maxsize=1024, drain_timeout=30.0, allow_one_to_many=False)
async
¶
Build a :class:BackgroundTasks queue bound to the app's lifecycle.
Sugar for await self._aenter(BackgroundTasks(...)): the worker starts at
startup and drains/stops at shutdown. Call inside wire after the services
its handlers use, so reverse-order shutdown drains the queue before those
services are torn down.
Source code in jero/core.py
4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 | |
_enter(cm)
¶
Open a sync context manager, closed at shutdown in reverse order.
Source code in jero/core.py
3411 3412 3413 | |
_include_cors(cors)
¶
Serve cross-origin browser callers app-wide with one default :class:CORS policy.
Call inside wire, at most once (order among the include_* calls doesn't
matter). Every include inherits the policy unless it passes its own cors= —
a different policy overrides it, CORS.OFF removes it. Skipping
_include_cors entirely means no CORS anywhere except includes that opt in
with their own cors=.
The policy is compiled at wiring: a wildcard origin becomes constant header
pairs in covered routes' responses (free per request), an origin allow-list one
set lookup + origin echo. Preflights (OPTIONS with
Access-Control-Request-Method) are answered per (path, requested method) on
the existing OPTIONS branch, so two verbs on one path may carry two policies.
Error responses carry the failing route's pairs — a browser page must be able
to read the 401/422 problem body; unrouted 404s carry this app default.
Source code in jero/core.py
3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 | |
_include_endpoint(endpoint, *, auth=None, cors=None, middleware=())
¶
_include_endpoint(
endpoint: Endpoint,
*,
auth: Auth[THeaders, TUser] | None = None,
cors: CORS | None = None,
middleware: Sequence[object] = (),
) -> None
_include_endpoint(
endpoint: Endpoint,
*,
auth: CookieAuth[TCookies, TUser] | None = None,
cors: CORS | None = None,
middleware: Sequence[object] = (),
) -> None
_include_endpoint(
endpoint: Endpoint,
*,
auth: HybridAuth[THeaders, TCookies, TUser]
| None = None,
cors: CORS | None = None,
middleware: Sequence[object] = (),
) -> None
Register an Endpoint's verb methods as routes, optionally behind auth.
An authenticator returning TUser gates every verb: no valid credentials, no
handler. One returning TUser | None makes credentials an input — a caller
presenting none is served with user=None, invalid credentials are still a 401,
and every handler on the route must declare user: TUser | None (all checked at
startup).
cors= sets this include's :class:CORS policy: omitted inherits the
_include_cors default, a policy overrides it, CORS.OFF opts out.
middleware= adds include-scoped middleware on top of any registered with
:meth:_include_middleware — scope is deployment policy, so it lives here at
the mount, not on the class. Scoped intercepts run post-resolve, pre-auth.
Source code in jero/core.py
3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 | |
_include_error_adapter(adapter)
¶
Replace the Problem family's wire body app-wide with adapter's composition.
Call inside wire, at most once. Every Problem-family error — the framework's
built-ins (404/405/422/500, …) and your own HTTPError subclasses, including
those returned by exception handlers — is rendered through adapter.compose
instead of RFC 9457 Problem Details, and the derived OpenAPI error responses
document the adapter's body. StructHTTPError\ s render themselves.
Source code in jero/core.py
3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 | |
_include_exception_handler(handler)
¶
Register a structurally typed custom exception handler.
The exception, JSON body, and typed-header types are inferred from the concrete
handle_exception signature and validated once during wiring.
Source code in jero/core.py
3643 3644 3645 3646 3647 3648 3649 3650 3651 | |
_include_middleware(middleware)
¶
Register one middleware app-wide: every route (current and later includes) is covered, and its intercepts run pre-routing — they can answer requests no route serves, which is how an OPTIONS-scoped intercept answers preflights for paths that would 404.
The middleware is a structurally typed object — no base class; its hooks
(response_headers attribute or method, intercept + intercept_methods,
observe) are introspected and validated here, fail-loud, and compiled into
the covered routes at wiring (see :class:~jero.CORS for the same idea as a
built-in). Register order is run order, and globals run before include-scoped
middleware. Only what a middleware defines costs anything: a constant
response_headers is baked into route header blocks for free, an off-scope
verb never reaches an intercept.
Source code in jero/core.py
3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 | |
_include_openapi(*, title, version, description=None, openapi_path='/openapi.json', docs_path='/docs', servers=(), tags=(), docs_html=None, favicon=None, scalar_config=None)
¶
Serve an auto-generated OpenAPI 3.1 document and a docs UI.
Call inside wire (order among the include_* calls doesn't matter — the
document is built once after wiring completes). openapi_path serves the JSON
spec; docs_path serves a Scalar UI pointed at it (pass None to omit the
UI, or docs_html to replace the page — e.g. for offline / strict-CSP hosting).
scalar_config is a typed :class:~jero.ScalarConfig tuning the Scalar UI — e.g.
scalar_config=ScalarConfig(hide_models=True) drops the global Models list, or set a
theme / layout. Only its set fields are sent, so Scalar's own defaults apply
otherwise. For options ScalarConfig doesn't model, supply a full docs_html
page instead (scalar_config is ignored when docs_html is given).
favicon gives the docs page an icon. A Path (the primary case) is read
once at wiring — a missing/unreadable file or an unsupported suffix
(.ico/.png/.svg) is a WiringError — and served as a precomputed
response at /favicon.ico; no runtime file I/O. A str is a URL (a
data: URI works too), emitted verbatim in the page's <link rel="icon">
with nothing served. Like the spec routes, /favicon.ico never appears in the
generated document. A custom docs_html page is never modified — reference
the favicon yourself there.
tags declares document-level Tag\ s to describe operation groups and pin the
order they appear in the docs UI. Operations may also define/use tags via their
meta (a bare name, or a Tag with a description); all are merged here. The one
rule: describing the same tag name two different ways is a startup WiringError.
The spec is derived from your wired resources/endpoints: their typed sources
(path/query/header params, request bodies), return types (responses), auth
(security), msgspec.Meta field constraints, and the metadata you declare —
OperationMeta (summary/description/tags/responses) and a model's ModelMeta.
Docstrings are never published; public prose is always explicit.
Source code in jero/core.py
3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 | |
_include_resource(resource, *, auth=None, cors=None, middleware=())
¶
_include_resource(
resource: Resource,
*,
auth: Auth[THeaders, TUser] | None = None,
cors: CORS | None = None,
middleware: Sequence[object] = (),
) -> None
_include_resource(
resource: Resource,
*,
auth: CookieAuth[TCookies, TUser] | None = None,
cors: CORS | None = None,
middleware: Sequence[object] = (),
) -> None
_include_resource(
resource: Resource,
*,
auth: HybridAuth[THeaders, TCookies, TUser]
| None = None,
cors: CORS | None = None,
middleware: Sequence[object] = (),
) -> None
Register a Resource's CRUD methods as routes, optionally behind auth.
An authenticator returning TUser gates every method: no valid credentials, no
handler. One returning TUser | None accepts anonymous callers instead — see
:meth:_include_endpoint.
cors= sets this include's :class:CORS policy: omitted inherits the
_include_cors default, a policy overrides it, CORS.OFF opts out.
middleware= adds include-scoped middleware on top of any registered with
:meth:_include_middleware — scope is deployment policy, so it lives here at
the mount, not on the class. Scoped intercepts run post-resolve, pre-auth.
Source code in jero/core.py
3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 | |
_include_websocket(endpoint, *, auth=None, max_frame_size=1024 * 1024, middleware=())
¶
_include_websocket(
endpoint: WebSocketEndpoint,
*,
auth: Auth[THeaders, TUser] | None = None,
max_frame_size: int = 1024 * 1024,
middleware: Sequence[object] = (),
) -> None
_include_websocket(
endpoint: WebSocketEndpoint,
*,
auth: CookieAuth[TCookies, TUser] | None = None,
max_frame_size: int = 1024 * 1024,
middleware: Sequence[object] = (),
) -> None
_include_websocket(
endpoint: WebSocketEndpoint,
*,
auth: HybridAuth[THeaders, TCookies, TUser]
| None = None,
max_frame_size: int = 1024 * 1024,
middleware: Sequence[object] = (),
) -> None
Register one typed WebSocket protocol and compile its handshake contract.
Source code in jero/core.py
3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 | |
wire()
abstractmethod
async
¶
Override to open resources (via _enter / _aenter) and include them.
Runs once at startup. Anything entered via the helpers is torn down (in reverse order) at shutdown.
Abstract: every BaseApp subclass must implement it. A subclass that
omits it is flagged at its instantiation site by the type checker.
Source code in jero/core.py
3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 | |
Base for an app's factory. Subclass and add create_* methods that
build services with self._enter / self._aenter.
Under an app, the app injects its exit stacks (es / aes); anything
opened via the helpers is closed when the app shuts down. Standalone —
scripts, cron jobs, notebooks — enter :meth:open instead and the factory
owns the same lifecycle itself.
Source code in jero/core.py
3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 | |
_aenter(cm)
async
¶
Open an async context manager on the factory's injected stack — under an app that is the app's stack, so the resource is closed at the app's shutdown.
Source code in jero/core.py
3316 3317 3318 3319 | |
_enter(cm)
¶
Open a sync context manager on the factory's injected stack — under an app that is the app's stack, so the resource is closed at the app's shutdown.
Source code in jero/core.py
3311 3312 3313 3314 | |
open()
async
classmethod
¶
Use the factory's service graph standalone, with real lifecycle::
async with Factory.open() as factory:
service = await factory.create_widget_service()
Creates a fresh exit-stack pair, builds the factory on them exactly as an
app does at startup, and unwinds on exit — async resources first, each
stack in reverse order, even if the block raises. (FactoryHarness is
this, bridged onto a background loop for synchronous tests.)
Source code in jero/core.py
3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 | |
Bases: TypeError
A router does not meet the framework contract. Raised at startup.
Lives here, on the shared wiring leaf, so both :mod:jero.core and the sender-free
:mod:jero._exception_handlers (which validates handlers at wiring time) can raise it
without importing each other.
Source code in jero/_wiring_types.py
47 48 49 50 51 52 53 | |
Routing¶
Bases: _Routable
One REST resource: subclass and define any of the CRUD methods.
read_one is the item route (its path may extend the mount with
the item id); read_many is the collection (its path is exact).
Optional OpenAPI metadata is declared at class definition: meta applies to every
operation, meta_<op> to one (meta_create, meta_read_one, …).
Source code in jero/core.py
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 | |
Bases: _Routable
One HTTP endpoint at a single path: subclass and define any of
get / post / put / patch / delete.
Unlike :class:Resource there are no CRUD semantics — the method name
is the verb, every verb returns 200, and the path is exact (no
trailing extension). A different path is a different Endpoint.
Optional OpenAPI metadata is declared at class definition: meta applies to every
operation, meta_<verb> to one (meta_get, meta_post, …).
Source code in jero/core.py
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 | |
Bases: Struct
OpenAPI metadata shared by all of a Resource's operations.
tags are the groups every operation belongs to — a bare str name or a Tag
that defines it with a description (see :class:EndpointMeta). responses declares
extra/override responses applied to every operation (a blanket 401, say); a
per-operation OperationMeta overrides it. exceptions declares jero error
classes every operation can raise — derived entirely from the class; a
per-operation OperationMeta extends it (both remain raiseable).
Source code in jero/_wiring_types.py
160 161 162 163 164 165 166 167 168 169 170 171 172 173 | |
Bases: Struct
OpenAPI metadata shared by all of an Endpoint's operations.
tags are the groups this endpoint belongs to. An entry is a bare str (the tag
name — the OpenAPI operation-tag shape; it picks up a description if one is defined for
that name, else stands alone) or a Tag to define the name with a description
inline (hoisted to the document's tag list). responses declares extra/override
responses applied to every operation (a blanket 401, say); a per-operation
OperationMeta overrides it. exceptions declares jero error classes every
operation can raise — their status, body schema, and description derive from the
class; a per-operation OperationMeta extends it (both remain raiseable).
Source code in jero/_wiring_types.py
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | |
Bases: Struct
OpenAPI metadata for a single operation (meta_get, meta_create, …).
operation_id lives here, never on the class-level meta — operation ids must
be unique, so they can't sensibly cascade to every operation. summary /
description are the operation's prose (explicit — docstrings are never published).
responses declares extra responses or overrides a derived one by reusing its status.
exceptions declares jero error classes this operation can raise — status, body
schema, and description all derive from the class; entries extend the class-level
meta's (both remain raiseable), several sharing a status document as a oneOf,
and an explicit responses entry for the same status wins.
tags (bare str names or describing Tag\ s) cascade from the class-level
meta by the container type: a list extends the class tags
(meta_get=OperationMeta(tags=["unsafe"]) -> class tags + unsafe), a non-empty
tuple replaces them (tags=("admin",) -> just admin); the default ()
inherits.
Source code in jero/_wiring_types.py
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
Bases: Struct
A response to document on an operation, declared in *Meta.responses.
Use it for responses the framework can't infer — a domain 409, a 429, a
richer error model — or to override a derived entry by reusing its status.
model is the response body Struct. content_type defaults to
application/json when a model is given; set it (with no model) to document
a schemaless body of another media type (e.g. text/csv). With neither, the
response has no body.
Source code in jero/openapi.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
Bases: Struct
A document-level tag — the only place a tag carries a description (docs UIs render
it as the blurb under the tag's section). Declare them on _include_openapi(tags=...)
to describe and order the groups; operations reference a tag by its name.
Source code in jero/openapi.py
140 141 142 143 144 145 146 147 148 149 150 151 152 153 | |
to_openapi()
¶
Render the OpenAPI tags entry for this tag.
Source code in jero/openapi.py
148 149 150 151 152 153 | |
Requests¶
Bases: Struct
The read-only, typed request view a middleware hook receives.
headers is the hook's own Struct: annotate request: Request[MyHeaders]
and wiring compiles a scanner that binds exactly the header keys the Struct's
fields name (origin -> origin, x_trace_id -> x-trace-id), exactly
like auth's headers parameter. Give a field a | None = None default when
the header may be absent; a missing required header is a 400.
method is the wire method — a HEAD request reads "HEAD" even though
routing serves it from a GET handler. received_at is time.perf_counter()
at dispatch, stamped only on routes a dynamic hook covers (0.0 otherwise).
Source code in jero/_middleware.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | |
Immutable, case-insensitive view of the request headers, preserving as-sent names and order.
For forwarding the whole header bag upstream or for diagnostics — not for
reading values you act on (model those in a typed headers Struct). Lookups
are case-insensitive (raw["X-Trace-Id"] == raw["x-traceid"]); iteration,
items and repr keep the casing as sent. Registers as
Mapping[str, str] so it drops straight into niquests(headers=...); pass
:meth:multi_items instead when repeated headers must survive.
Source code in jero/headers.py
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | |
_unique()
¶
First-seen pair for each name, compared case-insensitively (Mapping contract).
Source code in jero/headers.py
22 23 24 25 26 27 28 29 30 31 | |
get(key, default=None)
¶
The first value for key (case-insensitive), or default if absent.
Source code in jero/headers.py
40 41 42 43 44 45 | |
getlist(key)
¶
Every value sent under key (case-insensitive), in order.
Source code in jero/headers.py
47 48 49 50 | |
items()
¶
First-seen (name, value) pair per unique header name (Mapping contract).
Source code in jero/headers.py
69 70 71 | |
keys()
¶
Unique header names, first-seen casing.
Source code in jero/headers.py
61 62 63 | |
multi_items()
¶
Every header pair, repeats included — use for faithful forwarding.
Source code in jero/headers.py
73 74 75 | |
values()
¶
The value of the first occurrence of each unique header name.
Source code in jero/headers.py
65 66 67 | |
Bases: Struct
The empty headers Struct — the default H of :class:Request, for hooks
that bind no request headers (request: Request is Request[NoHeaders]).
Source code in jero/_middleware.py
45 46 47 | |
Bases: Struct
One multipart form part with envelope metadata.
Source code in jero/forms.py
11 12 13 14 15 16 17 | |
Bases: FormPart[bytes, H]
A file upload part with a required filename.
Source code in jero/forms.py
20 21 22 23 | |
Responses¶
Bases: BaseResponse[H]
A Struct encoded as JSON; content-type defaults to application/json.
Source code in jero/core.py
290 291 292 293 294 | |
Bases: BaseResponse[H]
Raw bytes; content-type defaults to application/octet-stream.
Source code in jero/core.py
283 284 285 286 287 | |
Bases: BaseResponse[H]
204, no body. Carries typed/raw headers, location, and links like any
response — a 204 may legitimately carry a Location or Link (RFC 9110 §15.3.5).
At 204 it emits neither content-type nor content-length, whatever headers
supplies; override status_code to a status that permits them and the body is still
empty, so content-length: 0 frames it. See :func:_no_content_headers.
Source code in jero/core.py
297 298 299 300 301 302 303 | |
Bases: BaseResponse[H]
201 + a JSON body, whatever status the verb would otherwise default to.
Deliberately a sibling of :class:JSONResponse rather than a subclass — it promises
a status JSONResponse does not, so it is not substitutable for one. As a subclass,
-> JSONResponse[T] would statically accept a returned Created and then send the
verb's status: a 200 from an object whose type says 201, invisible to every type
checker. The repeated json field is the price of that being a type error instead.
Source code in jero/core.py
306 307 308 309 310 311 312 313 314 315 316 317 | |
Bases: BaseResponse[H]
202 + a JSON body, whatever status the verb would otherwise default to.
A sibling of :class:JSONResponse, not a subclass, for the reason given on
:class:Created.
Source code in jero/core.py
320 321 322 323 324 325 326 327 | |
An RFC 9110 Location on a response — 201 Created, a redirect target, or the
status URL on a 202. Build with a constructor; resolution happens at response send.
Source code in jero/links.py
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
from_operation(operation, *, path=None)
classmethod
¶
Point at a mounted operation; path (type-checked here) fills its slots.
Source code in jero/links.py
121 122 123 124 125 126 127 | |
from_path(path)
classmethod
¶
Point at a root-relative path; it picks up the app's URL base (absolute origin / prefix) the same way a reversed operation does.
Source code in jero/links.py
134 135 136 137 138 | |
from_ref(ref, *, path=None)
classmethod
¶
Point at an operation by its class ref ("name.operation") — the
import-cycle hatch; prefer from_operation otherwise.
Source code in jero/links.py
140 141 142 143 144 | |
from_url(url)
classmethod
¶
Point at a fully-qualified URL, used verbatim — never rewritten.
Source code in jero/links.py
129 130 131 132 | |
An RFC 8288 web link. A list of links joins into one Link header. rel is
required; title and media_type (emitted as type=) are optional.
Source code in jero/links.py
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | |
from_operation(operation, *, rel, path=None, title=None, media_type=None)
classmethod
¶
Link to a mounted operation with relation rel; path fills its slots.
Source code in jero/links.py
157 158 159 160 161 162 163 164 165 166 167 168 169 | |
from_path(path, *, rel, title=None, media_type=None)
classmethod
¶
Link to a root-relative path; it picks up the app's URL base the same way a reversed operation does.
Source code in jero/links.py
178 179 180 181 182 183 184 | |
from_ref(ref, *, rel, path=None, title=None, media_type=None)
classmethod
¶
Link to an operation by its class ref ("name.operation") — the
import-cycle hatch; prefer from_operation otherwise.
Source code in jero/links.py
186 187 188 189 190 191 192 193 194 195 196 197 198 | |
from_url(url, *, rel, title=None, media_type=None)
classmethod
¶
Link to a fully-qualified URL, used verbatim — never rewritten.
Source code in jero/links.py
171 172 173 174 175 176 | |
Streaming¶
Bases: _StreamingResponse[bytes, H]
A response streamed as raw bytes chunks (application/octet-stream by
default; override via raw_headers).
Source code in jero/streaming.py
50 51 52 53 | |
Bases: _StreamingResponse[T, H]
A response streamed as newline-delimited JSON — one T Struct per line
(application/x-ndjson).
Source code in jero/streaming.py
56 57 58 59 | |
Bases: _StreamingResponse[T | ServerSentEvent[T], H]
A Server-Sent Events response (text/event-stream, GET-only). Yield a
Struct/str (sent as data) or a :class:ServerSentEvent. keepalive, if
set, emits a comment ping every N idle seconds.
Source code in jero/streaming.py
62 63 64 65 66 67 68 69 70 | |
One Server-Sent Event. Yield from an :class:SSEResponse stream to control
the event / id / retry fields; data is a Struct (encoded as
JSON) or a raw str.
Source code in jero/streaming.py
17 18 19 20 21 22 23 24 25 26 | |
Cookies¶
One Set-Cookie response header, secure by default.
A bare SetCookie("session", token) is already Path=/; Secure; HttpOnly;
SameSite=Lax — loosening any of that (http_only=False for a JS-readable
cookie, secure=False) is explicit and visible in review. Modern browsers treat
http://localhost as a trustworthy origin, so Secure cookies still work in
local dev.
Validation runs at construction (ValueError on the offending attribute), not at
emission — a rejected cookie never becomes a route's problem.
Source code in jero/cookies.py
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | |
expire(name, *, path='/', domain=None)
classmethod
¶
A cookie that clears name on the browser: empty value, Max-Age=0, and
Expires at the Unix epoch (belt and braces for clients that ignore Max-Age).
path/domain must match the cookie being cleared — a browser only removes a
cookie whose scope matches exactly.
Source code in jero/cookies.py
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | |
Errors¶
Bases: Exception
The abstract root of jero's API errors: the HTTP contract without a wire body.
Every concrete error declares status as a class option — it drives the response
status line and the OpenAPI docs. Subclass one of the two families, never this root
directly: :class:HTTPError (RFC 9457 Problem Details, the blessed default) or
:class:StructHTTPError (bring your own body Struct). except BaseHTTPError
means "any jero error"; except HTTPError catches only the Problem family.
Source code in jero/errors.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | |
response_body
property
¶
The Struct the framework encodes as this error's response body.
Bases: BaseHTTPError
A static typed API error rendered as RFC 9457 Problem Details (the blessed default).
Subclasses declare their stable contract as class options::
class AuthenticationRequiredError(
HTTPError,
type="authentication-required",
title="Authentication required",
status=401,
): ...
Source code in jero/errors.py
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | |
Bases: ParameterizedHTTPError[P], ABC
The ergonomic parameterized-error base used by dataclass error subclasses.
Source code in jero/errors.py
289 290 291 292 293 294 | |
Bases: HTTPError
An API error whose detail is rendered from a typed params Struct.
Source code in jero/errors.py
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 | |
problem
property
¶
Build the typed wire body for this error occurrence.
Bases: BaseHTTPError
An API error generic over your own wire Struct B — the bring-your-own-body
family. Class options declare how every field of B gets its value:
consts={"field": value}— a pinned constant: on the wire exactly as given, and an enum const in the OpenAPI schema.templates={"field": "..."}— rendered at raise time from the raise-time params ({{brace}}escaping ships literal braces, perstr.format).status_field="field"— an existing int field fed the class'sstatus(an enum const in the schema).params_field="field"— a Struct-typed field the raise-time params nest into.- anything left over — fed by a same-named raise-time param.
Total coverage is enforced loud at class creation: every field of B has exactly
one source. Raise-time params are one flat namespace (template placeholders, nested
params-Struct fields, and leftover body fields); pass them as keyword arguments, or —
the blessed, statically-typed form — decorate the subclass with @dataclass and
declare them as fields, so the generated __init__ carries real names and types::
class QuotaBody(Struct, rename="camel"):
error_code: str
error_message: str
status_code: int
@dataclass
class QuotaExceededError(
StructHTTPError[QuotaBody],
status=429,
description="Quota exceeded",
consts={"error_code": "quota-exceeded"},
templates={"error_message": "Limit is {limit} requests per {window}"},
status_field="status_code",
):
limit: int
window: str
raise QuotaExceededError(limit=100, window="minute")
# 429 {"errorCode": "quota-exceeded",
# "errorMessage": "Limit is 100 requests per minute", "statusCode": 429}
description is the OpenAPI response description (explicit — docstrings are never
published). The wire model — B's shape with const-fed fields narrowed to
Literal types — is composed once here at class creation; the request path is
construct-and-encode, and nothing you pass is ever mutated.
Source code in jero/errors.py
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 | |
body
property
¶
This occurrence as your body type — every field populated (consts, status,
templates, params), typed as B for code that inspects the error.
response_body
property
¶
Compose the wire body fresh: params, rendered templates, and the nested params
Struct; consts and the status arrive through the wire model's Literal
defaults. Nothing is ever mutated.
_variable_values()
¶
The variable fields' values: same-named params, rendered templates, and the
nested params Struct when params_field is declared.
Source code in jero/errors.py
690 691 692 693 694 695 696 697 698 699 | |
Bases: Struct
The wire representation of a static API error.
Source code in jero/errors.py
26 27 28 29 30 31 32 | |
Bases: Problem
The wire representation of an API error with occurrence-specific context.
Source code in jero/errors.py
35 36 37 38 39 | |
Bases: ABC
App-wide renderer for the Problem family: compose your own wire body from any
HTTPError — the framework built-ins included — registered via
BaseApp._include_error_adapter. StructHTTPError\ s render themselves, so every
error has exactly one renderer.
status_field optionally names a field the framework adds (typed to the exact
status) when composing the wire body; it must not exist on the body Struct, and the
Struct compose returns is never mutated. Keep compose pure — it receives only
the error; request-correlated data belongs in exception handlers, not here.
Source code in jero/errors.py
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 | |
_wire_model_for(status)
¶
The model encoded/documented for status: the body Struct itself, or — when
status_field is declared — the composed per-status model, built once and cached.
Source code in jero/errors.py
750 751 752 753 754 755 756 757 758 759 760 | |
compose(error)
abstractmethod
¶
Build your body from a Problem-family error (its type/title/status,
and str(error) for the human message — the rendered detail when parameterized).
Source code in jero/errors.py
745 746 747 748 | |
compose_wire(error)
¶
compose plus the declared status splice, mutating nothing. Un-underscored:
core calls it across the module boundary when rendering a Problem-family error.
Source code in jero/errors.py
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 | |
docs_model(status)
¶
The wire model documented for errors of status — what the OpenAPI build
references for derived error responses once this adapter is registered.
Source code in jero/errors.py
781 782 783 784 | |
Bases: Struct
Occurrence detail for the framework's decode/bind errors: the underlying msgspec
(or multipart) message, so a 400/422 says what actually failed rather than nothing.
The message is human-readable and names fields/paths, never submitted values — read
type/status as the machine contract, not this string.
Source code in jero/errors.py
805 806 807 808 809 810 811 | |
A typed JSON response returned by a custom exception handler.
Unlike a normal response wrapper, status_code is required: exception handling
has no operation-derived success status to fall back to. cookies works exactly
as on a normal response — an auth failure that must also expire a stale session
cookie is a real case.
Source code in jero/_exception_handlers.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | |
Shipped errors¶
Bases: HTTPError
Authentication credentials are absent or invalid.
Source code in jero/errors.py
834 835 836 837 838 839 840 | |
Bases: HTTPError
The request conflicts with the current state of the resource.
Source code in jero/errors.py
861 862 863 864 865 866 867 | |
Bases: HTTPError
The caller is authenticated but not allowed to perform this operation.
Source code in jero/errors.py
852 853 854 855 856 857 858 | |
Bases: HTTPError
The resource existed but has been permanently removed.
Source code in jero/errors.py
870 871 872 873 874 875 876 | |
Bases: HTTPError
An unexpected server-side failure whose internals are not exposed.
Source code in jero/errors.py
888 889 890 891 892 893 894 | |
Bases: ParameterizedHTTPError[ErrorReason]
The request cannot be parsed or bound.
Source code in jero/errors.py
814 815 816 817 818 819 820 821 | |
Bases: HTTPError
The path exists but does not support the requested method.
Source code in jero/errors.py
796 797 798 799 800 801 802 | |
Bases: HTTPError
No route or resource matches the requested path.
Source code in jero/errors.py
787 788 789 790 791 792 793 | |
Bases: HTTPError
The caller has exceeded a rate limit.
Source code in jero/errors.py
879 880 881 882 883 884 885 | |
Bases: HTTPError
The request body does not use the media type required by the operation.
Source code in jero/errors.py
843 844 845 846 847 848 849 | |
Bases: ParameterizedHTTPError[ErrorReason]
The request is syntactically valid but does not match its typed contract.
Source code in jero/errors.py
824 825 826 827 828 829 830 831 | |
Authentication¶
Bases: Protocol
Implement authenticate; raise an HTTPError subclass to reject.
headers is bound from the request headers into your declared
Struct (header names map x-trace-id -> x_trace_id). The
returned Struct is what handlers receive as user.
The return type is the route's auth policy. Declaring -> TUser gates the routes
it is mounted on: a caller without valid credentials never reaches a handler. Declaring
-> TUser | None makes credentials an input instead — returning None reports
that the caller presented none, and the handler is invoked with user=None. Raising
is rejection in both cases, so invalid credentials are always a 401. Handlers must
match: user: TUser against the first, user: TUser | None against the second,
checked at startup.
An app that wants both usually defines two authenticators over one shared resolution
step (TokenAuth / OptionalTokenAuth), so which policy a route gets is visible in
what its mount passes.
authenticate only sees credentials your THeaders Struct can bind: a Struct
whose fields are all required makes a credential-less request a 401 before your code
runs. Give the field a | None default (authorization: str | None = None) to
have absence reach authenticate and become your decision — the same rule applies
to a :class:CookieAuth/:class:HybridAuth cookie field.
Source code in jero/core.py
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 | |
authenticate(headers)
¶
Validate headers and return the user Struct; raise HTTPError to reject.
Return None only to report that no credentials were presented (see the class
docstring); it is never a way to say "these credentials are bad".
Source code in jero/core.py
545 546 547 548 549 550 551 | |
Bases: Protocol
Implement authenticate; raise an HTTPError subclass to reject.
Session-cookie auth: cookies binds from the request's Cookie header into your
declared Struct, verbatim and case-sensitively (no snake_case mangle, unlike
headers — see the cookies binding source). Otherwise identical to
:class:Auth: the return type is the route's auth policy (-> TUser gates,
-> TUser | None accepts anonymous callers), and both apply on a WebSocket
handshake — the motivating case, since a browser's WebSocket API cannot set an
Authorization header but always sends cookies.
Source code in jero/core.py
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 | |
authenticate(cookies)
¶
Validate cookies and return the user Struct; raise HTTPError to reject.
Return None only to report that no credentials were presented (see the class
docstring); it is never a way to say "these credentials are bad".
Source code in jero/core.py
566 567 568 569 570 571 572 | |
Bases: Protocol
Implement authenticate; raise an HTTPError subclass to reject.
Both headers and cookies bind — one app serving bearer-token API clients and
cookie-authenticated browser clients on the same routes. Otherwise identical to
:class:Auth: the return type is the route's auth policy.
Source code in jero/core.py
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 | |
authenticate(headers, cookies)
¶
Validate headers/cookies and return the user Struct; raise to reject.
Return None only to report that no credentials were presented (see the class
docstring); it is never a way to say "these credentials are bad".
Source code in jero/core.py
583 584 585 586 587 588 589 590 591 | |
Bases: Auth[THeaders, TUser]
An Auth whose operations advertise HTTP bearer in the OpenAPI spec.
Sugar over the openapi_security attribute the spec generator reads — subclass
this instead of writing the attribute by hand. Implement authenticate as usual.
Source code in jero/core.py
594 595 596 597 598 599 600 601 | |
Bases: Auth[THeaders, TUser]
An Auth whose operations advertise HTTP basic in the OpenAPI spec.
Source code in jero/core.py
604 605 606 607 | |
Bases: Struct
One OpenAPI security scheme, declared on an authenticator via the
openapi_security class attribute (the :class:~jero.BearerAuth /
:class:~jero.BasicAuth bases set it for you).
Build one with a constructor rather than by hand. scheme_name is the key the
scheme is registered under in components.securitySchemes and referenced by each
operation's security. Note that a bearer token carried in a cookie is not an
http/bearer scheme (that is the Authorization header specifically) — it
is api_key(location="cookie").
Source code in jero/openapi.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
api_key(*, name, location, description=None, scheme_name='apiKeyAuth')
classmethod
¶
A token carried in a named header, query param, or cookie.
Source code in jero/openapi.py
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
http_basic(*, description=None, scheme_name='basicAuth')
classmethod
¶
An Authorization: Basic <credentials> scheme.
Source code in jero/openapi.py
66 67 68 69 70 71 | |
http_bearer(*, bearer_format=None, description=None, scheme_name='bearerAuth')
classmethod
¶
An Authorization: Bearer <token> scheme (the authed-route default).
Source code in jero/openapi.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | |
to_openapi()
¶
Render the OpenAPI securitySchemes entry for this scheme.
Source code in jero/openapi.py
91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
Middleware & CORS¶
Bases: Struct
One cross-origin resource sharing policy.
Register an app-wide default with _include_cors(CORS(...)); override it per
include with the cors= keyword on _include_resource / _include_endpoint,
or opt a route out with cors=CORS.OFF. An include that passes nothing inherits
the app default; an app that never calls _include_cors serves no CORS headers
at all (pure opt-in).
allow_origins="*" compiles to constant header pairs in every covered route's
response — free at request time. An explicit origin tuple compiles to an origin
echo whose verdict is memoized per origin (and a Vary: Origin pair) instead.
Combining an
origin allow-list with allow_credentials=True echoes the origin with
Access-Control-Allow-Credentials: true; combining credentials with "*" is
spec-forbidden and fails wiring loud.
Source code in jero/_middleware.py
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | |
Background tasks¶
A queue of fire-and-forget background work, dispatched by item type.
Register one handler per Struct type (inferred from the handler's parameter);
endpoints call :meth:add to enqueue an item, and a single serial worker dispatches
each to its handler. Build it with self._create_background_tasks(...) inside
wire (sugar for await self._aenter(BackgroundTasks(...))) so the worker
starts at startup and drains/stops at shutdown.
Create it after the resources its handlers use, so reverse-order shutdown drains the queue before those resources are torn down.
Source code in jero/background.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | |
_run()
async
¶
Pull items forever, dispatching each to its handler(s). Errors are isolated.
Source code in jero/background.py
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
add(item)
async
¶
Enqueue an item for background processing (awaits if the queue is full).
Source code in jero/background.py
105 106 107 | |
register(handler)
¶
Register a handler; its item type is inferred from its single parameter.
One handler per type by default — a second for the same type is a WiringError
unless this was built with allow_one_to_many=True.
Source code in jero/background.py
91 92 93 94 95 96 97 98 99 100 101 102 103 | |
Models & codecs¶
Bases: Struct
jero's Struct base. Use it instead of msgspec.Struct so a model may carry
an OpenAPI description via the meta= class keyword:
class Widget(Struct, meta=ModelMeta(description="A widget.")): ...
The meta is read by the OpenAPI generator (as __model_meta__); a wire field
named meta is unaffected (the class keyword and a field are different
namespaces).
Source code in jero/structs.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
Bases: Struct
OpenAPI metadata for a model (a wire Struct), attached via the meta= class
keyword of :class:~jero.Struct.
description becomes the model's schema description — explicit, never inferred from
the class docstring. name overrides the key the model gets under
components.schemas (and every $ref that points at it); use it to give a model a
stable public name or to disambiguate two same-named Structs that would otherwise
collide.
Source code in jero/openapi.py
125 126 127 128 129 130 131 132 133 134 135 136 137 | |
OpenAPI¶
Bases: Struct
A typed subset of the Scalar docs-UI configuration, passed to
:meth:~jero.BaseApp._include_openapi as scalar_config and forwarded to the viewer as
its data-configuration.
Only the commonly-useful, verified options are modeled — jero blesses a subset rather
than mirroring Scalar's whole (evolving) surface; for anything not here, supply a full
docs_html page instead. Unset fields are omitted from the wire, so Scalar's own
defaults apply untouched.
Source code in jero/openapi.py
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | |
Testing¶
Synchronous in-process client. Prefer with TestClient(app) as c.
cookie_jar=True opts into automatic cookie persistence across requests (off by
default — every request then sends only what you explicitly pass). When on, each
response's Set-Cookie values are stored in client.cookie_jar (a plain,
directly inspectable and mutable dict[str, str]) and attached to subsequent
requests and WebSocket handshakes; an expiring Set-Cookie (Max-Age=0 or a
past Expires) removes its entry. Per-request cookies= merges over the jar
(explicit wins on name collisions). The jar is name -> value only, with no
path/domain scoping — the harness is single-origin and in-process, so RFC 6265
scoping rules would be dead code here.
Source code in jero/testing.py
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 | |
_apply_response_cookies(multi_headers)
¶
Fold a response's Set-Cookie headers into the jar, when enabled: store a
live cookie, drop one that expires itself (Max-Age=0 or a past Expires).
Source code in jero/testing.py
514 515 516 517 518 519 520 521 522 523 524 525 526 | |
_merge_cookies(headers, cookies)
staticmethod
¶
headers with cookies folded in as one Cookie header. Passing both
cookies= and an explicit Cookie entry in headers= is ambiguous.
Source code in jero/testing.py
487 488 489 490 491 492 493 494 495 496 497 498 499 | |
_outgoing_cookies(headers, cookies)
¶
cookies merged over the jar (explicit wins on a name collision), when the
jar is enabled. Skipped when headers already carries an explicit Cookie
entry: that escape hatch must not silently gain jar cookies, and the ambiguity
check in :meth:_merge_cookies must fire only when the caller actually passed
both cookies= and a header — not merely because the jar happens to hold
something.
Source code in jero/testing.py
501 502 503 504 505 506 507 508 509 510 511 512 | |
close()
¶
Run the app's lifespan shutdown and stop the background loop.
Source code in jero/testing.py
1029 1030 1031 1032 | |
delete(path, *, params=None, headers=None, cookies=None)
¶
Issue a DELETE request.
Source code in jero/testing.py
857 858 859 860 861 862 863 864 865 866 | |
get(path, *, params=None, headers=None, cookies=None)
¶
Issue a GET request.
Source code in jero/testing.py
813 814 815 816 817 818 819 820 821 822 | |
head(path, *, params=None, headers=None, cookies=None)
¶
Issue a HEAD request.
Source code in jero/testing.py
835 836 837 838 839 840 841 842 843 844 | |
options(path, *, params=None, headers=None, cookies=None)
¶
Issue an OPTIONS request.
Source code in jero/testing.py
846 847 848 849 850 851 852 853 854 855 | |
patch(path, *, json=None, content=None, data=None, files=None, params=None, headers=None, cookies=None)
¶
Issue a PATCH request (JSON, raw bytes, or multipart form).
Source code in jero/testing.py
979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 | |
post(path, *, json=None, content=None, data=None, files=None, params=None, headers=None, cookies=None)
¶
Issue a POST request (JSON, raw bytes, or multipart form).
Source code in jero/testing.py
879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 | |
put(path, *, json=None, content=None, data=None, files=None, params=None, headers=None, cookies=None)
¶
Issue a PUT request (JSON, raw bytes, or multipart form).
Source code in jero/testing.py
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 | |
request(method, path, *, params=None, json=None, content=None, data=None, files=None, headers=None, cookies=None)
¶
Issue a request and return the buffered response.
Source code in jero/testing.py
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 | |
stream_delete(path, *, params=None, headers=None, cookies=None)
¶
Open a streaming DELETE request.
Source code in jero/testing.py
868 869 870 871 872 873 874 875 876 877 | |
stream_get(path, *, params=None, headers=None, cookies=None)
¶
Open a streaming GET request.
Source code in jero/testing.py
824 825 826 827 828 829 830 831 832 833 | |
stream_patch(path, *, json=None, content=None, data=None, files=None, params=None, headers=None, cookies=None)
¶
Open a streaming PATCH request.
Source code in jero/testing.py
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 | |
stream_post(path, *, json=None, content=None, data=None, files=None, params=None, headers=None, cookies=None)
¶
Open a streaming POST request.
Source code in jero/testing.py
904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 | |
stream_put(path, *, json=None, content=None, data=None, files=None, params=None, headers=None, cookies=None)
¶
Open a streaming PUT request.
Source code in jero/testing.py
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 | |
stream_request(method, path, *, params=None, json=None, content=None, data=None, files=None, headers=None, cookies=None)
¶
Issue a request and return a streaming session for its chunks.
Source code in jero/testing.py
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 | |
websocket(path, *, inbound, outbound, params=None, headers=None, cookies=None, denial_response_extension=True)
¶
Open a typed in-process WebSocket connection.
Source code in jero/testing.py
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 | |
A captured HTTP response: status code, headers, and body bytes.
Source code in jero/testing.py
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | |
cookies
property
¶
The response's Set-Cookie headers, parsed and keyed by cookie name.
text
property
¶
The response body decoded as UTF-8 text.
json()
¶
The response body decoded as JSON.
Source code in jero/testing.py
137 138 139 | |
One parsed Set-Cookie response header. Attribute names parse
case-insensitively; expires is the raw wire value, unparsed.
Source code in jero/testing.py
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
One decoded Server-Sent Event captured from a streaming response.
Source code in jero/testing.py
151 152 153 154 155 156 157 158 159 160 | |
Build a factory in isolation and exercise its create_* methods from sync tests.
The factory-level sibling of :class:TestClient, and a thin sync bridge over
:meth:BaseFactory.open — lifecycle has that one code path. The harness enters
Factory.open() on a background loop, so services are built — and their
resources opened and torn down — exactly as under a live app, but drivable from
synchronous test code (matching the sync TestClient). Use it to test the
real factory wiring that an app's factory= seam mocks away; in async code
(scripts, cron jobs, notebooks) use async with Factory.open() directly.
with FactoryHarness(Factory) as harness:
service = harness.run(harness.factory.create_widget_service())
assert isinstance(service, WidgetService)
# everything opened on the stacks is closed here
Synchronous create_* methods can be called directly on harness.factory;
run awaits the async ones on the harness's loop.
Source code in jero/testing.py
1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 | |
close()
¶
Exit Factory.open() — closing everything the factory opened — then stop
the loop.
Source code in jero/testing.py
1072 1073 1074 1075 1076 | |
run(coro)
¶
Await an async create_* coroutine on the harness's loop.
Source code in jero/testing.py
1068 1069 1070 | |