Coverage for dynamodx / keys.py: 85%
79 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-18 12:56 -0300
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-18 12:56 -0300
1from abc import ABC, abstractmethod
2from dataclasses import dataclass
3from typing import Any, Self
6class Key(dict, ABC):
7 @abstractmethod
8 def expr_attr_names(self) -> dict[str, str]: ...
10 @abstractmethod
11 def expr_attr_values(self) -> dict[str, Any]: ...
14class SortKey(str):
15 """
16 SortKey encapsulates a sort key value and optionally stores metadata
17 used for nested data extraction and output transformation.
19 Parameters
20 ----------
21 path_spec : str, optional
22 JMESPath expression used to project nested data from the item.
24 rename_key : str, optional
25 If provided, renames the sort key in the output.
27 projection_expr : str, optional
28 Projection expression associated with the key.
30 expr_attr_names : dict[str, str], optional
31 Attribute name mapping used with projection expressions.
32 """
34 __slots__ = (
35 'sk',
36 'path_spec',
37 'rename_key',
38 'projection_expr',
39 'expr_attr_names',
40 )
42 sk: str | None
43 path_spec: str | None
44 rename_key: str | None
45 projection_expr: str | None
46 expr_attr_names: dict[str, str] | None
48 def __new__(
49 cls,
50 *,
51 path_spec: str | None = None,
52 rename_key: str | None = None,
53 projection_expr: str | None = None,
54 expr_attr_names: dict[str, str] | None = None,
55 **kwargs: str,
56 ) -> Self:
57 if len(kwargs) != 1:
58 raise TypeError(
59 f'SortKey() takes exactly one keyword argument ({len(kwargs)} given)'
60 )
62 ((name_sk, value_sk),) = kwargs.items()
63 obj = super().__new__(cls, value_sk)
65 obj.sk = name_sk
66 obj.path_spec = path_spec
67 obj.rename_key = rename_key
68 obj.projection_expr = projection_expr
69 obj.expr_attr_names = expr_attr_names
71 return obj
74class PartitionKey(Key):
75 """Represents a partition key"""
77 def __init__(
78 self,
79 *,
80 table_name: str | None = None,
81 **kwargs,
82 ) -> None:
83 if len(kwargs) != 1:
84 raise TypeError(
85 f'PartitionKey() takes exactly one key=value argument '
86 f'({len(kwargs)} given)'
87 )
89 ((name_pk, value_pk),) = kwargs.items()
90 super().__init__(**{name_pk: value_pk})
92 self.name_pk = name_pk
93 self.table_name = table_name
95 def expr_attr_names(self) -> dict[str, str]:
96 return {'#pk': self.name_pk}
98 def expr_attr_values(self) -> dict[str, Any]:
99 return {':pk': self[self.name_pk]}
101 def __add__(self, other: SortKey) -> 'PrimaryKey':
102 pk = self.name_pk
103 sk = other.sk
104 kwargs = {
105 pk: self[pk],
106 sk: other,
107 'table_name': self.table_name,
108 }
109 return PrimaryKey(**kwargs)
112class PrimaryKey(Key):
113 """Represents a composite key (partition key and sort key)"""
115 def __init__(
116 self,
117 *,
118 table_name: str | None = None,
119 **kwargs,
120 ) -> None:
121 """
122 Initializes a composite key using partition and sort key.
124 Parameters
125 ----------
126 pk : str
127 The partition key.
128 sk : str
129 The sort key.
130 table_name : str, optional
131 """
132 if len(kwargs) != 2:
133 raise TypeError(
134 f'PrimaryKey() takes exactly two key=value arguments '
135 f'({len(kwargs)} given)'
136 )
138 (
139 (name_pk, value_pk),
140 (name_sk, value_sk),
141 ) = kwargs.items()
142 super().__init__(**{name_pk: value_pk, name_sk: value_sk})
144 self.name_pk = name_pk
145 self.name_sk = name_sk
146 self.table_name = table_name
148 @property
149 def sk(self) -> SortKey | str:
150 return self[self.name_sk]
152 def expr_attr_names(self) -> dict[str, str]:
153 return {
154 '#pk': self.name_pk,
155 '#sk': self.name_sk,
156 }
158 def expr_attr_values(self) -> dict[str, Any]:
159 return {
160 ':pk': self[self.name_pk],
161 ':sk': self[self.name_sk],
162 }
164 def __add__(self, other: Self | SortKey) -> 'PrimaryKeySet':
165 if isinstance(other, PrimaryKey):
166 return PrimaryKeySet((self, other))
168 if isinstance(other, SortKey):
169 if other.sk is None:
170 raise ValueError('SortKey name is required')
172 pk, sk = self.name_pk, other.sk
173 kwargs = {pk: self[pk], sk: other}
174 return PrimaryKeySet((self, PrimaryKey(**kwargs)))
176 return NotImplementedError
178 def __radd__(self, other: Any):
179 if isinstance(other, PrimaryKeySet):
180 return other + self
182 return NotImplementedError
185@dataclass(frozen=True)
186class PrimaryKeySet:
187 pairs: tuple[PrimaryKey, ...] = ()
189 def __add__(self, other: PrimaryKey | SortKey) -> 'PrimaryKeySet':
190 if not isinstance(other, (PrimaryKey, SortKey)):
191 return NotImplementedError
193 if isinstance(other, PrimaryKey):
194 return PrimaryKeySet(pairs=self.pairs + (other,))
196 if not self.pairs:
197 raise ValueError('Cannot add SortKey to empty PrimaryKeySet')
199 last_pair = self.pairs[-1]
200 next_pair = PrimaryKey(
201 **{
202 last_pair.name_pk: last_pair[last_pair.name_pk],
203 last_pair.name_sk: other,
204 },
205 table_name=last_pair.table_name,
206 )
208 if next_pair in self.pairs:
209 return self
211 return PrimaryKeySet(pairs=self.pairs + (next_pair,))