Skip to content

Commit 76ba5c7

Browse files
committed
feat(repository): add pk_attr_names property for primary key attribute access
1 parent 6aff8dd commit 76ba5c7

7 files changed

Lines changed: 40 additions & 20 deletions

File tree

advanced_alchemy/repository/_async.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ class SQLAlchemyAsyncRepositoryProtocol(FilterableRepositoryProtocol[ModelT], Pr
9494
error_messages: Optional[ErrorMessages] = None
9595
wrap_exceptions: bool = True
9696

97+
@property
98+
def pk_attr_names(self) -> tuple[str, ...]: ...
99+
97100
@property
98101
def has_composite_pk(self) -> bool: ...
99102

@@ -720,6 +723,15 @@ def set_id_attribute_value(
720723
setattr(item, id_attribute if id_attribute is not None else cls.id_attribute, item_id)
721724
return item
722725

726+
@property
727+
def pk_attr_names(self) -> tuple[str, ...]:
728+
"""Get primary key attribute names.
729+
730+
Returns:
731+
Tuple of ORM attribute names for primary key columns.
732+
"""
733+
return self._pk_attr_names
734+
723735
@property
724736
def has_composite_pk(self) -> bool:
725737
"""Check if model has a composite (multi-column) primary key.

advanced_alchemy/repository/_sync.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ class SQLAlchemySyncRepositoryProtocol(FilterableRepositoryProtocol[ModelT], Pro
9595
error_messages: Optional[ErrorMessages] = None
9696
wrap_exceptions: bool = True
9797

98+
@property
99+
def pk_attr_names(self) -> tuple[str, ...]: ...
100+
98101
@property
99102
def has_composite_pk(self) -> bool: ...
100103

@@ -721,6 +724,15 @@ def set_id_attribute_value(
721724
setattr(item, id_attribute if id_attribute is not None else cls.id_attribute, item_id)
722725
return item
723726

727+
@property
728+
def pk_attr_names(self) -> tuple[str, ...]:
729+
"""Get primary key attribute names.
730+
731+
Returns:
732+
Tuple of ORM attribute names for primary key columns.
733+
"""
734+
return self._pk_attr_names
735+
724736
@property
725737
def has_composite_pk(self) -> bool:
726738
"""Check if model has a composite (multi-column) primary key.

advanced_alchemy/repository/memory/_async.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def _pk_columns(self) -> tuple[Any, ...]:
136136
return tuple(mapper.primary_key)
137137

138138
@property
139-
def _pk_attr_names(self) -> tuple[str, ...]:
139+
def pk_attr_names(self) -> tuple[str, ...]:
140140
"""Get primary key attribute names from the model mapper.
141141
142142
Uses mapper.get_property_by_column() to get ORM attribute names,
@@ -167,7 +167,7 @@ def get_primary_key_value(self, instance: ModelT) -> PrimaryKeyType:
167167
- For single PK: scalar value
168168
- For composite PK: tuple of values in column order
169169
"""
170-
return extract_pk_value_from_instance(instance, self._pk_attr_names)
170+
return extract_pk_value_from_instance(instance, self.pk_attr_names)
171171

172172
def has_primary_key_values(self, instance: ModelT) -> bool:
173173
"""Check if all primary key values are set on an instance.
@@ -178,7 +178,7 @@ def has_primary_key_values(self, instance: ModelT) -> bool:
178178
Returns:
179179
True if all PK values are non-None, False otherwise.
180180
"""
181-
return pk_values_present(instance, self._pk_attr_names)
181+
return pk_values_present(instance, self.pk_attr_names)
182182

183183
def _normalize_pk_to_tuple(self, pk_value: PrimaryKeyType) -> tuple[Any, ...]:
184184
"""Normalize a primary key value to a tuple for consistent storage key generation.
@@ -189,7 +189,7 @@ def _normalize_pk_to_tuple(self, pk_value: PrimaryKeyType) -> tuple[Any, ...]:
189189
Returns:
190190
Tuple representation of the primary key.
191191
"""
192-
return normalize_pk_to_tuple(pk_value, self._pk_attr_names, self.model_type.__name__)
192+
return normalize_pk_to_tuple(pk_value, self.pk_attr_names, self.model_type.__name__)
193193

194194
def _get_store_key(self, pk_value: PrimaryKeyType) -> str:
195195
"""Generate a store key from a primary key value.

advanced_alchemy/repository/memory/_sync.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def _pk_columns(self) -> tuple[Any, ...]:
137137
return tuple(mapper.primary_key)
138138

139139
@property
140-
def _pk_attr_names(self) -> tuple[str, ...]:
140+
def pk_attr_names(self) -> tuple[str, ...]:
141141
"""Get primary key attribute names from the model mapper.
142142
143143
Uses mapper.get_property_by_column() to get ORM attribute names,
@@ -168,7 +168,7 @@ def get_primary_key_value(self, instance: ModelT) -> PrimaryKeyType:
168168
- For single PK: scalar value
169169
- For composite PK: tuple of values in column order
170170
"""
171-
return extract_pk_value_from_instance(instance, self._pk_attr_names)
171+
return extract_pk_value_from_instance(instance, self.pk_attr_names)
172172

173173
def has_primary_key_values(self, instance: ModelT) -> bool:
174174
"""Check if all primary key values are set on an instance.
@@ -179,7 +179,7 @@ def has_primary_key_values(self, instance: ModelT) -> bool:
179179
Returns:
180180
True if all PK values are non-None, False otherwise.
181181
"""
182-
return pk_values_present(instance, self._pk_attr_names)
182+
return pk_values_present(instance, self.pk_attr_names)
183183

184184
def _normalize_pk_to_tuple(self, pk_value: PrimaryKeyType) -> tuple[Any, ...]:
185185
"""Normalize a primary key value to a tuple for consistent storage key generation.
@@ -190,7 +190,7 @@ def _normalize_pk_to_tuple(self, pk_value: PrimaryKeyType) -> tuple[Any, ...]:
190190
Returns:
191191
Tuple representation of the primary key.
192192
"""
193-
return normalize_pk_to_tuple(pk_value, self._pk_attr_names, self.model_type.__name__)
193+
return normalize_pk_to_tuple(pk_value, self.pk_attr_names, self.model_type.__name__)
194194

195195
def _get_store_key(self, pk_value: PrimaryKeyType) -> str:
196196
"""Generate a store key from a primary key value.

advanced_alchemy/service/_async.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
should be a SQLAlchemy model.
55
"""
66

7-
# pyright: reportPrivateUsage=false
8-
97
from collections.abc import AsyncIterator, Iterable, Sequence
108
from contextlib import asynccontextmanager
119
from functools import cached_property
@@ -826,7 +824,7 @@ async def update(
826824
elif id_attribute is None and self.repository.has_composite_pk:
827825
# For composite PKs, check if all PK values are present
828826
if not self.repository.has_primary_key_values(data):
829-
pk_names = ", ".join(self.repository._pk_attr_names) # pyright: ignore[reportUnknownMemberType]
827+
pk_names = ", ".join(self.repository.pk_attr_names)
830828
msg = f"Could not identify composite PK values. Required attributes: {pk_names}"
831829
raise RepositoryError(msg)
832830
elif (

advanced_alchemy/service/_sync.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
should be a SQLAlchemy model.
77
"""
88

9-
# pyright: reportPrivateUsage=false
10-
119
from collections.abc import Iterable, Iterator, Sequence
1210
from contextlib import contextmanager
1311
from functools import cached_property
@@ -825,7 +823,7 @@ def update(
825823
elif id_attribute is None and self.repository.has_composite_pk:
826824
# For composite PKs, check if all PK values are present
827825
if not self.repository.has_primary_key_values(data):
828-
pk_names = ", ".join(self.repository._pk_attr_names) # pyright: ignore[reportUnknownMemberType]
826+
pk_names = ", ".join(self.repository.pk_attr_names)
829827
msg = f"Could not identify composite PK values. Required attributes: {pk_names}"
830828
raise RepositoryError(msg)
831829
elif (

pyproject.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ classmethod-decorators = [
361361
"advanced_alchemy/alembic/templates/*/env.py" = ["INP001"]
362362
"advanced_alchemy/repository/*.py" = ['C901', 'UP006', 'UP035']
363363
"advanced_alchemy/repository/memory/*.py" = ['UP006', 'UP035']
364-
"advanced_alchemy/service/*.py" = ["PLR0911", "SLF001", "UP006", "UP035"]
364+
"advanced_alchemy/service/*.py" = ["PLR0911", "UP006", "UP035"]
365365
"examples/flask.py" = ["ANN"]
366366
"examples/flask/*.py" = ["ANN"]
367367
"examples/litestar/*.py" = ["PLR6301", "DOC", "B008"]
@@ -467,10 +467,13 @@ module = [
467467
"s3fs.*",
468468
"argon2",
469469
"argon2.*",
470-
"dishka",
471-
"dishka.*",
472470
]
473471

472+
[[tool.mypy.overrides]]
473+
follow_imports = "skip"
474+
ignore_missing_imports = true
475+
module = ["dishka", "dishka.*"]
476+
474477
[[tool.mypy.overrides]]
475478
follow_imports = "skip"
476479
ignore_missing_imports = true
@@ -544,10 +547,7 @@ include = ["advanced_alchemy"]
544547
pythonVersion = "3.9"
545548
reportMissingTypeStubs = false
546549
reportPrivateImportUsage = false
547-
reportPrivateUsage = false
548-
reportUnknownArgumentType = false
549550
reportUnknownMemberType = false
550-
reportUnknownVariableType = false
551551
reportUnusedFunction = false
552552
strict = ["advanced_alchemy/**/*"]
553553
venv = ".venv"

0 commit comments

Comments
 (0)