exdrf-rcv 0.1.17__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.
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: exdrf-rcv
3
+ Version: 0.1.17
4
+ Summary: Shared Python runtime for remote-controlled-view backends and exdrf-gen-al2rcv-generated modules.
5
+ Author-email: Nicu Tofan <nicu.tofan@gmail.com>
6
+ License-Expression: MIT
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: Programming Language :: Python :: 3 :: Only
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Typing :: Typed
11
+ Requires-Python: >=3.12.2
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: exdrf>=0.1.17
14
+ Provides-Extra: dev
15
+ Requires-Dist: autoflake; extra == "dev"
16
+ Requires-Dist: black; extra == "dev"
17
+ Requires-Dist: build; extra == "dev"
18
+ Requires-Dist: flake8; extra == "dev"
19
+ Requires-Dist: isort; extra == "dev"
20
+ Requires-Dist: mypy; extra == "dev"
21
+ Requires-Dist: pre-commit; extra == "dev"
22
+ Requires-Dist: pyproject-flake8; extra == "dev"
23
+ Requires-Dist: pytest-cov; extra == "dev"
24
+ Requires-Dist: pytest-mock; extra == "dev"
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Requires-Dist: twine; extra == "dev"
27
+ Requires-Dist: wheel; extra == "dev"
28
+
29
+ # exdrf-rcv
30
+
31
+ The **remote-controlled-view (RCV)** stack renders **exdrf** resources and
32
+ related data into a user interface that the front end drives over HTTP.
33
+
34
+ **exdrf-rcv** is the **shared Python runtime** for RCV *backends*. It defines
35
+ **`RcvPlan`**, discriminated **`RcvField`** types, and helpers that
36
+ **`exdrf-gen-al2rcv`**-generated route modules import next to your FastAPI app.
37
+
38
+ The browser UI that consumes those endpoints lives in **fr-one** under
39
+ **`libs/rcv`**; this package stays on the **exdrf** / API side of that boundary.
40
+
41
+ Python **3.12.2+** is required. Install next to **exdrf** in the same
42
+ environment as **exdrf-gen-al2rcv** output.
43
+
44
+ ## Related packages
45
+
46
+ - **exdrf-gen-al2rcv** — emits `{resource}_rcv_paths.py` scaffolds and root
47
+ **`api.py`** wired to your **`--get-db`** callable.
48
+ - **exdrf-gen-al2r** — sibling FastAPI router codegen; similar category layout.
@@ -0,0 +1,20 @@
1
+ # exdrf-rcv
2
+
3
+ The **remote-controlled-view (RCV)** stack renders **exdrf** resources and
4
+ related data into a user interface that the front end drives over HTTP.
5
+
6
+ **exdrf-rcv** is the **shared Python runtime** for RCV *backends*. It defines
7
+ **`RcvPlan`**, discriminated **`RcvField`** types, and helpers that
8
+ **`exdrf-gen-al2rcv`**-generated route modules import next to your FastAPI app.
9
+
10
+ The browser UI that consumes those endpoints lives in **fr-one** under
11
+ **`libs/rcv`**; this package stays on the **exdrf** / API side of that boundary.
12
+
13
+ Python **3.12.2+** is required. Install next to **exdrf** in the same
14
+ environment as **exdrf-gen-al2rcv** output.
15
+
16
+ ## Related packages
17
+
18
+ - **exdrf-gen-al2rcv** — emits `{resource}_rcv_paths.py` scaffolds and root
19
+ **`api.py`** wired to your **`--get-db`** callable.
20
+ - **exdrf-gen-al2r** — sibling FastAPI router codegen; similar category layout.
@@ -0,0 +1,30 @@
1
+ """Shared runtime for remote-controlled-view (RCV) backends.
2
+
3
+ ``exdrf-gen-al2rcv`` emits modules that should import shared symbols from this
4
+ package as RCV behavior grows. The front-end RCV project is documented under
5
+ ``fr-one`` ``libs/rcv``.
6
+ """
7
+
8
+ from exdrf_rcv.models import RcvField, RcvPlan, RcvResourceDataAccess
9
+ from exdrf_rcv.plan_resolve import (
10
+ RcvPlanCache,
11
+ RcvPlanCacheKey,
12
+ clear_rcv_plan_overrides,
13
+ default_rcv_plan_cache,
14
+ register_rcv_plan_override,
15
+ resolve_rcv_plan,
16
+ unregister_rcv_plan_override,
17
+ )
18
+
19
+ __all__ = [
20
+ "RcvPlan",
21
+ "RcvField",
22
+ "RcvResourceDataAccess",
23
+ "RcvPlanCache",
24
+ "RcvPlanCacheKey",
25
+ "clear_rcv_plan_overrides",
26
+ "default_rcv_plan_cache",
27
+ "register_rcv_plan_override",
28
+ "resolve_rcv_plan",
29
+ "unregister_rcv_plan_override",
30
+ ]
@@ -0,0 +1,24 @@
1
+ """Package version from PEP 621 or installed metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from exdrf.pep621_version import distribution_version, version_tuple_from_string
8
+
9
+ _PYPROJECT = Path(__file__).resolve().parents[1] / "pyproject.toml"
10
+ _DIST_NAME = "exdrf-rcv"
11
+
12
+ __version__ = version = distribution_version(_DIST_NAME, _PYPROJECT)
13
+ __version_tuple__ = version_tuple = version_tuple_from_string(__version__)
14
+
15
+ __commit_id__ = commit_id = None
16
+
17
+ __all__ = [
18
+ "__version__",
19
+ "__version_tuple__",
20
+ "version",
21
+ "version_tuple",
22
+ "__commit_id__",
23
+ "commit_id",
24
+ ]
@@ -0,0 +1,435 @@
1
+ """Pydantic wire models for remote-controlled-view plans and field metadata.
2
+
3
+ ``kind`` values mirror ``FIELD_TYPE_*`` in ``exdrf.constants``; ``data`` carries
4
+ type-specific options aligned with ``exdrf.field_types`` ``*Field`` /
5
+ ``*Info`` shapes.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from datetime import date, datetime, time
11
+ from typing import Annotated, Any, Literal, Union
12
+
13
+ from pydantic import BaseModel, ConfigDict, Field
14
+
15
+ from exdrf.constants import (
16
+ FIELD_TYPE_BLOB,
17
+ FIELD_TYPE_BOOL,
18
+ FIELD_TYPE_DATE,
19
+ FIELD_TYPE_DT,
20
+ FIELD_TYPE_DURATION,
21
+ FIELD_TYPE_ENUM,
22
+ FIELD_TYPE_FILTER,
23
+ FIELD_TYPE_FLOAT,
24
+ FIELD_TYPE_FLOAT_LIST,
25
+ FIELD_TYPE_FORMATTED,
26
+ FIELD_TYPE_INT_LIST,
27
+ FIELD_TYPE_INTEGER,
28
+ FIELD_TYPE_REF_MANY_TO_MANY,
29
+ FIELD_TYPE_REF_MANY_TO_ONE,
30
+ FIELD_TYPE_REF_ONE_TO_MANY,
31
+ FIELD_TYPE_REF_ONE_TO_ONE,
32
+ FIELD_TYPE_SORT,
33
+ FIELD_TYPE_STRING,
34
+ FIELD_TYPE_STRING_LIST,
35
+ FIELD_TYPE_TIME,
36
+ RelType,
37
+ )
38
+
39
+
40
+ class RcvFieldBase(BaseModel):
41
+ """Shared metadata for one field in an ``RcvPlan``.
42
+
43
+ Attributes:
44
+ name: Field name on the resource (snake_case).
45
+ title: Human-facing title when not inferred from ``name``.
46
+ description: Longer help text for the field.
47
+ category: Short logical grouping for layout.
48
+ pos_hint: UI ordering / placement hint from ``FieldInfo``.
49
+ required: Whether a value must be present for save/submit.
50
+ default: Wire default when omitted (type depends on ``kind``).
51
+ primary: Whether the field participates in record identity.
52
+ visible: Whether the field is shown to the user.
53
+ read_only: Whether the value is displayed but not editable.
54
+ nullable: Whether the underlying column allows null.
55
+ sortable: Whether list views may sort by this field.
56
+ filterable: Whether list views may filter by this field.
57
+ exportable: Whether user export may include this field.
58
+ qsearch: Whether quick-search uses this field.
59
+ resizable: Whether list column width is user-resizable.
60
+ derived: Optional ``(derivation_kind, source_field_name)`` tuple.
61
+ """
62
+
63
+ model_config = ConfigDict(extra="forbid")
64
+
65
+ name: str
66
+ title: str | None = None
67
+ description: str | None = None
68
+ category: str | None = None
69
+ pos_hint: str | None = None
70
+ required: bool = False
71
+ default: Any | None = None
72
+ primary: bool | None = None
73
+ visible: bool | None = None
74
+ read_only: bool | None = None
75
+ nullable: bool | None = None
76
+ sortable: bool | None = None
77
+ filterable: bool | None = None
78
+ exportable: bool | None = None
79
+ qsearch: bool | None = None
80
+ resizable: bool | None = None
81
+ derived: tuple[str, str] | None = None
82
+
83
+
84
+ class RcvEmptyData(BaseModel):
85
+ """Placeholder ``data`` for field kinds without extra options."""
86
+
87
+ model_config = ConfigDict(extra="forbid")
88
+
89
+
90
+ class RcvBlobFieldData(BaseModel):
91
+ """Options for ``FIELD_TYPE_BLOB`` (see ``BlobInfo``)."""
92
+
93
+ model_config = ConfigDict(extra="forbid")
94
+
95
+ mime_type: str | None = None
96
+
97
+
98
+ class RcvBoolFieldData(BaseModel):
99
+ """Options for ``FIELD_TYPE_BOOL`` (see ``BoolInfo``)."""
100
+
101
+ model_config = ConfigDict(extra="forbid")
102
+
103
+ true_str: str | None = None
104
+ false_str: str | None = None
105
+
106
+
107
+ class RcvStringFieldData(BaseModel):
108
+ """Options for string-like kinds (see ``StrInfo`` / ``StrListInfo``)."""
109
+
110
+ model_config = ConfigDict(extra="forbid")
111
+
112
+ multiline: bool | None = None
113
+ min_length: int | None = None
114
+ max_length: int | None = None
115
+ enum_values: list[tuple[str, str]] = Field(default_factory=list)
116
+ no_dia_field: str | None = None
117
+
118
+
119
+ class RcvFormattedFieldData(RcvStringFieldData):
120
+ """Options for ``FIELD_TYPE_FORMATTED`` (see ``FormattedInfo``)."""
121
+
122
+ format: Literal["json", "html", "xml"] | None = None
123
+
124
+
125
+ class RcvIntFieldData(BaseModel):
126
+ """Options for ``FIELD_TYPE_INTEGER`` / ``FIELD_TYPE_INT_LIST``."""
127
+
128
+ model_config = ConfigDict(extra="forbid")
129
+
130
+ min: int | None = None
131
+ max: int | None = None
132
+ unit: str | None = None
133
+ unit_symbol: str | None = None
134
+ enum_values: list[tuple[int, str]] = Field(default_factory=list)
135
+
136
+
137
+ class RcvFloatFieldData(BaseModel):
138
+ """Options for ``FIELD_TYPE_FLOAT`` / ``FIELD_TYPE_FLOAT_LIST``."""
139
+
140
+ model_config = ConfigDict(extra="forbid")
141
+
142
+ min: float | None = None
143
+ max: float | None = None
144
+ precision: int | None = None
145
+ scale: int | None = None
146
+ unit: str | None = None
147
+ unit_symbol: str | None = None
148
+ enum_values: list[tuple[float, str]] = Field(default_factory=list)
149
+
150
+
151
+ class RcvDateFieldData(BaseModel):
152
+ """Options for ``FIELD_TYPE_DATE`` (see ``DateInfo`` + ``DateField``)."""
153
+
154
+ model_config = ConfigDict(extra="forbid")
155
+
156
+ min: date | None = None
157
+ max: date | None = None
158
+ format: str | None = None
159
+
160
+
161
+ class RcvDateTimeFieldData(BaseModel):
162
+ """Options for ``FIELD_TYPE_DT`` (``DateTimeInfo`` / ``DateTimeField``)."""
163
+
164
+ model_config = ConfigDict(extra="forbid")
165
+
166
+ min: datetime | None = None
167
+ max: datetime | None = None
168
+ format: str | None = None
169
+
170
+
171
+ class RcvTimeFieldData(BaseModel):
172
+ """Options for ``FIELD_TYPE_TIME`` (see ``TimeInfo`` + ``TimeField``)."""
173
+
174
+ model_config = ConfigDict(extra="forbid")
175
+
176
+ min: time | None = None
177
+ max: time | None = None
178
+ format: str | None = None
179
+
180
+
181
+ class RcvDurationFieldData(BaseModel):
182
+ """Options for ``FIELD_TYPE_DURATION`` (see ``DurationInfo``)."""
183
+
184
+ model_config = ConfigDict(extra="forbid")
185
+
186
+ min: float | None = None
187
+ max: float | None = None
188
+
189
+
190
+ class RcvEnumFieldData(BaseModel):
191
+ """Options for ``FIELD_TYPE_ENUM`` (see ``EnumInfo`` / ``EnumField``)."""
192
+
193
+ model_config = ConfigDict(extra="forbid")
194
+
195
+ enum_values: list[str] = Field(default_factory=list)
196
+
197
+
198
+ class RcvRefFieldData(BaseModel):
199
+ """Options for relation kinds (see ``RelExtraInfo`` / ``RefBaseField``)."""
200
+
201
+ model_config = ConfigDict(extra="forbid")
202
+
203
+ ref: str
204
+ direction: RelType | None = None
205
+ subordinate: bool | None = None
206
+ expect_lots: bool | None = None
207
+ provides: list[str] = Field(default_factory=list)
208
+ depends_on: list[tuple[str, str]] = Field(default_factory=list)
209
+ bridge: str | None = None
210
+
211
+
212
+ class RcvBlobField(RcvFieldBase):
213
+ """RCV descriptor for a blob column."""
214
+
215
+ kind: Literal["blob"] = FIELD_TYPE_BLOB
216
+ data: RcvBlobFieldData = Field(default_factory=RcvBlobFieldData)
217
+
218
+
219
+ class RcvBoolField(RcvFieldBase):
220
+ """RCV descriptor for a boolean column."""
221
+
222
+ kind: Literal["bool"] = FIELD_TYPE_BOOL
223
+ data: RcvBoolFieldData = Field(default_factory=RcvBoolFieldData)
224
+
225
+
226
+ class RcvDateField(RcvFieldBase):
227
+ """RCV descriptor for a date column."""
228
+
229
+ kind: Literal["date"] = FIELD_TYPE_DATE
230
+ data: RcvDateFieldData = Field(default_factory=RcvDateFieldData)
231
+
232
+
233
+ class RcvDateTimeField(RcvFieldBase):
234
+ """RCV descriptor for a date-time column."""
235
+
236
+ kind: Literal["date-time"] = FIELD_TYPE_DT
237
+ data: RcvDateTimeFieldData = Field(default_factory=RcvDateTimeFieldData)
238
+
239
+
240
+ class RcvDurationField(RcvFieldBase):
241
+ """RCV descriptor for a duration column."""
242
+
243
+ kind: Literal["duration"] = FIELD_TYPE_DURATION
244
+ data: RcvDurationFieldData = Field(default_factory=RcvDurationFieldData)
245
+
246
+
247
+ class RcvEnumField(RcvFieldBase):
248
+ """RCV descriptor for an enum-like column."""
249
+
250
+ kind: Literal["enum"] = FIELD_TYPE_ENUM
251
+ data: RcvEnumFieldData = Field(default_factory=RcvEnumFieldData)
252
+
253
+
254
+ class RcvFilterField(RcvFieldBase):
255
+ """RCV descriptor for a filter pseudo-field."""
256
+
257
+ kind: Literal["filter"] = FIELD_TYPE_FILTER
258
+ data: RcvEmptyData = Field(default_factory=RcvEmptyData)
259
+
260
+
261
+ class RcvFloatField(RcvFieldBase):
262
+ """RCV descriptor for a float column."""
263
+
264
+ kind: Literal["float"] = FIELD_TYPE_FLOAT
265
+ data: RcvFloatFieldData = Field(default_factory=RcvFloatFieldData)
266
+
267
+
268
+ class RcvFloatListField(RcvFieldBase):
269
+ """RCV descriptor for a float list column."""
270
+
271
+ kind: Literal["float-list"] = FIELD_TYPE_FLOAT_LIST
272
+ data: RcvFloatFieldData = Field(default_factory=RcvFloatFieldData)
273
+
274
+
275
+ class RcvFormattedField(RcvFieldBase):
276
+ """RCV descriptor for formatted (rich text / markup) string columns."""
277
+
278
+ kind: Literal["formatted"] = FIELD_TYPE_FORMATTED
279
+ data: RcvFormattedFieldData = Field(default_factory=RcvFormattedFieldData)
280
+
281
+
282
+ class RcvIntField(RcvFieldBase):
283
+ """RCV descriptor for an integer column."""
284
+
285
+ kind: Literal["integer"] = FIELD_TYPE_INTEGER
286
+ data: RcvIntFieldData = Field(default_factory=RcvIntFieldData)
287
+
288
+
289
+ class RcvIntListField(RcvFieldBase):
290
+ """RCV descriptor for an integer list column."""
291
+
292
+ kind: Literal["int-list"] = FIELD_TYPE_INT_LIST
293
+ data: RcvIntFieldData = Field(default_factory=RcvIntFieldData)
294
+
295
+
296
+ class RcvManyToManyField(RcvFieldBase):
297
+ """RCV descriptor for a many-to-many relation field."""
298
+
299
+ kind: Literal["many-to-many"] = FIELD_TYPE_REF_MANY_TO_MANY
300
+ data: RcvRefFieldData
301
+
302
+
303
+ class RcvManyToOneField(RcvFieldBase):
304
+ """RCV descriptor for a many-to-one relation field."""
305
+
306
+ kind: Literal["many-to-one"] = FIELD_TYPE_REF_MANY_TO_ONE
307
+ data: RcvRefFieldData
308
+
309
+
310
+ class RcvOneToManyField(RcvFieldBase):
311
+ """RCV descriptor for a one-to-many relation field."""
312
+
313
+ kind: Literal["one-to-many"] = FIELD_TYPE_REF_ONE_TO_MANY
314
+ data: RcvRefFieldData
315
+
316
+
317
+ class RcvOneToOneField(RcvFieldBase):
318
+ """RCV descriptor for a one-to-one relation field."""
319
+
320
+ kind: Literal["one-to-one"] = FIELD_TYPE_REF_ONE_TO_ONE
321
+ data: RcvRefFieldData
322
+
323
+
324
+ class RcvStringField(RcvFieldBase):
325
+ """RCV descriptor for a string column."""
326
+
327
+ kind: Literal["string"] = FIELD_TYPE_STRING
328
+ data: RcvStringFieldData = Field(default_factory=RcvStringFieldData)
329
+
330
+
331
+ class RcvStringListField(RcvFieldBase):
332
+ """RCV descriptor for a string list column."""
333
+
334
+ kind: Literal["string-list"] = FIELD_TYPE_STRING_LIST
335
+ data: RcvStringFieldData = Field(default_factory=RcvStringFieldData)
336
+
337
+
338
+ class RcvSortField(RcvFieldBase):
339
+ """RCV descriptor for a sort pseudo-field."""
340
+
341
+ kind: Literal["sort"] = FIELD_TYPE_SORT
342
+ data: RcvEmptyData = Field(default_factory=RcvEmptyData)
343
+
344
+
345
+ class RcvTimeField(RcvFieldBase):
346
+ """RCV descriptor for a time-of-day column."""
347
+
348
+ kind: Literal["time"] = FIELD_TYPE_TIME
349
+ data: RcvTimeFieldData = Field(default_factory=RcvTimeFieldData)
350
+
351
+
352
+ RcvField = Annotated[
353
+ Union[
354
+ RcvBlobField,
355
+ RcvBoolField,
356
+ RcvDateField,
357
+ RcvDateTimeField,
358
+ RcvDurationField,
359
+ RcvEnumField,
360
+ RcvFilterField,
361
+ RcvFloatField,
362
+ RcvFloatListField,
363
+ RcvFormattedField,
364
+ RcvIntField,
365
+ RcvIntListField,
366
+ RcvManyToManyField,
367
+ RcvManyToOneField,
368
+ RcvOneToManyField,
369
+ RcvOneToOneField,
370
+ RcvSortField,
371
+ RcvStringField,
372
+ RcvStringListField,
373
+ RcvTimeField,
374
+ ],
375
+ Field(discriminator="kind"),
376
+ ]
377
+
378
+
379
+ class RcvResourceDataAccess(BaseModel):
380
+ """Where and how to load row payloads for this plan over HTTP.
381
+
382
+ Used for list data, edit/view refresh, and similar flows. The client should
383
+ prepend its API base (including ``root_path``) to ``url_pattern`` when
384
+ building the final request URL.
385
+
386
+ Attributes:
387
+ url_pattern: Path under the API root, typically an exdrf-gen-al2r list
388
+ GET path such as ``/classic/{category}/{resource_plural}/``. May later include
389
+ placeholders (for example ``{record_id}``) documented alongside
390
+ those features.
391
+ requires_org_id: When true, the client must include an ``org_id`` query
392
+ parameter (or the convention documented for this deployment) when
393
+ calling ``url_pattern``.
394
+ requires_town_id: When true, the client must include a ``town_id`` query
395
+ parameter when calling ``url_pattern``.
396
+ """
397
+
398
+ model_config = ConfigDict(extra="forbid")
399
+
400
+ url_pattern: str
401
+ requires_org_id: bool = False
402
+ requires_town_id: bool = False
403
+
404
+
405
+ class RcvPlan(BaseModel):
406
+ """A plan for rendering a resource, record and view type.
407
+
408
+ Attributes:
409
+ category: The category of the resource. In most cases this will be the
410
+ same as the value passed by the front-end.
411
+ resource: The name of the resource. In most cases this will be the
412
+ same as the value passed by the front-end.
413
+ record_id: The ID of the record. In most cases this will be the
414
+ same as the value passed by the front-end.
415
+ view_type: The type of view that is requested. In most cases this
416
+ will be the same as the value passed by the front-end.
417
+ render_type: The type of renderer to use. When a generated module omits
418
+ ``RCV_RENDER_TYPE`` or sets it to the placeholder ``"default"``,
419
+ :func:`exdrf_rcv.plan_resolve.resolve_rcv_plan` sets this to the
420
+ same value as ``view_type``.
421
+ fields: The fields to render.
422
+ resource_data_access: Optional hint for loading tabular row data or
423
+ refreshing a record from HTTP; omitted when the generated module
424
+ does not define ``RCV_RESOURCE_DATA_ACCESS``.
425
+ """
426
+
427
+ model_config = ConfigDict(extra="forbid")
428
+
429
+ category: str | None = None
430
+ resource: str | None = None
431
+ record_id: int | None = None
432
+ view_type: str | None = None
433
+ render_type: str
434
+ fields: list[RcvField]
435
+ resource_data_access: RcvResourceDataAccess | None = None
@@ -0,0 +1,10 @@
1
+ """RCV plan types shared between FastAPI routes and ``exdrf_rcv`` consumers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from exdrf_rcv.models import RcvPlan, RcvResourceDataAccess
6
+
7
+ __all__ = [
8
+ "RcvPlan",
9
+ "RcvResourceDataAccess",
10
+ ]