xtr-orm 5.2.1__tar.gz → 6.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/PKG-INFO +37 -5
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/README.md +35 -3
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/pyproject.toml +13 -7
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/pyproject.toml.orig +10 -7
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/.agents/skills/xtr-orm/SKILL.md +17 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/bundle/orm_bundle.py +101 -6
- xtr_orm-6.0.1/src/xtr_orm/messenger_transport.py +24 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/LICENSE +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/.agents/skills/xtr-orm/references/commands.md +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/.agents/skills/xtr-orm/references/connections.md +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/.agents/skills/xtr-orm/references/messenger.md +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/.agents/skills/xtr-orm/references/migrations.md +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/__init__.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/bundle/__init__.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/bundle/connection_config.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/bundle/orm_config.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/bundle/orm_mappings_pass.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/__init__.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/_reporting.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/connection_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/database_create_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/database_drop_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/migrations_current_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/migrations_diff_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/migrations_dump_schema_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/migrations_execute_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/migrations_generate_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/migrations_latest_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/migrations_list_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/migrations_migrate_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/migrations_rollup_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/migrations_status_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/migrations_up_to_date_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/migrations_version_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/command/run_sql_command.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/connection_registry.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/database/__init__.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/database/database_manager.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/exception/__init__.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/exception/invalid_argument_error.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/exception/migration_error.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/exception/missing_database_error.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/exception/orm_error.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/exception/session_unavailable_error.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/exception/unknown_connection_error.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/exception/unsupported_database_error.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/mapping_driver_interface.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/messenger/__init__.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/messenger/close_connection_middleware.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/messenger/open_transaction_logger_middleware.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/messenger/transaction_middleware.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/__init__.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/_environment.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/_history.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/_revisions.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/_schema.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/available_migration.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/direction.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/executed_migration.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/execution_result.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/migration_plan.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/migration_status.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/migrations_config.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/migrator.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/migrations/template/script.py.mako +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/py.typed +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/recipe/__init__.py +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/recipe/files/config/orm.py.tmpl +0 -0
- {xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/recipe/manifest.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xtr-orm
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0.1
|
|
4
4
|
Summary: Databases for async applications: engines, sessions and versioned schema migrations, configured once per connection.
|
|
5
5
|
Keywords: orm,database,migrations,sqlalchemy,alembic,asyncio
|
|
6
6
|
Author: Xterr
|
|
@@ -20,7 +20,7 @@ Requires-Dist: advanced-alchemy>=1.11
|
|
|
20
20
|
Requires-Dist: alembic>=1.20
|
|
21
21
|
Requires-Dist: sqlalchemy[asyncio]>=2.0.41
|
|
22
22
|
Requires-Dist: typing-extensions>=4.4
|
|
23
|
-
Requires-Dist: xtr-logging-contracts>=
|
|
23
|
+
Requires-Dist: xtr-logging-contracts>=6.0,<7
|
|
24
24
|
Requires-Dist: asyncmy>=0.2.9 ; extra == 'mysql'
|
|
25
25
|
Requires-Dist: asyncpg>=0.29 ; extra == 'postgres'
|
|
26
26
|
Requires-Dist: aiosqlite>=0.20 ; extra == 'sqlite'
|
|
@@ -75,6 +75,7 @@ uv add "xtr-orm[postgres]" # asyncpg
|
|
|
75
75
|
uv add "xtr-orm[postgres]" xtr-dependency-injection # + the container, for the OrmBundle
|
|
76
76
|
uv add xtr-console # beside it: the orm:* commands
|
|
77
77
|
uv add xtr-messenger # beside it: the message bus middleware
|
|
78
|
+
uv add "xtr-messenger[orm]" # beside it: the orm:// transport too
|
|
78
79
|
```
|
|
79
80
|
|
|
80
81
|
| Extra | Brings | For |
|
|
@@ -86,7 +87,8 @@ sessions, migrators from one configuration — is used when
|
|
|
86
87
|
[xtr-dependency-injection](../xtr-dependency-injection) is installed beside the package, and a
|
|
87
88
|
`Migrator` works without it, as the [Quick start](#quick-start) shows. Installed beside it too,
|
|
88
89
|
[xtr-console](../xtr-console) brings the `orm:*` commands and [xtr-messenger](../xtr-messenger)
|
|
89
|
-
the [message bus middleware](#message-bus-middleware)
|
|
90
|
+
the [message bus middleware](#message-bus-middleware); with
|
|
91
|
+
[xtr-orm-messenger](../xtr-orm-messenger) as well, the [`orm://` transport](#message-bus-transport).
|
|
90
92
|
|
|
91
93
|
## Quick start
|
|
92
94
|
|
|
@@ -201,7 +203,8 @@ removing it undoes.
|
|
|
201
203
|
- **Install** — `uv add "xtr-orm[postgres]" xtr-dependency-injection`: the bundle needs the
|
|
202
204
|
container installed beside it, never as an extra; `mysql` or `sqlite` instead of `postgres` for
|
|
203
205
|
those databases. Install xtr-console beside it for the `orm:*` commands, and xtr-messenger for
|
|
204
|
-
the [message bus middleware](#message-bus-middleware)
|
|
206
|
+
the [message bus middleware](#message-bus-middleware) — and xtr-orm-messenger, or
|
|
207
|
+
`xtr-messenger[orm]`, for the [`orm://` transport](#message-bus-transport).
|
|
205
208
|
- **Recipe** — `uv run xtr-recipes recipes:sync` does the *Activate*, *Configure* and
|
|
206
209
|
*Environment* steps below: it lists `OrmBundle`, writes a starting `<app>/config/packages/orm.py` with the
|
|
207
210
|
`default` connection, and `DATABASE_URL` in `.env`, and adds a PostgreSQL `database` service to
|
|
@@ -211,7 +214,8 @@ removing it undoes.
|
|
|
211
214
|
`xtr_orm.bundle`.
|
|
212
215
|
- **Brings along** — the logging and console bundles, when those packages are installed.
|
|
213
216
|
With the messenger bundle active too, the [middleware](#message-bus-middleware) are
|
|
214
|
-
registered for a bus to list
|
|
217
|
+
registered for a bus to list, and with xtr-orm-messenger installed the
|
|
218
|
+
[`orm://` transport](#message-bus-transport) too.
|
|
215
219
|
- **Configure** — the recipe writes a starting `<app>/config/packages/orm.py` with the `default`
|
|
216
220
|
connection reading `DATABASE_URL`, its revisions in `migrations` in the project directory and
|
|
217
221
|
diffs against the models advanced-alchemy registers. Add connections, replicas and table
|
|
@@ -532,6 +536,34 @@ Without a container, build them with a `ConnectionRegistry` whose connections ar
|
|
|
532
536
|
with a `session` — what returns the session the handlers use — and put the instances in the
|
|
533
537
|
configuration.
|
|
534
538
|
|
|
539
|
+
## Message bus transport
|
|
540
|
+
|
|
541
|
+
With the messenger bundle active and [xtr-orm-messenger](../xtr-orm-messenger) installed —
|
|
542
|
+
`uv add "xtr-messenger[orm]"` — a bus can keep its messages in a table on one of these
|
|
543
|
+
connections. There is no bundle to list: this one registers the transport factory with the
|
|
544
|
+
messenger, built on the connections, once it finds the package installed.
|
|
545
|
+
|
|
546
|
+
```python
|
|
547
|
+
# app/config/packages/messenger.py
|
|
548
|
+
MessageBusConfig(
|
|
549
|
+
transports={"jobs": TransportConfig("orm://default", queue="jobs")},
|
|
550
|
+
routing={IngestDocument: "jobs"},
|
|
551
|
+
)
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
- **The DSN names a connection.** `orm://default` is the `default` connection, `orm://` the
|
|
555
|
+
default one, whatever it is called; its URL stays in `OrmConfig`.
|
|
556
|
+
- **Migrations write the table.** Each connection's migrator adds the messages table of every
|
|
557
|
+
`orm://` transport on it to its table definitions, so `orm:migrations:diff` writes it like
|
|
558
|
+
one of the application's own tables. The bus configuration is read for that when a migrator
|
|
559
|
+
is first built, so the environment variables its DSNs name must be set then. A connection
|
|
560
|
+
keeping several sets of table definitions is refused, naming it.
|
|
561
|
+
- **Until the revision is applied**, the transport creates its table the first time it runs;
|
|
562
|
+
`auto_setup=false` on the DSN leaves it to the migrations alone.
|
|
563
|
+
|
|
564
|
+
Options, workers and notifications are the transport's: see
|
|
565
|
+
[xtr-orm-messenger](../xtr-orm-messenger).
|
|
566
|
+
|
|
535
567
|
## Creating and dropping databases
|
|
536
568
|
|
|
537
569
|
`DatabaseManager(url)` creates and drops the database a URL names. A database cannot be
|
|
@@ -43,6 +43,7 @@ uv add "xtr-orm[postgres]" # asyncpg
|
|
|
43
43
|
uv add "xtr-orm[postgres]" xtr-dependency-injection # + the container, for the OrmBundle
|
|
44
44
|
uv add xtr-console # beside it: the orm:* commands
|
|
45
45
|
uv add xtr-messenger # beside it: the message bus middleware
|
|
46
|
+
uv add "xtr-messenger[orm]" # beside it: the orm:// transport too
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
| Extra | Brings | For |
|
|
@@ -54,7 +55,8 @@ sessions, migrators from one configuration — is used when
|
|
|
54
55
|
[xtr-dependency-injection](../xtr-dependency-injection) is installed beside the package, and a
|
|
55
56
|
`Migrator` works without it, as the [Quick start](#quick-start) shows. Installed beside it too,
|
|
56
57
|
[xtr-console](../xtr-console) brings the `orm:*` commands and [xtr-messenger](../xtr-messenger)
|
|
57
|
-
the [message bus middleware](#message-bus-middleware)
|
|
58
|
+
the [message bus middleware](#message-bus-middleware); with
|
|
59
|
+
[xtr-orm-messenger](../xtr-orm-messenger) as well, the [`orm://` transport](#message-bus-transport).
|
|
58
60
|
|
|
59
61
|
## Quick start
|
|
60
62
|
|
|
@@ -169,7 +171,8 @@ removing it undoes.
|
|
|
169
171
|
- **Install** — `uv add "xtr-orm[postgres]" xtr-dependency-injection`: the bundle needs the
|
|
170
172
|
container installed beside it, never as an extra; `mysql` or `sqlite` instead of `postgres` for
|
|
171
173
|
those databases. Install xtr-console beside it for the `orm:*` commands, and xtr-messenger for
|
|
172
|
-
the [message bus middleware](#message-bus-middleware)
|
|
174
|
+
the [message bus middleware](#message-bus-middleware) — and xtr-orm-messenger, or
|
|
175
|
+
`xtr-messenger[orm]`, for the [`orm://` transport](#message-bus-transport).
|
|
173
176
|
- **Recipe** — `uv run xtr-recipes recipes:sync` does the *Activate*, *Configure* and
|
|
174
177
|
*Environment* steps below: it lists `OrmBundle`, writes a starting `<app>/config/packages/orm.py` with the
|
|
175
178
|
`default` connection, and `DATABASE_URL` in `.env`, and adds a PostgreSQL `database` service to
|
|
@@ -179,7 +182,8 @@ removing it undoes.
|
|
|
179
182
|
`xtr_orm.bundle`.
|
|
180
183
|
- **Brings along** — the logging and console bundles, when those packages are installed.
|
|
181
184
|
With the messenger bundle active too, the [middleware](#message-bus-middleware) are
|
|
182
|
-
registered for a bus to list
|
|
185
|
+
registered for a bus to list, and with xtr-orm-messenger installed the
|
|
186
|
+
[`orm://` transport](#message-bus-transport) too.
|
|
183
187
|
- **Configure** — the recipe writes a starting `<app>/config/packages/orm.py` with the `default`
|
|
184
188
|
connection reading `DATABASE_URL`, its revisions in `migrations` in the project directory and
|
|
185
189
|
diffs against the models advanced-alchemy registers. Add connections, replicas and table
|
|
@@ -500,6 +504,34 @@ Without a container, build them with a `ConnectionRegistry` whose connections ar
|
|
|
500
504
|
with a `session` — what returns the session the handlers use — and put the instances in the
|
|
501
505
|
configuration.
|
|
502
506
|
|
|
507
|
+
## Message bus transport
|
|
508
|
+
|
|
509
|
+
With the messenger bundle active and [xtr-orm-messenger](../xtr-orm-messenger) installed —
|
|
510
|
+
`uv add "xtr-messenger[orm]"` — a bus can keep its messages in a table on one of these
|
|
511
|
+
connections. There is no bundle to list: this one registers the transport factory with the
|
|
512
|
+
messenger, built on the connections, once it finds the package installed.
|
|
513
|
+
|
|
514
|
+
```python
|
|
515
|
+
# app/config/packages/messenger.py
|
|
516
|
+
MessageBusConfig(
|
|
517
|
+
transports={"jobs": TransportConfig("orm://default", queue="jobs")},
|
|
518
|
+
routing={IngestDocument: "jobs"},
|
|
519
|
+
)
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
- **The DSN names a connection.** `orm://default` is the `default` connection, `orm://` the
|
|
523
|
+
default one, whatever it is called; its URL stays in `OrmConfig`.
|
|
524
|
+
- **Migrations write the table.** Each connection's migrator adds the messages table of every
|
|
525
|
+
`orm://` transport on it to its table definitions, so `orm:migrations:diff` writes it like
|
|
526
|
+
one of the application's own tables. The bus configuration is read for that when a migrator
|
|
527
|
+
is first built, so the environment variables its DSNs name must be set then. A connection
|
|
528
|
+
keeping several sets of table definitions is refused, naming it.
|
|
529
|
+
- **Until the revision is applied**, the transport creates its table the first time it runs;
|
|
530
|
+
`auto_setup=false` on the DSN leaves it to the migrations alone.
|
|
531
|
+
|
|
532
|
+
Options, workers and notifications are the transport's: see
|
|
533
|
+
[xtr-orm-messenger](../xtr-orm-messenger).
|
|
534
|
+
|
|
503
535
|
## Creating and dropping databases
|
|
504
536
|
|
|
505
537
|
`DatabaseManager(url)` creates and drops the database a URL names. A database cannot be
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "xtr-orm"
|
|
3
|
-
version = "
|
|
3
|
+
version = "6.0.1"
|
|
4
4
|
description = "Databases for async applications: engines, sessions and versioned schema migrations, configured once per connection."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -30,7 +30,7 @@ dependencies = [
|
|
|
30
30
|
"alembic>=1.20",
|
|
31
31
|
"sqlalchemy[asyncio]>=2.0.41",
|
|
32
32
|
"typing-extensions>=4.4",
|
|
33
|
-
"xtr-logging-contracts>=
|
|
33
|
+
"xtr-logging-contracts>=6.0,<7",
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
[[project.authors]]
|
|
@@ -57,11 +57,12 @@ dev = [
|
|
|
57
57
|
"anyio>=4.0",
|
|
58
58
|
"ty>=0.0.83",
|
|
59
59
|
"aiosqlite>=0.20",
|
|
60
|
-
"xtr-console>=
|
|
61
|
-
"xtr-dependency-injection>=
|
|
62
|
-
"xtr-logging>=
|
|
63
|
-
"xtr-messenger>=
|
|
64
|
-
"xtr-
|
|
60
|
+
"xtr-console>=6.0,<7",
|
|
61
|
+
"xtr-dependency-injection>=6.0,<7",
|
|
62
|
+
"xtr-logging>=6.0,<7",
|
|
63
|
+
"xtr-messenger>=6.0,<7",
|
|
64
|
+
"xtr-orm-messenger>=6.0,<7",
|
|
65
|
+
"xtr-service-contracts>=6.0,<7",
|
|
65
66
|
]
|
|
66
67
|
|
|
67
68
|
[build-system]
|
|
@@ -143,6 +144,11 @@ unfixable = []
|
|
|
143
144
|
"TC002",
|
|
144
145
|
"TC003",
|
|
145
146
|
]
|
|
147
|
+
"src/xtr_orm/messenger_transport.py" = [
|
|
148
|
+
"TC001",
|
|
149
|
+
"TC002",
|
|
150
|
+
"TC003",
|
|
151
|
+
]
|
|
146
152
|
"tests/**/*.py" = [
|
|
147
153
|
"S101",
|
|
148
154
|
"ARG",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "xtr-orm"
|
|
3
|
-
version = "
|
|
3
|
+
version = "6.0.1"
|
|
4
4
|
description = "Databases for async applications: engines, sessions and versioned schema migrations, configured once per connection."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -26,7 +26,7 @@ dependencies = [
|
|
|
26
26
|
"alembic>=1.20",
|
|
27
27
|
"sqlalchemy[asyncio]>=2.0.41",
|
|
28
28
|
"typing-extensions>=4.4",
|
|
29
|
-
"xtr-logging-contracts>=
|
|
29
|
+
"xtr-logging-contracts>=6.0,<7",
|
|
30
30
|
]
|
|
31
31
|
|
|
32
32
|
[project.optional-dependencies]
|
|
@@ -59,11 +59,12 @@ dev = [
|
|
|
59
59
|
"ty>=0.0.83",
|
|
60
60
|
# Every test runs on SQLite files under the test's own directory: no server.
|
|
61
61
|
"aiosqlite>=0.20",
|
|
62
|
-
"xtr-console>=
|
|
63
|
-
"xtr-dependency-injection>=
|
|
64
|
-
"xtr-logging>=
|
|
65
|
-
"xtr-messenger>=
|
|
66
|
-
"xtr-
|
|
62
|
+
"xtr-console>=6.0,<7",
|
|
63
|
+
"xtr-dependency-injection>=6.0,<7",
|
|
64
|
+
"xtr-logging>=6.0,<7",
|
|
65
|
+
"xtr-messenger>=6.0,<7",
|
|
66
|
+
"xtr-orm-messenger>=6.0,<7",
|
|
67
|
+
"xtr-service-contracts>=6.0,<7",
|
|
67
68
|
]
|
|
68
69
|
|
|
69
70
|
[build-system]
|
|
@@ -147,6 +148,8 @@ unfixable = []
|
|
|
147
148
|
# runtime, and the container resolves its config's fields; deferring these
|
|
148
149
|
# would register the factories under nothing.
|
|
149
150
|
"src/xtr_orm/bundle/*.py" = ["TC001", "TC002", "TC003"]
|
|
151
|
+
# The same for the orm:// transport factory the bundle registers.
|
|
152
|
+
"src/xtr_orm/messenger_transport.py" = ["TC001", "TC002", "TC003"]
|
|
150
153
|
"tests/**/*.py" = [
|
|
151
154
|
"S101", # assert is the point of pytest
|
|
152
155
|
"ARG", # fixtures look unused
|
|
@@ -317,6 +317,23 @@ A handler asks for `Injected[AsyncSession]` like any service and adds rows; it n
|
|
|
317
317
|
More, including building the middleware without a container:
|
|
318
318
|
[references/messenger.md](references/messenger.md).
|
|
319
319
|
|
|
320
|
+
## A queue in a table
|
|
321
|
+
|
|
322
|
+
With the messenger bundle active and xtr-orm-messenger installed (`uv add "xtr-messenger[orm]"`),
|
|
323
|
+
this bundle registers the `orm://` transport on its connections — no other bundle to list.
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
# <app>/config/packages/messenger.py
|
|
327
|
+
MessageBusConfig(
|
|
328
|
+
transports={"jobs": TransportConfig("orm://default", queue="jobs")},
|
|
329
|
+
routing={IngestDocument: "jobs"},
|
|
330
|
+
)
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Each connection's migrator adds the messages table of every `orm://` transport on it, so
|
|
334
|
+
`orm:migrations:diff` writes it. The transport itself — options, workers, notifications — is
|
|
335
|
+
the `xtr-orm-messenger` skill's.
|
|
336
|
+
|
|
320
337
|
## Creating and dropping a database
|
|
321
338
|
|
|
322
339
|
```python
|
|
@@ -21,14 +21,20 @@ disposed when the container closes. When the console bundle is active, the
|
|
|
21
21
|
``orm_open_transaction_logger`` middleware are, for a bus to list; when the
|
|
22
22
|
logging bundle is, an ``orm`` channel is added and every migrator logs there.
|
|
23
23
|
|
|
24
|
+
With the messenger bundle active and xtr-orm-messenger installed, the
|
|
25
|
+
``orm://`` transport is registered with the messenger too, built on these
|
|
26
|
+
connections, and each connection's migrator writes the messages tables of the
|
|
27
|
+
``orm://`` transports on it.
|
|
28
|
+
|
|
24
29
|
Nothing connects until a service is first used, and a URL given as
|
|
25
30
|
``env(...)`` is read only then.
|
|
26
31
|
"""
|
|
27
32
|
|
|
28
33
|
from __future__ import annotations
|
|
29
34
|
|
|
30
|
-
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
35
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
|
|
31
36
|
from functools import partial
|
|
37
|
+
from importlib.util import find_spec
|
|
32
38
|
from operator import contains
|
|
33
39
|
from typing import Final, TypeVar, cast, final
|
|
34
40
|
|
|
@@ -37,6 +43,7 @@ from advanced_alchemy.config import AsyncSessionConfig, EngineConfig, SQLAlchemy
|
|
|
37
43
|
from advanced_alchemy.config.routing import EngineConfig as ReplicaEngine
|
|
38
44
|
from advanced_alchemy.config.routing import RoutingConfig, RoutingStrategy
|
|
39
45
|
from advanced_alchemy.routing import RoutingAsyncSessionMaker, stick_to_primary_var
|
|
46
|
+
from sqlalchemy import MetaData
|
|
40
47
|
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
|
|
41
48
|
from typing_extensions import override
|
|
42
49
|
from xtr_dependency_injection import (
|
|
@@ -48,6 +55,7 @@ from xtr_dependency_injection import (
|
|
|
48
55
|
current_unit_of_work,
|
|
49
56
|
named_factory,
|
|
50
57
|
optional_service,
|
|
58
|
+
qualified_name,
|
|
51
59
|
required_bundle,
|
|
52
60
|
)
|
|
53
61
|
from xtr_logging_contracts import LoggerInterface
|
|
@@ -55,7 +63,7 @@ from xtr_service_contracts import ContainerInterface
|
|
|
55
63
|
|
|
56
64
|
from xtr_orm.connection_registry import ConnectionRegistry
|
|
57
65
|
from xtr_orm.database import DatabaseManager
|
|
58
|
-
from xtr_orm.exception import SessionUnavailableError
|
|
66
|
+
from xtr_orm.exception import InvalidArgumentError, SessionUnavailableError
|
|
59
67
|
from xtr_orm.migrations import MigrationsConfig, Migrator
|
|
60
68
|
|
|
61
69
|
from .connection_config import ConnectionConfig
|
|
@@ -71,6 +79,9 @@ _SessionMaker = type[async_sessionmaker[AsyncSession]] | type[RoutingAsyncSessio
|
|
|
71
79
|
ORM_CHANNEL: Final = "orm"
|
|
72
80
|
"""The logging channel migrators write to."""
|
|
73
81
|
|
|
82
|
+
_MESSENGER_TRANSPORT: Final = "xtr_orm_messenger"
|
|
83
|
+
"""The package of the ``orm://`` transport, registered with the messenger when installed."""
|
|
84
|
+
|
|
74
85
|
_PER_CONNECTION: Final[tuple[type, ...]] = (
|
|
75
86
|
SQLAlchemyAsyncConfig,
|
|
76
87
|
AsyncEngine,
|
|
@@ -108,6 +119,8 @@ class OrmBundle(Bundle[OrmConfig]):
|
|
|
108
119
|
builder: ContainerBuilder,
|
|
109
120
|
) -> None:
|
|
110
121
|
"""Register every connection's services under its name, the registry, the commands."""
|
|
122
|
+
messenger = bundle_active(builder, "messenger")
|
|
123
|
+
transport = messenger and find_spec(_MESSENGER_TRANSPORT) is not None
|
|
111
124
|
for name, connection in config.connections.items():
|
|
112
125
|
maker_type: _SessionMaker = (
|
|
113
126
|
RoutingAsyncSessionMaker
|
|
@@ -123,7 +136,15 @@ class OrmBundle(Bundle[OrmConfig]):
|
|
|
123
136
|
else _session_maker_factory(name),
|
|
124
137
|
"session_maker",
|
|
125
138
|
),
|
|
126
|
-
(
|
|
139
|
+
(
|
|
140
|
+
_migrator_factory(
|
|
141
|
+
name,
|
|
142
|
+
connection,
|
|
143
|
+
default=config.default_connection,
|
|
144
|
+
messages_tables=transport,
|
|
145
|
+
),
|
|
146
|
+
"migrator",
|
|
147
|
+
),
|
|
127
148
|
(_database_factory(connection), "database"),
|
|
128
149
|
)
|
|
129
150
|
for factory, kind in factories:
|
|
@@ -148,8 +169,37 @@ class OrmBundle(Bundle[OrmConfig]):
|
|
|
148
169
|
|
|
149
170
|
if bundle_active(builder, "console"):
|
|
150
171
|
services.load("xtr_orm.command")
|
|
151
|
-
if
|
|
172
|
+
if messenger:
|
|
152
173
|
services.load("xtr_orm.messenger")
|
|
174
|
+
if transport:
|
|
175
|
+
_register_orm_transport(services)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _register_orm_transport(services: ServiceConfigurator) -> None:
|
|
179
|
+
"""Register the ``orm://`` transport factory with the messenger, built on these connections.
|
|
180
|
+
|
|
181
|
+
Registered under the messenger's transport factory interface and tagged
|
|
182
|
+
with its transport factory tag, which is how the messenger consults a
|
|
183
|
+
factory built from services ahead of the ones it discovers by DSN scheme.
|
|
184
|
+
"""
|
|
185
|
+
# Optional peers: importable only once the messenger bundle is active and
|
|
186
|
+
# the transport installed, which the caller checked.
|
|
187
|
+
from xtr_messenger import TransportFactoryInterface # noqa: PLC0415 — optional peer
|
|
188
|
+
from xtr_messenger.bundle import TRANSPORT_FACTORY_TAG # noqa: PLC0415 — optional peer
|
|
189
|
+
|
|
190
|
+
from xtr_orm.messenger_transport import ( # noqa: PLC0415 — needs the optional peers
|
|
191
|
+
OrmTransportFactory,
|
|
192
|
+
orm_transport_factory,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
qualifier = qualified_name(OrmTransportFactory)
|
|
196
|
+
_ = services.set(orm_transport_factory, qualifier=qualifier).add_tag(TRANSPORT_FACTORY_TAG)
|
|
197
|
+
services.alias(
|
|
198
|
+
TransportFactoryInterface,
|
|
199
|
+
OrmTransportFactory,
|
|
200
|
+
alias_qualifier=qualifier,
|
|
201
|
+
target_qualifier=qualifier,
|
|
202
|
+
)
|
|
153
203
|
|
|
154
204
|
|
|
155
205
|
@final
|
|
@@ -291,9 +341,14 @@ def _session_factory(
|
|
|
291
341
|
|
|
292
342
|
|
|
293
343
|
def _migrator_factory(
|
|
294
|
-
name: str, connection: ConnectionConfig
|
|
344
|
+
name: str, connection: ConnectionConfig, *, default: str, messages_tables: bool
|
|
295
345
|
) -> Callable[[ContainerInterface], Awaitable[Migrator]]:
|
|
296
|
-
"""Build the factory of the connection ``name``'s migrator, logging to ``orm`` when it can.
|
|
346
|
+
"""Build the factory of the connection ``name``'s migrator, logging to ``orm`` when it can.
|
|
347
|
+
|
|
348
|
+
With ``messages_tables``, the messages tables of the ``orm://`` transports
|
|
349
|
+
on this connection join its table definitions first, so a diff writes them
|
|
350
|
+
like any of the application's own tables.
|
|
351
|
+
"""
|
|
297
352
|
|
|
298
353
|
async def migrator(container: ContainerInterface) -> Migrator:
|
|
299
354
|
resolved = connection.with_url_options()
|
|
@@ -302,6 +357,8 @@ def _migrator_factory(
|
|
|
302
357
|
if resolved.metadata is not None
|
|
303
358
|
else metadata_registry.get(resolved.bind_key)
|
|
304
359
|
)
|
|
360
|
+
if messages_tables:
|
|
361
|
+
await _map_messages_tables(container, name, default, metadata)
|
|
305
362
|
built = Migrator(
|
|
306
363
|
await container.get(AsyncEngine, name),
|
|
307
364
|
resolved.migrations or MigrationsConfig(),
|
|
@@ -316,6 +373,44 @@ def _migrator_factory(
|
|
|
316
373
|
return migrator
|
|
317
374
|
|
|
318
375
|
|
|
376
|
+
async def _map_messages_tables(
|
|
377
|
+
container: ContainerInterface,
|
|
378
|
+
name: str,
|
|
379
|
+
default: str,
|
|
380
|
+
metadata: MetaData | Sequence[MetaData],
|
|
381
|
+
) -> None:
|
|
382
|
+
"""Add the messages table of every ``orm://`` transport on the connection ``name``.
|
|
383
|
+
|
|
384
|
+
The transports are read from the bus configuration as the application
|
|
385
|
+
resolved it, so a DSN given as ``env(...)`` is read here — when a migrator
|
|
386
|
+
is first built.
|
|
387
|
+
|
|
388
|
+
Raises:
|
|
389
|
+
InvalidArgumentError: When a transport keeps a table on a connection
|
|
390
|
+
that keeps several sets of table definitions.
|
|
391
|
+
"""
|
|
392
|
+
# Optional peers: importable only once the messenger bundle is active and
|
|
393
|
+
# the transport installed, which the bundle checked.
|
|
394
|
+
from xtr_messenger import MessageBusConfig # noqa: PLC0415 — optional peer
|
|
395
|
+
from xtr_orm_messenger import ( # noqa: PLC0415 — optional peer
|
|
396
|
+
MessagesTableDriver,
|
|
397
|
+
messages_tables,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
bus = await optional_service(container, MessageBusConfig)
|
|
401
|
+
tables = messages_tables(bus, name, default) if bus is not None else ()
|
|
402
|
+
if not tables:
|
|
403
|
+
return
|
|
404
|
+
if not isinstance(metadata, MetaData):
|
|
405
|
+
raise InvalidArgumentError(
|
|
406
|
+
f'The "{name}" connection keeps several sets of table definitions; the messages '
|
|
407
|
+
f"tables of its orm:// transports need the one they belong to. Give that connection "
|
|
408
|
+
f"a single MetaData.",
|
|
409
|
+
)
|
|
410
|
+
for table in tables:
|
|
411
|
+
_ = MessagesTableDriver(table).load_metadata(metadata)
|
|
412
|
+
|
|
413
|
+
|
|
319
414
|
def _database_factory(connection: ConnectionConfig) -> Callable[[], DatabaseManager]:
|
|
320
415
|
"""Build the factory of ``connection``'s database manager."""
|
|
321
416
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Building the ``orm://`` transport factory from the container's services.
|
|
2
|
+
|
|
3
|
+
The orm bundle registers it when the messenger bundle is active and
|
|
4
|
+
xtr-orm-messenger is installed: the factory needs this package's connections,
|
|
5
|
+
which only this package's bundle holds. Importing this module therefore needs
|
|
6
|
+
both xtr-messenger and xtr-orm-messenger; the bundle imports it only once it
|
|
7
|
+
has found them, and nothing scans it.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from xtr_messenger import SerializerInterface
|
|
13
|
+
from xtr_orm_messenger import OrmTransportFactory
|
|
14
|
+
|
|
15
|
+
from xtr_orm.connection_registry import ConnectionRegistry
|
|
16
|
+
|
|
17
|
+
__all__ = ["OrmTransportFactory", "orm_transport_factory"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def orm_transport_factory(
|
|
21
|
+
registry: ConnectionRegistry, serializer: SerializerInterface
|
|
22
|
+
) -> OrmTransportFactory:
|
|
23
|
+
"""Build the factory on the application's connections, with the messenger's serializer."""
|
|
24
|
+
return OrmTransportFactory(registry, serializer)
|
|
File without changes
|
|
File without changes
|
{xtr_orm-5.2.1 → xtr_orm-6.0.1}/src/xtr_orm/.agents/skills/xtr-orm/references/connections.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|