11from flask import current_app , Flask
22from hashids import Hashids as _Hashids
3- from typing import Dict , Optional , Tuple , Union
3+ from typing import Optional , Tuple , Union
44from 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
3635class 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
8481class 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.
0 commit comments