muscles-data 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- muscles_data-0.1.0/PKG-INFO +321 -0
- muscles_data-0.1.0/README.md +309 -0
- muscles_data-0.1.0/pyproject.toml +29 -0
- muscles_data-0.1.0/setup.cfg +4 -0
- muscles_data-0.1.0/src/muscles_data/__init__.py +158 -0
- muscles_data-0.1.0/src/muscles_data/actions.py +144 -0
- muscles_data-0.1.0/src/muscles_data/adapters/__init__.py +3 -0
- muscles_data-0.1.0/src/muscles_data/adapters/memory.py +610 -0
- muscles_data-0.1.0/src/muscles_data/catalog.py +66 -0
- muscles_data-0.1.0/src/muscles_data/config.py +47 -0
- muscles_data-0.1.0/src/muscles_data/contracts.py +140 -0
- muscles_data-0.1.0/src/muscles_data/errors.py +61 -0
- muscles_data-0.1.0/src/muscles_data/models.py +323 -0
- muscles_data-0.1.0/src/muscles_data/package.py +210 -0
- muscles_data-0.1.0/src/muscles_data/ports.py +196 -0
- muscles_data-0.1.0/src/muscles_data/runtime.py +251 -0
- muscles_data-0.1.0/src/muscles_data.egg-info/PKG-INFO +321 -0
- muscles_data-0.1.0/src/muscles_data.egg-info/SOURCES.txt +22 -0
- muscles_data-0.1.0/src/muscles_data.egg-info/dependency_links.txt +1 -0
- muscles_data-0.1.0/src/muscles_data.egg-info/requires.txt +4 -0
- muscles_data-0.1.0/src/muscles_data.egg-info/top_level.txt +1 -0
- muscles_data-0.1.0/tests/test_contracts.py +51 -0
- muscles_data-0.1.0/tests/test_data_runtime.py +431 -0
- muscles_data-0.1.0/tests/test_event_contracts.py +171 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: muscles-data
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Framework-level named data resources and typed ports for Muscles
|
|
5
|
+
Author-email: "Denis B." <denis@butko.info>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: muscles<2.0.0,>=1.0.0rc1
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
|
|
12
|
+
|
|
13
|
+
# muscles-data
|
|
14
|
+
|
|
15
|
+
Framework-level named data resources and typed ports for the Muscles ecosystem.
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
|
|
19
|
+
`muscles-data` gives projects one small runtime for declaring, inspecting and
|
|
20
|
+
resolving data backends through narrow typed ports:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
config -> named resource -> capability check -> lazy adapter -> typed port
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
It does not replace SQL libraries, search clients, document databases, object
|
|
27
|
+
storage SDKs or project repositories. Framework packages should depend on ports,
|
|
28
|
+
not vendor clients.
|
|
29
|
+
|
|
30
|
+
## Scope
|
|
31
|
+
|
|
32
|
+
The core package owns:
|
|
33
|
+
|
|
34
|
+
- `DataResourceConfig` and `DataCapability`;
|
|
35
|
+
- `DataAdapterCatalog`;
|
|
36
|
+
- lazy `DataRuntime`;
|
|
37
|
+
- `DataResourceHandle`;
|
|
38
|
+
- typed ports:
|
|
39
|
+
- `VectorSearchPort`;
|
|
40
|
+
- `SearchIndexPort`;
|
|
41
|
+
- `ObjectStorePort`;
|
|
42
|
+
- `KeyValuePort`;
|
|
43
|
+
- `LockPort`;
|
|
44
|
+
- `StreamPort`;
|
|
45
|
+
- `EventPublisherPort`;
|
|
46
|
+
- `EventConsumerPort`;
|
|
47
|
+
- `EventStorePort`;
|
|
48
|
+
- `DocumentStorePort`;
|
|
49
|
+
- `SqlResourcePort`;
|
|
50
|
+
- safe `data.resources.list`, `data.resource.inspect`, `data.doctor` actions;
|
|
51
|
+
- in-memory adapters for tests and examples;
|
|
52
|
+
- the `type: sql` bridge to a `muscles-sql` compatible registry.
|
|
53
|
+
|
|
54
|
+
It intentionally does not own:
|
|
55
|
+
|
|
56
|
+
- project business schemas;
|
|
57
|
+
- universal CRUD/query API;
|
|
58
|
+
- ORM models, repositories, Unit of Work or migrations;
|
|
59
|
+
- vendor clients such as SQLAlchemy, Redis, Elasticsearch, OpenSearch, Qdrant,
|
|
60
|
+
PyMongo or boto3;
|
|
61
|
+
- RAG, document parsing, embeddings, prompts or LLM calls;
|
|
62
|
+
- protocol routes;
|
|
63
|
+
- distributed transactions across backends.
|
|
64
|
+
|
|
65
|
+
## Event-driven data contract
|
|
66
|
+
|
|
67
|
+
`DataEventEnvelope` and the publish/read/ack request and result models are core
|
|
68
|
+
`muscles.Model` schemas. They are visible through `inspect_application` and can
|
|
69
|
+
be reused by protocol projections without a second DTO or schema system.
|
|
70
|
+
|
|
71
|
+
The MVP includes `EventPublisherPort`, `EventConsumerPort` and `EventStorePort`,
|
|
72
|
+
plus the `memory_event` adapter for tests and local examples. Event schemas
|
|
73
|
+
describe semantic facts; `StreamPort` remains a lower-level transport
|
|
74
|
+
primitive. Production transports, outbox/inbox workers and distributed
|
|
75
|
+
transactions remain separate extensions.
|
|
76
|
+
|
|
77
|
+
Diagnostics expose only safe counts and resource metadata; event payloads are
|
|
78
|
+
never included in `inspect` or `doctor` output.
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
Core resources work without external database packages:
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
data:
|
|
86
|
+
resources:
|
|
87
|
+
vector.docs:
|
|
88
|
+
type: memory_vector
|
|
89
|
+
|
|
90
|
+
search.docs:
|
|
91
|
+
type: memory_search
|
|
92
|
+
|
|
93
|
+
cache.default:
|
|
94
|
+
type: memory_kv
|
|
95
|
+
|
|
96
|
+
objects.docs:
|
|
97
|
+
type: memory_object
|
|
98
|
+
|
|
99
|
+
documents.local:
|
|
100
|
+
type: memory_document
|
|
101
|
+
|
|
102
|
+
sql.main:
|
|
103
|
+
type: sql
|
|
104
|
+
connection: main
|
|
105
|
+
role: read_write
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
External adapter packages add real backend resource types. Their config still
|
|
109
|
+
lives in the project, but the factory comes from the adapter package:
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
data:
|
|
113
|
+
resources:
|
|
114
|
+
search.elastic:
|
|
115
|
+
type: elasticsearch
|
|
116
|
+
url_env: ELASTICSEARCH_URL
|
|
117
|
+
api_key: ${ELASTICSEARCH_API_KEY}
|
|
118
|
+
index: docs
|
|
119
|
+
|
|
120
|
+
search.public:
|
|
121
|
+
type: opensearch
|
|
122
|
+
url: ${OPENSEARCH_URL}
|
|
123
|
+
username: ${OPENSEARCH_USER}
|
|
124
|
+
password: ${OPENSEARCH_PASSWORD}
|
|
125
|
+
index: docs
|
|
126
|
+
|
|
127
|
+
cache.redis:
|
|
128
|
+
type: redis
|
|
129
|
+
url_env: REDIS_URL
|
|
130
|
+
namespace: app
|
|
131
|
+
stream_group: workers
|
|
132
|
+
consumer: index-worker
|
|
133
|
+
|
|
134
|
+
vector.qdrant:
|
|
135
|
+
type: qdrant
|
|
136
|
+
url_env: QDRANT_URL
|
|
137
|
+
api_key: ${QDRANT_API_KEY}
|
|
138
|
+
collection: docs
|
|
139
|
+
vector_size: 1536
|
|
140
|
+
distance: cosine
|
|
141
|
+
|
|
142
|
+
mongo.content:
|
|
143
|
+
type: mongodb
|
|
144
|
+
url: ${MONGO_URL}
|
|
145
|
+
database: content
|
|
146
|
+
|
|
147
|
+
objects.docs:
|
|
148
|
+
type: s3
|
|
149
|
+
endpoint_url: ${S3_ENDPOINT}
|
|
150
|
+
bucket: documents
|
|
151
|
+
prefix: raw
|
|
152
|
+
|
|
153
|
+
sql.local:
|
|
154
|
+
type: sqlalchemy
|
|
155
|
+
url: sqlite:///:memory:
|
|
156
|
+
name: local_sqlite
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## External Adapter Packages
|
|
160
|
+
|
|
161
|
+
The adapter packages are separate repositories and dependencies:
|
|
162
|
+
|
|
163
|
+
| Resource type | Package | Port | Example |
|
|
164
|
+
| --- | --- | --- | --- |
|
|
165
|
+
| `elasticsearch` | [`muscles-data-elasticsearch`](https://github.com/butkoden/muscles-data-elasticsearch) | `SearchIndexPort` | [`example_data_elasticsearch_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_elasticsearch_1) |
|
|
166
|
+
| `opensearch` | [`muscles-data-opensearch`](https://github.com/butkoden/muscles-data-opensearch) | `SearchIndexPort` | [`example_data_opensearch_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_opensearch_1) |
|
|
167
|
+
| `redis` | [`muscles-data-redis`](https://github.com/butkoden/muscles-data-redis) | `KeyValuePort`, `LockPort`, `StreamPort` | [`example_data_redis_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_redis_1) |
|
|
168
|
+
| `qdrant` | [`muscles-data-qdrant`](https://github.com/butkoden/muscles-data-qdrant) | `VectorSearchPort` | [`example_data_qdrant_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_qdrant_1) |
|
|
169
|
+
| `mongodb` | [`muscles-data-mongodb`](https://github.com/butkoden/muscles-data-mongodb) | `DocumentStorePort` | [`example_data_mongodb_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_mongodb_1) |
|
|
170
|
+
| `s3` | [`muscles-data-s3`](https://github.com/butkoden/muscles-data-s3) | `ObjectStorePort` | [`example_data_s3_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_s3_1) |
|
|
171
|
+
| `sqlalchemy` | [`muscles-data-sqlalchemy`](https://github.com/butkoden/muscles-data-sqlalchemy) | `SqlResourcePort` | [`example_data_sqlalchemy_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_sqlalchemy_1) |
|
|
172
|
+
|
|
173
|
+
Register external factories in the project composition root:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from muscles_data.catalog import DataAdapterCatalog
|
|
177
|
+
from muscles_data_elasticsearch import ElasticsearchSearchFactory
|
|
178
|
+
from muscles_data_qdrant import QdrantVectorFactory
|
|
179
|
+
from muscles_data_redis import RedisDataFactory
|
|
180
|
+
|
|
181
|
+
catalog = DataAdapterCatalog.with_defaults()
|
|
182
|
+
catalog.register(ElasticsearchSearchFactory())
|
|
183
|
+
catalog.register(QdrantVectorFactory())
|
|
184
|
+
catalog.register(RedisDataFactory())
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`muscles-data` core does not import these packages automatically. This keeps
|
|
188
|
+
framework startup small and avoids pulling database SDKs into projects that do
|
|
189
|
+
not need them. Registration is deliberately manual: projects install and
|
|
190
|
+
register only the adapter packages they need.
|
|
191
|
+
|
|
192
|
+
## Runtime API
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from muscles_data import DataRuntime
|
|
196
|
+
from muscles_data.config import DataConfig
|
|
197
|
+
from muscles_data.catalog import DataAdapterCatalog
|
|
198
|
+
from muscles_data.ports import KeyValuePort, VectorSearchPort
|
|
199
|
+
|
|
200
|
+
runtime = DataRuntime(
|
|
201
|
+
config=DataConfig.from_raw({
|
|
202
|
+
"data": {
|
|
203
|
+
"resources": {
|
|
204
|
+
"vector.docs": {"type": "memory_vector"},
|
|
205
|
+
"cache.default": {"type": "memory_kv"},
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}),
|
|
209
|
+
catalog=DataAdapterCatalog.with_defaults(),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
vector = runtime.require_port("vector.docs", VectorSearchPort)
|
|
213
|
+
cache = runtime.require_port("cache.default", KeyValuePort)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Adapters are initialized lazily: package init and resource listing do not open
|
|
217
|
+
connections. Concrete adapters are created by:
|
|
218
|
+
|
|
219
|
+
- `runtime.require_port(name, PortType)`;
|
|
220
|
+
- `runtime.require_resource(name, capability)`;
|
|
221
|
+
- `runtime.doctor()` when health checks are enabled.
|
|
222
|
+
|
|
223
|
+
## SQL Resources
|
|
224
|
+
|
|
225
|
+
`type: sql` is the only real backend bridge built into core. It delegates to a
|
|
226
|
+
named registry owned by `muscles-sql` or by the project:
|
|
227
|
+
|
|
228
|
+
```yaml
|
|
229
|
+
data:
|
|
230
|
+
resources:
|
|
231
|
+
sql.documents:
|
|
232
|
+
type: sql
|
|
233
|
+
connection: documents_metadata
|
|
234
|
+
role: read_write
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
from muscles_data.ports import SqlResourcePort
|
|
239
|
+
|
|
240
|
+
sql = runtime.require_port("sql.documents", SqlResourcePort)
|
|
241
|
+
with sql.session() as session:
|
|
242
|
+
...
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`muscles-data` does not create SQL engines, repositories, Unit of Work objects
|
|
246
|
+
or migrations. `session()`, `session_factory()`, `inspect()` and `doctor()`
|
|
247
|
+
delegate to the supplied registry.
|
|
248
|
+
|
|
249
|
+
Use `muscles-data-sqlalchemy` when a project wants direct SQLAlchemy sessions
|
|
250
|
+
through the same `SqlResourcePort` without using `muscles-sql`.
|
|
251
|
+
|
|
252
|
+
## Native Escape Hatch
|
|
253
|
+
|
|
254
|
+
The preferred path is always a typed port. A project may explicitly request a
|
|
255
|
+
native/internal backend handle only when the resource declares the
|
|
256
|
+
`native_client` capability:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
from muscles_data import DataCapability
|
|
260
|
+
|
|
261
|
+
handle = runtime.require_resource("cache.default", DataCapability.NATIVE_CLIENT)
|
|
262
|
+
native = handle.native_client()
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
This is an advanced escape hatch for project-specific operations. Framework
|
|
266
|
+
packages should not build their primary logic on native clients. Native handles,
|
|
267
|
+
credentials and raw payloads are never included in inspect/doctor output.
|
|
268
|
+
|
|
269
|
+
## Actions
|
|
270
|
+
|
|
271
|
+
- `data.resources.list` — list configured resources, capabilities and lazy init
|
|
272
|
+
state without health checks.
|
|
273
|
+
- `data.resource.inspect` — inspect one resource with redacted options.
|
|
274
|
+
- `data.doctor` — validate factories and run safe health checks with partial
|
|
275
|
+
failure reporting.
|
|
276
|
+
|
|
277
|
+
All actions are normal Muscles actions registered through the core action
|
|
278
|
+
contract. Protocol packages see them through `ActionDispatcher`; there is no
|
|
279
|
+
package-specific protocol routing.
|
|
280
|
+
|
|
281
|
+
## Telemetry
|
|
282
|
+
|
|
283
|
+
`muscles-data` resolves telemetry through the neutral Muscles provider. It does
|
|
284
|
+
not import `muscles-otel` directly.
|
|
285
|
+
|
|
286
|
+
Safe attributes are resource name/type, capability, operation, status and safe
|
|
287
|
+
counts. Do not add DSNs, tokens, passwords, raw query payloads, document text,
|
|
288
|
+
object content, vector payloads or native clients to spans.
|
|
289
|
+
|
|
290
|
+
## Examples
|
|
291
|
+
|
|
292
|
+
Run the core smoke examples:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
PYTHONPATH=../muscles/src:src python3 examples/run_data_runtime.py
|
|
296
|
+
PYTHONPATH=../muscles/src:src python3 examples/run_sql_resource_port.py
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Real backend examples live in
|
|
300
|
+
[`muscular-example`](https://github.com/butkoden/muscular-example) as
|
|
301
|
+
`example_data_[adapter]_1` packages.
|
|
302
|
+
|
|
303
|
+
Run tests:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
PYTHONPATH=../muscles/src:src python3 -m pytest -q
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Run the real-backend integration suite locally (Docker and the optional SDKs
|
|
310
|
+
are required):
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
make data-integration-test
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
The command starts isolated Elasticsearch, OpenSearch, Qdrant, Redis,
|
|
317
|
+
MongoDB, MinIO and PostgreSQL containers, runs every adapter's contract and
|
|
318
|
+
backend lifecycle tests, and removes the containers on exit. Set
|
|
319
|
+
`MUSCLES_DATA_PYTHON` to use a different Python environment. The compose file
|
|
320
|
+
and port map are documented in
|
|
321
|
+
[`infra/docker-compose.integration.yml`](infra/docker-compose.integration.yml).
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# muscles-data
|
|
2
|
+
|
|
3
|
+
Framework-level named data resources and typed ports for the Muscles ecosystem.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
`muscles-data` gives projects one small runtime for declaring, inspecting and
|
|
8
|
+
resolving data backends through narrow typed ports:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
config -> named resource -> capability check -> lazy adapter -> typed port
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
It does not replace SQL libraries, search clients, document databases, object
|
|
15
|
+
storage SDKs or project repositories. Framework packages should depend on ports,
|
|
16
|
+
not vendor clients.
|
|
17
|
+
|
|
18
|
+
## Scope
|
|
19
|
+
|
|
20
|
+
The core package owns:
|
|
21
|
+
|
|
22
|
+
- `DataResourceConfig` and `DataCapability`;
|
|
23
|
+
- `DataAdapterCatalog`;
|
|
24
|
+
- lazy `DataRuntime`;
|
|
25
|
+
- `DataResourceHandle`;
|
|
26
|
+
- typed ports:
|
|
27
|
+
- `VectorSearchPort`;
|
|
28
|
+
- `SearchIndexPort`;
|
|
29
|
+
- `ObjectStorePort`;
|
|
30
|
+
- `KeyValuePort`;
|
|
31
|
+
- `LockPort`;
|
|
32
|
+
- `StreamPort`;
|
|
33
|
+
- `EventPublisherPort`;
|
|
34
|
+
- `EventConsumerPort`;
|
|
35
|
+
- `EventStorePort`;
|
|
36
|
+
- `DocumentStorePort`;
|
|
37
|
+
- `SqlResourcePort`;
|
|
38
|
+
- safe `data.resources.list`, `data.resource.inspect`, `data.doctor` actions;
|
|
39
|
+
- in-memory adapters for tests and examples;
|
|
40
|
+
- the `type: sql` bridge to a `muscles-sql` compatible registry.
|
|
41
|
+
|
|
42
|
+
It intentionally does not own:
|
|
43
|
+
|
|
44
|
+
- project business schemas;
|
|
45
|
+
- universal CRUD/query API;
|
|
46
|
+
- ORM models, repositories, Unit of Work or migrations;
|
|
47
|
+
- vendor clients such as SQLAlchemy, Redis, Elasticsearch, OpenSearch, Qdrant,
|
|
48
|
+
PyMongo or boto3;
|
|
49
|
+
- RAG, document parsing, embeddings, prompts or LLM calls;
|
|
50
|
+
- protocol routes;
|
|
51
|
+
- distributed transactions across backends.
|
|
52
|
+
|
|
53
|
+
## Event-driven data contract
|
|
54
|
+
|
|
55
|
+
`DataEventEnvelope` and the publish/read/ack request and result models are core
|
|
56
|
+
`muscles.Model` schemas. They are visible through `inspect_application` and can
|
|
57
|
+
be reused by protocol projections without a second DTO or schema system.
|
|
58
|
+
|
|
59
|
+
The MVP includes `EventPublisherPort`, `EventConsumerPort` and `EventStorePort`,
|
|
60
|
+
plus the `memory_event` adapter for tests and local examples. Event schemas
|
|
61
|
+
describe semantic facts; `StreamPort` remains a lower-level transport
|
|
62
|
+
primitive. Production transports, outbox/inbox workers and distributed
|
|
63
|
+
transactions remain separate extensions.
|
|
64
|
+
|
|
65
|
+
Diagnostics expose only safe counts and resource metadata; event payloads are
|
|
66
|
+
never included in `inspect` or `doctor` output.
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
Core resources work without external database packages:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
data:
|
|
74
|
+
resources:
|
|
75
|
+
vector.docs:
|
|
76
|
+
type: memory_vector
|
|
77
|
+
|
|
78
|
+
search.docs:
|
|
79
|
+
type: memory_search
|
|
80
|
+
|
|
81
|
+
cache.default:
|
|
82
|
+
type: memory_kv
|
|
83
|
+
|
|
84
|
+
objects.docs:
|
|
85
|
+
type: memory_object
|
|
86
|
+
|
|
87
|
+
documents.local:
|
|
88
|
+
type: memory_document
|
|
89
|
+
|
|
90
|
+
sql.main:
|
|
91
|
+
type: sql
|
|
92
|
+
connection: main
|
|
93
|
+
role: read_write
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
External adapter packages add real backend resource types. Their config still
|
|
97
|
+
lives in the project, but the factory comes from the adapter package:
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
data:
|
|
101
|
+
resources:
|
|
102
|
+
search.elastic:
|
|
103
|
+
type: elasticsearch
|
|
104
|
+
url_env: ELASTICSEARCH_URL
|
|
105
|
+
api_key: ${ELASTICSEARCH_API_KEY}
|
|
106
|
+
index: docs
|
|
107
|
+
|
|
108
|
+
search.public:
|
|
109
|
+
type: opensearch
|
|
110
|
+
url: ${OPENSEARCH_URL}
|
|
111
|
+
username: ${OPENSEARCH_USER}
|
|
112
|
+
password: ${OPENSEARCH_PASSWORD}
|
|
113
|
+
index: docs
|
|
114
|
+
|
|
115
|
+
cache.redis:
|
|
116
|
+
type: redis
|
|
117
|
+
url_env: REDIS_URL
|
|
118
|
+
namespace: app
|
|
119
|
+
stream_group: workers
|
|
120
|
+
consumer: index-worker
|
|
121
|
+
|
|
122
|
+
vector.qdrant:
|
|
123
|
+
type: qdrant
|
|
124
|
+
url_env: QDRANT_URL
|
|
125
|
+
api_key: ${QDRANT_API_KEY}
|
|
126
|
+
collection: docs
|
|
127
|
+
vector_size: 1536
|
|
128
|
+
distance: cosine
|
|
129
|
+
|
|
130
|
+
mongo.content:
|
|
131
|
+
type: mongodb
|
|
132
|
+
url: ${MONGO_URL}
|
|
133
|
+
database: content
|
|
134
|
+
|
|
135
|
+
objects.docs:
|
|
136
|
+
type: s3
|
|
137
|
+
endpoint_url: ${S3_ENDPOINT}
|
|
138
|
+
bucket: documents
|
|
139
|
+
prefix: raw
|
|
140
|
+
|
|
141
|
+
sql.local:
|
|
142
|
+
type: sqlalchemy
|
|
143
|
+
url: sqlite:///:memory:
|
|
144
|
+
name: local_sqlite
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## External Adapter Packages
|
|
148
|
+
|
|
149
|
+
The adapter packages are separate repositories and dependencies:
|
|
150
|
+
|
|
151
|
+
| Resource type | Package | Port | Example |
|
|
152
|
+
| --- | --- | --- | --- |
|
|
153
|
+
| `elasticsearch` | [`muscles-data-elasticsearch`](https://github.com/butkoden/muscles-data-elasticsearch) | `SearchIndexPort` | [`example_data_elasticsearch_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_elasticsearch_1) |
|
|
154
|
+
| `opensearch` | [`muscles-data-opensearch`](https://github.com/butkoden/muscles-data-opensearch) | `SearchIndexPort` | [`example_data_opensearch_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_opensearch_1) |
|
|
155
|
+
| `redis` | [`muscles-data-redis`](https://github.com/butkoden/muscles-data-redis) | `KeyValuePort`, `LockPort`, `StreamPort` | [`example_data_redis_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_redis_1) |
|
|
156
|
+
| `qdrant` | [`muscles-data-qdrant`](https://github.com/butkoden/muscles-data-qdrant) | `VectorSearchPort` | [`example_data_qdrant_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_qdrant_1) |
|
|
157
|
+
| `mongodb` | [`muscles-data-mongodb`](https://github.com/butkoden/muscles-data-mongodb) | `DocumentStorePort` | [`example_data_mongodb_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_mongodb_1) |
|
|
158
|
+
| `s3` | [`muscles-data-s3`](https://github.com/butkoden/muscles-data-s3) | `ObjectStorePort` | [`example_data_s3_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_s3_1) |
|
|
159
|
+
| `sqlalchemy` | [`muscles-data-sqlalchemy`](https://github.com/butkoden/muscles-data-sqlalchemy) | `SqlResourcePort` | [`example_data_sqlalchemy_1`](https://github.com/butkoden/muscular-example/tree/master/example_data_sqlalchemy_1) |
|
|
160
|
+
|
|
161
|
+
Register external factories in the project composition root:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from muscles_data.catalog import DataAdapterCatalog
|
|
165
|
+
from muscles_data_elasticsearch import ElasticsearchSearchFactory
|
|
166
|
+
from muscles_data_qdrant import QdrantVectorFactory
|
|
167
|
+
from muscles_data_redis import RedisDataFactory
|
|
168
|
+
|
|
169
|
+
catalog = DataAdapterCatalog.with_defaults()
|
|
170
|
+
catalog.register(ElasticsearchSearchFactory())
|
|
171
|
+
catalog.register(QdrantVectorFactory())
|
|
172
|
+
catalog.register(RedisDataFactory())
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`muscles-data` core does not import these packages automatically. This keeps
|
|
176
|
+
framework startup small and avoids pulling database SDKs into projects that do
|
|
177
|
+
not need them. Registration is deliberately manual: projects install and
|
|
178
|
+
register only the adapter packages they need.
|
|
179
|
+
|
|
180
|
+
## Runtime API
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from muscles_data import DataRuntime
|
|
184
|
+
from muscles_data.config import DataConfig
|
|
185
|
+
from muscles_data.catalog import DataAdapterCatalog
|
|
186
|
+
from muscles_data.ports import KeyValuePort, VectorSearchPort
|
|
187
|
+
|
|
188
|
+
runtime = DataRuntime(
|
|
189
|
+
config=DataConfig.from_raw({
|
|
190
|
+
"data": {
|
|
191
|
+
"resources": {
|
|
192
|
+
"vector.docs": {"type": "memory_vector"},
|
|
193
|
+
"cache.default": {"type": "memory_kv"},
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}),
|
|
197
|
+
catalog=DataAdapterCatalog.with_defaults(),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
vector = runtime.require_port("vector.docs", VectorSearchPort)
|
|
201
|
+
cache = runtime.require_port("cache.default", KeyValuePort)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Adapters are initialized lazily: package init and resource listing do not open
|
|
205
|
+
connections. Concrete adapters are created by:
|
|
206
|
+
|
|
207
|
+
- `runtime.require_port(name, PortType)`;
|
|
208
|
+
- `runtime.require_resource(name, capability)`;
|
|
209
|
+
- `runtime.doctor()` when health checks are enabled.
|
|
210
|
+
|
|
211
|
+
## SQL Resources
|
|
212
|
+
|
|
213
|
+
`type: sql` is the only real backend bridge built into core. It delegates to a
|
|
214
|
+
named registry owned by `muscles-sql` or by the project:
|
|
215
|
+
|
|
216
|
+
```yaml
|
|
217
|
+
data:
|
|
218
|
+
resources:
|
|
219
|
+
sql.documents:
|
|
220
|
+
type: sql
|
|
221
|
+
connection: documents_metadata
|
|
222
|
+
role: read_write
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
from muscles_data.ports import SqlResourcePort
|
|
227
|
+
|
|
228
|
+
sql = runtime.require_port("sql.documents", SqlResourcePort)
|
|
229
|
+
with sql.session() as session:
|
|
230
|
+
...
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`muscles-data` does not create SQL engines, repositories, Unit of Work objects
|
|
234
|
+
or migrations. `session()`, `session_factory()`, `inspect()` and `doctor()`
|
|
235
|
+
delegate to the supplied registry.
|
|
236
|
+
|
|
237
|
+
Use `muscles-data-sqlalchemy` when a project wants direct SQLAlchemy sessions
|
|
238
|
+
through the same `SqlResourcePort` without using `muscles-sql`.
|
|
239
|
+
|
|
240
|
+
## Native Escape Hatch
|
|
241
|
+
|
|
242
|
+
The preferred path is always a typed port. A project may explicitly request a
|
|
243
|
+
native/internal backend handle only when the resource declares the
|
|
244
|
+
`native_client` capability:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from muscles_data import DataCapability
|
|
248
|
+
|
|
249
|
+
handle = runtime.require_resource("cache.default", DataCapability.NATIVE_CLIENT)
|
|
250
|
+
native = handle.native_client()
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
This is an advanced escape hatch for project-specific operations. Framework
|
|
254
|
+
packages should not build their primary logic on native clients. Native handles,
|
|
255
|
+
credentials and raw payloads are never included in inspect/doctor output.
|
|
256
|
+
|
|
257
|
+
## Actions
|
|
258
|
+
|
|
259
|
+
- `data.resources.list` — list configured resources, capabilities and lazy init
|
|
260
|
+
state without health checks.
|
|
261
|
+
- `data.resource.inspect` — inspect one resource with redacted options.
|
|
262
|
+
- `data.doctor` — validate factories and run safe health checks with partial
|
|
263
|
+
failure reporting.
|
|
264
|
+
|
|
265
|
+
All actions are normal Muscles actions registered through the core action
|
|
266
|
+
contract. Protocol packages see them through `ActionDispatcher`; there is no
|
|
267
|
+
package-specific protocol routing.
|
|
268
|
+
|
|
269
|
+
## Telemetry
|
|
270
|
+
|
|
271
|
+
`muscles-data` resolves telemetry through the neutral Muscles provider. It does
|
|
272
|
+
not import `muscles-otel` directly.
|
|
273
|
+
|
|
274
|
+
Safe attributes are resource name/type, capability, operation, status and safe
|
|
275
|
+
counts. Do not add DSNs, tokens, passwords, raw query payloads, document text,
|
|
276
|
+
object content, vector payloads or native clients to spans.
|
|
277
|
+
|
|
278
|
+
## Examples
|
|
279
|
+
|
|
280
|
+
Run the core smoke examples:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
PYTHONPATH=../muscles/src:src python3 examples/run_data_runtime.py
|
|
284
|
+
PYTHONPATH=../muscles/src:src python3 examples/run_sql_resource_port.py
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Real backend examples live in
|
|
288
|
+
[`muscular-example`](https://github.com/butkoden/muscular-example) as
|
|
289
|
+
`example_data_[adapter]_1` packages.
|
|
290
|
+
|
|
291
|
+
Run tests:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
PYTHONPATH=../muscles/src:src python3 -m pytest -q
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Run the real-backend integration suite locally (Docker and the optional SDKs
|
|
298
|
+
are required):
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
make data-integration-test
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The command starts isolated Elasticsearch, OpenSearch, Qdrant, Redis,
|
|
305
|
+
MongoDB, MinIO and PostgreSQL containers, runs every adapter's contract and
|
|
306
|
+
backend lifecycle tests, and removes the containers on exit. Set
|
|
307
|
+
`MUSCLES_DATA_PYTHON` to use a different Python environment. The compose file
|
|
308
|
+
and port map are documented in
|
|
309
|
+
[`infra/docker-compose.integration.yml`](infra/docker-compose.integration.yml).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "muscles-data"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Framework-level named data resources and typed ports for Muscles"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "Denis B.", email = "denis@butko.info" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"muscles>=1.0.0rc1,<2.0.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=8.0,<9.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[tool.pytest.ini_options]
|
|
23
|
+
testpaths = ["tests"]
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
package-dir = {"" = "src"}
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
where = ["src"]
|