pydoptic 0.0.2.post1.dev1__tar.gz → 0.0.2.post1.dev2__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.
Files changed (21) hide show
  1. {pydoptic-0.0.2.post1.dev1/src/pydoptic.egg-info → pydoptic-0.0.2.post1.dev2}/PKG-INFO +11 -11
  2. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/README.md +10 -10
  3. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/src/pydoptic/__init__.py +2 -2
  4. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/src/pydoptic/base_model.py +17 -17
  5. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2/src/pydoptic.egg-info}/PKG-INFO +11 -11
  6. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/src/pydoptic.egg-info/SOURCES.txt +1 -1
  7. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/src/pydoptic.egg-info/scm_file_list.json +1 -1
  8. pydoptic-0.0.2.post1.dev2/src/pydoptic.egg-info/scm_version.json +8 -0
  9. pydoptic-0.0.2.post1.dev1/test/test_partial_model.py → pydoptic-0.0.2.post1.dev2/test/test_partial.py +27 -27
  10. pydoptic-0.0.2.post1.dev1/src/pydoptic.egg-info/scm_version.json +0 -8
  11. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/LICENSE +0 -0
  12. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/pyproject.toml +0 -0
  13. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/setup.cfg +0 -0
  14. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/src/pydoptic/py.typed +0 -0
  15. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/src/pydoptic/selector.py +0 -0
  16. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/src/pydoptic/validate_types.py +0 -0
  17. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/src/pydoptic.egg-info/dependency_links.txt +0 -0
  18. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/src/pydoptic.egg-info/requires.txt +0 -0
  19. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/src/pydoptic.egg-info/top_level.txt +0 -0
  20. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/test/test_base_model.py +0 -0
  21. {pydoptic-0.0.2.post1.dev1 → pydoptic-0.0.2.post1.dev2}/test/test_selector.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydoptic
3
- Version: 0.0.2.post1.dev1
3
+ Version: 0.0.2.post1.dev2
4
4
  Summary: Model data using reified optics
5
5
  Author: John Hungerford
6
6
  License-Expression: MIT
@@ -35,7 +35,7 @@ The examples below assume this import, unless a different one is shown:
35
35
 
36
36
  ```python3
37
37
  from pydoptic import (
38
- BaseModel, PartialModel, select,
38
+ BaseModel, Partial, select,
39
39
  Discrim, Prop, PropArr, PropOpt, PropOptArr, PropSelect,
40
40
  Select, SelectArr, SelectOpt, SelectOptArr, SelectVal, SelectValue,
41
41
  )
@@ -87,8 +87,8 @@ model_1_kw: Model2 = Model2(prop_one=42, prop_two="hello")
87
87
  model_2_pr: Model2 = Model2.construct(Model1.prop_1.param(42), Model1.prop_2.param("hello"))
88
88
 
89
89
  # Partial model
90
- model_1_partial_kw: PartialModel[Model2] = Model2.partial(prop_one=42)
91
- model_1_partial_pr: PartialModel[Model2] = Model2.construct_partial(
90
+ model_1_partial_kw: Partial[Model2] = Model2.partial(prop_one=42)
91
+ model_1_partial_pr: Partial[Model2] = Model2.construct_partial(
92
92
  Model.prop_2.param("hello"),
93
93
  )
94
94
 
@@ -221,7 +221,7 @@ model: MyModel = MyModel(prop_1='value', prop_2=23, ...)
221
221
  dict_value: Dict[str, Any] = model.as_dict_full()
222
222
  str_value: str = json.dumps(dict_value)
223
223
 
224
- partial_model: PartialModel[MyModel] = MyModel.partial(prop_1='value', prop_2=23, ...)
224
+ partial_model: Partial[MyModel] = MyModel.partial(prop_1='value', prop_2=23, ...)
225
225
  partial_dict: Dict[str, Any] = partial_model.as_dict_full()
226
226
  partial_str: str = json.dumps(partial_dict)
227
227
  ```
@@ -237,7 +237,7 @@ model: MyModel = MyModel(**dict_value)
237
237
 
238
238
  partial_str: str = ???
239
239
  partial_dict: Dict[str, Any] = json.loads(partial_str)
240
- partial_model: PartialModel[MyModel] = MyModel.partial(**partial_dict)
240
+ partial_model: Partial[MyModel] = MyModel.partial(**partial_dict)
241
241
  ```
242
242
 
243
243
  Note that given how data can be read from and written to objects, there is no necessity that you validate to models or even partial models at all. My own preferred workflow is to use models for ingestion only -- that is, to consume external data and convert them to my domain in the form of Pydoptic models, and then feed these into the data backend. Once valid data is persisted in a backend, it can then retrieved and manipulated in the form of `dict`s, without ever having to generate a full model. This is a matter of preference, however.
@@ -305,9 +305,9 @@ Conversely, if you only wanted to contact one POC (and you don't care whether th
305
305
 
306
306
  In the above case, if there are many `Person`s selected byte `poc_select`, only the first one will be returned. If none are, `poc` will be `None`.
307
307
 
308
- To retrieve values from an incomplete representation of the data, such as a `PartialModel` or a `dict`, there are two variants of `get` that you can use. `get_unsafe` will try to retrieve the selected value and fail with a `ValueError` if it's inaccessible due to invalid data (if its inaccessible due to a valid optional property being missing, it will generate an empty `SelectValue` as expected). `get_safe`, on the other hand, will always fail silently, generating empty `SelectValue` when data is missing.
308
+ To retrieve values from an incomplete representation of the data, such as a `Partial` or a `dict`, there are two variants of `get` that you can use. `get_unsafe` will try to retrieve the selected value and fail with a `ValueError` if it's inaccessible due to invalid data (if its inaccessible due to a valid optional property being missing, it will generate an empty `SelectValue` as expected). `get_safe`, on the other hand, will always fail silently, generating empty `SelectValue` when data is missing.
309
309
 
310
- Note that the type signature of `get_unsafe` and `get_safe` are a bit misleading. They will provide accurate types for non-model values (e.g., primitive properties like `int` or `str`), but they will treat nested models as though they are fully typed. When you retrieve a nested model from a `dict` or `PartialModel`, however, in all likelihood it is going to be another `dict` or `PartialModel`.
310
+ Note that the type signature of `get_unsafe` and `get_safe` are a bit misleading. They will provide accurate types for non-model values (e.g., primitive properties like `int` or `str`), but they will treat nested models as though they are fully typed. When you retrieve a nested model from a `dict` or `Partial`, however, in all likelihood it is going to be another `dict` or `Partial`.
311
311
 
312
312
  ```python3
313
313
  class Inner(BaseModel):
@@ -328,7 +328,7 @@ inner_result_full: SelectValue[Inner] = Outer.inner.get(full)
328
328
  partial_val = Outer.partial(inner=Inner.partial(prop=23))
329
329
  # Accurately typed
330
330
  prop_result_partial: SelectValue[int] = prop_select.get_unsafe(partial_val)
331
- # Inaccurate type! Should be SelectValue[PartialModel[Inner]]
331
+ # Inaccurate type! Should be SelectValue[Partial[Inner]]
332
332
  inner_result_partial: SelectValue[Inner] = Outer.inner.get_unsafe(partial_val)
333
333
 
334
334
  dict_val = {'inner': {'prop': 23}}
@@ -403,7 +403,7 @@ for arr in prop_arr_select.get(value).as_list:
403
403
  arr.append(len(arr))
404
404
  ```
405
405
 
406
- To update incomplete data, use `update_unsafe` and `update_safe`. `update_unsafe` will fail with an exception if it's unable to access the data to be updated due to invalid data, whereas `update_safe` will simply ignore (and therefore skip) invalid cases. Like `get`, you need to be careful when updating nested models with the partial variants of `update`. The types will appear to be complete models when they are most likely `PartialModel`s or `dict`s.
406
+ To update incomplete data, use `update_unsafe` and `update_safe`. `update_unsafe` will fail with an exception if it's unable to access the data to be updated due to invalid data, whereas `update_safe` will simply ignore (and therefore skip) invalid cases. Like `get`, you need to be careful when updating nested models with the partial variants of `update`. The types will appear to be complete models when they are most likely `Partial`s or `dict`s.
407
407
 
408
408
 
409
409
  #### `set`, `set_unsafe`, `set_safe`
@@ -478,7 +478,7 @@ print(value)
478
478
 
479
479
  In the above example, while `sel_opt` is able to clear the `opt` property, `sel_req` cannot clear the selected `req` property so instead it clears the next clearable property, setting `c` to an empty array.
480
480
 
481
- Clearing partial data is even more complicated, since partial data (`PartialModel`s and `dict`s) *can* be cleared even when its required in the model. For this reason, the partial variants of `clear` are distinguished into "strict" and "non-strict" variants. `clear_unsafe_strict` works just like `clear`: it will only clear clearable (optional/array) properties, and will try to clear selected property and work its way backward across the property chain until it finds a clearable property. `clear_unsafe`, on the other hand, will clear the selected property whether or not it's a required field. `clear_safe_strict` and `clear_safe` work the same way, but does not raise exceptions when they encounter invalid data, but simply skip it and move on.
481
+ Clearing partial data is even more complicated, since partial data (`Partial`s and `dict`s) *can* be cleared even when its required in the model. For this reason, the partial variants of `clear` are distinguished into "strict" and "non-strict" variants. `clear_unsafe_strict` works just like `clear`: it will only clear clearable (optional/array) properties, and will try to clear selected property and work its way backward across the property chain until it finds a clearable property. `clear_unsafe`, on the other hand, will clear the selected property whether or not it's a required field. `clear_safe_strict` and `clear_safe` work the same way, but does not raise exceptions when they encounter invalid data, but simply skip it and move on.
482
482
 
483
483
  #### Precise `Select`s
484
484
 
@@ -17,7 +17,7 @@ The examples below assume this import, unless a different one is shown:
17
17
 
18
18
  ```python3
19
19
  from pydoptic import (
20
- BaseModel, PartialModel, select,
20
+ BaseModel, Partial, select,
21
21
  Discrim, Prop, PropArr, PropOpt, PropOptArr, PropSelect,
22
22
  Select, SelectArr, SelectOpt, SelectOptArr, SelectVal, SelectValue,
23
23
  )
@@ -69,8 +69,8 @@ model_1_kw: Model2 = Model2(prop_one=42, prop_two="hello")
69
69
  model_2_pr: Model2 = Model2.construct(Model1.prop_1.param(42), Model1.prop_2.param("hello"))
70
70
 
71
71
  # Partial model
72
- model_1_partial_kw: PartialModel[Model2] = Model2.partial(prop_one=42)
73
- model_1_partial_pr: PartialModel[Model2] = Model2.construct_partial(
72
+ model_1_partial_kw: Partial[Model2] = Model2.partial(prop_one=42)
73
+ model_1_partial_pr: Partial[Model2] = Model2.construct_partial(
74
74
  Model.prop_2.param("hello"),
75
75
  )
76
76
 
@@ -203,7 +203,7 @@ model: MyModel = MyModel(prop_1='value', prop_2=23, ...)
203
203
  dict_value: Dict[str, Any] = model.as_dict_full()
204
204
  str_value: str = json.dumps(dict_value)
205
205
 
206
- partial_model: PartialModel[MyModel] = MyModel.partial(prop_1='value', prop_2=23, ...)
206
+ partial_model: Partial[MyModel] = MyModel.partial(prop_1='value', prop_2=23, ...)
207
207
  partial_dict: Dict[str, Any] = partial_model.as_dict_full()
208
208
  partial_str: str = json.dumps(partial_dict)
209
209
  ```
@@ -219,7 +219,7 @@ model: MyModel = MyModel(**dict_value)
219
219
 
220
220
  partial_str: str = ???
221
221
  partial_dict: Dict[str, Any] = json.loads(partial_str)
222
- partial_model: PartialModel[MyModel] = MyModel.partial(**partial_dict)
222
+ partial_model: Partial[MyModel] = MyModel.partial(**partial_dict)
223
223
  ```
224
224
 
225
225
  Note that given how data can be read from and written to objects, there is no necessity that you validate to models or even partial models at all. My own preferred workflow is to use models for ingestion only -- that is, to consume external data and convert them to my domain in the form of Pydoptic models, and then feed these into the data backend. Once valid data is persisted in a backend, it can then retrieved and manipulated in the form of `dict`s, without ever having to generate a full model. This is a matter of preference, however.
@@ -287,9 +287,9 @@ Conversely, if you only wanted to contact one POC (and you don't care whether th
287
287
 
288
288
  In the above case, if there are many `Person`s selected byte `poc_select`, only the first one will be returned. If none are, `poc` will be `None`.
289
289
 
290
- To retrieve values from an incomplete representation of the data, such as a `PartialModel` or a `dict`, there are two variants of `get` that you can use. `get_unsafe` will try to retrieve the selected value and fail with a `ValueError` if it's inaccessible due to invalid data (if its inaccessible due to a valid optional property being missing, it will generate an empty `SelectValue` as expected). `get_safe`, on the other hand, will always fail silently, generating empty `SelectValue` when data is missing.
290
+ To retrieve values from an incomplete representation of the data, such as a `Partial` or a `dict`, there are two variants of `get` that you can use. `get_unsafe` will try to retrieve the selected value and fail with a `ValueError` if it's inaccessible due to invalid data (if its inaccessible due to a valid optional property being missing, it will generate an empty `SelectValue` as expected). `get_safe`, on the other hand, will always fail silently, generating empty `SelectValue` when data is missing.
291
291
 
292
- Note that the type signature of `get_unsafe` and `get_safe` are a bit misleading. They will provide accurate types for non-model values (e.g., primitive properties like `int` or `str`), but they will treat nested models as though they are fully typed. When you retrieve a nested model from a `dict` or `PartialModel`, however, in all likelihood it is going to be another `dict` or `PartialModel`.
292
+ Note that the type signature of `get_unsafe` and `get_safe` are a bit misleading. They will provide accurate types for non-model values (e.g., primitive properties like `int` or `str`), but they will treat nested models as though they are fully typed. When you retrieve a nested model from a `dict` or `Partial`, however, in all likelihood it is going to be another `dict` or `Partial`.
293
293
 
294
294
  ```python3
295
295
  class Inner(BaseModel):
@@ -310,7 +310,7 @@ inner_result_full: SelectValue[Inner] = Outer.inner.get(full)
310
310
  partial_val = Outer.partial(inner=Inner.partial(prop=23))
311
311
  # Accurately typed
312
312
  prop_result_partial: SelectValue[int] = prop_select.get_unsafe(partial_val)
313
- # Inaccurate type! Should be SelectValue[PartialModel[Inner]]
313
+ # Inaccurate type! Should be SelectValue[Partial[Inner]]
314
314
  inner_result_partial: SelectValue[Inner] = Outer.inner.get_unsafe(partial_val)
315
315
 
316
316
  dict_val = {'inner': {'prop': 23}}
@@ -385,7 +385,7 @@ for arr in prop_arr_select.get(value).as_list:
385
385
  arr.append(len(arr))
386
386
  ```
387
387
 
388
- To update incomplete data, use `update_unsafe` and `update_safe`. `update_unsafe` will fail with an exception if it's unable to access the data to be updated due to invalid data, whereas `update_safe` will simply ignore (and therefore skip) invalid cases. Like `get`, you need to be careful when updating nested models with the partial variants of `update`. The types will appear to be complete models when they are most likely `PartialModel`s or `dict`s.
388
+ To update incomplete data, use `update_unsafe` and `update_safe`. `update_unsafe` will fail with an exception if it's unable to access the data to be updated due to invalid data, whereas `update_safe` will simply ignore (and therefore skip) invalid cases. Like `get`, you need to be careful when updating nested models with the partial variants of `update`. The types will appear to be complete models when they are most likely `Partial`s or `dict`s.
389
389
 
390
390
 
391
391
  #### `set`, `set_unsafe`, `set_safe`
@@ -460,7 +460,7 @@ print(value)
460
460
 
461
461
  In the above example, while `sel_opt` is able to clear the `opt` property, `sel_req` cannot clear the selected `req` property so instead it clears the next clearable property, setting `c` to an empty array.
462
462
 
463
- Clearing partial data is even more complicated, since partial data (`PartialModel`s and `dict`s) *can* be cleared even when its required in the model. For this reason, the partial variants of `clear` are distinguished into "strict" and "non-strict" variants. `clear_unsafe_strict` works just like `clear`: it will only clear clearable (optional/array) properties, and will try to clear selected property and work its way backward across the property chain until it finds a clearable property. `clear_unsafe`, on the other hand, will clear the selected property whether or not it's a required field. `clear_safe_strict` and `clear_safe` work the same way, but does not raise exceptions when they encounter invalid data, but simply skip it and move on.
463
+ Clearing partial data is even more complicated, since partial data (`Partial`s and `dict`s) *can* be cleared even when its required in the model. For this reason, the partial variants of `clear` are distinguished into "strict" and "non-strict" variants. `clear_unsafe_strict` works just like `clear`: it will only clear clearable (optional/array) properties, and will try to clear selected property and work its way backward across the property chain until it finds a clearable property. `clear_unsafe`, on the other hand, will clear the selected property whether or not it's a required field. `clear_safe_strict` and `clear_safe` work the same way, but does not raise exceptions when they encounter invalid data, but simply skip it and move on.
464
464
 
465
465
  #### Precise `Select`s
466
466
 
@@ -1,4 +1,4 @@
1
- from pydoptic.base_model import BaseModel, PartialModel, select
1
+ from pydoptic.base_model import BaseModel, Partial, select
2
2
  from pydoptic.selector import (
3
3
  Discrim,
4
4
  LinkedSelect,
@@ -25,7 +25,7 @@ from pydoptic.selector import (
25
25
  from pydoptic.validate_types import Validator
26
26
 
27
27
  __all__ = [
28
- 'BaseModel', 'PartialModel', 'select',
28
+ 'BaseModel', 'Partial', 'select',
29
29
  'Discrim', 'LinkedSelect', 'LinkedSelectArr', 'LinkedSelectOpt', 'LinkedSelectOptArr', 'LinkedSelectVal',
30
30
  'MatchSelect', 'ModelLike', 'Param', 'Prop', 'PropArr', 'PropOpt', 'PropOptArr', 'PropSelect',
31
31
  'Select', 'Selectable', 'SelectArr', 'SelectOpt', 'SelectOptArr', 'SelectVal', 'SelectValue',
@@ -172,7 +172,7 @@ class BaseModelMeta(type):
172
172
  def _fully_validate(target: Type[M], value: Any, validators: Dict[Type[Any], Validator]) -> M:
173
173
  if isinstance(value, target):
174
174
  return value
175
- elif isinstance(value, PartialModel):
175
+ elif isinstance(value, Partial):
176
176
  if not issubclass(value.model, target):
177
177
  raise ValueError(f'received partial model of {value.model} instead of expected model {target}')
178
178
  return target(**value.as_dict(), _allow_extra_args=True, _validators=validators)
@@ -187,7 +187,7 @@ class BaseModel(ModelLike, metaclass=BaseModelMeta):
187
187
 
188
188
  A Pydoptic model consists of `PropSelect` class attributes (`Prop`, `PropOpt`, `PropArr`, and `PropOptArr`) which
189
189
  (1) determine which instance attributes are supported, and (2) provide a mechanism for accessing and manipulating data
190
- both within instances and other, potentially incomplete data sources (e.g., `PartialModel` or `dict`)
190
+ both within instances and other, potentially incomplete data sources (e.g., `Partial` or `dict`)
191
191
 
192
192
  Instances are fully validated.
193
193
 
@@ -212,34 +212,34 @@ class BaseModel(ModelLike, metaclass=BaseModelMeta):
212
212
  return BaseModel._properties[cls]
213
213
 
214
214
  @classmethod
215
- def partial(cls, **kwargs) -> PartialModel[Self]:
215
+ def partial(cls, **kwargs) -> Partial[Self]:
216
216
  """
217
217
  Create a partial instance of this model with a subset of properties. Required properties can be
218
- omitted, but must be otherwise valid. See `PartialModel`.
218
+ omitted, but must be otherwise valid. See `Partial`.
219
219
  """
220
- return PartialModel(cls, **kwargs)
220
+ return Partial(cls, **kwargs)
221
221
 
222
222
  @classmethod
223
- def construct_partial(cls, *values: Param[Self, Any], **kwargs) -> PartialModel[Self]:
223
+ def construct_partial(cls, *values: Param[Self, Any], **kwargs) -> Partial[Self]:
224
224
  """
225
225
  Create a partial instance of this model using selectors to specify fields. Required properties can be
226
- omitted, but must be otherwise valid. See `PartialModel`.
226
+ omitted, but must be otherwise valid. See `Partial`.
227
227
  """
228
- return PartialModel(cls, **{p.label: p.value for p in values}, **kwargs)
228
+ return Partial(cls, **{p.label: p.value for p in values}, **kwargs)
229
229
 
230
230
  @classmethod
231
231
  def construct(cls, *values: Param[Self, Any], **kwargs) -> Self:
232
232
  """
233
233
  Create an instance of this model using selectors to specify fields instead of keyword arguments.
234
- Required properties can be omitted, but must be otherwise valid. See `PartialModel`.
234
+ Required properties can be omitted, but must be otherwise valid. See `Partial`.
235
235
  """
236
236
  return cls(**{p.label: p.value for p in values}, **kwargs)
237
237
 
238
- def as_partial(self) -> PartialModel[Self]:
238
+ def as_partial(self) -> Partial[Self]:
239
239
  """
240
- Convert to a `PartialModel`
240
+ Convert to a `Partial`
241
241
  """
242
- return PartialModel(self.__class__, **self.as_dict())
242
+ return Partial(self.__class__, **self.as_dict())
243
243
 
244
244
  def __init__(self, **kwargs):
245
245
  """
@@ -336,7 +336,7 @@ class BaseModel(ModelLike, metaclass=BaseModelMeta):
336
336
  ...
337
337
  return mapping
338
338
 
339
- def select_partial(self, *selectors: Select[Self, Any]) -> PartialModel[Self]:
339
+ def select_partial(self, *selectors: Select[Self, Any]) -> Partial[Self]:
340
340
  """
341
341
  Generate a partial version of this instance, selecting the data to retain with one or more `Select` instances.
342
342
  """
@@ -345,10 +345,10 @@ class BaseModel(ModelLike, metaclass=BaseModelMeta):
345
345
  selector.copy_to(self, data)
346
346
  return self.__class__.partial(**data)
347
347
 
348
- def _partly_validate(target: Type[M], value: Any, validators: Dict[Type[Any], Validator]) -> M | PartialModel[M]:
348
+ def _partly_validate(target: Type[M], value: Any, validators: Dict[Type[Any], Validator]) -> M | Partial[M]:
349
349
  if isinstance(value, target):
350
350
  return value
351
- elif isinstance(value, PartialModel):
351
+ elif isinstance(value, Partial):
352
352
  if not issubclass(value.model, target):
353
353
  raise ValueError(f'received partial model of {value.model} instead of expected model {target}')
354
354
  return value
@@ -359,7 +359,7 @@ def _partly_validate(target: Type[M], value: Any, validators: Dict[Type[Any], Va
359
359
 
360
360
  M = TypeVar('M', bound=BaseModel)
361
361
 
362
- class PartialModel(Generic[M], Selectable[M]):
362
+ class Partial(Generic[M], Selectable[M]):
363
363
  """
364
364
  An incomplete version of model instances. Validates data without requiring that all required properties are present.
365
365
  """
@@ -453,7 +453,7 @@ class PartialModel(Generic[M], Selectable[M]):
453
453
  def as_dict(self) -> Mapping[str, Any]:
454
454
  return self._dict
455
455
 
456
- def select_partial(self, *selectors: Select[M, Any]) -> PartialModel[M]:
456
+ def select_partial(self, *selectors: Select[M, Any]) -> Partial[M]:
457
457
  """
458
458
  Generate another partial instance, selecting the data to retain with one or more `Select` instances.
459
459
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydoptic
3
- Version: 0.0.2.post1.dev1
3
+ Version: 0.0.2.post1.dev2
4
4
  Summary: Model data using reified optics
5
5
  Author: John Hungerford
6
6
  License-Expression: MIT
@@ -35,7 +35,7 @@ The examples below assume this import, unless a different one is shown:
35
35
 
36
36
  ```python3
37
37
  from pydoptic import (
38
- BaseModel, PartialModel, select,
38
+ BaseModel, Partial, select,
39
39
  Discrim, Prop, PropArr, PropOpt, PropOptArr, PropSelect,
40
40
  Select, SelectArr, SelectOpt, SelectOptArr, SelectVal, SelectValue,
41
41
  )
@@ -87,8 +87,8 @@ model_1_kw: Model2 = Model2(prop_one=42, prop_two="hello")
87
87
  model_2_pr: Model2 = Model2.construct(Model1.prop_1.param(42), Model1.prop_2.param("hello"))
88
88
 
89
89
  # Partial model
90
- model_1_partial_kw: PartialModel[Model2] = Model2.partial(prop_one=42)
91
- model_1_partial_pr: PartialModel[Model2] = Model2.construct_partial(
90
+ model_1_partial_kw: Partial[Model2] = Model2.partial(prop_one=42)
91
+ model_1_partial_pr: Partial[Model2] = Model2.construct_partial(
92
92
  Model.prop_2.param("hello"),
93
93
  )
94
94
 
@@ -221,7 +221,7 @@ model: MyModel = MyModel(prop_1='value', prop_2=23, ...)
221
221
  dict_value: Dict[str, Any] = model.as_dict_full()
222
222
  str_value: str = json.dumps(dict_value)
223
223
 
224
- partial_model: PartialModel[MyModel] = MyModel.partial(prop_1='value', prop_2=23, ...)
224
+ partial_model: Partial[MyModel] = MyModel.partial(prop_1='value', prop_2=23, ...)
225
225
  partial_dict: Dict[str, Any] = partial_model.as_dict_full()
226
226
  partial_str: str = json.dumps(partial_dict)
227
227
  ```
@@ -237,7 +237,7 @@ model: MyModel = MyModel(**dict_value)
237
237
 
238
238
  partial_str: str = ???
239
239
  partial_dict: Dict[str, Any] = json.loads(partial_str)
240
- partial_model: PartialModel[MyModel] = MyModel.partial(**partial_dict)
240
+ partial_model: Partial[MyModel] = MyModel.partial(**partial_dict)
241
241
  ```
242
242
 
243
243
  Note that given how data can be read from and written to objects, there is no necessity that you validate to models or even partial models at all. My own preferred workflow is to use models for ingestion only -- that is, to consume external data and convert them to my domain in the form of Pydoptic models, and then feed these into the data backend. Once valid data is persisted in a backend, it can then retrieved and manipulated in the form of `dict`s, without ever having to generate a full model. This is a matter of preference, however.
@@ -305,9 +305,9 @@ Conversely, if you only wanted to contact one POC (and you don't care whether th
305
305
 
306
306
  In the above case, if there are many `Person`s selected byte `poc_select`, only the first one will be returned. If none are, `poc` will be `None`.
307
307
 
308
- To retrieve values from an incomplete representation of the data, such as a `PartialModel` or a `dict`, there are two variants of `get` that you can use. `get_unsafe` will try to retrieve the selected value and fail with a `ValueError` if it's inaccessible due to invalid data (if its inaccessible due to a valid optional property being missing, it will generate an empty `SelectValue` as expected). `get_safe`, on the other hand, will always fail silently, generating empty `SelectValue` when data is missing.
308
+ To retrieve values from an incomplete representation of the data, such as a `Partial` or a `dict`, there are two variants of `get` that you can use. `get_unsafe` will try to retrieve the selected value and fail with a `ValueError` if it's inaccessible due to invalid data (if its inaccessible due to a valid optional property being missing, it will generate an empty `SelectValue` as expected). `get_safe`, on the other hand, will always fail silently, generating empty `SelectValue` when data is missing.
309
309
 
310
- Note that the type signature of `get_unsafe` and `get_safe` are a bit misleading. They will provide accurate types for non-model values (e.g., primitive properties like `int` or `str`), but they will treat nested models as though they are fully typed. When you retrieve a nested model from a `dict` or `PartialModel`, however, in all likelihood it is going to be another `dict` or `PartialModel`.
310
+ Note that the type signature of `get_unsafe` and `get_safe` are a bit misleading. They will provide accurate types for non-model values (e.g., primitive properties like `int` or `str`), but they will treat nested models as though they are fully typed. When you retrieve a nested model from a `dict` or `Partial`, however, in all likelihood it is going to be another `dict` or `Partial`.
311
311
 
312
312
  ```python3
313
313
  class Inner(BaseModel):
@@ -328,7 +328,7 @@ inner_result_full: SelectValue[Inner] = Outer.inner.get(full)
328
328
  partial_val = Outer.partial(inner=Inner.partial(prop=23))
329
329
  # Accurately typed
330
330
  prop_result_partial: SelectValue[int] = prop_select.get_unsafe(partial_val)
331
- # Inaccurate type! Should be SelectValue[PartialModel[Inner]]
331
+ # Inaccurate type! Should be SelectValue[Partial[Inner]]
332
332
  inner_result_partial: SelectValue[Inner] = Outer.inner.get_unsafe(partial_val)
333
333
 
334
334
  dict_val = {'inner': {'prop': 23}}
@@ -403,7 +403,7 @@ for arr in prop_arr_select.get(value).as_list:
403
403
  arr.append(len(arr))
404
404
  ```
405
405
 
406
- To update incomplete data, use `update_unsafe` and `update_safe`. `update_unsafe` will fail with an exception if it's unable to access the data to be updated due to invalid data, whereas `update_safe` will simply ignore (and therefore skip) invalid cases. Like `get`, you need to be careful when updating nested models with the partial variants of `update`. The types will appear to be complete models when they are most likely `PartialModel`s or `dict`s.
406
+ To update incomplete data, use `update_unsafe` and `update_safe`. `update_unsafe` will fail with an exception if it's unable to access the data to be updated due to invalid data, whereas `update_safe` will simply ignore (and therefore skip) invalid cases. Like `get`, you need to be careful when updating nested models with the partial variants of `update`. The types will appear to be complete models when they are most likely `Partial`s or `dict`s.
407
407
 
408
408
 
409
409
  #### `set`, `set_unsafe`, `set_safe`
@@ -478,7 +478,7 @@ print(value)
478
478
 
479
479
  In the above example, while `sel_opt` is able to clear the `opt` property, `sel_req` cannot clear the selected `req` property so instead it clears the next clearable property, setting `c` to an empty array.
480
480
 
481
- Clearing partial data is even more complicated, since partial data (`PartialModel`s and `dict`s) *can* be cleared even when its required in the model. For this reason, the partial variants of `clear` are distinguished into "strict" and "non-strict" variants. `clear_unsafe_strict` works just like `clear`: it will only clear clearable (optional/array) properties, and will try to clear selected property and work its way backward across the property chain until it finds a clearable property. `clear_unsafe`, on the other hand, will clear the selected property whether or not it's a required field. `clear_safe_strict` and `clear_safe` work the same way, but does not raise exceptions when they encounter invalid data, but simply skip it and move on.
481
+ Clearing partial data is even more complicated, since partial data (`Partial`s and `dict`s) *can* be cleared even when its required in the model. For this reason, the partial variants of `clear` are distinguished into "strict" and "non-strict" variants. `clear_unsafe_strict` works just like `clear`: it will only clear clearable (optional/array) properties, and will try to clear selected property and work its way backward across the property chain until it finds a clearable property. `clear_unsafe`, on the other hand, will clear the selected property whether or not it's a required field. `clear_safe_strict` and `clear_safe` work the same way, but does not raise exceptions when they encounter invalid data, but simply skip it and move on.
482
482
 
483
483
  #### Precise `Select`s
484
484
 
@@ -14,5 +14,5 @@ src/pydoptic.egg-info/scm_file_list.json
14
14
  src/pydoptic.egg-info/scm_version.json
15
15
  src/pydoptic.egg-info/top_level.txt
16
16
  test/test_base_model.py
17
- test/test_partial_model.py
17
+ test/test_partial.py
18
18
  test/test_selector.py
@@ -9,7 +9,7 @@
9
9
  "src/pydoptic/selector.py",
10
10
  "src/pydoptic/validate_types.py",
11
11
  "test/test_base_model.py",
12
- "test/test_partial_model.py",
12
+ "test/test_partial.py",
13
13
  "test/test_selector.py"
14
14
  ]
15
15
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.0.2",
3
+ "distance": 2,
4
+ "node": "g9a2420ef5de99bf2d4026d1af9ef469fad88bcab",
5
+ "dirty": false,
6
+ "branch": "main",
7
+ "node_date": "2026-09-07"
8
+ }
@@ -24,7 +24,7 @@ class Other(BaseModel):
24
24
  class Another(BaseModel):
25
25
  value: Prop['Another', float]
26
26
 
27
- def test_partial_model_constructed_from_full_model():
27
+ def test_partial_constructed_from_full_model():
28
28
  model_value = TestModel(
29
29
  annotation="str",
30
30
  select_val_empty=1,
@@ -55,7 +55,7 @@ def test_partial_model_constructed_from_full_model():
55
55
  assert model_value.other.value == True # type: ignore[attr-defined]
56
56
  assert model_value.other.another.value == 0.2 # type: ignore[attr-defined]
57
57
 
58
- def test_partial_model_should_accept_all_valid_values():
58
+ def test_partial_should_accept_all_valid_values():
59
59
  model_value = TestModel.partial(
60
60
  annotation="str",
61
61
  select_val_empty=1,
@@ -86,7 +86,7 @@ def test_partial_model_should_accept_all_valid_values():
86
86
  assert model_value.other.value == True # type: ignore[attr-defined]
87
87
  assert model_value.other.another.value == 0.2 # type: ignore[attr-defined]
88
88
 
89
- def test_complete_partial_model_should_become_full_model():
89
+ def test_complete_partial_should_become_full_model():
90
90
  model_value = TestModel.partial(
91
91
  annotation="str",
92
92
  select_val_empty=1,
@@ -117,7 +117,7 @@ def test_complete_partial_model_should_become_full_model():
117
117
  assert model_value.other.value == True # type: ignore[attr-defined]
118
118
  assert model_value.other.another.value == 0.2 # type: ignore[attr-defined]
119
119
 
120
- def test_partial_model_should_accept_valid_missing_arguments():
120
+ def test_partial_should_accept_valid_missing_arguments():
121
121
  model_value = TestModel.partial(
122
122
  annotation="str",
123
123
  select_val_empty=1,
@@ -146,7 +146,7 @@ def test_partial_model_should_accept_valid_missing_arguments():
146
146
  with pytest.raises(AttributeError):
147
147
  model_value.select_opt_arr_renamed # type: ignore[attr-defined]
148
148
 
149
- def test_partial_model_should_accept_partial_model_for_nested_models_if_complete():
149
+ def test_partial_should_accept_partial_for_nested_models_if_complete():
150
150
  model_value = TestModel.partial(
151
151
  annotation="str",
152
152
  select_val_empty=1,
@@ -161,14 +161,14 @@ def test_partial_model_should_accept_partial_model_for_nested_models_if_complete
161
161
  ),
162
162
  )
163
163
 
164
- assert isinstance(model_value.other, PartialModel) # type: ignore[attr-defined]
164
+ assert isinstance(model_value.other, Partial) # type: ignore[attr-defined]
165
165
  assert model_value.other.model is Other # type: ignore[attr-defined]
166
166
  assert model_value.other.value == True # type: ignore[attr-defined]
167
- assert isinstance(model_value.other.another, PartialModel) # type: ignore[attr-defined]
167
+ assert isinstance(model_value.other.another, Partial) # type: ignore[attr-defined]
168
168
  assert model_value.other.another.model is Another # type: ignore[attr-defined]
169
169
  assert model_value.other.another.value == 0.2 # type: ignore[attr-defined]
170
170
 
171
- def test_partial_model_should_accept_dict_for_nested_models_if_complete():
171
+ def test_partial_should_accept_dict_for_nested_models_if_complete():
172
172
  model_value = TestModel.partial(
173
173
  annotation="str",
174
174
  select_val_empty=1,
@@ -183,14 +183,14 @@ def test_partial_model_should_accept_dict_for_nested_models_if_complete():
183
183
  },
184
184
  )
185
185
 
186
- assert isinstance(model_value.other, PartialModel) # type: ignore[attr-defined]
186
+ assert isinstance(model_value.other, Partial) # type: ignore[attr-defined]
187
187
  assert model_value.other.model is Other # type: ignore[attr-defined]
188
188
  assert model_value.other.value == True # type: ignore[attr-defined]
189
- assert isinstance(model_value.other.another, PartialModel) # type: ignore[attr-defined]
189
+ assert isinstance(model_value.other.another, Partial) # type: ignore[attr-defined]
190
190
  assert model_value.other.another.model is Another # type: ignore[attr-defined]
191
191
  assert model_value.other.another.value == 0.2 # type: ignore[attr-defined]
192
192
 
193
- def test_partial_model_init_should_succeed_if_required_param_is_missing():
193
+ def test_partial_init_should_succeed_if_required_param_is_missing():
194
194
  model_value = TestModel.partial(
195
195
  select_val_empty=1,
196
196
  select_val_renamed=1,
@@ -219,7 +219,7 @@ def test_partial_model_init_should_succeed_if_required_param_is_missing():
219
219
  assert model_value.select_opt_arr_empty == [1,2,3] # type: ignore[attr-defined]
220
220
  assert model_value.select_opt_arr_renamed == [1,2,3] # type: ignore[attr-defined]
221
221
 
222
- def test_incomplete_partial_model_should_fail_to_become_full_model_if_missing_params_are_not_provided():
222
+ def test_incomplete_partial_should_fail_to_become_full_model_if_missing_params_are_not_provided():
223
223
  passed = False
224
224
  try:
225
225
  TestModel.partial(
@@ -244,7 +244,7 @@ def test_incomplete_partial_model_should_fail_to_become_full_model_if_missing_pa
244
244
  assert passed
245
245
 
246
246
 
247
- def test_incomplete_partial_model_should_become_full_model_if_missing_params_are_provided():
247
+ def test_incomplete_partial_should_become_full_model_if_missing_params_are_provided():
248
248
  model_value = TestModel.partial(
249
249
  annotation="str",
250
250
  select_val_renamed=1,
@@ -276,7 +276,7 @@ def test_incomplete_partial_model_should_become_full_model_if_missing_params_are
276
276
  assert model_value.other.value == True # type: ignore[attr-defined]
277
277
  assert model_value.other.another.value == 0.2 # type: ignore[attr-defined]
278
278
 
279
- def test_partial_model_init_should_fail_if_primitive_type_is_wrong():
279
+ def test_partial_init_should_fail_if_primitive_type_is_wrong():
280
280
  passed = True
281
281
  try:
282
282
  TestModel.partial(
@@ -302,7 +302,7 @@ def test_partial_model_init_should_fail_if_primitive_type_is_wrong():
302
302
 
303
303
  assert passed is False
304
304
 
305
- def test_partial_model_init_should_fail_if_primitive_array_param_receives_non_array():
305
+ def test_partial_init_should_fail_if_primitive_array_param_receives_non_array():
306
306
  passed = True
307
307
  try:
308
308
  TestModel.partial(
@@ -328,7 +328,7 @@ def test_partial_model_init_should_fail_if_primitive_array_param_receives_non_ar
328
328
 
329
329
  assert passed is False
330
330
 
331
- def test_partial_model_init_should_fail_if_primitive_array_param_contains_invalid_element():
331
+ def test_partial_init_should_fail_if_primitive_array_param_contains_invalid_element():
332
332
  passed = True
333
333
  try:
334
334
  TestModel(
@@ -355,7 +355,7 @@ def test_partial_model_init_should_fail_if_primitive_array_param_contains_invali
355
355
 
356
356
  assert passed is False
357
357
 
358
- def test_partial_model_init_should_fail_if_wrong_model_is_passed():
358
+ def test_partial_init_should_fail_if_wrong_model_is_passed():
359
359
  passed = True
360
360
  try:
361
361
  TestModel(
@@ -378,7 +378,7 @@ def test_partial_model_init_should_fail_if_wrong_model_is_passed():
378
378
 
379
379
  assert passed is False
380
380
 
381
- def test_partial_model_init_should_fail_if_nested_model_is_invalid_dict():
381
+ def test_partial_init_should_fail_if_nested_model_is_invalid_dict():
382
382
  passed = True
383
383
  try:
384
384
  TestModel(
@@ -405,7 +405,7 @@ def test_partial_model_init_should_fail_if_nested_model_is_invalid_dict():
405
405
 
406
406
  assert passed is False
407
407
 
408
- def test_partial_model_init_should_accept_incomplete_nested_partial_model():
408
+ def test_partial_init_should_accept_incomplete_nested_partial():
409
409
  TestModel.partial(
410
410
  annotation="str",
411
411
  select_val_empty=1,
@@ -426,14 +426,14 @@ def test_partial_model_init_should_accept_incomplete_nested_partial_model():
426
426
  class NestedModelExample(BaseModel):
427
427
  nested_model: PropArr['NestedModelExample', Another] = select()
428
428
 
429
- def test_partial_model_init_should_accept_valid_nested_model_array():
429
+ def test_partial_init_should_accept_valid_nested_model_array():
430
430
  nested_vals = [Another(value=2.0), {'value': 0.2}, Another.partial(value=23.042)]
431
431
  result = NestedModelExample.partial(
432
432
  nested_model=nested_vals,
433
433
  )
434
434
  assert [v.value for v in result.nested_model] == [2.0, 0.2, 23.042] # type: ignore[attr-defined]
435
435
 
436
- def test_partial_model_init_should_fail_if_nested_model_array_is_not_array():
436
+ def test_partial_init_should_fail_if_nested_model_array_is_not_array():
437
437
  passed = True
438
438
  try:
439
439
  NestedModelExample.partial(
@@ -445,7 +445,7 @@ def test_partial_model_init_should_fail_if_nested_model_array_is_not_array():
445
445
 
446
446
  assert passed
447
447
 
448
- def test_patial_model_init_should_fail_if_nested_model_array_contains_invald_model():
448
+ def test_partial_init_should_fail_if_nested_model_array_contains_invalid_model():
449
449
  passed = True
450
450
  try:
451
451
  NestedModelExample.partial(
@@ -460,7 +460,7 @@ def test_patial_model_init_should_fail_if_nested_model_array_contains_invald_mod
460
460
 
461
461
  assert passed
462
462
 
463
- def test_partial_model_as_mapping_full_should_be_reversable():
463
+ def test_partial_as_mapping_full_should_be_reversable():
464
464
  model_value = TestModel.partial(
465
465
  annotation="str",
466
466
  select_val_empty=1,
@@ -480,7 +480,7 @@ def test_partial_model_as_mapping_full_should_be_reversable():
480
480
 
481
481
  assert model_value == model_value_2
482
482
 
483
- def test_base_model_select_parts_should_generate_partial_model_from_selectors_p():
483
+ def test_base_model_select_parts_should_generate_partial_from_selectors_p():
484
484
  model_value = TestModel.partial(
485
485
  annotation="str",
486
486
  select_val_renamed=1,
@@ -526,21 +526,21 @@ class Sub2(Base):
526
526
  class Root(BaseModel):
527
527
  next: Prop['Root', Base]
528
528
 
529
- def test_partial_model_should_construct_with_discrim():
529
+ def test_partial_should_construct_with_discrim():
530
530
  value_1 = Root.partial(next=Sub1.partial(v='1', s1v=23))
531
531
  value_2 = Root.partial(next=Sub2.partial(v='2', s2v=True))
532
532
 
533
533
  assert value_1.next.d == 'Sub1' # type: ignore
534
534
  assert value_2.next.d == 'Sub2' # type: ignore
535
535
 
536
- def test_partial_model_should_forbid_constructing_with_invalid_discrim():
536
+ def test_partial_should_forbid_constructing_with_invalid_discrim():
537
537
  value = Root.partial(next=Sub1.partial(d='Sub1', v='1', s1v=23))
538
538
  assert value.next.d == 'Sub1' # type: ignore
539
539
 
540
540
  with pytest.raises(ValueError):
541
541
  Root.partial(next=Sub2.partial(d='wrong!', v='2', s2v=True))
542
542
 
543
- def test_partial_model_should_forbid_setting_invalid_discrim():
543
+ def test_partial_should_forbid_setting_invalid_discrim():
544
544
  value = Root.partial(next=Sub1.partial(d='Sub1', v='1', s1v=23))
545
545
  assert value.next.d == 'Sub1' # type: ignore
546
546
 
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.0.2",
3
- "distance": 1,
4
- "node": "ga0068181cb2d8dfd1b14469d4ad3bdc4cfd63617",
5
- "dirty": false,
6
- "branch": "main",
7
- "node_date": "2026-08-18"
8
- }