1- from flask import abort , current_app , Flask
1+ from flask import current_app , Flask
22from hashids import Hashids as _Hashids
3- from typing import Any , Dict
4- from werkzeug .routing import BaseConverter
3+ from typing import Any , Dict , Tuple , Union
4+ from werkzeug .routing import BaseConverter , ValidationError
55
66
77class HashidMixin :
@@ -17,38 +17,45 @@ class HashidMixin:
1717 __id_attribute__ and must be of type int!
1818 '''
1919
20- __id_attribute__ = 'id'
20+ __id_attribute__ : str = 'id'
2121
2222 @property
2323 def hashid (self ) -> str :
24- id = getattr (self , self .__class__ .__id_attribute__ )
24+ id : int = getattr (self , self .__class__ .__id_attribute__ )
2525 return current_app .extensions ['hashids' ].encode (id )
2626
2727
2828class HashidConverter (BaseConverter ):
29- '''
30- Hashid Converter.
29+ ''' Hashid Converter.
3130
32- Converts given hashids from routes to integers .
33- Example :
31+ Converts and decodes a hashid from routes .
32+ Examples :
3433 @app.route('/resources/<hashid:resource_id')
3534 def get_resource(resource_id: int):
3635 print(isinstance(resource_id, int)) # True
3736
38- Converts integers to hashids when generating urls.
39- Example:
37+ @app.route('/resources/<hashid:resource_ids')
38+ def get_resources(resource_ids: Tuple[int, ...]):
39+ print(isinstance(resource_ids, tuple)) # True
40+ print(all(isinstance(i, int) for i in resource_ids)) # True
41+
42+ Converts and encodes values when generating urls.
43+ Examples:
4044 url_for('get_resource', resource_id=123) # /resources/Mj3
45+
46+ url_for('get_resource', resource_id=(123, 456)) # /resources/Nk4
4147 '''
4248
43- def to_python (self , value : str ) -> int :
44- try :
45- decoded_value = current_app .extensions ['hashids' ].decode (value )
46- except IndexError :
47- abort (404 )
48- return decoded_value
49+ def to_python (self , hashid : str ) -> Union [int , Tuple [int , ...]]:
50+ decoded_hashid = current_app .extensions ['hashids' ].decode (hashid )
51+ if isinstance (decoded_hashid , tuple ) and len (decoded_hashid ) == 0 :
52+ raise ValidationError ()
53+ return decoded_hashid
4954
50- def to_url (self , value : int ) -> str :
51- return current_app .extensions ['hashids' ].encode (value )
55+ def to_url (self , value_or_values : Union [int , Tuple [int , ...]]) -> str :
56+ if isinstance (value_or_values , int ):
57+ return current_app .extensions ['hashids' ].encode (value_or_values )
58+ return current_app .extensions ['hashids' ].encode (* value_or_values )
5259
5360
5461class Hashids :
@@ -75,10 +82,19 @@ def init_app(self, app: Flask):
7582 app .extensions ['hashids' ] = self
7683 app .url_map .converters ['hashid' ] = HashidConverter
7784
78- def decode (self , value : str ) -> int :
79- ''' Decode a hashid to an integer. '''
80- return self ._hashids .decode (value )[0 ]
81-
82- def encode (self , value : int ) -> str :
83- ''' Encode an integer to a hashid. '''
84- return self ._hashids .encode (value )
85+ def decode (self , hashid : str ) -> Union [int , Tuple [int , ...], Tuple [()]]:
86+ ''' Decode the passed `hashid`.
87+
88+ Possible return values:
89+ - `int` if the hashid contains only one value.
90+ - `Tuple[int, ...]` if the hashid contains multiple values.
91+ - `Tuple[()]` if the hashid is invalid.
92+ '''
93+ decoded_hashid = self ._hashids .decode (hashid )
94+ if len (decoded_hashid ) == 1 :
95+ return decoded_hashid [0 ]
96+ return decoded_hashid
97+
98+ def encode (self , * values : int ) -> str :
99+ ''' Encode the passed `values`. '''
100+ return self ._hashids .encode (* values )
0 commit comments