Skip to content

Commit a17b24b

Browse files
committed
Improve documentation and error handling
1 parent 8b2d4c0 commit a17b24b

2 files changed

Lines changed: 56 additions & 51 deletions

File tree

flask_hashids.py

Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from flask import current_app, Flask
22
from hashids import Hashids as _Hashids
3-
from typing import Dict, Optional, Tuple, Union
3+
from typing import Optional, Tuple, Union
44
from werkzeug.routing import BaseConverter, ValidationError
55

66

@@ -9,83 +9,80 @@ class HashidMixin:
99
1010
The HashidMixin class adds a hashid property to a class. This property
1111
will compute a hashid based on the attribute specified by the class
12-
variable `__id_attribute__: str = 'id'`.
13-
14-
The class can be used with SQLAlchemy models.
15-
This won't add a column to the table, the hashid is implemented as a
16-
property and computed on runtime.
17-
18-
NOTE: The extended class must have an attribute named after the value of
19-
`__id_attribute__: str` and must be of type `int`!
12+
variable `__id_attribute__` (defaults to `'id'`).
13+
14+
- The class variable `__id_attribute__` must contain the name of an `int`
15+
attribute of the extended class and must be of type `str`
16+
- The class can be used with SQLAlchemy models. This won't add a column to
17+
the table, the hashid is implemented as a property and computed on
18+
runtime
2019
'''
2120

22-
__id_attribute__: str = 'id'
21+
__id_attribute__ = 'id'
2322

2423
@property
2524
def hashid(self) -> str:
2625
''' Hashid property.
2726
2827
Runtime computed hashid based on the attribute specified by the class
29-
variable `__id_attribute__: str`.
28+
variable `__id_attribute__`.
3029
'''
30+
hashids: Hashids = current_app.extensions['hashids']
3131
id_attribute: int = getattr(self, self.__class__.__id_attribute__)
32-
hashids_extension: Hashids = current_app.extensions['hashids']
33-
return hashids_extension.encode(id_attribute)
32+
return hashids.encode(id_attribute)
3433

3534

3635
class HashidConverter(BaseConverter):
3736
''' Hashid Converter.
3837
39-
Converts and decodes a hashid from routes.\n
40-
Examples:
41-
```python
42-
@app.route('/resources/<hashid:resource_id')
43-
def get_resource(resource_id: int):
44-
print(isinstance(resource_id, int)) # True
45-
46-
@app.route('/resources/<hashid:resource_ids')
47-
def get_resources(resource_ids: Tuple[int, ...]):
48-
print(isinstance(resource_ids, tuple)) # True
49-
print(all(isinstance(i, int) for i in resource_ids)) # True
50-
```
51-
52-
Converts and encodes values when generating urls.\n
53-
Examples:
54-
```python
55-
url_for('get_resource', resource_id=123) # /resources/Mj3
56-
57-
url_for('get_resource', resource_id=(123, 456)) # /resources/Nk4
58-
```
38+
Converts and decodes a hashid from routes.
39+
```python
40+
@app.route('/resources/<hashid:resource_id')
41+
def get_resource(resource_id: int):
42+
print(isinstance(resource_id, int)) # True
43+
44+
@app.route('/resources/<hashid:resource_ids')
45+
def get_resources(resource_ids: Tuple[int, ...]):
46+
print(isinstance(resource_ids, tuple)) # True
47+
print(all(isinstance(i, int) for i in resource_ids)) # True
48+
```
49+
50+
Converts and encodes values when generating urls.
51+
```python
52+
url_for('get_resource', resource_id=123) # /resources/Mj3
53+
54+
url_for('get_resource', resource_id=(123, 456)) # /resources/Nk4
55+
```
5956
'''
6057

6158
def to_python(self, hashid: str) -> Union[int, Tuple[int, ...]]:
6259
''' Decodes matched hashid in a URL to an int or tuple of ints '''
63-
hashids_extension: Hashids = current_app.extensions['hashids']
64-
decoded_hashid: Union[int, Tuple[int, ...], Tuple[()]] = hashids_extension.decode(hashid)
60+
hashids: Hashids = current_app.extensions['hashids']
61+
decoded_hashid: Union[int, Tuple[int, ...], Tuple[()]] = hashids.decode(hashid)
6562
if isinstance(decoded_hashid, tuple) and len(decoded_hashid) == 0:
6663
raise ValidationError()
6764
return decoded_hashid
6865

6966
def to_url(self, value: Union[int, Tuple[int, ...]]) -> str:
7067
''' Encodes an int or tuple of ints to a hashid when building a URL '''
71-
hashids_extension: Hashids = current_app.extensions['hashids']
68+
hashids: Hashids = current_app.extensions['hashids']
7269
if isinstance(value, int):
73-
return hashids_extension.encode(value)
70+
return hashids.encode(value)
7471
elif isinstance(value, tuple):
7572
if len(value) == 0:
7673
raise ValueError('Tuple must not be empty')
7774
if not all(isinstance(v, int) for v in value):
7875
raise TypeError('Tuple must only contain integers')
79-
return hashids_extension.encode(*value)
76+
return hashids.encode(*value)
8077
else:
81-
raise TypeError('Value must be int or tuple of ints')
78+
raise TypeError('Value must be an int or a tuple of ints')
8279

8380

8481
class Hashids:
8582
''' Wrapper class to easily integrate Hashids in Flask '''
8683

8784
def __init__(self, app: Optional[Flask] = None) -> None:
88-
self.app: Optional[Flask] = app
85+
self.app = app
8986
if app is not None:
9087
self.init_app(app)
9188

@@ -96,23 +93,31 @@ def init_app(self, app: Flask) -> None:
9693
`config` attribute of the passed `app` object. It will also register
9794
the HashidConverter.
9895
'''
99-
hashids_config: Dict = {}
96+
hashids_config = {}
10097
if 'HASHIDS_ALPHABET' in app.config:
101-
hashids_config['alphabet']: str = app.config['HASHIDS_ALPHABET']
98+
hashids_config['alphabet'] = app.config['HASHIDS_ALPHABET']
10299
if 'HASHIDS_MIN_LENGTH' in app.config:
103-
hashids_config['min_length']: int = int(app.config['HASHIDS_MIN_LENGTH'])
100+
hashids_config['min_length'] = int(app.config['HASHIDS_MIN_LENGTH'])
104101
if 'HASHIDS_SALT' in app.config:
105-
hashids_config['salt']: str = app.config['HASHIDS_SALT']
106-
self._hashids: _Hashids = _Hashids(**hashids_config)
107-
if not hasattr(app, 'extensions'):
108-
app.extensions: Dict = {}
109-
app.extensions['hashids']: Hashids = self
110-
app.url_map.converters['hashid']: HashidConverter = HashidConverter
102+
hashids_config['salt'] = app.config['HASHIDS_SALT']
103+
self._wrapped_hashids = _Hashids(**hashids_config)
104+
105+
app.extensions = getattr(app, 'extensions', {})
106+
app.extensions['hashids'] = self
107+
app.url_map.converters['hashid'] = HashidConverter
108+
109+
@property
110+
def _hashids(self) -> _Hashids:
111+
''' Returns the wrapped Hashids instance. '''
112+
try:
113+
return self._wrapped_hashids
114+
except AttributeError:
115+
raise AttributeError('Hashids extension not initialized')
111116

112117
def decode(self, hashid: str) -> Union[int, Tuple[int, ...], Tuple[()]]:
113118
''' Decode the passed `hashid`.
114119
115-
Possible return values:
120+
Possible return types:
116121
- `int` if the hashid contains only one value.
117122
- `Tuple[int, ...]` if the hashid contains multiple values.
118123
- `Tuple[()]` if the hashid is invalid.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
setup(
99
name='Flask-Hashids',
10-
version='1.0.2',
10+
version='1.0.3',
1111
url='https://github.com/Pevtrick/Flask-Hashids',
1212
author='Patrick Jentsch',
1313
author_email='patrickjentsch@gmx.net',

0 commit comments

Comments
 (0)