activemodel 0.3.0__py3-none-any.whl → 0.7.0__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.
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.4
2
+ Name: activemodel
3
+ Version: 0.7.0
4
+ Summary: Make SQLModel more like an a real ORM
5
+ Project-URL: Repository, https://github.com/iloveitaly/activemodel
6
+ Author-email: Michael Bianco <iloveitaly@gmail.com>
7
+ License-File: LICENSE
8
+ Keywords: activemodel,activerecord,orm,sqlalchemy,sqlmodel
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: pydash>=8.0.4
11
+ Requires-Dist: python-decouple-typed>=3.11.0
12
+ Requires-Dist: sqlmodel>=0.0.22
13
+ Requires-Dist: typeid-python>=0.3.1
14
+ Description-Content-Type: text/markdown
15
+
16
+ # ActiveModel: ORM Wrapper for SQLModel
17
+
18
+ No, this isn't *really* [ActiveModel](https://guides.rubyonrails.org/active_model_basics.html). It's just a wrapper around SQLModel that provides a more ActiveRecord-like interface.
19
+
20
+ SQLModel is *not* an ORM. It's a SQL query builder and a schema definition tool.
21
+
22
+ This package provides a thin wrapper around SQLModel that provides a more ActiveRecord-like interface with things like:
23
+
24
+ * Timestamp column mixins
25
+ * Lifecycle hooks
26
+
27
+ ## Getting Started
28
+
29
+ First, setup your DB:
30
+
31
+ ```python
32
+
33
+ ```
34
+
35
+ Then, setup some models:
36
+
37
+ ```python
38
+ from activemodel import BaseModel
39
+ from activemodel.mixins import TimestampsMixin, TypeIDMixin
40
+
41
+ class User(
42
+ BaseModel,
43
+ # optionally, obviously
44
+ TimestampsMixin,
45
+ # you can use a different pk type, but why would you?
46
+ # put this mixin last otherwise `id` will not be the first column in the DB
47
+ TypeIDMixin("user"),
48
+ # wire this model into the DB, without this alembic will not generate a migration
49
+ table=True
50
+ ):
51
+ a_field: str
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ ### Integrating Alembic
57
+
58
+ `alembic init` will not work out of the box. You need to mutate a handful of files:
59
+
60
+ * To import all of your models you want in your DB. [Here's my recommended way to do this.](https://github.com/iloveitaly/python-starter-template/blob/master/app/models/__init__.py)
61
+ * Use your DB URL from the ENV
62
+ * Target sqlalchemy metadata to the sqlmodel-generated metadata
63
+
64
+ [Take a look at these scripts for an example of how to fully integrate Alembic into your development workflow.](https://github.com/iloveitaly/python-starter-template/blob/0af2c7e95217e34bde7357cc95be048900000e48/Justfile#L618-L712)
65
+
66
+ Here's a diff from the bare `alembic init` from version `1.14.1`.
67
+
68
+ ```diff
69
+ diff --git i/test/migrations/alembic.ini w/test/migrations/alembic.ini
70
+ index 0d07420..a63631c 100644
71
+ --- i/test/migrations/alembic.ini
72
+ +++ w/test/migrations/alembic.ini
73
+ @@ -3,13 +3,14 @@
74
+ [alembic]
75
+ # path to migration scripts
76
+ # Use forward slashes (/) also on windows to provide an os agnostic path
77
+ -script_location = .
78
+ +script_location = migrations
79
+
80
+ # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
81
+ # Uncomment the line below if you want the files to be prepended with date and time
82
+ # see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
83
+ # for all available tokens
84
+ # file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
85
+ +file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(rev)s_%%(slug)s
86
+
87
+ # sys.path path, will be prepended to sys.path if present.
88
+ # defaults to the current working directory.
89
+ diff --git i/test/migrations/env.py w/test/migrations/env.py
90
+ index 36112a3..a1e15c2 100644
91
+ --- i/test/migrations/env.py
92
+ +++ w/test/migrations/env.py
93
+ @@ -1,3 +1,6 @@
94
+ +# fmt: off
95
+ +# isort: off
96
+ +
97
+ from logging.config import fileConfig
98
+
99
+ from sqlalchemy import engine_from_config
100
+ @@ -14,11 +17,17 @@ config = context.config
101
+ if config.config_file_name is not None:
102
+ fileConfig(config.config_file_name)
103
+
104
+ +from sqlmodel import SQLModel
105
+ +from test.models import *
106
+ +from test.utils import database_url
107
+ +
108
+ +config.set_main_option("sqlalchemy.url", database_url())
109
+ +
110
+ # add your model's MetaData object here
111
+ # for 'autogenerate' support
112
+ # from myapp import mymodel
113
+ # target_metadata = mymodel.Base.metadata
114
+ -target_metadata = None
115
+ +target_metadata = SQLModel.metadata
116
+
117
+ # other values from the config, defined by the needs of env.py,
118
+ # can be acquired:
119
+ diff --git i/test/migrations/script.py.mako w/test/migrations/script.py.mako
120
+ index fbc4b07..9dc78bb 100644
121
+ --- i/test/migrations/script.py.mako
122
+ +++ w/test/migrations/script.py.mako
123
+ @@ -9,6 +9,8 @@ from typing import Sequence, Union
124
+
125
+ from alembic import op
126
+ import sqlalchemy as sa
127
+ +import sqlmodel
128
+ +import activemodel
129
+ ${imports if imports else ""}
130
+
131
+ # revision identifiers, used by Alembic.
132
+ ```
133
+
134
+ Here are some useful resources around Alembic + SQLModel:
135
+
136
+ * https://github.com/fastapi/sqlmodel/issues/85
137
+ * https://testdriven.io/blog/fastapi-sqlmodel/
138
+
139
+ ### Query Wrapper
140
+
141
+ This tool is added to all `BaseModel`s and makes it easy to write SQL queries. Some examples:
142
+
143
+
144
+
145
+ ### Easy Database Sessions
146
+
147
+ I hate the idea f
148
+
149
+ * Behavior should be intuitive and easy to understand. If you run `save()`, it should save, not stick the save in a transaction.
150
+ * Don't worry about dead sessions. This makes it easy to lazy-load computed properties and largely eliminates the need to think about database sessions.
151
+
152
+ There are a couple of thorny problems we need to solve for here:
153
+
154
+ * In-memory fastapi servers are not the same as a uvicorn server, which is threaded *and* uses some sort of threadpool model for handling async requests. I don't claim to understand the entire implementation. For global DB session state (a) we can't use global variables (b) we can't use thread-local variables.
155
+ *
156
+
157
+ https://github.com/tomwojcik/starlette-context
158
+
159
+ ### Example Queries
160
+
161
+ * Conditional: `Scrape.select().where(Scrape.id < last_scraped.id).all()`
162
+ * Equality: `MenuItem.select().where(MenuItem.menu_id == menu.id).all()`
163
+ * `IN` example: `CanonicalMenuItem.select().where(col(CanonicalMenuItem.id).in_(canonized_ids)).all()`
164
+
165
+ ### TypeID
166
+
167
+ I'm a massive fan of Stripe-style prefixed UUIDs. [There's an excellent project](https://github.com/jetify-com/typeid)
168
+ that defined a clear spec for these IDs. I've used the python implementation of this spec and developed a clean integration
169
+ with SQLModel that plays well with fastapi as well.
170
+
171
+ Here's an example of defining a relationship:
172
+
173
+ ```python
174
+ import uuid
175
+
176
+ from activemodel import BaseModel
177
+ from activemodel.mixins import TimestampsMixin, TypeIDMixin
178
+ from activemodel.types import TypeIDType
179
+ from sqlmodel import Field, Relationship
180
+
181
+ from .patient import Patient
182
+
183
+ class Appointment(
184
+ BaseModel,
185
+ # this adds an `id` field to the model with the correct type
186
+ TypeIDMixin("appointment"),
187
+ table=True
188
+ ):
189
+ # `foreign_key` is a activemodel-specific method to generate the right `Field` for the relationship
190
+ # TypeIDType is really important here for fastapi serialization
191
+ doctor_id: TypeIDType = Doctor.foreign_key()
192
+ doctor: Doctor = Relationship()
193
+ ```
194
+
195
+ ## Limitations
196
+
197
+ ### Validation
198
+
199
+ SQLModel does not currently support pydantic validations (when `table=True`). This is very surprising, but is actually the intended functionality:
200
+
201
+ * https://github.com/fastapi/sqlmodel/discussions/897
202
+ * https://github.com/fastapi/sqlmodel/pull/1041
203
+ * https://github.com/fastapi/sqlmodel/issues/453
204
+ * https://github.com/fastapi/sqlmodel/issues/52#issuecomment-1311987732
205
+
206
+ For validation:
207
+
208
+ * When consuming API data, use a separate shadow model to validate the data with `table=False` and then inherit from that model in a model with `table=True`.
209
+ * When validating ORM data, use SQL Alchemy hooks.
210
+
211
+ <!--
212
+
213
+ This looks neat
214
+ https://github.com/DarylStark/my_data/blob/a17b8b3a8463b9953821b89fee895e272f94d2a4/src/my_model/model.py#L155
215
+ schema_extra={
216
+ 'pattern': r'^[a-z0-9_\-\.]+\@[a-z0-9_\-\.]+\.[a-z\.]+$'
217
+ },
218
+
219
+ extra constraints
220
+
221
+ https://github.com/DarylStark/my_data/blob/a17b8b3a8463b9953821b89fee895e272f94d2a4/src/my_model/model.py#L424C1-L426C6
222
+ -->
223
+ ## Related Projects
224
+
225
+ * https://github.com/woofz/sqlmodel-basecrud
226
+ * https://github.com/0xthiagomartins/sqlmodel-controller
227
+
228
+ ## Inspiration
229
+
230
+ * https://github.com/peterdresslar/fastapi-sqlmodel-alembic-pg
231
+ * [Albemic instructions](https://github.com/fastapi/sqlmodel/pull/899/files)
232
+ * https://github.com/fastapiutils/fastapi-utils/
233
+ * https://github.com/fastapi/full-stack-fastapi-template
234
+ * https://github.com/DarylStark/my_data/
235
+ * https://github.com/petrgazarov/FastAPI-app/tree/main/fastapi_app
@@ -0,0 +1,24 @@
1
+ activemodel/__init__.py,sha256=q_lHQyIM70ApvjduTo9GtenQjJXsfYZsAAquD_51kF4,137
2
+ activemodel/base_model.py,sha256=aa5cJYZsRvYYk-TvPP_DoblvtZrn6-KGB7YS0rbAJbw,10964
3
+ activemodel/celery.py,sha256=F2X5VJoNej6yg__nbF2VXaq6MK8jmgUlfo5XXivfgtU,740
4
+ activemodel/errors.py,sha256=wycWYmk9ws4TZpxvTdtXVy2SFESb8NqKgzdivBoF0vw,115
5
+ activemodel/get_column_from_field_patch.py,sha256=Rl1KIsELSLxDGDb4K7l97Fjr1qyXZtlTJlMa7-ddllc,4869
6
+ activemodel/logger.py,sha256=vU7QiGSy_AJuJFmClUocqIJ-Ltku_8C24ZU8L6fLJR0,53
7
+ activemodel/query_wrapper.py,sha256=rNdvueppMse2MIi-RafTEC34GPGRal_wqH2CzhmlWS8,2520
8
+ activemodel/session_manager.py,sha256=JpCN_mTsbYOOud9HsX6mP_HOf3i57O5haMgnv9WOeyU,3874
9
+ activemodel/utils.py,sha256=g17UqkphzTmb6YdpmYwT1TM00eDiXXuWn39-xNiu0AA,2112
10
+ activemodel/mixins/__init__.py,sha256=05EQl2u_Wgf_wkly-GTaTsR7zWpmpKcb96Js7r_rZTw,160
11
+ activemodel/mixins/pydantic_json.py,sha256=HfnUKy_XA7MQcmR9yMTBweaNgxJhhEo3Z07hFz31gIg,2555
12
+ activemodel/mixins/soft_delete.py,sha256=Ax4mGsQI7AVTE8c4GiWxpyB_W179-dDct79GtjP0owU,461
13
+ activemodel/mixins/timestamps.py,sha256=Q-IFljeVVJQqw3XHdOi7dkqzefiVg1zhJvq_bldpmjg,992
14
+ activemodel/mixins/typeid.py,sha256=DGjlIg8PRBYoaBbWkkxc6jkScyl-p53KuSR98lLgAvE,1284
15
+ activemodel/pytest/__init__.py,sha256=W9KKQHbPkyq0jrMXaiL8hG2Nsbjy_LN9HhvgGm8W_7g,98
16
+ activemodel/pytest/transaction.py,sha256=rrsoHnbu79kNdnI5fZeOZr5hzrLB-cQH10MueQp5jV4,1670
17
+ activemodel/pytest/truncate.py,sha256=IGiPLkUm2yyOKww6c6CKcVbwi2xAAFBopx9q2ABfu8w,1582
18
+ activemodel/types/__init__.py,sha256=y5fiGVtPJxGEhuf-TvyrkhM2yaKRcIWo6XAx-CFFjM8,31
19
+ activemodel/types/typeid.py,sha256=uTCtTm-HdvqZ2_wkIAOkCwDMNiNZna9AbSbOBNBca7o,7225
20
+ activemodel-0.7.0.dist-info/METADATA,sha256=Ht_6c2mD2_qGnrEDfMFxazdNqB9P4POPAU7dCrZ0s94,8216
21
+ activemodel-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
+ activemodel-0.7.0.dist-info/entry_points.txt,sha256=YLX62TP_hR-n3HMBkdBex4W7XRiyOtIPkwy22puIjjQ,61
23
+ activemodel-0.7.0.dist-info/licenses/LICENSE,sha256=L8mmpX47rB-xtJ_HsK0zpfO6viEjxbLYGn70BMp8os4,1071
24
+ activemodel-0.7.0.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
@@ -1,34 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: activemodel
3
- Version: 0.3.0
4
- Summary: Make SQLModel more like an a real ORM
5
- Author-email: Michael Bianco <iloveitaly@gmail.com>
6
- Project-URL: Repository, https://github.com/iloveitaly/activemodel
7
- Keywords: sqlmodel,orm,activerecord,activemodel,sqlalchemy
8
- Requires-Python: >=3.10
9
- Description-Content-Type: text/markdown
10
- License-File: LICENSE
11
- Requires-Dist: pydash>=8.0.4
12
- Requires-Dist: sqlmodel>=0.0.22
13
-
14
- # ActiveModel: ORM Wrapper for SQLModel
15
-
16
- No, this isn't *really* [ActiveModel](https://guides.rubyonrails.org/active_model_basics.html). It's just a wrapper around SQLModel that provides a more ActiveRecord-like interface.
17
-
18
- SQLModel is *not* an ORM. It's a SQL query builder and a schema definition tool.
19
-
20
- This package provides a thin wrapper around SQLModel that provides a more ActiveRecord-like interface with things like:
21
-
22
- * Timestamp column mixins
23
- * Lifecycle hooks
24
-
25
- ## Related Projects
26
-
27
- * https://github.com/woofz/sqlmodel-basecrud
28
-
29
- ## Inspiration
30
-
31
- * https://github.com/peterdresslar/fastapi-sqlmodel-alembic-pg
32
- * [Albemic instructions](https://github.com/fastapi/sqlmodel/pull/899/files)
33
- * https://github.com/fastapiutils/fastapi-utils/
34
- *
@@ -1,10 +0,0 @@
1
- activemodel/__init__.py,sha256=WWLMytAi2VO3-HdySf4IpbeSCPMlVVO_GGQlQNA6UWU,216
2
- activemodel/base_model.py,sha256=fk_g6jC1GCkAFo78UYjTONyFE8iYQH1bfvxdWG-_178,4200
3
- activemodel/query_wrapper.py,sha256=JiYN8EN9_gXpKrFw348gs4QixwtrEN50fJF-3uv5Gmg,2319
4
- activemodel/timestamps.py,sha256=8odUxQ1c0OouPAVioMTkD277w6S28Pk17pwCsaxKgww,991
5
- activemodel-0.3.0.dist-info/LICENSE,sha256=L8mmpX47rB-xtJ_HsK0zpfO6viEjxbLYGn70BMp8os4,1071
6
- activemodel-0.3.0.dist-info/METADATA,sha256=PrnCUuQVLSuCQ-tbwEMzHXrqMHq9MHTNYVgvRh9uXng,1174
7
- activemodel-0.3.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
8
- activemodel-0.3.0.dist-info/entry_points.txt,sha256=YLX62TP_hR-n3HMBkdBex4W7XRiyOtIPkwy22puIjjQ,61
9
- activemodel-0.3.0.dist-info/top_level.txt,sha256=JCMUN_seFIi6GXtnTQRWfxXDx6Oj1uok8qapQWbWKDM,12
10
- activemodel-0.3.0.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- activemodel