aldict 1.0.3__tar.gz → 1.1.1__tar.gz

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.
@@ -1,19 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aldict
3
- Version: 1.0.3
3
+ Version: 1.1.1
4
4
  Summary: Multi-key dictionary, supports adding and manipulating key-aliases pointing to shared values
5
- Author-email: Kaloyan Ivanov <kaloyan.ivanov88@gmail.com>
6
- Project-URL: repository, https://github.com/kaliv0/aldict
7
5
  Keywords: multi-key dictionary,multidict,alias-dict
6
+ Author-Email: kaliv0 <kaloyan.ivanov88@gmail.com>
7
+ License-File: LICENSE
8
+ Project-URL: repository, https://github.com/kaliv0/aldict
8
9
  Requires-Python: >=3.10
9
10
  Description-Content-Type: text/markdown
10
- License-File: LICENSE
11
- Provides-Extra: dev
12
- Requires-Dist: pytest>=8.3.4; extra == "dev"
13
- Requires-Dist: ruff>=0.8.3; extra == "dev"
14
- Requires-Dist: build>=1.2.2; extra == "dev"
15
- Requires-Dist: twine>=6.0.1; extra == "dev"
16
- Dynamic: license-file
17
11
 
18
12
  <p align="center">
19
13
  <img src="https://github.com/kaliv0/aldict/blob/main/assets/alter-ego.jpg?raw=true" width="250" alt="Alter Ego">
@@ -33,20 +27,32 @@ Multi-key dictionary, supports adding and manipulating key-aliases pointing to s
33
27
  ---
34
28
  ## How to use
35
29
 
30
+ - initialize with aliases
31
+ <br>(one-liner with <i>aliases</i> dict mapping <i>key</i> to list of <i>aliases</i>)
32
+ ```python
33
+ ad = AliasDict({"a": 1, "b": 2}, aliases={"a": ["aa", "aaa"], "b": ["bb"]})
34
+ assert ad["a"] == ad["aa"] == ad["aaa"] == 1
35
+ assert ad["b"] == ad["bb"] == 2
36
+ ```
36
37
  - add_alias
37
- <br>(pass <i>key</i> as first parameter and <i>alias(es)</i> as variadic params)
38
+ <br>(pass <i>key</i> as first parameter and <i>alias(es)</i> as variadic params, list or tuple)
38
39
  ```python
39
40
  ad = AliasDict({"a": 1, "b": 2})
40
41
  ad.add_alias("a", "aa")
41
42
  ad.add_alias("b", "bb", "Bbb")
42
- assert ad["a"] == ad["aa"] == 1
43
- assert ad["b"] == ad["bb"] == ad["Bbb"] == 2
43
+ ad.add_alias("a", ["aaa", "aaaa"])
44
+ ad.add_alias("b", ("bbb",))
45
+
46
+ assert ad["a"] == ad["aa"] == ad["aaa"] == ad["aaaa"] == 1
47
+ assert ad["b"] == ad["bb"] == ad["Bbb"] == ad["bbb"] == 2
44
48
  ```
45
49
  - remove_alias
46
- <br>(pass <i>alias(es)</i> to be removed as variadic parameters)
50
+ <br>(pass <i>alias(es)</i> to be removed as variadic params, list or tuple)
47
51
  ```python
48
52
  ad.remove_alias("aa")
49
53
  ad.remove_alias("bb", "Bbb")
54
+ ad.remove_alias(["aaa", "aaaa"])
55
+ ad.remove_alias(("bbb",))
50
56
  assert len(ad.aliases()) == 0
51
57
  ```
52
58
  - clear_aliases
@@ -73,10 +79,10 @@ ad.add_alias("b", "bb", "B")
73
79
  ad.add_alias("a", "ab", "A")
74
80
  assert list(ad.aliases()) == ['aa', 'bb', 'B', 'ab', 'A']
75
81
  ```
76
- - aliased_keys
82
+ - keys_with_aliases
77
83
  <br>(read <i>keys</i> with corresponding <i>alias(es)</i>)
78
84
  ```python
79
- assert dict(ad.aliased_keys()) == {'a': ['aa', 'ab', 'A'], 'b': ['bb', 'B']}
85
+ assert dict(ad.keys_with_aliases()) == {'a': ['aa', 'ab', 'A'], 'b': ['bb', 'B']}
80
86
  ```
81
87
  - read dictviews
82
88
  <br>(<i>dict.keys()</i> and <i>dict.items()</i> include <i>aliased</i> versions)
@@ -98,11 +104,6 @@ ad.items()
98
104
  ad.pop("y")
99
105
  assert list(ad.items()) == [('x', 10), ('Xx', 10)]
100
106
  ```
101
- - origin_keys
102
- <br>(get original <i>keys</i> only)
103
- ```python
104
- assert list(ad.origin_keys()) == ['x', 'y']
105
- ```
106
107
  - origin_len
107
108
  <br>(get original dict <i>length</i> without aliases)
108
109
  ```python
@@ -112,3 +113,57 @@ assert list(ad.keys()) == ["a", "b", "aa"]
112
113
  assert len(ad) == 3
113
114
  assert ad.origin_len() == 2
114
115
  ```
116
+ - origin_keys
117
+ <br>(get original <i>keys</i> only)
118
+ ```python
119
+ assert list(ad.origin_keys()) == ['x', 'y']
120
+ ```
121
+ - origin_key
122
+ <br>(get original <i>key</i> for an <i>alias</i>)
123
+ ```python
124
+ ad = AliasDict({"a": 1, "b": 2})
125
+ ad.add_alias("a", "aa")
126
+ assert ad.origin_key("aa") == "a"
127
+ assert ad.origin_key("a") is None # not an alias
128
+ ```
129
+ - is_alias
130
+ <br>(check if <i>key</i> is an <i>alias</i>)
131
+ ```python
132
+ ad = AliasDict({"a": 1, "b": 2})
133
+ ad.add_alias("a", "aa")
134
+ assert ad.is_alias("aa") is True
135
+ assert ad.is_alias("a") is False
136
+ ```
137
+ - has_aliases
138
+ <br>(check if <i>key</i> has any <i>aliases</i>)
139
+ ```python
140
+ ad = AliasDict({"a": 1, "b": 2})
141
+ ad.add_alias("a", "aa")
142
+ assert ad.has_aliases("a") is True
143
+ assert ad.has_aliases("b") is False
144
+ ```
145
+ - copy
146
+ ```python
147
+ ad = AliasDict({"a": 1, "b": 2})
148
+ ad.add_alias("a", "aa")
149
+ ad_copy = ad.copy()
150
+ assert ad_copy == ad
151
+ assert ad_copy is not ad
152
+ ```
153
+ - merge with | and |= operators
154
+ ```python
155
+ ad1 = AliasDict({"a": 1}, aliases={"a": ["aa"]})
156
+ ad2 = AliasDict({"b": 2}, aliases={"b": ["bb"]})
157
+
158
+ merged = ad1 | ad2
159
+ assert merged["aa"] == 1
160
+ assert merged["bb"] == 2
161
+
162
+ ad1 |= {"c": 3}
163
+ assert ad1["c"] == 3
164
+ ```
165
+ - fromkeys
166
+ ```python
167
+ ad = AliasDict.fromkeys(["a", "b", "c"], 0, aliases={"a": ["aa"]})
168
+ assert ad["a"] == ad["aa"] == 0
169
+ ```
@@ -1,20 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: aldict
3
- Version: 1.0.3
4
- Summary: Multi-key dictionary, supports adding and manipulating key-aliases pointing to shared values
5
- Author-email: Kaloyan Ivanov <kaloyan.ivanov88@gmail.com>
6
- Project-URL: repository, https://github.com/kaliv0/aldict
7
- Keywords: multi-key dictionary,multidict,alias-dict
8
- Requires-Python: >=3.10
9
- Description-Content-Type: text/markdown
10
- License-File: LICENSE
11
- Provides-Extra: dev
12
- Requires-Dist: pytest>=8.3.4; extra == "dev"
13
- Requires-Dist: ruff>=0.8.3; extra == "dev"
14
- Requires-Dist: build>=1.2.2; extra == "dev"
15
- Requires-Dist: twine>=6.0.1; extra == "dev"
16
- Dynamic: license-file
17
-
18
1
  <p align="center">
19
2
  <img src="https://github.com/kaliv0/aldict/blob/main/assets/alter-ego.jpg?raw=true" width="250" alt="Alter Ego">
20
3
  </p>
@@ -33,20 +16,32 @@ Multi-key dictionary, supports adding and manipulating key-aliases pointing to s
33
16
  ---
34
17
  ## How to use
35
18
 
19
+ - initialize with aliases
20
+ <br>(one-liner with <i>aliases</i> dict mapping <i>key</i> to list of <i>aliases</i>)
21
+ ```python
22
+ ad = AliasDict({"a": 1, "b": 2}, aliases={"a": ["aa", "aaa"], "b": ["bb"]})
23
+ assert ad["a"] == ad["aa"] == ad["aaa"] == 1
24
+ assert ad["b"] == ad["bb"] == 2
25
+ ```
36
26
  - add_alias
37
- <br>(pass <i>key</i> as first parameter and <i>alias(es)</i> as variadic params)
27
+ <br>(pass <i>key</i> as first parameter and <i>alias(es)</i> as variadic params, list or tuple)
38
28
  ```python
39
29
  ad = AliasDict({"a": 1, "b": 2})
40
30
  ad.add_alias("a", "aa")
41
31
  ad.add_alias("b", "bb", "Bbb")
42
- assert ad["a"] == ad["aa"] == 1
43
- assert ad["b"] == ad["bb"] == ad["Bbb"] == 2
32
+ ad.add_alias("a", ["aaa", "aaaa"])
33
+ ad.add_alias("b", ("bbb",))
34
+
35
+ assert ad["a"] == ad["aa"] == ad["aaa"] == ad["aaaa"] == 1
36
+ assert ad["b"] == ad["bb"] == ad["Bbb"] == ad["bbb"] == 2
44
37
  ```
45
38
  - remove_alias
46
- <br>(pass <i>alias(es)</i> to be removed as variadic parameters)
39
+ <br>(pass <i>alias(es)</i> to be removed as variadic params, list or tuple)
47
40
  ```python
48
41
  ad.remove_alias("aa")
49
42
  ad.remove_alias("bb", "Bbb")
43
+ ad.remove_alias(["aaa", "aaaa"])
44
+ ad.remove_alias(("bbb",))
50
45
  assert len(ad.aliases()) == 0
51
46
  ```
52
47
  - clear_aliases
@@ -73,10 +68,10 @@ ad.add_alias("b", "bb", "B")
73
68
  ad.add_alias("a", "ab", "A")
74
69
  assert list(ad.aliases()) == ['aa', 'bb', 'B', 'ab', 'A']
75
70
  ```
76
- - aliased_keys
71
+ - keys_with_aliases
77
72
  <br>(read <i>keys</i> with corresponding <i>alias(es)</i>)
78
73
  ```python
79
- assert dict(ad.aliased_keys()) == {'a': ['aa', 'ab', 'A'], 'b': ['bb', 'B']}
74
+ assert dict(ad.keys_with_aliases()) == {'a': ['aa', 'ab', 'A'], 'b': ['bb', 'B']}
80
75
  ```
81
76
  - read dictviews
82
77
  <br>(<i>dict.keys()</i> and <i>dict.items()</i> include <i>aliased</i> versions)
@@ -98,11 +93,6 @@ ad.items()
98
93
  ad.pop("y")
99
94
  assert list(ad.items()) == [('x', 10), ('Xx', 10)]
100
95
  ```
101
- - origin_keys
102
- <br>(get original <i>keys</i> only)
103
- ```python
104
- assert list(ad.origin_keys()) == ['x', 'y']
105
- ```
106
96
  - origin_len
107
97
  <br>(get original dict <i>length</i> without aliases)
108
98
  ```python
@@ -112,3 +102,57 @@ assert list(ad.keys()) == ["a", "b", "aa"]
112
102
  assert len(ad) == 3
113
103
  assert ad.origin_len() == 2
114
104
  ```
105
+ - origin_keys
106
+ <br>(get original <i>keys</i> only)
107
+ ```python
108
+ assert list(ad.origin_keys()) == ['x', 'y']
109
+ ```
110
+ - origin_key
111
+ <br>(get original <i>key</i> for an <i>alias</i>)
112
+ ```python
113
+ ad = AliasDict({"a": 1, "b": 2})
114
+ ad.add_alias("a", "aa")
115
+ assert ad.origin_key("aa") == "a"
116
+ assert ad.origin_key("a") is None # not an alias
117
+ ```
118
+ - is_alias
119
+ <br>(check if <i>key</i> is an <i>alias</i>)
120
+ ```python
121
+ ad = AliasDict({"a": 1, "b": 2})
122
+ ad.add_alias("a", "aa")
123
+ assert ad.is_alias("aa") is True
124
+ assert ad.is_alias("a") is False
125
+ ```
126
+ - has_aliases
127
+ <br>(check if <i>key</i> has any <i>aliases</i>)
128
+ ```python
129
+ ad = AliasDict({"a": 1, "b": 2})
130
+ ad.add_alias("a", "aa")
131
+ assert ad.has_aliases("a") is True
132
+ assert ad.has_aliases("b") is False
133
+ ```
134
+ - copy
135
+ ```python
136
+ ad = AliasDict({"a": 1, "b": 2})
137
+ ad.add_alias("a", "aa")
138
+ ad_copy = ad.copy()
139
+ assert ad_copy == ad
140
+ assert ad_copy is not ad
141
+ ```
142
+ - merge with | and |= operators
143
+ ```python
144
+ ad1 = AliasDict({"a": 1}, aliases={"a": ["aa"]})
145
+ ad2 = AliasDict({"b": 2}, aliases={"b": ["bb"]})
146
+
147
+ merged = ad1 | ad2
148
+ assert merged["aa"] == 1
149
+ assert merged["bb"] == 2
150
+
151
+ ad1 |= {"c": 3}
152
+ assert ad1["c"] == 3
153
+ ```
154
+ - fromkeys
155
+ ```python
156
+ ad = AliasDict.fromkeys(["a", "b", "c"], 0, aliases={"a": ["aa"]})
157
+ assert ad["a"] == ad["aa"] == 0
158
+ ```
@@ -1,3 +1,6 @@
1
1
  from .alias_dict import AliasDict as AliasDict
2
2
  from .exception import AliasError as AliasError
3
3
  from .exception import AliasValueError as AliasValueError
4
+
5
+ __version__ = "1.1.1"
6
+ __all__ = ["AliasDict", "AliasError", "AliasValueError"]
@@ -0,0 +1,180 @@
1
+ from collections import UserDict, defaultdict
2
+ from collections.abc import Mapping
3
+ from itertools import chain
4
+
5
+ from aldict.exception import AliasError, AliasValueError
6
+
7
+
8
+ class AliasDict(UserDict):
9
+ """Dict with key-aliases pointing to shared values."""
10
+
11
+ def __init__(self, dict_=None, /, aliases=None):
12
+ self._alias_dict = {}
13
+ if isinstance(dict_, AliasDict):
14
+ super().__init__(dict_.data)
15
+ self._alias_dict = dict(dict_._alias_dict)
16
+ else:
17
+ super().__init__(dict_)
18
+
19
+ if aliases:
20
+ for key, alias_list in aliases.items():
21
+ self.add_alias(key, alias_list)
22
+
23
+ def add_alias(self, key, *aliases):
24
+ """Add one or more aliases to a key. Accepts *args or a list/tuple."""
25
+ if key not in self.data:
26
+ raise KeyError(key)
27
+
28
+ for alias in self._unpack(aliases):
29
+ if alias == key:
30
+ raise AliasValueError(f"Key and corresponding alias cannot be equal: '{key}'")
31
+ if alias in self.data:
32
+ raise AliasValueError(f"Alias '{alias}' already exists as a key in the dictionary")
33
+ self._alias_dict[alias] = key
34
+
35
+ def remove_alias(self, *aliases):
36
+ """Remove one or more aliases. Accepts *args or a list/tuple."""
37
+ for alias in self._unpack(aliases):
38
+ try:
39
+ self._alias_dict.__delitem__(alias)
40
+ except KeyError as e:
41
+ raise AliasError(f"Alias '{alias}' not found") from e
42
+
43
+ @staticmethod
44
+ def _unpack(args):
45
+ return args[0] if len(args) == 1 and isinstance(args[0], (list, tuple)) else args
46
+
47
+ @classmethod
48
+ def fromkeys(cls, iterable, value=None, aliases=None):
49
+ """Create an AliasDict from an iterable of keys with optional aliases."""
50
+ return cls(dict.fromkeys(iterable, value), aliases=aliases)
51
+
52
+ def clear(self):
53
+ """Clear all data and aliases."""
54
+ super().clear()
55
+ self._alias_dict.clear()
56
+
57
+ def clear_aliases(self):
58
+ """Remove all aliases."""
59
+ self._alias_dict.clear()
60
+
61
+ def aliases(self):
62
+ """Return all aliases."""
63
+ return self._alias_dict.keys()
64
+
65
+ def is_alias(self, key):
66
+ """Return True if the key is an alias, False otherwise."""
67
+ return key in self._alias_dict
68
+
69
+ def has_aliases(self, key):
70
+ """Return True if the key has any aliases, False otherwise."""
71
+ return key in self._alias_dict.values()
72
+
73
+ def keys_with_aliases(self):
74
+ """Return keys with their aliases."""
75
+ result = defaultdict(list)
76
+ for alias, key in self._alias_dict.items():
77
+ result[key].append(alias)
78
+ return result.items()
79
+
80
+ def origin_keys(self):
81
+ """Return original keys (without aliases)."""
82
+ return self.data.keys()
83
+
84
+ def origin_key(self, alias):
85
+ """Return the original key for an alias, or None if not an alias."""
86
+ return self._alias_dict.get(alias)
87
+
88
+ def keys(self):
89
+ """Return all keys and aliases."""
90
+ return dict(**self.data, **self._alias_dict).keys()
91
+ # NB: could be optimized as 'return iter(self)' but we won't be able to call e.g. len(alias_dict.keys())
92
+
93
+ def values(self):
94
+ """Return all values."""
95
+ return self.data.values()
96
+
97
+ def items(self):
98
+ """Return all items (including alias/value pairs)."""
99
+ return dict(**self.data, **{k: self.data[v] for k, v in self._alias_dict.items()}).items()
100
+ # NB: could be optimized as
101
+ # 'return chain(self.data.items(), ((k, self.data[v]) for k, v in self._alias_dict.items()))'
102
+ # (same as .keys() above)
103
+
104
+ def origin_len(self):
105
+ """Return count of original keys (without aliases)."""
106
+ return len(self.data)
107
+
108
+ def __len__(self):
109
+ return len(self.data) + len(self._alias_dict)
110
+
111
+ def __missing__(self, key):
112
+ try:
113
+ return super().__getitem__(self._alias_dict[key])
114
+ except KeyError:
115
+ raise KeyError(key) from None
116
+
117
+ def __setitem__(self, key, value):
118
+ try:
119
+ key = self._alias_dict[key]
120
+ except KeyError:
121
+ pass
122
+ super().__setitem__(key, value)
123
+
124
+ def __delitem__(self, key):
125
+ try:
126
+ self.data.__delitem__(key)
127
+ for alias in [k for k, v in self._alias_dict.items() if v == key]:
128
+ del self._alias_dict[alias]
129
+ except KeyError:
130
+ return self.remove_alias(key)
131
+
132
+ def __contains__(self, item):
133
+ return item in self.data or item in self._alias_dict
134
+
135
+ def __iter__(self):
136
+ return chain(self.data, self._alias_dict)
137
+
138
+ def __reversed__(self):
139
+ return chain(reversed(self._alias_dict), reversed(self.data))
140
+
141
+ def copy(self):
142
+ """Return a shallow copy of the AliasDict."""
143
+ return type(self)(self)
144
+
145
+ def __repr__(self):
146
+ return f"AliasDict({dict(self.items())})"
147
+
148
+ def __eq__(self, other):
149
+ if not isinstance(other, AliasDict):
150
+ return NotImplemented
151
+ return self.data == other.data and self._alias_dict == other._alias_dict
152
+
153
+ def __or__(self, other):
154
+ if not isinstance(other, Mapping):
155
+ return NotImplemented
156
+ new = self.copy()
157
+ if isinstance(other, AliasDict):
158
+ new.update(other.data)
159
+ new._alias_dict.update(other._alias_dict)
160
+ else:
161
+ new.update(other)
162
+ return new
163
+
164
+ def __ror__(self, other):
165
+ if not isinstance(other, Mapping):
166
+ return NotImplemented
167
+ new = AliasDict(other)
168
+ new.update(self.data)
169
+ new._alias_dict.update(self._alias_dict)
170
+ return new
171
+
172
+ def __ior__(self, other):
173
+ if isinstance(other, AliasDict):
174
+ self.update(other.data)
175
+ self._alias_dict.update(other._alias_dict)
176
+ else:
177
+ self.update(other)
178
+ return self
179
+
180
+ __hash__ = None
@@ -0,0 +1,42 @@
1
+ [project]
2
+ name = "aldict"
3
+ version = "1.1.1"
4
+ requires-python = ">=3.10"
5
+ authors = [
6
+ { name = "kaliv0", email = "kaloyan.ivanov88@gmail.com" },
7
+ ]
8
+ description = "Multi-key dictionary, supports adding and manipulating key-aliases pointing to shared values"
9
+ keywords = [
10
+ "multi-key dictionary",
11
+ "multidict",
12
+ "alias-dict",
13
+ ]
14
+ readme = "README.md"
15
+ license-files = [
16
+ "LICENSE",
17
+ ]
18
+ dependencies = []
19
+
20
+ [project.urls]
21
+ repository = "https://github.com/kaliv0/aldict"
22
+
23
+ [dependency-groups]
24
+ dev = [
25
+ "ruff>=0.12.4",
26
+ "pytest>=8.3.2",
27
+ ]
28
+
29
+ [build-system]
30
+ requires = [
31
+ "pdm-backend",
32
+ ]
33
+ build-backend = "pdm.backend"
34
+
35
+ [tool.pdm.build]
36
+ excludes = [
37
+ "tests/",
38
+ ]
39
+
40
+ [tool.ruff]
41
+ fix = true
42
+ line-length = 100
aldict-1.0.3/README.md DELETED
@@ -1,97 +0,0 @@
1
- <p align="center">
2
- <img src="https://github.com/kaliv0/aldict/blob/main/assets/alter-ego.jpg?raw=true" width="250" alt="Alter Ego">
3
- </p>
4
-
5
- ---
6
- # Aldict
7
-
8
- [![tests](https://img.shields.io/github/actions/workflow/status/kaliv0/aldict/ci.yml)](https://github.com/kaliv0/aldict/actions/workflows/ci.yml)
9
- ![Python 3.x](https://img.shields.io/badge/python-^3.10-blue?style=flat-square&logo=Python&logoColor=white)
10
- [![PyPI](https://img.shields.io/pypi/v/aldict.svg)](https://pypi.org/project/aldict/)
11
- [![License](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)](https://github.com/kaliv0/aldict/blob/main/LICENSE)
12
- [![Downloads](https://static.pepy.tech/badge/aldict)](https://pepy.tech/projects/aldict)
13
-
14
- Multi-key dictionary, supports adding and manipulating key-aliases pointing to shared values
15
-
16
- ---
17
- ## How to use
18
-
19
- - add_alias
20
- <br>(pass <i>key</i> as first parameter and <i>alias(es)</i> as variadic params)
21
- ```python
22
- ad = AliasDict({"a": 1, "b": 2})
23
- ad.add_alias("a", "aa")
24
- ad.add_alias("b", "bb", "Bbb")
25
- assert ad["a"] == ad["aa"] == 1
26
- assert ad["b"] == ad["bb"] == ad["Bbb"] == 2
27
- ```
28
- - remove_alias
29
- <br>(pass <i>alias(es)</i> to be removed as variadic parameters)
30
- ```python
31
- ad.remove_alias("aa")
32
- ad.remove_alias("bb", "Bbb")
33
- assert len(ad.aliases()) == 0
34
- ```
35
- - clear_aliases
36
- <br>(remove all <i>aliases</i> at once)
37
- ```python
38
- ad.clear_aliases()
39
- assert len(ad.aliases()) == 0
40
- ```
41
- - update alias
42
- <br>(point <i>alias</i> to different <i>key</i>)
43
- ```python
44
- ad = AliasDict({"a": 1, "b": 2})
45
- ad.add_alias("a", "ab")
46
- assert list(ad.items()) == [('a', 1), ('b', 2), ('ab', 1)]
47
-
48
- ad.add_alias("b", "ab")
49
- assert list(ad.items()) == [('a', 1), ('b', 2), ('ab', 2)]
50
- ```
51
- - read all aliases
52
- ```python
53
- ad = AliasDict({"a": 1, "b": 2})
54
- ad.add_alias("a", "aa")
55
- ad.add_alias("b", "bb", "B")
56
- ad.add_alias("a", "ab", "A")
57
- assert list(ad.aliases()) == ['aa', 'bb', 'B', 'ab', 'A']
58
- ```
59
- - aliased_keys
60
- <br>(read <i>keys</i> with corresponding <i>alias(es)</i>)
61
- ```python
62
- assert dict(ad.aliased_keys()) == {'a': ['aa', 'ab', 'A'], 'b': ['bb', 'B']}
63
- ```
64
- - read dictviews
65
- <br>(<i>dict.keys()</i> and <i>dict.items()</i> include <i>aliased</i> versions)
66
- ```python
67
- ad = AliasDict({"x": 10, "y": 20})
68
- ad.add_alias("x", "Xx")
69
- ad.add_alias("y", "Yy", "xyz")
70
-
71
- ad.keys()
72
- ad.values()
73
- ad.items()
74
-
75
- # dict_keys(['x', 'y', 'Xx', 'Yy', 'xyz'])
76
- # dict_values([10, 20])
77
- # dict_items([('x', 10), ('y', 20), ('Xx', 10), ('Yy', 20), ('xyz', 20)])
78
- ```
79
- - remove key and aliases
80
- ```python
81
- ad.pop("y")
82
- assert list(ad.items()) == [('x', 10), ('Xx', 10)]
83
- ```
84
- - origin_keys
85
- <br>(get original <i>keys</i> only)
86
- ```python
87
- assert list(ad.origin_keys()) == ['x', 'y']
88
- ```
89
- - origin_len
90
- <br>(get original dict <i>length</i> without aliases)
91
- ```python
92
- ad = AliasDict({"a": 1, "b": 2})
93
- ad.add_alias("a", "aa")
94
- assert list(ad.keys()) == ["a", "b", "aa"]
95
- assert len(ad) == 3
96
- assert ad.origin_len() == 2
97
- ```
@@ -1,105 +0,0 @@
1
- from collections import UserDict, defaultdict
2
-
3
- from aldict.exception import AliasError, AliasValueError
4
-
5
-
6
- class AliasDict(UserDict):
7
- """Custom Dict class supporting key-aliases pointing to shared values"""
8
-
9
- def __init__(self, dict_):
10
- self._alias_dict = {}
11
- super().__init__(**dict_)
12
-
13
- def add_alias(self, key, *aliases):
14
- """Adds one or more aliases to specified key in the dictionary"""
15
- if key not in self.data.keys():
16
- raise KeyError(key)
17
- for alias in aliases:
18
- if alias == key:
19
- raise AliasValueError(f"Key and corresponding alias cannot be equal: '{key}'")
20
- self._alias_dict[alias] = key
21
-
22
- def remove_alias(self, *aliases):
23
- """Removes one or more aliases"""
24
- for alias in aliases:
25
- try:
26
- self._alias_dict.__delitem__(alias)
27
- except KeyError as e:
28
- raise AliasError(alias) from e
29
-
30
- def clear_aliases(self):
31
- """Removes all aliases"""
32
- self._alias_dict.clear()
33
-
34
- def aliases(self):
35
- """Returns all aliases present in the dictionary"""
36
- return self._alias_dict.keys()
37
-
38
- def aliased_keys(self):
39
- """Returns a dictview of all keys with their corresponding aliases"""
40
- result = defaultdict(list)
41
- for alias, key in self._alias_dict.items():
42
- result[key].append(alias)
43
- return result.items()
44
-
45
- def origin_keys(self):
46
- """Returns all keys"""
47
- return self.data.keys()
48
-
49
- def keys(self):
50
- """Returns all keys and aliases"""
51
- return dict(**self.data, **self._alias_dict).keys()
52
-
53
- def values(self):
54
- """Returns all values"""
55
- return self.data.values()
56
-
57
- def items(self):
58
- """Returns a dictview with all items (including alias/value tuples)"""
59
- return dict(**self.data, **{k: self.data[v] for k, v in self._alias_dict.items()}).items()
60
-
61
- def origin_len(self):
62
- """Returns the length of the original dictionary (without aliases)"""
63
- return len(self.data)
64
-
65
- def __len__(self):
66
- return len(self.keys())
67
-
68
- def __missing__(self, key):
69
- try:
70
- return super().__getitem__(self._alias_dict[key])
71
- except AttributeError as e:
72
- raise KeyError(key) from e
73
-
74
- def __setitem__(self, key, value):
75
- try:
76
- key = self._alias_dict[key]
77
- except KeyError:
78
- pass
79
- super().__setitem__(key, value)
80
-
81
- def __delitem__(self, key):
82
- try:
83
- self.data.__delitem__(key)
84
- self._alias_dict = {k: v for k, v in self._alias_dict.items() if v != key}
85
- except KeyError:
86
- # in case we try to delete alias via pop() or del
87
- return self.remove_alias(key)
88
-
89
- def __contains__(self, item):
90
- return item in set(self.keys())
91
-
92
- def __iter__(self):
93
- for item in self.keys():
94
- yield item
95
-
96
- def __repr__(self):
97
- return f"AliasDict({self.items()})"
98
-
99
- def __eq__(self, other):
100
- if not isinstance(other, AliasDict):
101
- raise TypeError(f"{other} is not an AliasDict")
102
- return self.data == other.data and self._alias_dict == other._alias_dict
103
-
104
- def __hash__(self):
105
- return hash((self.data, self._alias_dict))
@@ -1,12 +0,0 @@
1
- LICENSE
2
- README.md
3
- pyproject.toml
4
- aldict/__init__.py
5
- aldict/alias_dict.py
6
- aldict/exception.py
7
- aldict.egg-info/PKG-INFO
8
- aldict.egg-info/SOURCES.txt
9
- aldict.egg-info/dependency_links.txt
10
- aldict.egg-info/requires.txt
11
- aldict.egg-info/top_level.txt
12
- tests/test_alias_dict.py
@@ -1,6 +0,0 @@
1
-
2
- [dev]
3
- pytest>=8.3.4
4
- ruff>=0.8.3
5
- build>=1.2.2
6
- twine>=6.0.1
@@ -1 +0,0 @@
1
- aldict
@@ -1,27 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools>=75.6.0", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "aldict"
7
- version = "1.0.3"
8
- readme = "README.md"
9
- authors = [{ name = "Kaloyan Ivanov", email = "kaloyan.ivanov88@gmail.com" }]
10
- description = "Multi-key dictionary, supports adding and manipulating key-aliases pointing to shared values"
11
- keywords = ["multi-key dictionary", "multidict", "alias-dict"]
12
- urls = { repository = "https://github.com/kaliv0/aldict" }
13
-
14
- requires-python = ">= 3.10"
15
-
16
- [project.optional-dependencies]
17
- dev = [
18
- "pytest>=8.3.4",
19
- "ruff>=0.8.3",
20
- "build>=1.2.2",
21
- "twine>=6.0.1",
22
- ]
23
-
24
- [tool.setuptools.packages.find]
25
- where = ["."]
26
- include = ["aldict"]
27
- exclude = ["tests"]
aldict-1.0.3/setup.cfg DELETED
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
@@ -1,251 +0,0 @@
1
- import pytest
2
-
3
- from aldict import AliasDict, AliasValueError, AliasError
4
-
5
-
6
- def test_alias_dict(alias_dict):
7
- assert alias_dict[".toml"] == {
8
- "callable": "load",
9
- "import_mod": "tomli",
10
- "read_mode": "r",
11
- }
12
- assert (
13
- alias_dict[".yml"]
14
- == alias_dict[".yaml"]
15
- == {"callable": "safe_load", "import_mod": "yaml", "read_mode": "r"}
16
- )
17
-
18
-
19
- def test_add_alias(alias_dict):
20
- alias_dict.add_alias(".toml", ".tml")
21
- assert (
22
- alias_dict[".toml"]
23
- == alias_dict[".tml"]
24
- == {"callable": "load", "import_mod": "tomli", "read_mode": "r"}
25
- )
26
-
27
-
28
- def test_add_multiple_aliases(alias_dict):
29
- alias_dict.add_alias(".json", ".jsn", ".joojoo", ".jazz")
30
- assert list(alias_dict.keys()) == [
31
- ".json",
32
- ".yaml",
33
- ".toml",
34
- ".yml",
35
- ".jsn",
36
- ".joojoo",
37
- ".jazz",
38
- ]
39
-
40
-
41
- def test_add_alias_raises(alias_dict):
42
- with pytest.raises(
43
- AliasValueError, match="Key and corresponding alias cannot be equal: '.toml'"
44
- ):
45
- alias_dict.add_alias(".toml", ".toml")
46
-
47
-
48
- def test_update_alias(alias_dict):
49
- # redirect ".yml" to point to ".toml"
50
- alias_dict.add_alias(".toml", ".yml")
51
- assert list(alias_dict.items()) == [
52
- (".json", {"callable": "load", "import_mod": "json", "read_mode": "r"}),
53
- (".yaml", {"callable": "safe_load", "import_mod": "yaml", "read_mode": "r"}),
54
- (".toml", {"callable": "load", "import_mod": "tomli", "read_mode": "r"}),
55
- (".yml", {"callable": "load", "import_mod": "tomli", "read_mode": "r"}),
56
- ]
57
-
58
-
59
- def test_update_alias_raises(alias_dict):
60
- with pytest.raises(KeyError, match=".foo"):
61
- alias_dict.add_alias(".foo", ".bar")
62
-
63
-
64
- def test_remove_alias(alias_dict):
65
- assert list(alias_dict.keys()) == [".json", ".yaml", ".toml", ".yml"]
66
-
67
- alias_dict.remove_alias(".yml")
68
- assert list(alias_dict.keys()) == [".json", ".yaml", ".toml"]
69
- assert list(alias_dict.items()) == [
70
- (".json", {"callable": "load", "import_mod": "json", "read_mode": "r"}),
71
- (".yaml", {"callable": "safe_load", "import_mod": "yaml", "read_mode": "r"}),
72
- (".toml", {"callable": "load", "import_mod": "tomli", "read_mode": "r"}),
73
- ]
74
-
75
-
76
- def test_remove_alias_raises(alias_dict):
77
- assert list(alias_dict.keys()) == [".json", ".yaml", ".toml", ".yml"]
78
- with pytest.raises(AliasError, match=".foo"):
79
- alias_dict.remove_alias(".foo")
80
-
81
-
82
- def test_remove_multiple_aliases(alias_dict):
83
- alias_dict.add_alias(".json", ".jsn")
84
- assert list(alias_dict.keys()) == [".json", ".yaml", ".toml", ".yml", ".jsn"]
85
-
86
- alias_dict.remove_alias(".yml", ".jsn")
87
- assert list(alias_dict.keys()) == [".json", ".yaml", ".toml"]
88
-
89
-
90
- def test_read_aliases(alias_dict):
91
- alias_dict.add_alias(".toml", ".tml")
92
- alias_dict.add_alias(".json", ".jsn")
93
- alias_dict.add_alias(".json", ".whaaaaat!")
94
- assert list(alias_dict.aliases()) == [".yml", ".tml", ".jsn", ".whaaaaat!"]
95
-
96
-
97
- def test_dictviews(alias_dict):
98
- assert list(alias_dict.keys()) == [".json", ".yaml", ".toml", ".yml"]
99
- assert list(alias_dict.values()) == [
100
- {"import_mod": "json", "callable": "load", "read_mode": "r"},
101
- {"import_mod": "yaml", "callable": "safe_load", "read_mode": "r"},
102
- {"import_mod": "tomli", "callable": "load", "read_mode": "r"},
103
- ]
104
- assert list(alias_dict.items()) == [
105
- (".json", {"callable": "load", "import_mod": "json", "read_mode": "r"}),
106
- (".yaml", {"callable": "safe_load", "import_mod": "yaml", "read_mode": "r"}),
107
- (".toml", {"callable": "load", "import_mod": "tomli", "read_mode": "r"}),
108
- (".yml", {"callable": "safe_load", "import_mod": "yaml", "read_mode": "r"}),
109
- ]
110
-
111
-
112
- def test_remove_key_and_aliases(alias_dict):
113
- assert list(alias_dict.keys()) == [".json", ".yaml", ".toml", ".yml"]
114
- alias_dict.pop(".yaml")
115
- assert list(alias_dict.keys()) == [".json", ".toml"]
116
-
117
-
118
- def test_contains(alias_dict):
119
- alias_dict.add_alias(".toml", ".tml")
120
- assert (".toml" in alias_dict) is True
121
- assert (".tml" in alias_dict) is True
122
- assert (".foo" in alias_dict) is False
123
-
124
-
125
- def test_get(alias_dict):
126
- assert alias_dict.get(".yaml") == {
127
- "callable": "safe_load",
128
- "import_mod": "yaml",
129
- "read_mode": "r",
130
- }
131
- assert alias_dict.get(".yml") == {
132
- "callable": "safe_load",
133
- "import_mod": "yaml",
134
- "read_mode": "r",
135
- }
136
- assert alias_dict.get(".foo") is None
137
-
138
-
139
- def test_pop_alias_doesnt_remove_key(alias_dict):
140
- assert alias_dict.pop(".yml") == {
141
- "callable": "safe_load",
142
- "import_mod": "yaml",
143
- "read_mode": "r",
144
- }
145
- assert list(alias_dict.keys()) == [".json", ".yaml", ".toml"]
146
-
147
-
148
- def test_iter(alias_dict):
149
- assert [k for k in alias_dict] == [".json", ".yaml", ".toml", ".yml"]
150
-
151
-
152
- def test_origin_keys(alias_dict):
153
- assert list(alias_dict.origin_keys()) == [".json", ".yaml", ".toml"]
154
-
155
-
156
- def test_aliased_keys(alias_dict):
157
- assert list(alias_dict.aliased_keys()) == [(".yaml", [".yml"])]
158
- alias_dict.add_alias(".toml", ".tml", ".tommy", ".tomograph")
159
- assert list(alias_dict.aliased_keys()) == [
160
- (".yaml", [".yml"]),
161
- (".toml", [".tml", ".tommy", ".tomograph"]),
162
- ]
163
-
164
-
165
- def test_repr(alias_dict):
166
- assert str(alias_dict) == (
167
- "AliasDict(dict_items(["
168
- "('.json', {'import_mod': 'json', 'callable': 'load', 'read_mode': 'r'}), "
169
- "('.yaml', {'import_mod': 'yaml', 'callable': 'safe_load', 'read_mode': 'r'}), "
170
- "('.toml', {'import_mod': 'tomli', 'callable': 'load', 'read_mode': 'r'}), "
171
- "('.yml', {'import_mod': 'yaml', 'callable': 'safe_load', 'read_mode': 'r'})"
172
- "]))"
173
- )
174
-
175
-
176
- def test_eq():
177
- ad_0 = {"a": 1, "b": 2}
178
- ad_1 = AliasDict(ad_0)
179
- ad_1.add_alias("a", "aa", "aaa")
180
-
181
- ad_2 = AliasDict(ad_0)
182
- ad_2.add_alias("a", "aa", "aaa")
183
-
184
- ad_3 = AliasDict(ad_0)
185
- ad_3.add_alias("a", "abc")
186
-
187
- assert ad_1 == ad_2
188
- assert ad_1 != ad_3
189
- assert ad_2 != ad_3
190
-
191
-
192
- def test_dict_len_includes_aliases(alias_dict):
193
- assert list(alias_dict.keys()) == [".json", ".yaml", ".toml", ".yml"]
194
- assert len(alias_dict) == 4
195
-
196
-
197
- def test_dict_origin_len_excludes_aliases(alias_dict):
198
- assert list(alias_dict.keys()) == [".json", ".yaml", ".toml", ".yml"]
199
- assert alias_dict.origin_len() == 3
200
-
201
-
202
- def test_popitem(alias_dict):
203
- # pops first item -> MutableMapping.popitem()
204
- assert alias_dict.popitem() == (
205
- ".json",
206
- {"callable": "load", "import_mod": "json", "read_mode": "r"},
207
- )
208
- assert alias_dict.popitem() == (
209
- ".yaml",
210
- {"callable": "safe_load", "import_mod": "yaml", "read_mode": "r"},
211
- )
212
- assert len(alias_dict.aliased_keys()) == 0
213
- assert list(alias_dict.keys()) == [".toml"]
214
-
215
-
216
- def test_clear(alias_dict):
217
- alias_dict.clear()
218
- assert len(alias_dict.items()) == 0
219
-
220
-
221
- def test_clear_aliases(alias_dict):
222
- alias_dict.clear_aliases()
223
- assert len(alias_dict.aliases()) == 0
224
- assert list(alias_dict.items()) == [
225
- (".json", {"callable": "load", "import_mod": "json", "read_mode": "r"}),
226
- (".yaml", {"callable": "safe_load", "import_mod": "yaml", "read_mode": "r"}),
227
- (".toml", {"callable": "load", "import_mod": "tomli", "read_mode": "r"}),
228
- ]
229
-
230
-
231
- def test_setdefault():
232
- ad = AliasDict({"a": 1, "b": 2})
233
- ad.setdefault("foo", "bar")
234
- ad.add_alias("foo", "fizz")
235
- assert ad["foo"] == "bar"
236
- assert ad["fizz"] == "bar"
237
-
238
-
239
- def test_setdefault_on_existing_aliased_key():
240
- ad = AliasDict({"a": 1, "b": 2})
241
- ad.setdefault("a", 42)
242
- ad.add_alias("a", "aa")
243
- assert ad["a"] == 1
244
- assert ad["aa"] == 1
245
-
246
-
247
- def test_update_modifies_aliases():
248
- ad = AliasDict({"a": 1, "b": 2})
249
- ad.add_alias("a", "aa", "aaa")
250
- ad.update(**{"a": 40, "y": 50})
251
- assert list(ad.items()) == [("a", 40), ("b", 2), ("y", 50), ("aa", 40), ("aaa", 40)]
File without changes
File without changes