44from werkzeug .routing import BaseConverter , ValidationError
55
66
7- DecodedHashid = Union [Tuple [int , ...], Tuple [()]]
8- SimplifiedDecodedHashid = Union [int , Tuple [int , ...], Tuple [()]]
9-
10-
117class HashidMixin :
12- '''
13- The HashidMixin class adds a hashid property to a class instance. This
14- property will compute a hashid based on the attribute specified by a
15- special class variable called __id_attribute__ (defaults to "id").
8+ ''' Hashid Mixin class.
9+
10+ The HashidMixin class adds a hashid property to a class. This property
11+ will compute a hashid based on the attribute specified by the class
12+ variable `__id_attribute__: str = 'id'`.
1613
1714 The class can be used with SQLAlchemy models.
1815 This won't add a column to the table, the hashid is implemented as a
1916 property and computed on runtime.
2017
2118 NOTE: The extended class must have an attribute named after the value of
22- __id_attribute__ ( str) and must be of type int!
19+ ` __id_attribute__: str` and must be of type ` int` !
2320 '''
2421
2522 __id_attribute__ : str = 'id'
2623
2724 @property
2825 def hashid (self ) -> str :
26+ ''' Hashid property.
27+
28+ Runtime computed hashid based on the attribute specified by the class
29+ variable `__id_attribute__: str`.
30+ '''
2931 id_attribute : int = getattr (self , self .__class__ .__id_attribute__ )
3032 hashids_extension : Hashids = current_app .extensions ['hashids' ]
3133 return hashids_extension .encode (id_attribute )
@@ -34,8 +36,9 @@ def hashid(self) -> str:
3436class HashidConverter (BaseConverter ):
3537 ''' Hashid Converter.
3638
37- Converts and decodes a hashid from routes.
39+ Converts and decodes a hashid from routes.\n
3840 Examples:
41+ ```python
3942 @app.route('/resources/<hashid:resource_id')
4043 def get_resource(resource_id: int):
4144 print(isinstance(resource_id, int)) # True
@@ -44,43 +47,60 @@ def get_resource(resource_id: int):
4447 def get_resources(resource_ids: Tuple[int, ...]):
4548 print(isinstance(resource_ids, tuple)) # True
4649 print(all(isinstance(i, int) for i in resource_ids)) # True
50+ ```
4751
48- Converts and encodes values when generating urls.
52+ Converts and encodes values when generating urls.\n
4953 Examples:
54+ ```python
5055 url_for('get_resource', resource_id=123) # /resources/Mj3
5156
5257 url_for('get_resource', resource_id=(123, 456)) # /resources/Nk4
58+ ```
5359 '''
5460
55- def to_python (self , hashid : str ) -> DecodedHashid :
61+ def to_python (self , hashid : str ) -> Union [int , Tuple [int , ...]]:
62+ ''' Decodes matched hashid in a URL to an int or tuple of ints '''
5663 hashids_extension : Hashids = current_app .extensions ['hashids' ]
57- decoded_hashid : SimplifiedDecodedHashid = hashids_extension .decode (hashid )
64+ decoded_hashid : Union [ int , Tuple [ int , ...], Tuple [()]] = hashids_extension .decode (hashid )
5865 if isinstance (decoded_hashid , tuple ) and len (decoded_hashid ) == 0 :
5966 raise ValidationError ()
6067 return decoded_hashid
6168
62- def to_url (self , value : DecodedHashid ) -> str :
69+ def to_url (self , value : Union [int , Tuple [int , ...]]) -> str :
70+ ''' Encodes an int or tuple of ints to a hashid when building a URL '''
6371 hashids_extension : Hashids = current_app .extensions ['hashids' ]
64- normalized_value : Tuple [int , ...] = (value ,) if isinstance (value , int ) else value
65- return hashids_extension .encode (* normalized_value )
72+ if isinstance (value , int ):
73+ return hashids_extension .encode (value )
74+ elif isinstance (value , tuple ):
75+ if len (value ) == 0 :
76+ raise ValueError ('Tuple must not be empty' )
77+ if not all (isinstance (v , int ) for v in value ):
78+ raise TypeError ('Tuple must only contain integers' )
79+ return hashids_extension .encode (* value )
80+ else :
81+ raise TypeError ('Value must be int or tuple of ints' )
6682
6783
6884class Hashids :
69- ''' Wrapper class to easily integrate hashids in Flask '''
85+ ''' Wrapper class to easily integrate Hashids in Flask '''
7086
7187 def __init__ (self , app : Optional [Flask ] = None ) -> None :
72- self .app : Union [Flask , None ] = app
88+ self .app : Optional [Flask ] = app
7389 if app is not None :
7490 self .init_app (app )
7591
7692 def init_app (self , app : Flask ) -> None :
77- ''' Setup Hashids, includes the integration of the HashidConverter. '''
93+ ''' Initialize Hashids extension
94+
95+ This method will configure the Hashids extension according to the
96+ `config` attribute of the passed `app` object. It will also register
97+ the HashidConverter.
98+ '''
7899 hashids_config : Dict = {}
79100 if 'HASHIDS_ALPHABET' in app .config :
80101 hashids_config ['alphabet' ]: str = app .config ['HASHIDS_ALPHABET' ]
81102 if 'HASHIDS_MIN_LENGTH' in app .config :
82- hashids_config ['min_length' ]: int = \
83- int (app .config ['HASHIDS_MIN_LENGTH' ])
103+ hashids_config ['min_length' ]: int = int (app .config ['HASHIDS_MIN_LENGTH' ])
84104 if 'HASHIDS_SALT' in app .config :
85105 hashids_config ['salt' ]: str = app .config ['HASHIDS_SALT' ]
86106 self ._hashids : _Hashids = _Hashids (** hashids_config )
@@ -89,15 +109,15 @@ def init_app(self, app: Flask) -> None:
89109 app .extensions ['hashids' ]: Hashids = self
90110 app .url_map .converters ['hashid' ]: HashidConverter = HashidConverter
91111
92- def decode (self , hashid : str ) -> SimplifiedDecodedHashid :
112+ def decode (self , hashid : str ) -> Union [ int , Tuple [ int , ...], Tuple [()]] :
93113 ''' Decode the passed `hashid`.
94114
95115 Possible return values:
96116 - `int` if the hashid contains only one value.
97117 - `Tuple[int, ...]` if the hashid contains multiple values.
98118 - `Tuple[()]` if the hashid is invalid.
99119 '''
100- decoded_hashid : DecodedHashid = self ._hashids .decode (hashid )
120+ decoded_hashid : Union [ Tuple [ int , ...], Tuple [()]] = self ._hashids .decode (hashid )
101121 if len (decoded_hashid ) == 1 :
102122 return decoded_hashid [0 ]
103123 return decoded_hashid
0 commit comments