0
# Utilities
1
2
Enhanced collections and helper functions for HAL processing. RestNavigator provides specialized data structures and utilities that support CURIE prefixes, link disambiguation, and HAL-specific data manipulation.
3
4
## Capabilities
5
6
### Enhanced Collections
7
8
Specialized collection classes that provide HAL-specific functionality for managing links and data.
9
10
```python { .api }
11
class CurieDict(dict):
12
def __init__(self, default_curie: str, d: dict):
13
"""
14
Dictionary supporting CURIE prefixes for link relations.
15
16
Parameters:
17
- default_curie: Default CURIE prefix for unprefixed relations
18
- d: Initial dictionary data
19
"""
20
21
@property
22
def default_curie(self) -> str:
23
"""Default CURIE prefix"""
24
```
25
26
```python { .api }
27
class LinkList(list):
28
def get_by(self, prop: str, val, raise_exc: bool = False):
29
"""
30
Get single item by property value.
31
32
Parameters:
33
- prop: Property name to search by
34
- val: Property value to match
35
- raise_exc: Whether to raise exception if not found
36
37
Returns:
38
First matching item or None
39
"""
40
41
def getall_by(self, prop: str, val) -> list:
42
"""
43
Get all items matching property value.
44
45
Parameters:
46
- prop: Property name to search by
47
- val: Property value to match
48
49
Returns:
50
List of all matching items
51
"""
52
53
def named(self, name: str):
54
"""
55
Get item by name property (HAL standard).
56
57
Parameters:
58
- name: Name value to search for
59
60
Returns:
61
Item with matching name property
62
"""
63
64
def append_with(self, obj, **properties) -> None:
65
"""
66
Add item with metadata properties.
67
68
Parameters:
69
- obj: Object to add to list
70
- **properties: Metadata properties to associate
71
"""
72
```
73
74
### Core Utility Functions
75
76
Essential helper functions for URL processing, data manipulation, and HAL-specific operations.
77
78
```python { .api }
79
def fix_scheme(url: str) -> str:
80
"""
81
Add http:// scheme if missing, validate scheme.
82
83
Parameters:
84
- url: URL to fix
85
86
Returns:
87
URL with proper scheme
88
89
Raises:
90
WileECoyoteException: For invalid schemes
91
ZachMorrisException: For multiple schemes
92
"""
93
94
def normalize_getitem_args(args) -> list:
95
"""
96
Normalize __getitem__ arguments to list.
97
98
Parameters:
99
- args: Arguments from __getitem__ call
100
101
Returns:
102
Normalized list of arguments
103
"""
104
105
def namify(root_uri: str) -> str:
106
"""
107
Convert URI to readable API name.
108
109
Parameters:
110
- root_uri: API root URI
111
112
Returns:
113
Human-readable API name
114
"""
115
116
def objectify_uri(relative_uri: str) -> str:
117
"""
118
Convert URI to object notation string.
119
120
Parameters:
121
- relative_uri: Relative URI path
122
123
Returns:
124
Object notation representation
125
"""
126
127
def parse_media_type(media_type: str) -> tuple:
128
"""
129
Parse HTTP media type header.
130
131
Parameters:
132
- media_type: Media type string from HTTP header
133
134
Returns:
135
Tuple of (type, subtype, parameters)
136
"""
137
138
def getpath(d: dict, json_path: str, default=None, sep: str = '.') -> any:
139
"""
140
Get nested dictionary value by path.
141
142
Parameters:
143
- d: Dictionary to search
144
- json_path: Dot-separated path to value
145
- default: Default value if path not found
146
- sep: Path separator character
147
148
Returns:
149
Value at path or default
150
"""
151
152
def getstate(d: dict) -> dict:
153
"""
154
Deep copy dict removing HAL keys (_links, _embedded).
155
156
Parameters:
157
- d: Dictionary to clean
158
159
Returns:
160
Clean copy without HAL metadata
161
"""
162
```
163
164
## Usage Examples
165
166
### Working with CurieDict
167
168
```python
169
# CurieDict automatically handles CURIE prefixes
170
links = CurieDict('ex', {})
171
172
# These are equivalent when default_curie is 'ex'
173
links['ex:users'] = user_navigator
174
links['users'] = user_navigator # Automatically becomes 'ex:users'
175
176
# Access with or without CURIE
177
user_nav = links['users'] # Works
178
user_nav = links['ex:users'] # Also works
179
180
# IANA standard relations have precedence
181
links['next'] = next_page # Standard 'next' relation
182
links['ex:next'] = custom_next # Custom next relation
183
print(links['next']) # Returns standard 'next', not 'ex:next'
184
```
185
186
### Working with LinkList
187
188
```python
189
# LinkList for managing HAL link arrays
190
links = LinkList()
191
192
# Add links with properties
193
links.append_with(widget1_nav, name='widget1', profile='widget')
194
links.append_with(widget2_nav, name='widget2', profile='widget')
195
links.append_with(gadget_nav, name='gadget1', profile='gadget')
196
197
# Find by property
198
widget1 = links.get_by('name', 'widget1')
199
gadget = links.get_by('profile', 'gadget')
200
201
# Get all matching items
202
all_widgets = links.getall_by('profile', 'widget')
203
204
# Use standard HAL name property
205
specific_item = links.named('gadget1') # Same as get_by('name', 'gadget1')
206
207
# Handle missing items
208
missing = links.get_by('name', 'nonexistent', raise_exc=False) # Returns None
209
```
210
211
### URL and Scheme Utilities
212
213
```python
214
from restnavigator.utils import fix_scheme, namify, objectify_uri
215
216
# Fix URL schemes
217
clean_url = fix_scheme('api.example.com') # Returns 'http://api.example.com'
218
clean_url = fix_scheme('https://api.example.com') # Returns unchanged
219
220
# Generate API names
221
api_name = namify('https://api.github.com/v3') # Returns 'Github'
222
api_name = namify('http://haltalk.herokuapp.com') # Returns 'Haltalk'
223
224
# Convert URIs to object notation
225
obj_notation = objectify_uri('/users/123/posts') # Returns 'users.123.posts'
226
obj_notation = objectify_uri('/api/v1/repos') # Returns 'api.v1.repos'
227
```
228
229
### Data Path Utilities
230
231
```python
232
from restnavigator.utils import getpath, getstate
233
234
# Navigate nested data structures
235
data = {
236
'user': {
237
'profile': {
238
'name': 'John Doe',
239
'settings': {
240
'theme': 'dark'
241
}
242
}
243
}
244
}
245
246
# Get nested values
247
name = getpath(data, 'user.profile.name') # Returns 'John Doe'
248
theme = getpath(data, 'user.profile.settings.theme') # Returns 'dark'
249
missing = getpath(data, 'user.missing.field', 'default') # Returns 'default'
250
251
# Custom separator
252
theme = getpath(data, 'user/profile/settings/theme', sep='/') # Returns 'dark'
253
```
254
255
### HAL Data Cleaning
256
257
```python
258
from restnavigator.utils import getstate
259
260
# Remove HAL metadata from response data
261
hal_response = {
262
'id': 1,
263
'name': 'John Doe',
264
'_links': {
265
'self': {'href': '/users/1'},
266
'posts': {'href': '/users/1/posts'}
267
},
268
'_embedded': {
269
'posts': [
270
{
271
'id': 1,
272
'title': 'First Post',
273
'_links': {'self': {'href': '/posts/1'}}
274
}
275
]
276
}
277
}
278
279
# Get clean state without HAL metadata
280
clean_data = getstate(hal_response)
281
# Returns: {'id': 1, 'name': 'John Doe'}
282
```
283
284
### Media Type Parsing
285
286
```python
287
from restnavigator.utils import parse_media_type
288
289
# Parse content-type headers
290
main_type, sub_type, params = parse_media_type('application/hal+json; charset=utf-8')
291
# main_type: 'application'
292
# sub_type: 'hal+json'
293
# params: {'charset': 'utf-8'}
294
295
# Handle complex media types
296
main_type, sub_type, params = parse_media_type('text/html; charset=utf-8; boundary=something')
297
# params: {'charset': 'utf-8', 'boundary': 'something'}
298
```
299
300
### Navigation Argument Processing
301
302
```python
303
from restnavigator.utils import normalize_getitem_args
304
305
# Normalize different argument patterns
306
args1 = normalize_getitem_args('single') # Returns ['single']
307
args2 = normalize_getitem_args(('a', 'b')) # Returns ['a', 'b']
308
args3 = normalize_getitem_args(['x', 'y', 'z']) # Returns ['x', 'y', 'z']
309
310
# Used internally for bracket notation like:
311
# api['users', 'posts', 0] # Gets normalized to ['users', 'posts', 0]
312
```
313
314
### Advanced Collection Patterns
315
316
```python
317
# Building dynamic link collections
318
def build_link_collection(api_links):
319
"""Build organized link collection from API links"""
320
organized = {}
321
322
for rel, links in api_links.items():
323
if isinstance(links, list):
324
link_list = LinkList()
325
for link in links:
326
# Extract properties from link metadata
327
props = getattr(link, 'props', {})
328
link_list.append_with(link, **props)
329
organized[rel] = link_list
330
else:
331
organized[rel] = links
332
333
return organized
334
335
# Custom CURIE handling
336
def smart_curie_lookup(curie_dict, relation):
337
"""Smart lookup with fallback logic"""
338
# Try exact match first
339
if relation in curie_dict:
340
return curie_dict[relation]
341
342
# Try with default CURIE
343
if curie_dict.default_curie:
344
full_rel = f"{curie_dict.default_curie}:{relation}"
345
if full_rel in curie_dict:
346
return curie_dict[full_rel]
347
348
# Try without CURIE if relation includes one
349
if ':' in relation:
350
bare_rel = relation.split(':', 1)[1]
351
if bare_rel in curie_dict:
352
return curie_dict[bare_rel]
353
354
return None
355
```