API reference¶
Generated from the source. See Usage for a narrative introduction.
Repository¶
Bases: Generic[Model]
Repository over a model, bound to a database or cluster.
The database resolves to a :meth:using override if given, otherwise to
the process-wide default built from DATABASE_* settings -- so in the
common single-database case no binding is needed at all. Repositories
accessed through a unit of work are bound to the unit's database.
__init__ deliberately takes no arguments so subclasses work directly
as FastAPI dependencies (Depends(UserRepository)) without leaking
routing knobs as query parameters.
Source code in sqlargon/repository.py
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 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 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 223 224 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 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 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 441 442 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 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 | |
bulk_update(values: MultipleValues, *args: Any, on_: set[str] | None = None, **kwargs: Any) -> None
async
¶
Update many rows in a single executemany statement.
An executemany cannot return rows; use :meth:update_many when the
updated models are needed.
Source code in sqlargon/repository.py
using(hint: str | None = None, *, db: AnyDatabase | None = None, read_only: bool | None = None, shard_key: Any | None = None) -> Self
¶
Return a copy of this repository with a routing preference baked in.
The returned repository routes its statements to the given database, replica or shard, unless a transaction already pins another one::
await repo.using("replica_0").all()
await repo.using(read_only=True).count()
await repo.using(shard_key=tenant_id).create(**values)
await repo.using(db=other_database).all()
read_only=False explicitly forces the primary even when the
repository inherited read_only=True; None keeps it.
Source code in sqlargon/repository.py
Bases: SQLAlchemyRepository[SoftDeleteModel]
Repository that tombstones rows instead of deleting them.
Every statement is scoped to live rows: selects and :meth:count skip
tombstoned rows, :meth:update refuses to touch them, and :meth:delete
is rewritten into an update raising the flag -- so remove,
delete_one and delete_many all soft delete::
class User(UUIDModelMixin, SoftDeleteBase): ...
class UserRepository(SoftDeleteRepository[User]): ...
users = UserRepository()
await users.remove(User.id == user_id) # UPDATE ... SET tombstone = true
await users.list() # the row is gone from reads
await users.restore(User.id == user_id) # and back again
The flag belongs to :meth:delete and :meth:restore alone: it is left
out of the default ON CONFLICT DO UPDATE set, so an upsert cannot
silently resurrect a deleted row. Reach past the scope with
:meth:with_deleted, :meth:only_deleted and :meth:hard_delete.
The model type variable is bound to
:class:~sqlargon.mixins.SoftDeleteBase, so a type checker rejects a
model that cannot be soft deleted. At runtime the looser
:class:~sqlargon.mixins.SoftDeleteMixin is enough; anything else raises
TypeError on subclassing.
Source code in sqlargon/repository.py
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 | |
delete(*, return_results: bool = False) -> Self
¶
Raise the tombstone on the matched rows instead of removing them.
get_or_create(defaults: SingleValue | None = None, **kwargs: Any) -> SoftDeleteModel
async
¶
Get the live row matching kwargs or create it.
Raises :class:DeletedRowExistsError when the row exists but is
tombstoned: creating it would violate the unique key, and returning it
would resurrect a deleted row behind the caller's back.
Source code in sqlargon/repository.py
hard_delete(*args: _ColumnExpressionArgument[bool], **kwargs: Any) -> None
async
¶
Physically delete the matched rows, bypassing the tombstone.
Source code in sqlargon/repository.py
only_deleted() -> Self
¶
Return a copy scoped to tombstoned rows.
The scope holds for every statement the copy builds, so
only_deleted().count() counts the trash and
only_deleted().hard_delete() empties it.
Source code in sqlargon/repository.py
restore(*args: _ColumnExpressionArgument[bool], **kwargs: Any) -> Sequence[SoftDeleteModel]
async
¶
Clear the tombstone on the matched rows and return them.
Source code in sqlargon/repository.py
with_deleted() -> Self
¶
Return a copy whose statements cover tombstoned rows as well.
Run a repository method within a single transaction.
All queries issued by the decorated method share one session_context
on the repository's bound database, committed on success and rolled back
on error. Objects without a routing_context method (only the db
property) are still accepted; their statements route with ambient
:func:using markers alone.
Source code in sqlargon/functools.py
Unit of work¶
Bases: AbstractUnitOfWork
Unit of work over a single database or cluster.
The database is a :meth:using override if given, otherwise the
process-wide default built from DATABASE_* settings. Declared
repositories are bound to that database, so all work inside the unit
shares one transaction -- a unit of work never spans databases (there is
no two-phase commit).
__init__ deliberately takes no arguments so subclasses work directly
as FastAPI dependencies (Depends(OrdersUow)) without leaking routing
knobs as query parameters. Per-use routing (e.g. a shard) is chosen with
:meth:using; on a cluster the member database is resolved once on
__aenter__ and pinned for the whole transaction::
async with OrdersUow().using(shard_key=tenant_id) as uow:
await uow.orders.create(**values)
Source code in sqlargon/uow.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 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 | |
using(hint: str | None = None, *, db: AnyDatabase | None = None, read_only: bool | None = None, shard_key: Any | None = None) -> Self
¶
Return a fresh unit of work with a routing preference baked in.
The returned unit is not yet entered, so this is safe to call on a dependency-injected instance::
async with uow.using(shard_key=tenant_id):
...
Source code in sqlargon/uow.py
Databases¶
Bases: ABC
Shared interface of a single :class:Database and a
:class:~sqlargon.DatabaseCluster.
Everything built purely on sessions -- statement execution, named locks
and the atomic/with_lock/inject_session decorators -- lives
here, so code can accept either implementation interchangeably.
Source code in sqlargon/database.py
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 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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
atomic(fn: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]
¶
Run the decorated coroutine within a single transaction.
Source code in sqlargon/database.py
create_all() -> None
abstractmethod
async
¶
dispose() -> None
abstractmethod
async
¶
drop_all() -> None
abstractmethod
async
¶
execute(query: Executable | TypedReturnsRows, params: Params | None = None, **kwargs: Any) -> Result[Any]
async
¶
Execute a statement on the routed context-local session.
Source code in sqlargon/database.py
inject_session(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]
¶
Provide the context-local session as the session keyword
argument unless the caller already passed one.
Source code in sqlargon/database.py
lock(name: str) -> AsyncGenerator[None]
async
¶
Hold a named lock: a native (advisory) lock when the dialect supports one, a process-local asyncio lock otherwise.
Source code in sqlargon/database.py
native_lock(name: str) -> AsyncGenerator[None]
async
¶
Hold a database-native advisory lock for the duration of the block.
Source code in sqlargon/database.py
route(context: RoutingContext | None = None) -> Database
abstractmethod
¶
session(context: RoutingContext | None = None) -> AbstractAsyncContextManager[AsyncSession]
abstractmethod
¶
Open a fresh session, committed on success and rolled back on error.
session_context(context: RoutingContext | None = None) -> AbstractAsyncContextManager[AsyncSession]
abstractmethod
¶
Yield the context-local session, opening one if none is active.
verify_connection() -> None
abstractmethod
async
¶
with_lock(fn: Callable[P, Awaitable[R]] | None = None, *, key: str) -> Callable[P, Awaitable[R]] | Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]
¶
Run the decorated coroutine while holding the named :meth:lock.
Source code in sqlargon/database.py
Bases: BaseDatabase
A single (writable) database: one engine, sessions and query builder.
Routing policy lives outside this class -- to front replicas or shards,
compose Database objects into a :class:~sqlargon.DatabaseCluster.
Source code in sqlargon/database.py
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 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 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 | |
from_env(**kwargs: Any) -> Self
classmethod
¶
Build a single database from DATABASE_* settings.
To route across replicas, build a :class:~sqlargon.DatabaseCluster
with :meth:~sqlargon.DatabaseCluster.from_env instead.
Source code in sqlargon/database.py
route(context: RoutingContext | None = None) -> Database
¶
Return the database a statement should run against (always self).
Exists so a single database and a cluster expose the same interface.
session(context: RoutingContext | None = None) -> AsyncGenerator[AsyncSession]
async
¶
Open a fresh session, committed on success and rolled back on error.
context is accepted for interface parity with
:class:~sqlargon.DatabaseCluster and ignored.
Source code in sqlargon/database.py
session_context(context: RoutingContext | None = None) -> AsyncGenerator[AsyncSession]
async
¶
Yield the context-local session, opening one if none is active.
context is accepted for interface parity with
:class:~sqlargon.DatabaseCluster and ignored -- there is only one
database to route to.
The current session lives in a :class:~contextvars.ContextVar, so
it is task-local by construction; no lock is needed around the
get-or-open below.
Source code in sqlargon/database.py
Bases: Database
A read replica that rejects any write statement at the engine level.
Compiled DML and textual write statements are rejected client side (a best-effort early error); the server-side guard is authoritative: on PostgreSQL the connection is put into read-only transaction mode, on SQLite and MySQL/MariaDB every new connection is switched to a read-only session mode.
Source code in sqlargon/database.py
Bases: BaseDatabase
Named databases behind a routing policy, usable wherever a
:class:~sqlargon.Database is.
Repositories and units of work bind to the cluster; each statement is routed to a member database by, in order of precedence:
- the database pinned by an open transaction in the current context,
- an explicit hint (
using(...), repository binding, unit of work), - the :class:
~sqlargon.routing.Router,
A hint conflicting with the pinned database raises
:class:~sqlargon.RoutingError instead of silently splitting the
transaction. All members must share one SQL dialect, since queries are
built before they are routed.
Source code in sqlargon/cluster.py
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 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 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 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 | |
from_env(*, router: Router | None = None, **kwargs: Any) -> DatabaseCluster
classmethod
¶
Build a cluster from DATABASE_* settings.
Always returns a cluster: primary plus any DATABASE_READ_REPLICAS
(a single-primary cluster when none are configured, ready to grow).
Pass router to replace the primary/replica policy, e.g. with a
:class:~sqlargon.ShardRouter over the configured databases.
Source code in sqlargon/cluster.py
route(context: RoutingContext | None = None) -> Database
¶
Return the member database the given statement should run against.
Source code in sqlargon/cluster.py
session_context(context: RoutingContext | None = None) -> AsyncGenerator[AsyncSession]
async
¶
Yield a session on the routed database, pinning it for the context.
While the session is open, every statement in the same async context routes to the same database, so a unit of work never straddles two databases.
Source code in sqlargon/cluster.py
using(hint: str | None = None, *, read_only: bool | None = None, shard_key: Any | None = None) -> UsingContext
¶
Return a :func:~sqlargon.using marker validated against this cluster.
Source code in sqlargon/cluster.py
with_replicas(url: str, *, read_replicas: Sequence[ReplicaConfig] | None = None, auto_route: bool = False, replica_strategy: Literal['random', 'round_robin'] = 'random', **kwargs: Any) -> DatabaseCluster
classmethod
¶
Build a primary/replica cluster from connection URLs.
Each replica is either a URL (inheriting the primary's engine
options) or a mapping with a url key plus per-replica engine
option overrides.
Source code in sqlargon/cluster.py
Return the default database, building it from DATABASE_* settings
on first use.
The built default is a plain :class:~sqlargon.Database, or a
primary/replica :class:~sqlargon.DatabaseCluster when
DATABASE_READ_REPLICAS is configured. Building is guarded by a lock,
so concurrent first calls share one instance instead of leaking engines.
Source code in sqlargon/registry.py
Set the process-wide default database (or clear it with None).
Repositories and units of work that are not explicitly bound to a database fall back to this one.
Returns the previously set database (if any) so the caller can
dispose() it once it is no longer used -- replacing the default does
not close the old engine's connection pool.
Source code in sqlargon/registry.py
Routing¶
Bases: Protocol
Routing policy: pick a database for a statement.
Routers are pure policy objects -- they never own engines and are only consulted when no transaction is pinned and no explicit hint is set.
Source code in sqlargon/routing.py
Everything a :class:Router may consult to pick a database.
operation is derived from the statement type (writes are any DML),
read_only records an explicit user request for a read replica, and
hint/shard_key carry explicit user choices made via
:func:using, repository binding or unit-of-work arguments.
Source code in sqlargon/routing.py
create(statement: Any | None = None, model: type[Base] | None = None, *, read_only: bool | None = None, hint: str | None = None, shard_key: Any | None = None) -> RoutingContext
classmethod
¶
Build a context, merging explicit arguments with :func:using markers.
Explicit arguments win over ambient context variables: read_only
is tri-state, so an explicit False overrides an ambient
using(read_only=True) scope while None inherits it. The
operation defaults to write for statements that cannot be proven
read-only, so unknown statements never leak onto a replica.
Source code in sqlargon/routing.py
Explicit, ambient routing preferences (as opposed to the per-statement
:class:RoutingContext, which is derived fresh for every execution).
Carried by repository instances and by the context-local :func:using
marker. Immutable so copies can share it safely; derive a changed variant
with :meth:merge.
Source code in sqlargon/routing.py
context(statement: Any | None = None, model: type[Base] | None = None, *, read_only: bool | None = None) -> RoutingContext
¶
Build the per-statement :class:RoutingContext for these options.
Source code in sqlargon/routing.py
merge(hint: str | None = None, *, db: Database | DatabaseCluster | None = None, read_only: bool | None = None, shard_key: Any | None = None) -> RoutingOptions
¶
Return a copy with the given preferences overriding unset ones.
read_only is tri-state: None keeps the inherited value, while
an explicit False overrides it, so a primary read can be forced
from within a read_only=True scope.
Source code in sqlargon/routing.py
Mark statements in the wrapped scope with a routing preference.
Use it as a context manager (plain with -- the marker is a context
variable, so it composes with async code) or as a decorator for async
functions::
with using("replica_eu"):
users = await repo.all()
@using(read_only=True)
async def report() -> None: ...
Source code in sqlargon/routing.py
Routing marker returned by :func:using.
A synchronous context manager (safe inside async code -- the marker is a context variable) and a decorator for async functions. Not reentrant and not safe to share between concurrently running tasks; create one per scope (the decorator form does this automatically on every call).
Source code in sqlargon/routing.py
Build a dependency applying :func:using for the span of a request.
Designed for DI frameworks with generator dependencies (e.g. FastAPI): the returned zero-argument async generator sets the routing marker before the endpoint runs and resets it afterwards, so every repository or unit of work resolved in the same request routes accordingly::
@app.get("/users", dependencies=[Depends(use_context("replica_0"))])
async def list_users(repo: UserRepository = Depends()) -> list[UserOut]:
return await repo.all()
Source code in sqlargon/routing.py
Route reads issued within fn to a read replica when one is available.
Use it to opt a repository method into replica routing without enabling
auto_route globally::
class UserRepository(SQLAlchemyRepository[User]):
@read_only
async def active(self) -> Sequence[User]:
return await self.filter(is_active=True).all()
Source code in sqlargon/routing.py
Writes go to the primary, reads may go to a replica.
auto_route diverts SELECT statements to replicas; an explicit
read_only request always forces a replica. With sticky_reads
(the default) reads issued after a write in the same async context stay
on the primary so the caller can read its own writes; disable it for
long-lived worker tasks where the flag would never reset.
Source code in sqlargon/routing.py
Route by model: vertical partitioning of tables across databases.
Resolution order: the explicit mapping (keyed by model class or table
name), then the model's __database__ attribute, then default.
Source code in sqlargon/routing.py
Route by shard key: horizontal partitioning across databases.
The shard key comes from the routing context (set explicitly or via
using(shard_key=...)); shards maps it to a database name, either
as a mapping or as a callable (e.g. hash-mod). A missing key falls back
to default when given, otherwise raises :class:RoutingError --
sharded writes without a key are a caller bug, not a soft default.
Source code in sqlargon/routing.py
Query builder¶
Source code in sqlargon/query_builder.py
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 105 106 107 108 109 110 111 112 113 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 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 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 223 224 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 | |
Bases: QueryBuilderError
Source code in sqlargon/query_builder.py
Pagination¶
Bases: ABC
Immutable pagination configuration, attached to a repository class.
Concrete strategies act as descriptors: accessed on a repository instance
they return a :class:Paginator bound to it, typed with the repository's
model, so a repository declares its pagination once::
class UserRepository(SQLAlchemyRepository[User]):
paginate = PageNumberPagination(default_page_size=25)
page = await UserRepository().paginate(page=2) # NumberedPage[User]
Paging inputs are not validated; constrain them at the API boundary, e.g. with pydantic-typed FastAPI parameters.
Source code in sqlargon/pagination/abc.py
Bases: Generic[ModelT]
A pagination strategy bound to a repository instance.
Concrete paginators are awaitable callables returning a page and expose
a pages() async iterator walking consecutive pages until exhaustion.
Source code in sqlargon/pagination/abc.py
Bases: Protocol[ModelT]
Structural view of a repository, as required by pagination strategies.
Source code in sqlargon/pagination/abc.py
Bases: PaginationStrategy
Page-number pagination: ?page=3&page_size=25.
Page numbers are 1-based. The returned :class:NumberedPage carries
has_more detected by over-fetching a single row, so no COUNT query
is issued.
Source code in sqlargon/pagination/page.py
bind(source: SupportsPagination[ModelT]) -> PageNumberPaginator[ModelT]
¶
Bind this strategy to a repository, returning a callable paginator.
Bases: PaginationStrategy
Page-number pagination that also reports total rows and pages.
Runs an additional COUNT query in the same session as the page query and
returns a :class:TotalNumberedPage.
Source code in sqlargon/pagination/page.py
bind(source: SupportsPagination[ModelT]) -> TotalPageNumberPaginator[ModelT]
¶
Bind this strategy to a repository, returning a callable paginator.
Bases: PaginationStrategy
Offset/limit pagination: ?offset=200&limit=100.
Suited for background processing and internal APIs. The returned
:class:OffsetPage carries has_more detected by over-fetching a
single row, so no COUNT query is issued.
Source code in sqlargon/pagination/offset_limit.py
bind(source: SupportsPagination[ModelT]) -> LimitOffsetPaginator[ModelT]
¶
Bind this strategy to a repository, returning a callable paginator.
Bases: PaginationStrategy
Offset/limit pagination that also reports the total number of rows.
Runs an additional COUNT query in the same session as the page query and
returns a :class:TotalOffsetPage.
Source code in sqlargon/pagination/offset_limit.py
bind(source: SupportsPagination[ModelT]) -> TotalLimitOffsetPaginator[ModelT]
¶
Bind this strategy to a repository, returning a callable paginator.
Bases: PaginationStrategy
Keyset (cursor) pagination for large datasets: ?cursor=<token>.
Cursors are opaque bookmarks produced by sqlakeyset; paging is stable
under concurrent inserts and cheap on large offsets. The unique
option does not apply to this strategy.
Source code in sqlargon/pagination/cursor.py
bind(source: SupportsPagination[ModelT]) -> CursorPaginator[ModelT]
¶
Bind this strategy to a repository, returning a callable paginator.
CursorPage
dataclass
¶
Bases: Page[T]
Page of results addressed by opaque keyset cursors.
Source code in sqlargon/pagination/models.py
NumberedPage
dataclass
¶
Bases: Page[T]
Page of results addressed by a 1-based page number.
Source code in sqlargon/pagination/models.py
OffsetPage
dataclass
¶
Page
dataclass
¶
Bases: Generic[T]
Base page shape shared by all pagination strategies.
Source code in sqlargon/pagination/models.py
TotalNumberedPage
dataclass
¶
Bases: NumberedPage[T]
Numbered page including the total number of matching rows and pages.
Source code in sqlargon/pagination/models.py
TotalOffsetPage
dataclass
¶
Cron¶
Database-backed cron scheduler for a single namespace.
Declarative mode: decorate functions with :meth:task and call
:meth:run (or :meth:sync); declared tasks are created or updated and
tasks no longer declared are deleted. Imperative mode: manage schedules
at runtime with :meth:schedule and :meth:unschedule.
Multiple instances may run the same namespace concurrently; due tasks
are claimed with FOR UPDATE SKIP LOCKED so each run executes once.
Source code in sqlargon/cron/manager.py
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 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 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 223 224 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 | |
limiter: CapacityLimiter
property
¶
The bound on concurrent executions.
Built on first execution rather than in __init__, so a scheduler
can be constructed at import time, before an event loop exists.
pause(func: TaskFunc | str) -> None
async
¶
register(func: TaskFunc, *, name: str | None = None) -> str
¶
Make func executable by this scheduler and return its name.
Sync functions are wrapped once here to run in a worker thread. A name registered again keeps the schedule it was declared with.
Source code in sqlargon/cron/manager.py
resume(func: TaskFunc | str) -> None
async
¶
run(*, task_status: TaskStatus[None] = TASK_STATUS_IGNORED) -> None
async
¶
Sync declarative tasks, then poll and execute due ones until cancelled.
Polling errors are logged and retried on the next tick, so a database blip never takes the scheduler down.
Source code in sqlargon/cron/manager.py
running() -> AsyncGenerator[Cron]
async
¶
Run the scheduler in the background, e.g. in an ASGI lifespan.
Source code in sqlargon/cron/manager.py
schedule(func: TaskFunc | str, schedule: str, *args: Any, name: str | None = None, **kwargs: Any) -> CronTask
async
¶
Create or update a task at runtime.
func may be a callable (registered automatically) or the name of
a function registered on another instance of this namespace. Extra
args and kwargs must be JSON-serializable; they are stored
with the task and passed to the function on execution.
Source code in sqlargon/cron/manager.py
sync() -> None
async
¶
Reconcile declarative tasks with the database: create missing ones, update changed schedules and delete undeclared ones.
A declared name already held by an imperative row takes that row over rather than leaving the declaration unapplied.
Source code in sqlargon/cron/manager.py
task(schedule: str | None = None, *, name: str | None = None) -> Callable[[F], F]
¶
Register the decorated function; with schedule it becomes a
declarative task reconciled by :meth:sync.
Source code in sqlargon/cron/manager.py
tasks() -> Sequence[CronTask]
async
¶
unschedule(func: TaskFunc | str) -> None
async
¶
Bases: UUIDModelMixin, CreatedUpdatedMixin, Base
A scheduled task persisted per namespace.
Source code in sqlargon/cron/models.py
Bases: SQLAlchemyRepository[CronTask]
Repository for :class:CronTask rows.
Source code in sqlargon/cron/repository.py
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 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 128 129 130 131 132 133 | |
claim_due(namespace: str, now: datetime, *, names: Collection[str] | None = None, limit: int = 100) -> Sequence[CronTask]
async
¶
Claim tasks due at now and return them.
Due rows are locked with FOR UPDATE SKIP LOCKED and their
next_run_at advanced within the same transaction, so concurrent
instances polling the same namespace never claim the same run.
With names, only those tasks are claimed -- a caller that cannot
execute a task leaves its run to an instance that can, instead of
consuming the slot and dropping it.
Source code in sqlargon/cron/repository.py
reconcile(namespace: str, schedules: Mapping[str, str], now: datetime) -> None
async
¶
Make the declarative tasks of namespace exactly schedules.
The declared tasks are upserted -- creating the missing ones and taking over any row already holding their name -- and one delete removes the declarative rows no longer declared. Rows scheduled imperatively are left alone.
next_run_at is only recomputed for a task whose schedule changed,
so a run that came due while the scheduler was down still fires.
Source code in sqlargon/cron/repository.py
Return schedule unchanged, raising ValueError if it is not a
valid cron expression.
Source code in sqlargon/cron/utils.py
ORM and types¶
Bases: DeclarativeBase
Source code in sqlargon/orm.py
__tablename__() -> str
¶
By default, turn the model's camel-case class name
into a snake-case table name. Override by providing
an explicit __tablename__ class property.
Source code in sqlargon/orm.py
GUID
¶
Bases: TypeDecorator
Platform-independent UUID type.
Uses PostgreSQL's UUID type, otherwise uses CHAR(36), storing as stringified hex values with hyphens.
Source code in sqlargon/types/uuid.py
Timestamp
¶
Bases: TypeDecorator
TypeDecorator that ensures that timestamps have a timezone.
For SQLite, all timestamps are converted to UTC (since they are stored as naive timestamps without timezones) and recovered as UTC.
Source code in sqlargon/types/datetime.py
now
¶
Bases: FunctionElement
Platform-independent "now" generator.
Source code in sqlargon/types/datetime.py
Bases: TypeDecorator
JSON type that returns SQLAlchemy's dialect-specific JSON types, where possible. Uses generic JSON otherwise.
The "base" type is postgresql.JSONB to expose useful methods prior to SQL compilation
Source code in sqlargon/types/json.py
Settings¶
Bases: BaseSettings