Skip to content

Commit 6d04566

Browse files
committed
added method for caching the PK of a Model
1 parent c845ef9 commit 6d04566

3 files changed

Lines changed: 55 additions & 11 deletions

File tree

datamodel/abstract.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ class ModelMeta(type):
153153
__fields__: List
154154
__field_types__: List
155155
__aliases__: Dict
156+
__primary_keys__: List
156157
# Class-level cache
157158
_base_class_cache = {}
158159

@@ -162,6 +163,8 @@ def _initialize_fields(attrs, annotations, strict):
162163
_types_local = {}
163164
_typing_args = {}
164165
aliases = {}
166+
primary_keys = [] # New list to collect primary key fields
167+
165168
for field, _type in annotations.items():
166169
if isinstance(_type, InitVar) or _type == InitVar:
167170
# Skip InitVar fields;
@@ -202,6 +205,10 @@ def _initialize_fields(attrs, annotations, strict):
202205
df.name = field
203206
df.type = _type
204207

208+
# Check for primary_key in field metadata
209+
if df.metadata.get("primary_key", False):
210+
primary_keys.append(field)
211+
205212
# Cache reflection info so we DON’T need to call
206213
# get_origin/get_args repeatedly:
207214
args = get_args(_type)
@@ -281,7 +288,7 @@ def _initialize_fields(attrs, annotations, strict):
281288
# Assign the field object to the attrs so dataclass can pick it up
282289
attrs[field] = df
283290
cols[field] = df
284-
return cols, _types_local, _typing_args, aliases
291+
return cols, _types_local, _typing_args, aliases, primary_keys
285292

286293
def __new__(cls, name, bases, attrs, **kwargs): # noqa
287294
annotations = attrs.get('__annotations__', {})
@@ -295,12 +302,14 @@ def __new__(cls, name, bases, attrs, **kwargs): # noqa
295302
_types = cached['types'].copy()
296303
_typing_args = cached['_typing_args'].copy()
297304
aliases = cached['aliases'].copy()
305+
primary_keys = cached['primary_keys'].copy()
298306
else:
299307
# Compute field from Bases:
300308
cols = OrderedDict()
301309
_types = {}
302310
_typing_args = {}
303311
aliases = {}
312+
primary_keys = []
304313

305314
# Step 1: Collect fields from parent classes
306315
for base in bases:
@@ -311,9 +320,11 @@ def __new__(cls, name, bases, attrs, **kwargs): # noqa
311320
_typing_args |= base.__typing_args__
312321
if hasattr(base, '__aliases__'):
313322
aliases |= base.__aliases__
323+
if hasattr(base, '__primary_keys__'):
324+
primary_keys += base.__primary_keys__
314325

315326
# Now initialize subclass-specific fields
316-
new_cols, new_types, new_typing_args, new_aliases = cls._initialize_fields(
327+
new_cols, new_types, new_typing_args, new_aliases, nw_primary_keys = cls._initialize_fields( # noqa
317328
attrs, annotations, strict
318329
)
319330

@@ -322,13 +333,15 @@ def __new__(cls, name, bases, attrs, **kwargs): # noqa
322333
_types.update(new_types)
323334
_typing_args.update(new_typing_args)
324335
aliases.update(new_aliases)
336+
primary_keys.extend(nw_primary_keys)
325337

326338
# Store computed results in cache
327339
cls._base_class_cache[base_key] = {
328340
'cols': cols.copy(),
329341
'types': _types.copy(),
330342
'_typing_args': _typing_args.copy(),
331343
'aliases': aliases.copy(),
344+
'primary_keys': primary_keys.copy(),
332345
}
333346

334347
_columns = cols.keys()
@@ -393,11 +406,33 @@ def __new__(cls, name, bases, attrs, **kwargs): # noqa
393406
dc.__initialised__ = False
394407
dc.__field_types__ = _types
395408
dc.__aliases__ = aliases
409+
# Set the primary_keys on the dataclass
410+
dc.__primary_keys__ = primary_keys
396411
dc.__typing_args__ = _typing_args
397412
dc.modelName = dc.__name__
398413

399414
# Override __setattr__ method
400415
setattr(dc, "__setattr__", _dc_method_setattr_)
416+
417+
# Method to get primary keys without further introspection
418+
def get_primary_keys(cls):
419+
return cls.__primary_keys__
420+
421+
# Add the method to the class
422+
dc.get_primary_keys = classmethod(get_primary_keys)
423+
424+
def get_primary_key_fields(cls):
425+
"""Return a dictionary of primary key fields with their types."""
426+
return {name: cls.__columns__[name] for name in cls.__primary_keys__}
427+
428+
dc.get_primary_key_fields = classmethod(get_primary_key_fields)
429+
430+
def get_primary_key_values(self):
431+
"""Get primary key values for this instance as a dictionary."""
432+
return {key: getattr(self, key) for key in self.__primary_keys__}
433+
434+
dc.get_primary_key_values = get_primary_key_values
435+
401436
return dc
402437

403438
def __init__(cls, *args, **kwargs) -> None:

datamodel/models.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22
import contextlib
3-
from typing import Any, Dict
3+
from typing import Any, Dict, get_args
44
from enum import Enum, EnumMeta
55
# Dataclass
66
import inspect
@@ -250,13 +250,22 @@ def __collapse_as_values__(
250250
)
251251
# if it's a list, might contain submodels or scalars
252252
elif isinstance(value, list):
253-
if field.origin is list and field.args:
254-
submodel_class = field.args[0] # The type inside the list
255-
if issubclass(
256-
submodel_class, ModelMixin
257-
) and not hasattr(submodel_class, name):
258-
out[name] = json_encoder(value)
259-
continue
253+
targs = field.args
254+
if len(targs) == 2 and targs[1] is type(None):
255+
# Checking if it's Optional[T]
256+
submodel_class = get_args(targs[0])
257+
elif field.origin is list and field.args:
258+
submodel_class = field.args[0]
259+
else:
260+
submodel_class = None
261+
if field.origin is list and submodel_class and (
262+
issubclass(
263+
submodel_class,
264+
ModelMixin
265+
) and not hasattr(submodel_class, name)
266+
):
267+
out[name] = json_encoder(value)
268+
continue
260269
items_out = []
261270
for item in value:
262271
if isinstance(item, ModelMixin):

datamodel/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
'simple library based on python +3.8 to use Dataclass-syntax'
77
'for interacting with Data'
88
)
9-
__version__ = '0.10.10'
9+
__version__ = '0.10.11'
1010
__copyright__ = 'Copyright (c) 2020-2024 Jesus Lara'
1111
__author__ = 'Jesus Lara'
1212
__author_email__ = 'jesuslarag@gmail.com'

0 commit comments

Comments
 (0)