ormlambda 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ormlambda-0.1.0/LICENSE +21 -0
- ormlambda-0.1.0/PKG-INFO +268 -0
- ormlambda-0.1.0/README.md +254 -0
- ormlambda-0.1.0/pyproject.toml +25 -0
- ormlambda-0.1.0/src/ormlambda/__init__.py +4 -0
- ormlambda-0.1.0/src/ormlambda/common/__init__.py +2 -0
- ormlambda-0.1.0/src/ormlambda/common/abstract_classes/__init__.py +3 -0
- ormlambda-0.1.0/src/ormlambda/common/abstract_classes/abstract_model.py +302 -0
- ormlambda-0.1.0/src/ormlambda/common/abstract_classes/non_query_base.py +33 -0
- ormlambda-0.1.0/src/ormlambda/common/abstract_classes/query_base.py +10 -0
- ormlambda-0.1.0/src/ormlambda/common/enums/__init__.py +2 -0
- ormlambda-0.1.0/src/ormlambda/common/enums/condition_types.py +16 -0
- ormlambda-0.1.0/src/ormlambda/common/enums/join_type.py +10 -0
- ormlambda-0.1.0/src/ormlambda/common/interfaces/INonQueryCommand.py +9 -0
- ormlambda-0.1.0/src/ormlambda/common/interfaces/IQueryCommand.py +11 -0
- ormlambda-0.1.0/src/ormlambda/common/interfaces/IRepositoryBase.py +67 -0
- ormlambda-0.1.0/src/ormlambda/common/interfaces/IStatements.py +227 -0
- ormlambda-0.1.0/src/ormlambda/common/interfaces/__init__.py +4 -0
- ormlambda-0.1.0/src/ormlambda/components/__init__.py +0 -0
- ormlambda-0.1.0/src/ormlambda/components/delete/IDelete.py +6 -0
- ormlambda-0.1.0/src/ormlambda/components/delete/__init__.py +2 -0
- ormlambda-0.1.0/src/ormlambda/components/delete/abstract_delete.py +14 -0
- ormlambda-0.1.0/src/ormlambda/components/insert/IInsert.py +6 -0
- ormlambda-0.1.0/src/ormlambda/components/insert/__init__.py +2 -0
- ormlambda-0.1.0/src/ormlambda/components/insert/abstract_insert.py +21 -0
- ormlambda-0.1.0/src/ormlambda/components/select/ISelect.py +14 -0
- ormlambda-0.1.0/src/ormlambda/components/select/__init__.py +2 -0
- ormlambda-0.1.0/src/ormlambda/components/select/table_column.py +39 -0
- ormlambda-0.1.0/src/ormlambda/components/update/IUpdate.py +7 -0
- ormlambda-0.1.0/src/ormlambda/components/update/__init__.py +2 -0
- ormlambda-0.1.0/src/ormlambda/components/update/abstract_update.py +25 -0
- ormlambda-0.1.0/src/ormlambda/components/upsert/IUpsert.py +6 -0
- ormlambda-0.1.0/src/ormlambda/components/upsert/__init__.py +2 -0
- ormlambda-0.1.0/src/ormlambda/components/upsert/abstract_upsert.py +21 -0
- ormlambda-0.1.0/src/ormlambda/components/where/__init__.py +1 -0
- ormlambda-0.1.0/src/ormlambda/components/where/abstract_where.py +11 -0
- ormlambda-0.1.0/src/ormlambda/databases/__init__.py +0 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/__init__.py +2 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/__init__.py +13 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/create_database.py +29 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/delete.py +54 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/drop_database.py +19 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/drop_table.py +23 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/insert.py +70 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/joins.py +103 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/limit.py +17 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/offset.py +17 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/order.py +29 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/select.py +172 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/update.py +52 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/upsert.py +68 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/clauses/where_condition.py +219 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/repository.py +192 -0
- ormlambda-0.1.0/src/ormlambda/databases/my_sql/statements.py +86 -0
- ormlambda-0.1.0/src/ormlambda/model_base.py +36 -0
- ormlambda-0.1.0/src/ormlambda/utils/__init__.py +3 -0
- ormlambda-0.1.0/src/ormlambda/utils/column.py +65 -0
- ormlambda-0.1.0/src/ormlambda/utils/dtypes.py +104 -0
- ormlambda-0.1.0/src/ormlambda/utils/foreign_key.py +36 -0
- ormlambda-0.1.0/src/ormlambda/utils/lambda_disassembler/__init__.py +4 -0
- ormlambda-0.1.0/src/ormlambda/utils/lambda_disassembler/dis_types.py +136 -0
- ormlambda-0.1.0/src/ormlambda/utils/lambda_disassembler/disassembler.py +69 -0
- ormlambda-0.1.0/src/ormlambda/utils/lambda_disassembler/dtypes.py +103 -0
- ormlambda-0.1.0/src/ormlambda/utils/lambda_disassembler/name_of.py +41 -0
- ormlambda-0.1.0/src/ormlambda/utils/lambda_disassembler/nested_element.py +44 -0
- ormlambda-0.1.0/src/ormlambda/utils/lambda_disassembler/tree_instruction.py +145 -0
- ormlambda-0.1.0/src/ormlambda/utils/module_tree/__init__.py +0 -0
- ormlambda-0.1.0/src/ormlambda/utils/module_tree/dfs_traversal.py +60 -0
- ormlambda-0.1.0/src/ormlambda/utils/module_tree/dynamic_module.py +237 -0
- ormlambda-0.1.0/src/ormlambda/utils/table_constructor.py +308 -0
ormlambda-0.1.0/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Pablo Hernández Zamora
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
ormlambda-0.1.0/PKG-INFO
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: ormlambda
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: ORM designed to interact with the database using lambda functions
|
5
|
+
Author: p-hzamora
|
6
|
+
Author-email: p.hzamora@icloud.com
|
7
|
+
Requires-Python: >=3.12,<4.0
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
10
|
+
Requires-Dist: mysql-connector (>=2.2.9,<3.0.0)
|
11
|
+
Requires-Dist: ruff (>=0.4.5,<0.5.0)
|
12
|
+
Description-Content-Type: text/markdown
|
13
|
+
|
14
|
+
# ormMySQL
|
15
|
+
This ORM is designed to connect with a MySQL server, facilitating the management of various database queries. Built with flexibility and efficiency in mind, this ORM empowers developers to interact with the database using lambda functions, allowing for concise and expressive query construction.
|
16
|
+
|
17
|
+
# Creating your first lambda query
|
18
|
+
|
19
|
+
## Initialize MySQLRepository
|
20
|
+
```python
|
21
|
+
from decouple import config
|
22
|
+
from src.ormmysql.databases.my_sql import MySQLRepository
|
23
|
+
|
24
|
+
USERNAME = config("USERNAME")
|
25
|
+
PASSWORD = config("PASSWORD")
|
26
|
+
HOST = config("HOST")
|
27
|
+
|
28
|
+
|
29
|
+
database = MySQLRepository(user=USERNAME, password=PASSWORD, database="sakila", host=HOST)
|
30
|
+
```
|
31
|
+
|
32
|
+
## Select all columns
|
33
|
+
```python
|
34
|
+
from src.test.models.address import AddressModel
|
35
|
+
|
36
|
+
result = AddressModel(database).select()
|
37
|
+
```
|
38
|
+
The `result` var will be of type `tuple[Address, ...]`
|
39
|
+
|
40
|
+
## Select multiples tables
|
41
|
+
Once the `AddressModel` class is created, we will not only be able to access all the information in that table, but also all the information in all the tables that have foreign keys related to it."
|
42
|
+
|
43
|
+
```python
|
44
|
+
from src.ormmysql.common.enums import ConditionType
|
45
|
+
from src.test.models.address import AddressModel
|
46
|
+
|
47
|
+
|
48
|
+
result = AddressModel(database).where(lambda x: (x.City.Country, ConditionType.REGEXP, r"^[aA]")).select(
|
49
|
+
lambda address: (
|
50
|
+
address,
|
51
|
+
address.City,
|
52
|
+
address.City.Country,
|
53
|
+
),
|
54
|
+
)
|
55
|
+
```
|
56
|
+
The `result` var will be of type `tuple[tuple[Address], tuple[City], tuple[Country]]`.
|
57
|
+
|
58
|
+
If we were used `select_one` method, we retrieved `tuple[Address, City, Country]`.
|
59
|
+
|
60
|
+
## Filter by `where` condition
|
61
|
+
|
62
|
+
```python
|
63
|
+
result = AddressModel(database).where(lambda x: 10 <= x.address_id <= 30).select()
|
64
|
+
```
|
65
|
+
|
66
|
+
Additionally, we can filter by others tables. For example, we can return all addresses for each city where `country_id` = 87 (Spain)
|
67
|
+
|
68
|
+
```python
|
69
|
+
result = AddressModel(database).where(lambda x: x.City.Country.country_id == 87).select()
|
70
|
+
```
|
71
|
+
|
72
|
+
We can also return `Address`, `City` or `Country` if needed.
|
73
|
+
|
74
|
+
```python
|
75
|
+
result = AddressModel(database).where(lambda x: x.City.Country.country_id == 87).select(lambda x: (x, x.City, x.City.Country))
|
76
|
+
```
|
77
|
+
|
78
|
+
### Pass variables to the `where` method
|
79
|
+
Since we generally work with lambda methods, I often have to work with `bytecode` to retrieve the name of the string variables. For this reason, it's imperative that we map these variables to replace them with the actual values.
|
80
|
+
|
81
|
+
```python
|
82
|
+
LOWER = 10
|
83
|
+
UPPER = 30
|
84
|
+
AddressModel(database).where(lambda x: LOWER <= x.address_id <= UPPER, LOWER=LOWER, UPPER=UPPER).select()
|
85
|
+
```
|
86
|
+
That solution is somewhat `awkward` and not very clean, but it's necessary for automating queries.
|
87
|
+
|
88
|
+
## Writable methods INSERT, UPDATE, DELETE
|
89
|
+
The easiest way to add or delete data in your database is by using its appropiate methods. You just need to instantiate an object with the data and pass it to the method
|
90
|
+
|
91
|
+
### Insert
|
92
|
+
```python
|
93
|
+
address = Address(address_id=1, address="C/ ...", phone="XXXXXXXXX", postal_code="28026")
|
94
|
+
|
95
|
+
AddressModel(database).insert(address)
|
96
|
+
```
|
97
|
+
|
98
|
+
### Update
|
99
|
+
|
100
|
+
You can use either the properties of the same object or `str` values.
|
101
|
+
```python
|
102
|
+
|
103
|
+
AddressModel(database).where(lambda x: x.address_id == 1).update(
|
104
|
+
{
|
105
|
+
Address.phone: "YYYYYYYYY",
|
106
|
+
Address.postal_code: "28030",
|
107
|
+
}
|
108
|
+
)
|
109
|
+
|
110
|
+
AddressModel(database).where(lambda x: x.address_id == 1).update(
|
111
|
+
{
|
112
|
+
"phone": "YYYYYYYYY",
|
113
|
+
"postal_code": "28030",
|
114
|
+
}
|
115
|
+
)
|
116
|
+
```
|
117
|
+
### Delete
|
118
|
+
|
119
|
+
```python
|
120
|
+
AddressModel(database).where(lambda x: x.address_id == 1).delete()
|
121
|
+
```
|
122
|
+
|
123
|
+
|
124
|
+
# Table Map
|
125
|
+
The most important aspect when creating classes to map database tables is to consider the importance of typing the variables that should behave as columns. In other words, variables that are typed will be those that are passed to the class constructor. This is why both `__table_name__` and variables that reference foreign classes, are not given a specific data tpye.
|
126
|
+
|
127
|
+
For example, imagine you have three Table in your database: `Addres`, `City` and `Country`. Each of them has its own Foreing keys.
|
128
|
+
|
129
|
+
`Address` has a FK relationship with `City`.
|
130
|
+
|
131
|
+
`City` has a FK relationship with `Country`.
|
132
|
+
|
133
|
+
The easiest way to map your tables is:
|
134
|
+
|
135
|
+
```python
|
136
|
+
from datetime import datetime
|
137
|
+
|
138
|
+
from src.ormmysql import (
|
139
|
+
Column,
|
140
|
+
Table,
|
141
|
+
BaseModel,
|
142
|
+
ForeignKey,
|
143
|
+
)
|
144
|
+
from src.ormmysql.common.interfaces import IStatements_two_generic, IRepositoryBase
|
145
|
+
|
146
|
+
|
147
|
+
class Country(Table):
|
148
|
+
__table_name__ = "country"
|
149
|
+
|
150
|
+
country_id: int = Column[int](is_primary_key=True)
|
151
|
+
country: str
|
152
|
+
last_update: datetime
|
153
|
+
|
154
|
+
|
155
|
+
class Address(Table):
|
156
|
+
__table_name__ = "address"
|
157
|
+
|
158
|
+
address_id: int = Column[int](is_primary_key=True)
|
159
|
+
address: str
|
160
|
+
address2: str
|
161
|
+
district: str
|
162
|
+
city_id: int
|
163
|
+
postal_code: str
|
164
|
+
phone: str
|
165
|
+
location: str
|
166
|
+
last_update: datetime = Column[datetime](is_auto_generated=True)
|
167
|
+
|
168
|
+
City = ForeignKey["Address", City](__table_name__, City, lambda a, c: a.city_id == c.city_id)
|
169
|
+
|
170
|
+
|
171
|
+
class City(Table):
|
172
|
+
__table_name__ = "city"
|
173
|
+
|
174
|
+
city_id: int = Column[int](is_primary_key=True)
|
175
|
+
city: str
|
176
|
+
country_id: int
|
177
|
+
last_update: datetime
|
178
|
+
|
179
|
+
Country = ForeignKey["City", Country](__table_name__, Country, lambda ci, co: ci.country_id == co.country_id)
|
180
|
+
```
|
181
|
+
|
182
|
+
Once created, you need to create a Model for each Table
|
183
|
+
|
184
|
+
```python
|
185
|
+
class CountryModel(BaseModel[Country]):
|
186
|
+
def __new__[TRepo](cls, repository: IRepositoryBase[TRepo]):
|
187
|
+
return super().__new__(cls, Country, repository)
|
188
|
+
|
189
|
+
|
190
|
+
class AddressModel(BaseModel[Address]):
|
191
|
+
def __new__[TRepo](cls, repository: IRepositoryBase[TRepo]):
|
192
|
+
return super().__new__(cls, Address, repository)
|
193
|
+
|
194
|
+
|
195
|
+
class CityModel(BaseModel[City]):
|
196
|
+
def __new__[TRepo](cls, repository: IRepositoryBase[TRepo]):
|
197
|
+
return super().__new__(cls, City, repository)
|
198
|
+
```
|
199
|
+
|
200
|
+
# Creating complex queries with lambda
|
201
|
+
|
202
|
+
We can use various methods such as `where`, `limit`, `offset`, `order`, etc...
|
203
|
+
|
204
|
+
# Filter using `where` method
|
205
|
+
To retrieve all `Address` object where the fk reference to the `City` table, and the fk reference to the `Country` table have a `country_id` value greater or equal than 50, ordered in `descending` order, then:
|
206
|
+
|
207
|
+
```python
|
208
|
+
result = (
|
209
|
+
AddressModel(database)
|
210
|
+
.order(lambda a: a.address_id, order_type="DESC")
|
211
|
+
.where(lambda x: x.City.Country.country_id >= 50)
|
212
|
+
.select(lambda a: (a))
|
213
|
+
)
|
214
|
+
|
215
|
+
```
|
216
|
+
Also you can use `ConditionType` enum for `regular expressions` and get, for example, all rows from a different table where the `Country` name starts with `A`, limited to `100`:
|
217
|
+
|
218
|
+
|
219
|
+
```python
|
220
|
+
address, city, country = (
|
221
|
+
AddressModel(database)
|
222
|
+
.order(lambda a: a.address_id, order_type="DESC")
|
223
|
+
.where(lambda x: (x.City.Country, ConditionType.REGEXP, r"^[A]"))
|
224
|
+
.limit(100)
|
225
|
+
.select(
|
226
|
+
lambda a: (
|
227
|
+
a,
|
228
|
+
a.City,
|
229
|
+
a.City.Country,
|
230
|
+
)
|
231
|
+
)
|
232
|
+
)
|
233
|
+
|
234
|
+
|
235
|
+
for a in address:
|
236
|
+
|
237
|
+
print(a.address_id)
|
238
|
+
|
239
|
+
for c in city:
|
240
|
+
print(c.city_id)
|
241
|
+
|
242
|
+
for co in country:
|
243
|
+
print(co.country)
|
244
|
+
```
|
245
|
+
|
246
|
+
# Transform Table objects into Iterable object
|
247
|
+
In the example above, we see that the `result` var returns a tuple of tuples. However, we can simplify the `result` var when needed by passing `flavour` attribute in `select` method to get a tuple of the specified data type.
|
248
|
+
|
249
|
+
```python
|
250
|
+
result = (
|
251
|
+
a_model
|
252
|
+
.where(lambda x: (x.City.Country, ConditionType.REGEXP, r"^[A]"))
|
253
|
+
.limit(100)
|
254
|
+
.select(
|
255
|
+
lambda a: (
|
256
|
+
a.address_id,
|
257
|
+
a.City.city_id,
|
258
|
+
a.City.Country.country_id,
|
259
|
+
a.City.Country.country,
|
260
|
+
),
|
261
|
+
flavour=dict,
|
262
|
+
)
|
263
|
+
)
|
264
|
+
```
|
265
|
+
|
266
|
+
with this approach, we will obtain a dictionary where the key will be the concatenation between the selected table name and the column name specified in the lambda function, to avoid overwritting data from tables that sharing column names.
|
267
|
+
|
268
|
+
|
@@ -0,0 +1,254 @@
|
|
1
|
+
# ormMySQL
|
2
|
+
This ORM is designed to connect with a MySQL server, facilitating the management of various database queries. Built with flexibility and efficiency in mind, this ORM empowers developers to interact with the database using lambda functions, allowing for concise and expressive query construction.
|
3
|
+
|
4
|
+
# Creating your first lambda query
|
5
|
+
|
6
|
+
## Initialize MySQLRepository
|
7
|
+
```python
|
8
|
+
from decouple import config
|
9
|
+
from src.ormmysql.databases.my_sql import MySQLRepository
|
10
|
+
|
11
|
+
USERNAME = config("USERNAME")
|
12
|
+
PASSWORD = config("PASSWORD")
|
13
|
+
HOST = config("HOST")
|
14
|
+
|
15
|
+
|
16
|
+
database = MySQLRepository(user=USERNAME, password=PASSWORD, database="sakila", host=HOST)
|
17
|
+
```
|
18
|
+
|
19
|
+
## Select all columns
|
20
|
+
```python
|
21
|
+
from src.test.models.address import AddressModel
|
22
|
+
|
23
|
+
result = AddressModel(database).select()
|
24
|
+
```
|
25
|
+
The `result` var will be of type `tuple[Address, ...]`
|
26
|
+
|
27
|
+
## Select multiples tables
|
28
|
+
Once the `AddressModel` class is created, we will not only be able to access all the information in that table, but also all the information in all the tables that have foreign keys related to it."
|
29
|
+
|
30
|
+
```python
|
31
|
+
from src.ormmysql.common.enums import ConditionType
|
32
|
+
from src.test.models.address import AddressModel
|
33
|
+
|
34
|
+
|
35
|
+
result = AddressModel(database).where(lambda x: (x.City.Country, ConditionType.REGEXP, r"^[aA]")).select(
|
36
|
+
lambda address: (
|
37
|
+
address,
|
38
|
+
address.City,
|
39
|
+
address.City.Country,
|
40
|
+
),
|
41
|
+
)
|
42
|
+
```
|
43
|
+
The `result` var will be of type `tuple[tuple[Address], tuple[City], tuple[Country]]`.
|
44
|
+
|
45
|
+
If we were used `select_one` method, we retrieved `tuple[Address, City, Country]`.
|
46
|
+
|
47
|
+
## Filter by `where` condition
|
48
|
+
|
49
|
+
```python
|
50
|
+
result = AddressModel(database).where(lambda x: 10 <= x.address_id <= 30).select()
|
51
|
+
```
|
52
|
+
|
53
|
+
Additionally, we can filter by others tables. For example, we can return all addresses for each city where `country_id` = 87 (Spain)
|
54
|
+
|
55
|
+
```python
|
56
|
+
result = AddressModel(database).where(lambda x: x.City.Country.country_id == 87).select()
|
57
|
+
```
|
58
|
+
|
59
|
+
We can also return `Address`, `City` or `Country` if needed.
|
60
|
+
|
61
|
+
```python
|
62
|
+
result = AddressModel(database).where(lambda x: x.City.Country.country_id == 87).select(lambda x: (x, x.City, x.City.Country))
|
63
|
+
```
|
64
|
+
|
65
|
+
### Pass variables to the `where` method
|
66
|
+
Since we generally work with lambda methods, I often have to work with `bytecode` to retrieve the name of the string variables. For this reason, it's imperative that we map these variables to replace them with the actual values.
|
67
|
+
|
68
|
+
```python
|
69
|
+
LOWER = 10
|
70
|
+
UPPER = 30
|
71
|
+
AddressModel(database).where(lambda x: LOWER <= x.address_id <= UPPER, LOWER=LOWER, UPPER=UPPER).select()
|
72
|
+
```
|
73
|
+
That solution is somewhat `awkward` and not very clean, but it's necessary for automating queries.
|
74
|
+
|
75
|
+
## Writable methods INSERT, UPDATE, DELETE
|
76
|
+
The easiest way to add or delete data in your database is by using its appropiate methods. You just need to instantiate an object with the data and pass it to the method
|
77
|
+
|
78
|
+
### Insert
|
79
|
+
```python
|
80
|
+
address = Address(address_id=1, address="C/ ...", phone="XXXXXXXXX", postal_code="28026")
|
81
|
+
|
82
|
+
AddressModel(database).insert(address)
|
83
|
+
```
|
84
|
+
|
85
|
+
### Update
|
86
|
+
|
87
|
+
You can use either the properties of the same object or `str` values.
|
88
|
+
```python
|
89
|
+
|
90
|
+
AddressModel(database).where(lambda x: x.address_id == 1).update(
|
91
|
+
{
|
92
|
+
Address.phone: "YYYYYYYYY",
|
93
|
+
Address.postal_code: "28030",
|
94
|
+
}
|
95
|
+
)
|
96
|
+
|
97
|
+
AddressModel(database).where(lambda x: x.address_id == 1).update(
|
98
|
+
{
|
99
|
+
"phone": "YYYYYYYYY",
|
100
|
+
"postal_code": "28030",
|
101
|
+
}
|
102
|
+
)
|
103
|
+
```
|
104
|
+
### Delete
|
105
|
+
|
106
|
+
```python
|
107
|
+
AddressModel(database).where(lambda x: x.address_id == 1).delete()
|
108
|
+
```
|
109
|
+
|
110
|
+
|
111
|
+
# Table Map
|
112
|
+
The most important aspect when creating classes to map database tables is to consider the importance of typing the variables that should behave as columns. In other words, variables that are typed will be those that are passed to the class constructor. This is why both `__table_name__` and variables that reference foreign classes, are not given a specific data tpye.
|
113
|
+
|
114
|
+
For example, imagine you have three Table in your database: `Addres`, `City` and `Country`. Each of them has its own Foreing keys.
|
115
|
+
|
116
|
+
`Address` has a FK relationship with `City`.
|
117
|
+
|
118
|
+
`City` has a FK relationship with `Country`.
|
119
|
+
|
120
|
+
The easiest way to map your tables is:
|
121
|
+
|
122
|
+
```python
|
123
|
+
from datetime import datetime
|
124
|
+
|
125
|
+
from src.ormmysql import (
|
126
|
+
Column,
|
127
|
+
Table,
|
128
|
+
BaseModel,
|
129
|
+
ForeignKey,
|
130
|
+
)
|
131
|
+
from src.ormmysql.common.interfaces import IStatements_two_generic, IRepositoryBase
|
132
|
+
|
133
|
+
|
134
|
+
class Country(Table):
|
135
|
+
__table_name__ = "country"
|
136
|
+
|
137
|
+
country_id: int = Column[int](is_primary_key=True)
|
138
|
+
country: str
|
139
|
+
last_update: datetime
|
140
|
+
|
141
|
+
|
142
|
+
class Address(Table):
|
143
|
+
__table_name__ = "address"
|
144
|
+
|
145
|
+
address_id: int = Column[int](is_primary_key=True)
|
146
|
+
address: str
|
147
|
+
address2: str
|
148
|
+
district: str
|
149
|
+
city_id: int
|
150
|
+
postal_code: str
|
151
|
+
phone: str
|
152
|
+
location: str
|
153
|
+
last_update: datetime = Column[datetime](is_auto_generated=True)
|
154
|
+
|
155
|
+
City = ForeignKey["Address", City](__table_name__, City, lambda a, c: a.city_id == c.city_id)
|
156
|
+
|
157
|
+
|
158
|
+
class City(Table):
|
159
|
+
__table_name__ = "city"
|
160
|
+
|
161
|
+
city_id: int = Column[int](is_primary_key=True)
|
162
|
+
city: str
|
163
|
+
country_id: int
|
164
|
+
last_update: datetime
|
165
|
+
|
166
|
+
Country = ForeignKey["City", Country](__table_name__, Country, lambda ci, co: ci.country_id == co.country_id)
|
167
|
+
```
|
168
|
+
|
169
|
+
Once created, you need to create a Model for each Table
|
170
|
+
|
171
|
+
```python
|
172
|
+
class CountryModel(BaseModel[Country]):
|
173
|
+
def __new__[TRepo](cls, repository: IRepositoryBase[TRepo]):
|
174
|
+
return super().__new__(cls, Country, repository)
|
175
|
+
|
176
|
+
|
177
|
+
class AddressModel(BaseModel[Address]):
|
178
|
+
def __new__[TRepo](cls, repository: IRepositoryBase[TRepo]):
|
179
|
+
return super().__new__(cls, Address, repository)
|
180
|
+
|
181
|
+
|
182
|
+
class CityModel(BaseModel[City]):
|
183
|
+
def __new__[TRepo](cls, repository: IRepositoryBase[TRepo]):
|
184
|
+
return super().__new__(cls, City, repository)
|
185
|
+
```
|
186
|
+
|
187
|
+
# Creating complex queries with lambda
|
188
|
+
|
189
|
+
We can use various methods such as `where`, `limit`, `offset`, `order`, etc...
|
190
|
+
|
191
|
+
# Filter using `where` method
|
192
|
+
To retrieve all `Address` object where the fk reference to the `City` table, and the fk reference to the `Country` table have a `country_id` value greater or equal than 50, ordered in `descending` order, then:
|
193
|
+
|
194
|
+
```python
|
195
|
+
result = (
|
196
|
+
AddressModel(database)
|
197
|
+
.order(lambda a: a.address_id, order_type="DESC")
|
198
|
+
.where(lambda x: x.City.Country.country_id >= 50)
|
199
|
+
.select(lambda a: (a))
|
200
|
+
)
|
201
|
+
|
202
|
+
```
|
203
|
+
Also you can use `ConditionType` enum for `regular expressions` and get, for example, all rows from a different table where the `Country` name starts with `A`, limited to `100`:
|
204
|
+
|
205
|
+
|
206
|
+
```python
|
207
|
+
address, city, country = (
|
208
|
+
AddressModel(database)
|
209
|
+
.order(lambda a: a.address_id, order_type="DESC")
|
210
|
+
.where(lambda x: (x.City.Country, ConditionType.REGEXP, r"^[A]"))
|
211
|
+
.limit(100)
|
212
|
+
.select(
|
213
|
+
lambda a: (
|
214
|
+
a,
|
215
|
+
a.City,
|
216
|
+
a.City.Country,
|
217
|
+
)
|
218
|
+
)
|
219
|
+
)
|
220
|
+
|
221
|
+
|
222
|
+
for a in address:
|
223
|
+
|
224
|
+
print(a.address_id)
|
225
|
+
|
226
|
+
for c in city:
|
227
|
+
print(c.city_id)
|
228
|
+
|
229
|
+
for co in country:
|
230
|
+
print(co.country)
|
231
|
+
```
|
232
|
+
|
233
|
+
# Transform Table objects into Iterable object
|
234
|
+
In the example above, we see that the `result` var returns a tuple of tuples. However, we can simplify the `result` var when needed by passing `flavour` attribute in `select` method to get a tuple of the specified data type.
|
235
|
+
|
236
|
+
```python
|
237
|
+
result = (
|
238
|
+
a_model
|
239
|
+
.where(lambda x: (x.City.Country, ConditionType.REGEXP, r"^[A]"))
|
240
|
+
.limit(100)
|
241
|
+
.select(
|
242
|
+
lambda a: (
|
243
|
+
a.address_id,
|
244
|
+
a.City.city_id,
|
245
|
+
a.City.Country.country_id,
|
246
|
+
a.City.Country.country,
|
247
|
+
),
|
248
|
+
flavour=dict,
|
249
|
+
)
|
250
|
+
)
|
251
|
+
```
|
252
|
+
|
253
|
+
with this approach, we will obtain a dictionary where the key will be the concatenation between the selected table name and the column name specified in the lambda function, to avoid overwritting data from tables that sharing column names.
|
254
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
[tool.ruff]
|
2
|
+
line-length = 320
|
3
|
+
|
4
|
+
[tool.poetry]
|
5
|
+
name = "ormlambda"
|
6
|
+
version = "0.1.0"
|
7
|
+
description = "ORM designed to interact with the database using lambda functions"
|
8
|
+
authors = ["p-hzamora <p.hzamora@icloud.com>"]
|
9
|
+
readme = "README.md"
|
10
|
+
|
11
|
+
[tool.poetry.dependencies]
|
12
|
+
python = "^3.12"
|
13
|
+
mysql-connector = "^2.2.9"
|
14
|
+
ruff = "^0.4.5"
|
15
|
+
|
16
|
+
[tool.poetry.group.test.dependencies]
|
17
|
+
pandas = "^2.2.2"
|
18
|
+
mysql-connector = "^2.2.9"
|
19
|
+
ruff = "^0.4.5"
|
20
|
+
python-decouple = "^3.8"
|
21
|
+
|
22
|
+
|
23
|
+
[build-system]
|
24
|
+
requires = ["poetry-core"]
|
25
|
+
build-backend = "poetry.core.masonry.api"
|