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 CHANGED
@@ -5,4 +5,4 @@ This file contains the Version info for this package.
5
5
  # SPDX-FileCopyrightText: 2023-present Robin van der Noord <robinvandernoord@gmail.com>
6
6
  #
7
7
  # SPDX-License-Identifier: MIT
8
- __version__ = "3.8.4"
8
+ __version__ = "3.9.0"
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
- inst.__dict__.update(row)
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
- records = records or {row.id: model(row) for row in rows}
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 = {str(fields[0]): Relationship(fields[0], condition=condition, join=method)}
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 = {str(k): r.clone(join=method) for k, r in relationships.items()}
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
- join.append(other.on(relation.condition(model, other)))
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
- other = other.with_alias(f"{key}_{hash(relation)}")
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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: TypeDAL
3
- Version: 3.8.4
3
+ Version: 3.9.0
4
4
  Summary: Typing support for PyDAL
5
5
  Project-URL: Documentation, https://typedal.readthedocs.io/
6
6
  Project-URL: Issues, https://github.com/trialandsuccess/TypeDAL/issues
@@ -1,9 +1,9 @@
1
- typedal/__about__.py,sha256=d3u5BMAb-o3c-QyqO-Md5FwDyd5-j9Je_83Jq3CDyjY,206
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=hjUrpHGcw2MnUG0Rx9QE6L9TOXwxn5fArUtP3jNloUw,100475
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.8.4.dist-info/METADATA,sha256=VgJ74rpykoi4ZdHfuDbGRJhNm6RON2noWyBgEsV2dMs,10463
17
- typedal-3.8.4.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
18
- typedal-3.8.4.dist-info/entry_points.txt,sha256=m1wqcc_10rHWPdlQ71zEkmJDADUAnZtn7Jac_6mbyUc,44
19
- typedal-3.8.4.dist-info/RECORD,,
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,,