@@ -42,75 +42,83 @@ async def login(
4242 :param identity: The user's identity, in case multiple are found.
4343 :return: The direct response from the Classeviva API containing the token.
4444 """
45- with self .get_cache () as cache :
46- if "salt" not in cache :
47- cache ["salt" ] = bcrypt .gensalt ()
48-
49- # hash the password to cache it
50- hashed_pw = bcrypt .hashpw (password .encode (), cache ["salt" ]).decode ()
51- if "logins" not in cache :
52- cache ["logins" ] = {}
53-
54- cache_key = f"{ username } :{ hashed_pw } "
55- login_cache = cache ["logins" ]
56- if identity :
57- cache_key += f":{ identity } "
58-
59- # check in the cache for the token and its expiration
60- if cache_key in login_cache :
61- this = login_cache [cache_key ]
62- expires_at = datetime .fromisoformat (this ["expire" ])
63- if expires_at > datetime .now (timezone .utc ):
64- return this
65-
66- req = {"uid" : username , "pass" : password }
67- if identity :
68- req ["ident" ] = identity
69-
70- # do the actual request to get the token, if expired or not found
71- async with ClientSession (loop = self .client .loop ) as session :
72- async with session .post (
73- urljoin (self .client .BASE_URL , "auth/login" ),
74- headers = {
75- "User-Agent" : CLIENT_USER_AGENT ,
76- "Z-Dev-Apikey" : CLIENT_DEV_APIKEY ,
77- "Content-Type" : CLIENT_CONTENT_TP ,
78- },
79- json = req ,
80- ) as resp :
81- content = await resp .json ()
82- if resp .status == 422 :
83- msg = {
84- "content" : content ,
85- "status" : resp .status ,
86- "status_reason" : resp .reason ,
87- }
88- raise find_exc (msg , AuthenticationError )
89-
90- if "choices" in content and (
91- not identity
92- or identity not in [c ["ident" ] for c in content ["choices" ]]
93- ):
94- choices = " * " + "\n * " .join (
95- f"{ c ['ident' ]} ({ c ['name' ]} )" for c in content ["choices" ]
96- )
97- msg = "Multiple identities have been found, but none has been specified"
98- if identity :
99- msg = "Could not find the requested identity"
100-
101- raise MultiIdentFound (
102- f"{ msg } . Possible choices are:\n { choices } "
103- )
104-
105- try :
106- resp .raise_for_status ()
107- except ClientResponseError as e :
108- raise AuthenticationError (content ) from e
109-
110- # cache response, will be re-cached as soon as the token expires
111- login_cache [cache_key ] = content
112- cache ["logins" ] = login_cache
113- return content
45+ with self .get_cache () as cache_ :
46+ if self .client .base_url not in cache_ :
47+ cache_ [self .client .base_url ] = {}
48+
49+ cache = cache_ [self .client .base_url ]
50+
51+ try :
52+ if "salt" not in cache :
53+ cache ["salt" ] = bcrypt .gensalt ()
54+
55+ # hash the password to cache it
56+ hashed_pw = bcrypt .hashpw (password .encode (), cache ["salt" ]).decode ()
57+ if "logins" not in cache :
58+ cache ["logins" ] = {}
59+
60+ cache_key = f"{ username } :{ hashed_pw } "
61+ login_cache = cache ["logins" ]
62+ if identity :
63+ cache_key += f":{ identity } "
64+
65+ # check in the cache for the token and its expiration
66+ if cache_key in login_cache :
67+ this = login_cache [cache_key ]
68+ expires_at = datetime .fromisoformat (this ["expire" ])
69+ if expires_at > datetime .now (timezone .utc ):
70+ return this
71+
72+ req = {"uid" : username , "pass" : password }
73+ if identity :
74+ req ["ident" ] = identity
75+
76+ # do the actual request to get the token, if expired or not found
77+ async with ClientSession (loop = self .client .loop ) as session :
78+ async with session .post (
79+ urljoin (self .client .base_url , "auth/login" ),
80+ headers = {
81+ "User-Agent" : CLIENT_USER_AGENT ,
82+ "Z-Dev-Apikey" : CLIENT_DEV_APIKEY ,
83+ "Content-Type" : CLIENT_CONTENT_TP ,
84+ },
85+ json = req ,
86+ ) as resp :
87+ content = await resp .json ()
88+ if resp .status == 422 :
89+ msg = {
90+ "content" : content ,
91+ "status" : resp .status ,
92+ "status_reason" : resp .reason ,
93+ }
94+ raise find_exc (msg , AuthenticationError )
95+
96+ if "choices" in content and (
97+ not identity
98+ or identity not in [c ["ident" ] for c in content ["choices" ]]
99+ ):
100+ choices = " * " + "\n * " .join (
101+ f"{ c ['ident' ]} ({ c ['name' ]} )"
102+ for c in content ["choices" ]
103+ )
104+ msg = "Multiple identities have been found, but none has been specified"
105+ if identity :
106+ msg = "Could not find the requested identity"
107+
108+ raise MultiIdentFound (
109+ f"{ msg } . Possible choices are:\n { choices } "
110+ )
111+
112+ try :
113+ resp .raise_for_status ()
114+ except ClientResponseError as e :
115+ raise AuthenticationError (content ) from e
116+
117+ # cache response, will be re-cached as soon as the token expires
118+ login_cache [cache_key ] = content
119+ return content
120+ finally :
121+ cache_ [self .client .base_url ] = cache
114122
115123 async def status (self , token : str ) -> dict :
116124 """
@@ -120,28 +128,34 @@ async def status(self, token: str) -> dict:
120128 :param token: The token to check the status of.
121129 :return: The direct response from the Classeviva API.
122130 """
123- with self .get_cache () as cache :
124- if "logins_status" not in cache :
125- cache ["logins_status" ] = {}
126-
127- status = cache ["logins_status" ]
128- if token in status :
129- this = status [token ]
130- expires_at = datetime .fromisoformat (this ["expire" ])
131- if expires_at > datetime .now (timezone .utc ):
132- return this
133-
134- async with ClientSession (loop = self .client .loop ) as session :
135- async with session .get (
136- urljoin (self .client .BASE_URL , "auth/status" ),
137- headers = {
138- "User-Agent" : CLIENT_USER_AGENT ,
139- "Z-Dev-Apikey" : CLIENT_DEV_APIKEY ,
140- "Content-Type" : CLIENT_CONTENT_TP ,
141- "Z-Auth-Token" : token ,
142- },
143- ) as resp :
144- resp .raise_for_status ()
145- status [token ] = (await resp .json ())["status" ]
146- cache ["logins_status" ] = status
147- return status [token ]
131+ with self .get_cache () as cache_ :
132+ if self .client .base_url not in cache_ :
133+ cache_ [self .client .base_url ] = {}
134+
135+ cache = cache_ [self .client .base_url ]
136+ try :
137+ if "logins_status" not in cache :
138+ cache ["logins_status" ] = {}
139+
140+ status = cache ["logins_status" ]
141+ if token in status :
142+ this = status [token ]
143+ expires_at = datetime .fromisoformat (this ["expire" ])
144+ if expires_at > datetime .now (timezone .utc ):
145+ return this
146+
147+ async with ClientSession (loop = self .client .loop ) as session :
148+ async with session .get (
149+ urljoin (self .client .base_url , "auth/status" ),
150+ headers = {
151+ "User-Agent" : CLIENT_USER_AGENT ,
152+ "Z-Dev-Apikey" : CLIENT_DEV_APIKEY ,
153+ "Content-Type" : CLIENT_CONTENT_TP ,
154+ "Z-Auth-Token" : token ,
155+ },
156+ ) as resp :
157+ resp .raise_for_status ()
158+ status [token ] = (await resp .json ())["status" ]
159+ return status [token ]
160+ finally :
161+ cache_ [self .client .base_url ] = cache
0 commit comments