TypeDAL 3.8.4__py3-none-any.whl → 3.9.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.
Potentially problematic release.
This version of TypeDAL might be problematic. Click here for more details.
- typedal/__about__.py +1 -1
- typedal/core.py +61 -15
- {typedal-3.8.4.dist-info → typedal-3.9.0.dist-info}/METADATA +1 -1
- {typedal-3.8.4.dist-info → typedal-3.9.0.dist-info}/RECORD +6 -6
- {typedal-3.8.4.dist-info → typedal-3.9.0.dist-info}/WHEEL +0 -0
- {typedal-3.8.4.dist-info → typedal-3.9.0.dist-info}/entry_points.txt +0 -0
typedal/__about__.py
CHANGED
typedal/core.py
CHANGED
|
@@ -130,6 +130,7 @@ class Relationship(typing.Generic[To_Type]):
|
|
|
130
130
|
_type: To_Type
|
|
131
131
|
table: Type["TypedTable"] | type | str
|
|
132
132
|
condition: Condition
|
|
133
|
+
condition_and: Condition
|
|
133
134
|
on: OnQuery
|
|
134
135
|
multiple: bool
|
|
135
136
|
join: JOIN_OPTIONS
|
|
@@ -140,6 +141,7 @@ class Relationship(typing.Generic[To_Type]):
|
|
|
140
141
|
condition: Condition = None,
|
|
141
142
|
join: JOIN_OPTIONS = None,
|
|
142
143
|
on: OnQuery = None,
|
|
144
|
+
condition_and: Condition = None,
|
|
143
145
|
):
|
|
144
146
|
"""
|
|
145
147
|
Should not be called directly, use relationship() instead!
|
|
@@ -152,6 +154,7 @@ class Relationship(typing.Generic[To_Type]):
|
|
|
152
154
|
self.condition = condition
|
|
153
155
|
self.join = "left" if on else join # .on is always left join!
|
|
154
156
|
self.on = on
|
|
157
|
+
self.condition_and = condition_and
|
|
155
158
|
|
|
156
159
|
if args := typing.get_args(_type):
|
|
157
160
|
self.table = unwrap_type(args[0])
|
|
@@ -172,6 +175,7 @@ class Relationship(typing.Generic[To_Type]):
|
|
|
172
175
|
update.get("condition") or self.condition,
|
|
173
176
|
update.get("join") or self.join,
|
|
174
177
|
update.get("on") or self.on,
|
|
178
|
+
update.get("condition_and") or self.condition_and,
|
|
175
179
|
)
|
|
176
180
|
|
|
177
181
|
def __repr__(self) -> str:
|
|
@@ -180,6 +184,10 @@ class Relationship(typing.Generic[To_Type]):
|
|
|
180
184
|
"""
|
|
181
185
|
if callback := self.condition or self.on:
|
|
182
186
|
src_code = inspect.getsource(callback).strip()
|
|
187
|
+
|
|
188
|
+
if c_and := self.condition_and:
|
|
189
|
+
and_code = inspect.getsource(c_and).strip()
|
|
190
|
+
src_code += " AND " + and_code
|
|
183
191
|
else:
|
|
184
192
|
cls_name = self._type if isinstance(self._type, str) else self._type.__name__ # type: ignore
|
|
185
193
|
src_code = f"to {cls_name} (missing condition)"
|
|
@@ -1050,11 +1058,12 @@ class TableMeta(type):
|
|
|
1050
1058
|
method: JOIN_OPTIONS = None,
|
|
1051
1059
|
on: OnQuery | list[Expression] | Expression = None,
|
|
1052
1060
|
condition: Condition = None,
|
|
1061
|
+
condition_and: Condition = None,
|
|
1053
1062
|
) -> "QueryBuilder[T_MetaInstance]":
|
|
1054
1063
|
"""
|
|
1055
1064
|
See QueryBuilder.join!
|
|
1056
1065
|
"""
|
|
1057
|
-
return QueryBuilder(self).join(*fields, on=on, condition=condition, method=method)
|
|
1066
|
+
return QueryBuilder(self).join(*fields, on=on, condition=condition, method=method, condition_and=condition_and)
|
|
1058
1067
|
|
|
1059
1068
|
def collect(self: Type[T_MetaInstance], verbose: bool = False) -> "TypedRows[T_MetaInstance]":
|
|
1060
1069
|
"""
|
|
@@ -1500,7 +1509,14 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
|
|
|
1500
1509
|
return None # type: ignore
|
|
1501
1510
|
|
|
1502
1511
|
inst._row = row
|
|
1503
|
-
|
|
1512
|
+
|
|
1513
|
+
if hasattr(row, "id"):
|
|
1514
|
+
inst.__dict__.update(row)
|
|
1515
|
+
else:
|
|
1516
|
+
# deal with _extra (and possibly others?)
|
|
1517
|
+
# Row <{actual: {}, _extra: ...}>
|
|
1518
|
+
inst.__dict__.update(row[str(cls)])
|
|
1519
|
+
|
|
1504
1520
|
inst._setup_instance_methods()
|
|
1505
1521
|
return inst
|
|
1506
1522
|
|
|
@@ -1828,7 +1844,22 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
|
|
|
1828
1844
|
`metadata` can be any (un)structured data
|
|
1829
1845
|
`model` is a Typed Table class
|
|
1830
1846
|
"""
|
|
1831
|
-
|
|
1847
|
+
|
|
1848
|
+
def _get_id(row: Row) -> int:
|
|
1849
|
+
"""
|
|
1850
|
+
Try to find the id field in a row.
|
|
1851
|
+
|
|
1852
|
+
If _extra exists, the row changes:
|
|
1853
|
+
<Row {'test_relationship': {'id': 1}, '_extra': {'COUNT("test_relationship"."querytable")': 8}}>
|
|
1854
|
+
"""
|
|
1855
|
+
if idx := getattr(row, "id", None):
|
|
1856
|
+
return typing.cast(int, idx)
|
|
1857
|
+
elif main := getattr(row, str(model), None):
|
|
1858
|
+
return typing.cast(int, main.id)
|
|
1859
|
+
else: # pragma: no cover
|
|
1860
|
+
raise NotImplementedError(f"`id` could not be found for {row}")
|
|
1861
|
+
|
|
1862
|
+
records = records or {_get_id(row): model(row) for row in rows}
|
|
1832
1863
|
super().__init__(rows.db, records, rows.colnames, rows.compact, rows.response, rows.fields)
|
|
1833
1864
|
self.model = model
|
|
1834
1865
|
self.metadata = metadata or {}
|
|
@@ -2295,6 +2326,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2295
2326
|
method: JOIN_OPTIONS = None,
|
|
2296
2327
|
on: OnQuery | list[Expression] | Expression = None,
|
|
2297
2328
|
condition: Condition = None,
|
|
2329
|
+
condition_and: Condition = None,
|
|
2298
2330
|
) -> "QueryBuilder[T_MetaInstance]":
|
|
2299
2331
|
"""
|
|
2300
2332
|
Include relationship fields in the result.
|
|
@@ -2304,8 +2336,13 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2304
2336
|
|
|
2305
2337
|
By default, the `method` defined in the relationship is used.
|
|
2306
2338
|
This can be overwritten with the `method` keyword argument (left or inner)
|
|
2339
|
+
|
|
2340
|
+
`condition_and` can be used to add extra conditions to an inner join.
|
|
2307
2341
|
"""
|
|
2308
2342
|
# todo: allow limiting amount of related rows returned for join?
|
|
2343
|
+
# todo: it would be nice if 'fields' could be an actual relationship
|
|
2344
|
+
# (Article.tags = list[Tag]) and you could change the .condition and .on
|
|
2345
|
+
# this could deprecate condition_and
|
|
2309
2346
|
|
|
2310
2347
|
relationships = self.model.get_relationships()
|
|
2311
2348
|
|
|
@@ -2318,7 +2355,9 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2318
2355
|
if isinstance(condition, pydal.objects.Query):
|
|
2319
2356
|
condition = as_lambda(condition)
|
|
2320
2357
|
|
|
2321
|
-
relationships = {
|
|
2358
|
+
relationships = {
|
|
2359
|
+
str(fields[0]): Relationship(fields[0], condition=condition, join=method, condition_and=condition_and)
|
|
2360
|
+
}
|
|
2322
2361
|
elif on:
|
|
2323
2362
|
if len(fields) != 1:
|
|
2324
2363
|
raise ValueError("join(field, on=...) can only be used with exactly one field!")
|
|
@@ -2328,15 +2367,17 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2328
2367
|
|
|
2329
2368
|
if isinstance(on, list):
|
|
2330
2369
|
on = as_lambda(on)
|
|
2331
|
-
relationships = {str(fields[0]): Relationship(fields[0], on=on, join=method)}
|
|
2370
|
+
relationships = {str(fields[0]): Relationship(fields[0], on=on, join=method, condition_and=condition_and)}
|
|
2332
2371
|
|
|
2333
2372
|
else:
|
|
2334
2373
|
if fields:
|
|
2335
2374
|
# join on every relationship
|
|
2336
|
-
relationships = {str(k): relationships[str(k)] for k in fields}
|
|
2375
|
+
relationships = {str(k): relationships[str(k)].clone(condition_and=condition_and) for k in fields}
|
|
2337
2376
|
|
|
2338
2377
|
if method:
|
|
2339
|
-
relationships = {
|
|
2378
|
+
relationships = {
|
|
2379
|
+
str(k): r.clone(join=method, condition_and=condition_and) for k, r in relationships.items()
|
|
2380
|
+
}
|
|
2340
2381
|
|
|
2341
2382
|
return self._extend(relationships=relationships)
|
|
2342
2383
|
|
|
@@ -2557,7 +2598,6 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2557
2598
|
|
|
2558
2599
|
metadata["relationships"] = set(self.relationships.keys())
|
|
2559
2600
|
|
|
2560
|
-
# query = self._update_query_for_inner(db, model, query)
|
|
2561
2601
|
join = []
|
|
2562
2602
|
for key, relation in self.relationships.items():
|
|
2563
2603
|
if not relation.condition or relation.join != "inner":
|
|
@@ -2565,7 +2605,11 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2565
2605
|
|
|
2566
2606
|
other = relation.get_table(db)
|
|
2567
2607
|
other = other.with_alias(f"{key}_{hash(relation)}")
|
|
2568
|
-
|
|
2608
|
+
condition = relation.condition(model, other)
|
|
2609
|
+
if callable(relation.condition_and):
|
|
2610
|
+
condition &= relation.condition_and(model, other)
|
|
2611
|
+
|
|
2612
|
+
join.append(other.on(condition))
|
|
2569
2613
|
|
|
2570
2614
|
if limitby := select_kwargs.pop("limitby", ()):
|
|
2571
2615
|
|
|
@@ -2615,12 +2659,12 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2615
2659
|
# .on not given, generate it:
|
|
2616
2660
|
other = other.with_alias(f"{key}_{hash(relation)}")
|
|
2617
2661
|
condition = typing.cast(Query, relation.condition(model, other))
|
|
2662
|
+
if callable(relation.condition_and):
|
|
2663
|
+
condition &= relation.condition_and(model, other)
|
|
2618
2664
|
left.append(other.on(condition))
|
|
2619
2665
|
else:
|
|
2620
2666
|
# else: inner join (handled earlier)
|
|
2621
2667
|
other = other.with_alias(f"{key}_{hash(relation)}") # only for replace
|
|
2622
|
-
# other = other.with_alias(f"{key}_{hash(relation)}")
|
|
2623
|
-
# query &= relation.condition(model, other)
|
|
2624
2668
|
|
|
2625
2669
|
# if no fields of 'other' are included, add other.ALL
|
|
2626
2670
|
# else: only add other.id if missing
|
|
@@ -2721,7 +2765,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2721
2765
|
"""
|
|
2722
2766
|
yield from self.collect()
|
|
2723
2767
|
|
|
2724
|
-
def count(self) -> int:
|
|
2768
|
+
def count(self, distinct: bool = None) -> int:
|
|
2725
2769
|
"""
|
|
2726
2770
|
Return the amount of rows matching the current query.
|
|
2727
2771
|
"""
|
|
@@ -2730,14 +2774,16 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2730
2774
|
query = self.query
|
|
2731
2775
|
|
|
2732
2776
|
for key, relation in self.relationships.items():
|
|
2733
|
-
if not relation.condition or relation.join != "inner":
|
|
2777
|
+
if (not relation.condition or relation.join != "inner") and not distinct:
|
|
2734
2778
|
continue
|
|
2735
2779
|
|
|
2736
2780
|
other = relation.get_table(db)
|
|
2737
|
-
|
|
2781
|
+
if not distinct:
|
|
2782
|
+
# todo: can this lead to other issues?
|
|
2783
|
+
other = other.with_alias(f"{key}_{hash(relation)}")
|
|
2738
2784
|
query &= relation.condition(model, other)
|
|
2739
2785
|
|
|
2740
|
-
return db(query).count()
|
|
2786
|
+
return db(query).count(distinct)
|
|
2741
2787
|
|
|
2742
2788
|
def __paginate(
|
|
2743
2789
|
self,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
typedal/__about__.py,sha256=
|
|
1
|
+
typedal/__about__.py,sha256=ZuJhJWjiRvul81lGSU613j5iNkWmLFCmfexofoyuMao,206
|
|
2
2
|
typedal/__init__.py,sha256=QQpLiVl9w9hm2LBxey49Y_tCF_VB2bScVaS_mCjYy54,366
|
|
3
3
|
typedal/caching.py,sha256=SMcJsahLlZ79yykWCveERFx1ZJUNEKhA9SPmCTIuLp8,11798
|
|
4
4
|
typedal/cli.py,sha256=wzyId6YwRyqfuJ2byxvl6YecDNaKpkLmo-R5HvRuTok,19265
|
|
5
5
|
typedal/config.py,sha256=0qy1zrTUdtmXPM9jHzFnSR1DJsqGJqcdG6pvhzKQHe0,11625
|
|
6
|
-
typedal/core.py,sha256=
|
|
6
|
+
typedal/core.py,sha256=LQi-aE0j3n_JjSeYmUmZ1XhW4snt7SrxPSZ3DY_WV-k,102403
|
|
7
7
|
typedal/fields.py,sha256=A4qt0aK4F_-UeOY-xJ0ObVY-tFEoLFy7TYRMHnp4g6o,6516
|
|
8
8
|
typedal/for_py4web.py,sha256=d07b8hL_PvNDUS26Z5fDH2OxWb-IETBuAFPSzrRwm04,1285
|
|
9
9
|
typedal/for_web2py.py,sha256=xn7zo6ImsmTkH6LacbjLQl2oqyBvP0zLqRxEJvMQk1w,1929
|
|
@@ -13,7 +13,7 @@ typedal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
13
13
|
typedal/types.py,sha256=1FIgv1s0be0E8r5Wd9E1nvDvK4ETV8u2NlfI7_P6UUY,6752
|
|
14
14
|
typedal/web2py_py4web_shared.py,sha256=VK9T8P5UwVLvfNBsY4q79ANcABv-jX76YKADt1Zz_co,1539
|
|
15
15
|
typedal/serializers/as_json.py,sha256=ffo152W-sARYXym4BzwX709rrO2-QwKk2KunWY8RNl4,2229
|
|
16
|
-
typedal-3.
|
|
17
|
-
typedal-3.
|
|
18
|
-
typedal-3.
|
|
19
|
-
typedal-3.
|
|
16
|
+
typedal-3.9.0.dist-info/METADATA,sha256=GJ6GuOyCvWzkkQiZ3p8UVKoAxMtmZfCpQtxQeig7J3Y,10463
|
|
17
|
+
typedal-3.9.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
18
|
+
typedal-3.9.0.dist-info/entry_points.txt,sha256=m1wqcc_10rHWPdlQ71zEkmJDADUAnZtn7Jac_6mbyUc,44
|
|
19
|
+
typedal-3.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|