activemodel 0.13.0__py3-none-any.whl → 0.14.1__py3-none-any.whl

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.
activemodel/base_model.py CHANGED
@@ -15,7 +15,7 @@ from sqlalchemy.orm import declared_attr
15
15
  from activemodel.mixins.pydantic_json import PydanticJSONMixin
16
16
 
17
17
  # NOTE: this patches a core method in sqlmodel to support db comments
18
- from . import get_column_from_field_patch # noqa: F401
18
+ from .patches import get_column_from_field_patch # noqa: F401
19
19
  from .query_wrapper import QueryWrapper
20
20
  from .session_manager import get_session
21
21
 
@@ -72,11 +72,9 @@ class BaseModel(SQLModel):
72
72
  def __init_subclass__(cls, **kwargs):
73
73
  super().__init_subclass__(**kwargs)
74
74
 
75
- from sqlmodel._compat import set_config_value
76
-
77
75
  # Enables field-level docstrings on the pydantic `description` field, which we
78
76
  # copy into table/column comments by patching SQLModel internals elsewhere.
79
- set_config_value(model=cls, parameter="use_attribute_docstrings", value=True)
77
+ cls.model_config["use_attribute_docstrings"] = True
80
78
 
81
79
  cls._apply_class_doc()
82
80
 
@@ -290,7 +288,7 @@ class BaseModel(SQLModel):
290
288
  # TODO where is this actually used? shoudl prob remove this
291
289
  # TODO should we even do this? Can we specify a better json rendering class?
292
290
  def json(self, **kwargs):
293
- return json.dumps(self.dict(), default=str, **kwargs)
291
+ return json.dumps(self.model_dump(), default=str, **kwargs)
294
292
 
295
293
  # TODO should move this to the wrapper
296
294
  @classmethod
@@ -325,7 +323,7 @@ class BaseModel(SQLModel):
325
323
  assert len(args) > 0, "Must pass at least one field name"
326
324
 
327
325
  for field_name in args:
328
- if field_name not in self.__fields__:
326
+ if field_name not in self.model_fields:
329
327
  raise ValueError(f"Field '{field_name}' does not exist in the model.")
330
328
 
331
329
  # check if the field exists
@@ -6,9 +6,9 @@ SQLModel lacks a direct JSONField equivalent (like Tortoise ORM's JSONField), ma
6
6
  Extensive discussion on the problem: https://github.com/fastapi/sqlmodel/issues/63
7
7
  """
8
8
 
9
- from types import UnionType
10
9
  from typing import get_args, get_origin
11
-
10
+ import typing
11
+ import types
12
12
  from pydantic import BaseModel as PydanticBaseModel
13
13
  from sqlalchemy.orm import reconstructor, attributes
14
14
 
@@ -21,6 +21,11 @@ class PydanticJSONMixin:
21
21
 
22
22
  >>> class ExampleWithJSON(BaseModel, PydanticJSONMixin, table=True):
23
23
  >>> list_field: list[SubObject] = Field(sa_type=JSONB()
24
+
25
+ Notes:
26
+
27
+ - Tuples of pydantic models are not supported, only lists.
28
+ - Nested lists of pydantic models are not supported, e.g. list[list[SubObject]]
24
29
  """
25
30
 
26
31
  @reconstructor
@@ -37,6 +42,7 @@ class PydanticJSONMixin:
37
42
  for field_name, field_info in self.model_fields.items():
38
43
  raw_value = getattr(self, field_name, None)
39
44
 
45
+ # if the field is not set on the model, we can avoid doing anything with it
40
46
  if raw_value is None:
41
47
  continue
42
48
 
@@ -44,32 +50,43 @@ class PydanticJSONMixin:
44
50
  origin = get_origin(annotation)
45
51
 
46
52
  # e.g. `dict` or `dict[str, str]`, we don't want to do anything with these
47
- if origin is dict:
53
+ if origin in (dict, tuple):
48
54
  continue
49
55
 
50
56
  annotation_args = get_args(annotation)
51
57
  is_top_level_list = origin is list
58
+ model_cls = annotation
52
59
 
60
+ # TODO not sure what was going on here...
53
61
  # if origin is not None:
54
62
  # assert annotation.__class__ == origin
55
63
 
56
- model_cls = annotation
64
+ # UnionType is only one way of defining an optional. If older typing syntax is used `Tuple[str] | None` the
65
+ # type annotation is different: `typing.Optional[typing.Tuple[float, float]]`. This is why we check both
66
+ # types below.
57
67
 
58
68
  # e.g. SomePydanticModel | None or list[SomePydanticModel] | None
59
- # annotation_args are (type, NoneType) in this case
60
- if isinstance(annotation, UnionType):
69
+ # annotation_args are (type, NoneType) in this case. Remove NoneType.
70
+ if origin in (typing.Union, types.UnionType):
61
71
  non_none_types = [t for t in annotation_args if t is not type(None)]
62
72
 
63
73
  if len(non_none_types) == 1:
64
74
  model_cls = non_none_types[0]
75
+ else:
76
+ # if there's more than one non-none type, it isn't meant to be serialized to JSON
77
+ pass
78
+
79
+ model_cls_origin = get_origin(model_cls)
65
80
 
66
81
  # e.g. list[SomePydanticModel] | None, we have to unpack it
67
82
  # model_cls will print as a list, but it contains a subtype if you dig into it
68
83
  if (
69
- get_origin(model_cls) is list
84
+ model_cls_origin is list
70
85
  and len(list_annotation_args := get_args(model_cls)) == 1
71
86
  ):
72
87
  model_cls = list_annotation_args[0]
88
+ model_cls_origin = get_origin(model_cls)
89
+
73
90
  is_top_level_list = True
74
91
 
75
92
  # e.g. list[SomePydanticModel] or list[SomePydanticModel] | None
@@ -82,6 +99,9 @@ class PydanticJSONMixin:
82
99
  attributes.set_committed_value(self, field_name, parsed_value)
83
100
  continue
84
101
 
102
+ if model_cls_origin in (list, tuple):
103
+ continue
104
+
85
105
  # single class
86
106
  if issubclass(model_cls, PydanticBaseModel):
87
107
  attributes.set_committed_value(self, field_name, model_cls(**raw_value))
@@ -0,0 +1,2 @@
1
+ # NOTE: this patches a core method in sqlmodel to support db comments
2
+ from . import get_column_from_field_patch # noqa: F401
@@ -13,26 +13,23 @@ Some ideas for this originally sourced from: https://github.com/fastapi/sqlmodel
13
13
  from typing import (
14
14
  TYPE_CHECKING,
15
15
  Any,
16
- Dict,
17
16
  Sequence,
18
17
  cast,
19
18
  )
20
19
 
21
20
  import sqlmodel
22
- from pydantic.fields import FieldInfo as PydanticFieldInfo
23
21
  from sqlalchemy import (
24
22
  Column,
25
23
  ForeignKey,
26
24
  )
27
25
  from sqlmodel._compat import ( # type: ignore[attr-defined]
28
- IS_PYDANTIC_V2,
29
26
  ModelMetaclass,
30
27
  Representation,
31
28
  Undefined,
32
29
  UndefinedType,
33
30
  is_field_noneable,
34
31
  )
35
- from sqlmodel.main import FieldInfo, get_sqlalchemy_type
32
+ from sqlmodel.main import get_sqlalchemy_type
36
33
 
37
34
  from activemodel.utils import hash_function_code
38
35
 
@@ -43,37 +40,31 @@ if TYPE_CHECKING:
43
40
  from pydantic_core import PydanticUndefinedType as UndefinedType
44
41
 
45
42
 
43
+ # https://github.com/fastapi/sqlmodel/blob/5c2dbe419edc2d15200eee5269c9508987944ed8/sqlmodel/main.py#L691
46
44
  assert (
47
45
  hash_function_code(sqlmodel.main.get_column_from_field)
48
- == "398006ef8fd8da191ca1a271ef25b6e135da0f400a80df2f29526d8674f9ec51"
46
+ == "ab3cdd5d20079358911b6aef76b3916ba3890b20eb07e970a0f35bd63e1713d9"
47
+ ), (
48
+ f"get_column_from_field has changed, please verify the patch is still valid: {hash_function_code(sqlmodel.main.get_column_from_field)}"
49
49
  )
50
50
 
51
51
 
52
- def get_column_from_field(field: PydanticFieldInfo | FieldInfo) -> Column: # type: ignore
53
- """
54
- Takes a field definition, which can either come from the sqlmodel FieldInfo class or the pydantic variant of that class,
55
- and converts it into a sqlalchemy Column object.
56
- """
57
- if IS_PYDANTIC_V2:
58
- field_info = field
59
- else:
60
- field_info = field.field_info
61
-
52
+ def get_column_from_field(field: Any) -> Column: # type: ignore
53
+ field_info = field
62
54
  sa_column = getattr(field_info, "sa_column", Undefined)
63
55
  if isinstance(sa_column, Column):
64
- # IMPORTANT: change from the original function
56
+ # <Change>
65
57
  if not sa_column.comment and (field_comment := field_info.description):
66
58
  sa_column.comment = field_comment
59
+ # </Change>
67
60
  return sa_column
68
-
61
+ sa_type = get_sqlalchemy_type(field)
69
62
  primary_key = getattr(field_info, "primary_key", Undefined)
70
63
  if primary_key is Undefined:
71
64
  primary_key = False
72
-
73
65
  index = getattr(field_info, "index", Undefined)
74
66
  if index is Undefined:
75
67
  index = False
76
-
77
68
  nullable = not primary_key and is_field_noneable(field)
78
69
  # Override derived nullability if the nullable property is set explicitly
79
70
  # on the field
@@ -103,7 +94,6 @@ def get_column_from_field(field: PydanticFieldInfo | FieldInfo) -> Column: # ty
103
94
  "index": index,
104
95
  "unique": unique,
105
96
  }
106
-
107
97
  sa_default = Undefined
108
98
  if field_info.default_factory:
109
99
  sa_default = field_info.default_factory
@@ -111,14 +101,12 @@ def get_column_from_field(field: PydanticFieldInfo | FieldInfo) -> Column: # ty
111
101
  sa_default = field_info.default
112
102
  if sa_default is not Undefined:
113
103
  kwargs["default"] = sa_default
114
-
115
104
  sa_column_args = getattr(field_info, "sa_column_args", Undefined)
116
105
  if sa_column_args is not Undefined:
117
106
  args.extend(list(cast(Sequence[Any], sa_column_args)))
118
-
119
107
  sa_column_kwargs = getattr(field_info, "sa_column_kwargs", Undefined)
120
108
 
121
- # IMPORTANT: change from the original function
109
+ # <Change>
122
110
  if field_info.description:
123
111
  if sa_column_kwargs is Undefined:
124
112
  sa_column_kwargs = {}
@@ -128,11 +116,10 @@ def get_column_from_field(field: PydanticFieldInfo | FieldInfo) -> Column: # ty
128
116
  # only update comments if not already set
129
117
  if "comment" not in sa_column_kwargs:
130
118
  sa_column_kwargs["comment"] = field_info.description
119
+ # </Change>
131
120
 
132
121
  if sa_column_kwargs is not Undefined:
133
- kwargs.update(cast(Dict[Any, Any], sa_column_kwargs))
134
-
135
- sa_type = get_sqlalchemy_type(field)
122
+ kwargs.update(cast(dict[Any, Any], sa_column_kwargs))
136
123
  return Column(sa_type, *args, **kwargs) # type: ignore
137
124
 
138
125
 
@@ -12,6 +12,7 @@ from polyfactory.field_meta import FieldMeta
12
12
  from typeid import TypeID
13
13
 
14
14
  from activemodel.session_manager import global_session
15
+ from activemodel.logger import logger
15
16
 
16
17
  # TODO not currently used
17
18
  # def type_id_provider(cls, field_meta):
@@ -56,11 +57,20 @@ class ActiveModelFactory[T](SQLModelFactory[T]):
56
57
  @classmethod
57
58
  def save(cls, *args, **kwargs) -> T:
58
59
  """
60
+ Builds and persists a new model to the database.
61
+
59
62
  Where this gets tricky, is this can be called multiple times within the same callstack. This can happen when
60
- a factory uses other factories to create relationships.
63
+ a factory uses other factories to create relationships. This is fine if `__sqlalchemy_session__` is set, but
64
+ if it's not (in the case of a truncation DB strategy) you'll run into issues.
61
65
 
62
66
  In a truncation strategy, the __sqlalchemy_session__ is set to None.
63
67
  """
68
+
69
+ if cls.__sqlalchemy_session__ is None:
70
+ logger.warning(
71
+ "No __sqlalchemy_session__ set on factory class, nested factory save() will fail. Use `db_session` or `db_truncate_session` to avoid this."
72
+ )
73
+
64
74
  with global_session(cls.__sqlalchemy_session__):
65
75
  return cls.build(*args, **kwargs).save()
66
76
 
@@ -74,6 +84,7 @@ class ActiveModelFactory[T](SQLModelFactory[T]):
74
84
  # TODO right now assumes the model is typeid, maybe we should assert against this?
75
85
  primary_key_name = cls.__model__.primary_key_column().name
76
86
  return TypeID(
87
+ # gets the prefix associated with the pk field
77
88
  cls.__model__.model_fields[primary_key_name].sa_column.type.prefix
78
89
  )
79
90
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  Currently provides:
4
4
 
5
- * ``db_session`` fixture quick access to a database session (see ``test_session``)
6
- * ``activemodel_preserve_tables`` ini option configure tables to preserve when using
5
+ * ``db_session`` fixture - quick access to a database session (see ``test_session``)
6
+ * ``activemodel_preserve_tables`` ini option - configure tables to preserve when using
7
7
  ``database_reset_truncate`` (comma separated list or multiple lines depending on config style)
8
8
 
9
9
  Configuration examples:
@@ -28,7 +28,10 @@ The list always implicitly includes ``alembic_version`` even if not specified.
28
28
  from activemodel.session_manager import global_session
29
29
  import pytest
30
30
 
31
- from .transaction import set_factory_session, set_polyfactory_session, test_session
31
+ from .transaction import (
32
+ set_factory_sessions,
33
+ test_session,
34
+ )
32
35
 
33
36
 
34
37
  def pytest_addoption(
@@ -53,7 +56,7 @@ def pytest_addoption(
53
56
  @pytest.fixture(scope="function")
54
57
  def db_session():
55
58
  """
56
- Helpful for tests that are more similar to unit tests. If you doing a routing or integration test, you
59
+ Helpful for tests that are similar to unit tests. If you doing a routing or integration test, you
57
60
  probably don't need this. If your unit test is simple (you are just creating a couple of models) you
58
61
  can most likely skip this.
59
62
 
@@ -71,11 +74,12 @@ def db_truncate_session():
71
74
  """
72
75
  Provides a database session for testing when using a truncation cleaning strategy.
73
76
 
74
- When not using a transaction cleaning strategy, no global test session is set
77
+ When using a truncation cleaning strategy, no global test session is set. This means all models that are created
78
+ are tied to a detached session, which makes it hard to mutate models after creation. This fixture fixes that problem
79
+ by setting the session used by all model factories to a global session.
75
80
  """
76
81
  with global_session() as session:
77
82
  # set global database sessions for model factories to avoid lazy loading issues
78
- set_factory_session(session)
79
- set_polyfactory_session(session)
83
+ set_factory_sessions(session)
80
84
 
81
85
  yield session
@@ -41,6 +41,13 @@ def set_polyfactory_session(session):
41
41
  ActiveModelFactory.__sqlalchemy_session__ = session
42
42
 
43
43
 
44
+ def set_factory_sessions(session):
45
+ "set all supported model factories to use the provided session"
46
+
47
+ set_factory_session(session)
48
+ set_polyfactory_session(session)
49
+
50
+
44
51
  @contextlib.contextmanager
45
52
  def test_session():
46
53
  """
@@ -1,7 +1,6 @@
1
1
  import os
2
2
  from typing import Iterable
3
3
 
4
- import pytest
5
4
  from sqlmodel import SQLModel
6
5
 
7
6
  from ..logger import logger
@@ -1,5 +1,6 @@
1
1
  import sqlmodel as sm
2
2
  from sqlmodel.sql.expression import SelectOfScalar
3
+ from typing import overload, Literal
3
4
 
4
5
  from activemodel.types.sqlalchemy_protocol import SQLAlchemyQueryMethods
5
6
 
@@ -48,6 +49,8 @@ class QueryWrapper[T: sm.SQLModel](SQLAlchemyQueryMethods[T]):
48
49
  with get_session() as session:
49
50
  return session.scalar(sm.select(sm.func.count()).select_from(self.target))
50
51
 
52
+ # TODO typing is broken here
53
+ # TODO would be great to define a default return type if nothing is found
51
54
  def scalar(self):
52
55
  """
53
56
  >>>
@@ -63,6 +66,17 @@ class QueryWrapper[T: sm.SQLModel](SQLAlchemyQueryMethods[T]):
63
66
  with get_session() as session:
64
67
  return session.delete(self.target)
65
68
 
69
+ def exists(self) -> bool:
70
+ """Return True if the current query yields at least one row.
71
+
72
+ Uses the SQLAlchemy exists() construct against a LIMIT 1 version of
73
+ the current target for efficiency. Keeps the original target intact.
74
+ """
75
+ with get_session() as session:
76
+ exists_stmt = sm.select(sm.exists(self.target))
77
+ result = session.scalar(exists_stmt)
78
+ return bool(result)
79
+
66
80
  def __getattr__(self, name):
67
81
  """
68
82
  This implements the magic that forwards function calls to sqlalchemy.
@@ -79,7 +93,7 @@ class QueryWrapper[T: sm.SQLModel](SQLAlchemyQueryMethods[T]):
79
93
 
80
94
  def wrapper(*args, **kwargs):
81
95
  result = sqlalchemy_target(*args, **kwargs)
82
- self.target = result
96
+ self.target = result # type: ignore[assignment]
83
97
  return self
84
98
 
85
99
  return wrapper
@@ -95,6 +109,48 @@ class QueryWrapper[T: sm.SQLModel](SQLAlchemyQueryMethods[T]):
95
109
 
96
110
  return compile_sql(self.target)
97
111
 
112
+ @overload
113
+ def sample(self) -> T | None: ...
114
+
115
+ @overload
116
+ def sample(self, n: Literal[1]) -> T | None: ...
117
+
118
+ @overload
119
+ def sample(self, n: int) -> list[T]: ...
120
+
121
+ def sample(self, n: int = 1) -> T | None | list[T]:
122
+ """Return a random sample of rows from the current query.
123
+
124
+ Parameters
125
+ ----------
126
+ n: int
127
+ Number of rows to return. Defaults to 1.
128
+
129
+ Behavior
130
+ --------
131
+ - Returns a single model instance when ``n == 1`` (or ``None`` if no rows)
132
+ - Returns a list[Model] when ``n > 1`` (possibly empty list when no rows)
133
+ - Sampling is performed by appending an ``ORDER BY RANDOM()`` / ``func.random()``
134
+ and ``LIMIT n`` clause to the existing query target.
135
+ - Keeps original query intact (does not mutate ``self.target``) so further
136
+ chaining works as expected.
137
+ """
138
+
139
+ if n < 1:
140
+ raise ValueError("n must be >= 1")
141
+
142
+ # Build a new randomized limited query leaving self.target untouched
143
+ randomized = self.target.order_by(sm.func.random()).limit(n)
144
+
145
+ with get_session() as session:
146
+ result = list(session.exec(randomized))
147
+
148
+ if n == 1:
149
+ # Return the single instance or None
150
+ return result[0] if result else None
151
+
152
+ return result
153
+
98
154
  def __repr__(self) -> str:
99
155
  # TODO we should improve structure of this a bit more, maybe wrap in <> or something?
100
156
  return f"{self.__class__.__name__}: Current SQL:\n{self.sql()}"
@@ -150,6 +150,14 @@ def global_session(session: Session | None = None):
150
150
  This may only be called a single time per callstack. There is one exception: if you call this multiple times
151
151
  and pass in the same session reference, it will result in a noop.
152
152
 
153
+ In complex testing code, you'll need to be careful here. For example:
154
+
155
+ - Unit test using a transaction db fixture (which sets __sqlalchemy_session__)
156
+ - Factory has a after_save hook
157
+ - That hook triggers a celery job
158
+ - The celery job (properly) calls `with global_session()`
159
+ - However, since `global_session()` is already set with __sqlalchemy_session__, this will raise an error
160
+
153
161
  Args:
154
162
  session: Use an existing session instead of creating a new one
155
163
  """
@@ -1,9 +1,7 @@
1
1
  # IMPORTANT: This file is auto-generated. Do not edit directly.
2
2
 
3
- from typing import Protocol, TypeVar, Any, Generic
3
+ from typing import Protocol
4
4
  import sqlmodel as sm
5
- from sqlalchemy.sql.base import _NoArg
6
- from typing import TYPE_CHECKING
7
5
 
8
6
 
9
7
  class SQLAlchemyQueryMethods[T: sm.SQLModel](Protocol):
@@ -1,6 +1,6 @@
1
1
  # IMPORTANT: This file is auto-generated. Do not edit directly.
2
2
 
3
- from typing import Protocol, TypeVar, Any, Generic
3
+ from typing import Protocol, Any
4
4
  import sqlmodel as sm
5
5
  from sqlalchemy.sql.base import _NoArg
6
6
 
@@ -1,3 +1,9 @@
1
+ """
2
+ Pydantic v2 support for TypeID.
3
+
4
+ TODO should push this upstream to the typeid package
5
+ """
6
+
1
7
  from typing import Any, Type
2
8
 
3
9
  from pydantic import GetCoreSchemaHandler
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: activemodel
3
- Version: 0.13.0
3
+ Version: 0.14.1
4
4
  Summary: Make SQLModel more like an a real ORM
5
5
  Project-URL: Repository, https://github.com/iloveitaly/activemodel
6
6
  Author-email: Michael Bianco <iloveitaly@gmail.com>
7
7
  License-File: LICENSE
8
8
  Keywords: activemodel,activerecord,orm,sqlalchemy,sqlmodel
9
- Requires-Python: >=3.10
9
+ Requires-Python: >=3.12
10
10
  Requires-Dist: python-decouple-typed>=3.11.0
11
- Requires-Dist: sqlmodel>=0.0.22
11
+ Requires-Dist: sqlmodel>=0.0.27
12
12
  Requires-Dist: textcase>=0.4.0
13
- Requires-Dist: typeid-python>=0.3.1
13
+ Requires-Dist: typeid-python==0.3.3
14
14
  Description-Content-Type: text/markdown
15
15
 
16
16
  # ActiveModel: ORM Wrapper for SQLModel
@@ -290,7 +290,8 @@ https://github.com/DarylStark/my_data/blob/a17b8b3a8463b9953821b89fee895e272f94d
290
290
 
291
291
  * https://github.com/woofz/sqlmodel-basecrud
292
292
  * https://github.com/0xthiagomartins/sqlmodel-controller
293
- * https://github.com/litestar-org/advanced-alchemy?tab=readme-ov-file
293
+ * https://github.com/litestar-org/advanced-alchemy
294
+ * https://github.com/dialoguemd/fastapi-sqla
294
295
 
295
296
  ## Inspiration
296
297
 
@@ -0,0 +1,31 @@
1
+ activemodel/__init__.py,sha256=q_lHQyIM70ApvjduTo9GtenQjJXsfYZsAAquD_51kF4,137
2
+ activemodel/base_model.py,sha256=qSbC_42o_kPEBx22Pl6AgrAwd6U-eNRBaenkFQmx-Ns,17430
3
+ activemodel/celery.py,sha256=L1vKcO_HoPA5ZCfsXjxgPpDUMYDuoQMakGA9rppN7Lo,897
4
+ activemodel/errors.py,sha256=wycWYmk9ws4TZpxvTdtXVy2SFESb8NqKgzdivBoF0vw,115
5
+ activemodel/logger.py,sha256=vU7QiGSy_AJuJFmClUocqIJ-Ltku_8C24ZU8L6fLJR0,53
6
+ activemodel/query_wrapper.py,sha256=DLfmpMQr5veBjQIU2KEsp7Pe3MvdxQ-R-C6tkj0SgU8,4832
7
+ activemodel/session_manager.py,sha256=4jK0rs3KxU84WOvHCX6iuy0mauJg02tQ2VUQj-OEk68,7124
8
+ activemodel/utils.py,sha256=tZlAk0G46g6dwYuN7dIr8xU9QC_aLZYqjDXYkGiCtUg,888
9
+ activemodel/cli/__init__.py,sha256=HrgJjB5pRuE6hbwgy0Dw4oHvGZ47kH0LPVAdG9l6-vw,5021
10
+ activemodel/mixins/__init__.py,sha256=05EQl2u_Wgf_wkly-GTaTsR7zWpmpKcb96Js7r_rZTw,160
11
+ activemodel/mixins/pydantic_json.py,sha256=Nm8Y0ra7N-2lEvLHtmZYq1XE1-4n2BIff812DzKgOb4,4461
12
+ activemodel/mixins/soft_delete.py,sha256=Ax4mGsQI7AVTE8c4GiWxpyB_W179-dDct79GtjP0owU,461
13
+ activemodel/mixins/timestamps.py,sha256=C6QQNnzrNUOW1EAsMpEVpImEeTIYDMPP0wocEw2RDQw,1078
14
+ activemodel/mixins/typeid.py,sha256=777btWRUW6YBGPApeaEdHQaoKmwblehukHzmkKoXv6o,1340
15
+ activemodel/patches/__init__.py,sha256=Jc6yYPtNCOBI6AdbKfeYUfLikpEdFyefM6h3nTLRID8,126
16
+ activemodel/patches/get_column_from_field_patch.py,sha256=HRAljRDxWF03kA4wICWDO9KUKToVzESYp1tbKs-bg5A,4798
17
+ activemodel/pytest/__init__.py,sha256=IJpD-BwJuPii5IxTJoOCryaq4_oyXNRj4RjlS5Plmc8,112
18
+ activemodel/pytest/factories.py,sha256=o9kBVCESZUD4ypTVfG2_FD2Zew3mAcnguVhagDH1Ry8,4016
19
+ activemodel/pytest/plugin.py,sha256=IbRKTCjYnay0N5ucTK7PmA9ETx4OQOuR5hlFf3Lyez4,2681
20
+ activemodel/pytest/transaction.py,sha256=ZeGj_7gaGREjZBqlPC9JhwouDOTUhqblT3070OyybII,5977
21
+ activemodel/pytest/truncate.py,sha256=YcDxNXF074VWP8IHnTU4K8bxnQTNKuaNi1ZyCUpLqro,4766
22
+ activemodel/types/__init__.py,sha256=y5fiGVtPJxGEhuf-TvyrkhM2yaKRcIWo6XAx-CFFjM8,31
23
+ activemodel/types/sqlalchemy_protocol.py,sha256=Uc8hKmTdx0boNQAthNYQNCbfxRwz0mGH044mvjZCj0A,182
24
+ activemodel/types/sqlalchemy_protocol.pyi,sha256=yhLXOiZtCHOLA6YNPu_-xSiUJhfi8dyfjcmcAFhDY-Y,5649
25
+ activemodel/types/typeid.py,sha256=qycqklKv5nKuCqjJRnxA-6MjtcWJ4vFUsAVBc1ySwfg,7865
26
+ activemodel/types/typeid_patch.py,sha256=Wkt8Ok_y49vEpuvhR8iB2xOcuFqa7vGC2RXYPcF6WZ0,670
27
+ activemodel-0.14.1.dist-info/METADATA,sha256=EUnStn5l6Cm_uTEu5BJBl6M_o9ROsnPEq2ya_BebwI8,10750
28
+ activemodel-0.14.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
29
+ activemodel-0.14.1.dist-info/entry_points.txt,sha256=rytVrsNgUT4oDiW9RvRH6JBTHQn0hPZLK-jzQt3dY9s,51
30
+ activemodel-0.14.1.dist-info/licenses/LICENSE,sha256=L8mmpX47rB-xtJ_HsK0zpfO6viEjxbLYGn70BMp8os4,1071
31
+ activemodel-0.14.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,30 +0,0 @@
1
- activemodel/__init__.py,sha256=q_lHQyIM70ApvjduTo9GtenQjJXsfYZsAAquD_51kF4,137
2
- activemodel/base_model.py,sha256=0QRs2C_QtJFx6voSWr0jFXsbEgtn_PmYA2rrwejVzCU,17496
3
- activemodel/celery.py,sha256=L1vKcO_HoPA5ZCfsXjxgPpDUMYDuoQMakGA9rppN7Lo,897
4
- activemodel/errors.py,sha256=wycWYmk9ws4TZpxvTdtXVy2SFESb8NqKgzdivBoF0vw,115
5
- activemodel/get_column_from_field_patch.py,sha256=wAEDm_ZvSqyJwfgkXVpxsevw11hd-7VLy7zuJG8Ak7Y,4986
6
- activemodel/logger.py,sha256=vU7QiGSy_AJuJFmClUocqIJ-Ltku_8C24ZU8L6fLJR0,53
7
- activemodel/query_wrapper.py,sha256=gXpEAAFpRqQNUbTubTX-qkMNHeztFnyxtjdJbplCkKA,2867
8
- activemodel/session_manager.py,sha256=Aqc96ByAgrcuRRPsMZiTN3tlSXUFZvM3U7N8vCO2TIk,6720
9
- activemodel/utils.py,sha256=tZlAk0G46g6dwYuN7dIr8xU9QC_aLZYqjDXYkGiCtUg,888
10
- activemodel/cli/__init__.py,sha256=HrgJjB5pRuE6hbwgy0Dw4oHvGZ47kH0LPVAdG9l6-vw,5021
11
- activemodel/mixins/__init__.py,sha256=05EQl2u_Wgf_wkly-GTaTsR7zWpmpKcb96Js7r_rZTw,160
12
- activemodel/mixins/pydantic_json.py,sha256=0pprGZA95BGZL4WOh--NJcvxLWey4YW85lLk4GGTjFM,3530
13
- activemodel/mixins/soft_delete.py,sha256=Ax4mGsQI7AVTE8c4GiWxpyB_W179-dDct79GtjP0owU,461
14
- activemodel/mixins/timestamps.py,sha256=C6QQNnzrNUOW1EAsMpEVpImEeTIYDMPP0wocEw2RDQw,1078
15
- activemodel/mixins/typeid.py,sha256=777btWRUW6YBGPApeaEdHQaoKmwblehukHzmkKoXv6o,1340
16
- activemodel/pytest/__init__.py,sha256=IJpD-BwJuPii5IxTJoOCryaq4_oyXNRj4RjlS5Plmc8,112
17
- activemodel/pytest/factories.py,sha256=d4Mt9Lto8Pqd2bXuxDSt92IeEzTM5VGL3rQwG8jY4x4,3475
18
- activemodel/pytest/plugin.py,sha256=QsHnaKmkFZjR_pUHLaU7tb47vrfcNU0zsvfBaN0g2f0,2509
19
- activemodel/pytest/transaction.py,sha256=KMQ7jHSU9Bf14rPwdVAFxxR9mZ41uRAXwiSQ3HAzkw0,5801
20
- activemodel/pytest/truncate.py,sha256=LeuG2fSq92oR-S1_gE-1Y3DhO48jw4ubMIRh2tb6U08,4780
21
- activemodel/types/__init__.py,sha256=y5fiGVtPJxGEhuf-TvyrkhM2yaKRcIWo6XAx-CFFjM8,31
22
- activemodel/types/sqlalchemy_protocol.py,sha256=2MSuGIp6pcIyiy8uK7qX3FLWABBMQOJGlIC969WRQdY,277
23
- activemodel/types/sqlalchemy_protocol.pyi,sha256=SP4Z50SGcw6qSexGgNd_4g6E_sQwpIE44vgNT4ncmeI,5667
24
- activemodel/types/typeid.py,sha256=qycqklKv5nKuCqjJRnxA-6MjtcWJ4vFUsAVBc1ySwfg,7865
25
- activemodel/types/typeid_patch.py,sha256=y6kiCJQ_NzeKfuI4UtRAs7QW_nEog5RIA_-k4HUBMkU,575
26
- activemodel-0.13.0.dist-info/METADATA,sha256=744GzqxDiyQQYxv5jlVcC8PBDLj8Xj0gL7P49oM3buI,10724
27
- activemodel-0.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
- activemodel-0.13.0.dist-info/entry_points.txt,sha256=rytVrsNgUT4oDiW9RvRH6JBTHQn0hPZLK-jzQt3dY9s,51
29
- activemodel-0.13.0.dist-info/licenses/LICENSE,sha256=L8mmpX47rB-xtJ_HsK0zpfO6viEjxbLYGn70BMp8os4,1071
30
- activemodel-0.13.0.dist-info/RECORD,,