arcane-flask 2.2.1__tar.gz → 3.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arcane-flask
3
- Version: 2.2.1
3
+ Version: 3.0.0
4
4
  Summary: Utility functions for flask apps.
5
5
  Author: Arcane
6
6
  Author-email: product@wearcane.com
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import functools
2
3
  import inspect
3
4
  import time
@@ -14,7 +15,6 @@ from google.cloud.exceptions import ServiceUnavailable
14
15
  from arcane.core import CLIENTS_PARAM, ALL_CLIENTS_RIGHTS, RightsLevelEnum, GOOGLE_EXCEPTIONS_TO_RETRY, BadRequestError
15
16
  from arcane.datastore import Client as DatastoreClient
16
17
  from arcane.pubsub import Client as PubSubClient
17
- import concurrent.futures
18
18
 
19
19
  from .tracking import send_tracking_information
20
20
 
@@ -75,75 +75,96 @@ def check_access_rights(service: str,
75
75
  start = time.time()
76
76
  is_appengine_cron = request.headers.get('X-Appengine-Cron', False)
77
77
 
78
- if is_appengine_cron:
79
- logging.info("App Engine calls service : " + service)
80
- return job_func(*args, **kwargs)
81
-
82
78
  claims = {}
83
79
  origin = ''
84
- # Check feature rights
85
- if auth_enabled:
86
- token = str(request.headers.get('Authorization', '')).split(' ').pop()
87
- origin = str(request.headers.get('origin', ''))
88
- try:
89
- claims = verify_token(token)
90
- except (firebase_auth.ExpiredIdTokenError,
91
- firebase_auth.RevokedIdTokenError,
92
- firebase_auth.InvalidIdTokenError) as e:
93
- if required_rights != RightsLevelEnum.NONE:
94
- return {'detail': 'You don\'t have access to Adscale resources\n', 'firebase_log': str(e)}, 401
95
- except ValueError as e:
96
- return {'detail': str(e)}, 401
97
80
 
98
- try:
99
- _check_feature_right_access(
100
- claims,
101
- required_rights,
102
- service_user_right
103
- )
104
- except BadRequestError:
105
- rights = _get_level_access_for_a_feature_from_claims(claims, service_user_right)
106
- return {'detail': f"You don't have access to this resource : your rights are "
107
- f"'{rights_mapper.get(str(rights), '0')}' for service '{service}'. "
108
- f"You need '{rights_mapper.get(str(required_rights), '0')}'.",
109
- 'claims': f"Your current rights are {json.dumps(claims, indent=4, sort_keys=True)}"}, 401
110
-
111
- # Fill authorized_clients
112
- if receive_rights_per_client:
113
- if not auth_enabled:
114
- authorized_clients = [ALL_CLIENTS_RIGHTS]
115
- else:
116
- # Value defined for service to service calls
117
- service_authorized_clients = claims.get('authorized_clients')
118
- db_user_id = claims.get('db_user_id')
119
- if service_authorized_clients and not db_user_id:
120
- authorized_clients = service_authorized_clients
81
+ async def async_execution():
82
+ nonlocal claims, origin
83
+
84
+ if is_appengine_cron:
85
+ logging.info("App Engine calls service : " + service)
86
+ if asyncio.iscoroutinefunction(job_func):
87
+ return await job_func(*args, **kwargs)
88
+ else:
89
+ return job_func(*args, **kwargs)
90
+
91
+ # Check feature rights
92
+ if auth_enabled:
93
+ token = str(request.headers.get('Authorization', '')).split(' ').pop()
94
+ origin = str(request.headers.get('origin', ''))
95
+ try:
96
+ claims = verify_token(token)
97
+ except (firebase_auth.ExpiredIdTokenError,
98
+ firebase_auth.RevokedIdTokenError,
99
+ firebase_auth.InvalidIdTokenError) as e:
100
+ if required_rights != RightsLevelEnum.NONE:
101
+ return {'detail': 'You don\'t have access to Adscale resources\n', 'firebase_log': str(e)}, 401
102
+ except ValueError as e:
103
+ return {'detail': str(e)}, 401
104
+
105
+ try:
106
+ _check_feature_right_access(
107
+ claims,
108
+ required_rights,
109
+ service_user_right
110
+ )
111
+ except BadRequestError:
112
+ rights = _get_level_access_for_a_feature_from_claims(claims, service_user_right)
113
+ return {'detail': f"You don't have access to this resource : your rights are "
114
+ f"'{rights_mapper.get(str(rights), '0')}' for service '{service}'. "
115
+ f"You need '{rights_mapper.get(str(required_rights), '0')}'.",
116
+ 'claims': f"Your current rights are {json.dumps(claims, indent=4, sort_keys=True)}"}, 401
117
+
118
+ # Fill authorized_clients
119
+ if receive_rights_per_client:
120
+ if not auth_enabled:
121
+ authorized_clients = [ALL_CLIENTS_RIGHTS]
121
122
  else:
122
- if not db_user_id:
123
- authorized_clients = []
123
+ # Value defined for service to service calls
124
+ service_authorized_clients = claims.get('authorized_clients')
125
+ db_user_id = claims.get('db_user_id')
126
+ if service_authorized_clients and not db_user_id:
127
+ authorized_clients = service_authorized_clients
124
128
  else:
125
- user = datastore_client.get_entity(USERS_KIND, int(db_user_id))
126
- if user is None:
129
+ if not db_user_id:
127
130
  authorized_clients = []
128
131
  else:
129
- authorized_clients = user.get('authorized_clients', [])
130
-
131
- if ALL_CLIENTS_RIGHTS in authorized_clients:
132
- authorized_clients.remove(ALL_CLIENTS_RIGHTS)
133
- clients_query = datastore_client.query()
134
- clients_query.kind = CLIENTS_KIND
135
- clients_query.keys_only()
136
- authorized_clients = [client_entity.key.name for client_entity in clients_query.fetch()]
137
-
138
- kwargs[CLIENTS_PARAM] = sorted(set(authorized_clients))
139
- try:
140
- with concurrent.futures.ThreadPoolExecutor() as executor:
141
- future = executor.submit(job_func, *args, **kwargs)
142
- func_response = future.result(timeout=timeout)
143
- except concurrent.futures.TimeoutError:
144
- func_response = {'detail': 'Request Timeout'}, 504
145
- except Exception as e:
146
- func_response = {'detail': f'Server Error: {str(e)}'}, 500
132
+ user = datastore_client.get_entity(USERS_KIND, int(db_user_id))
133
+ if user is None:
134
+ authorized_clients = []
135
+ else:
136
+ authorized_clients = user.get('authorized_clients', [])
137
+
138
+ if ALL_CLIENTS_RIGHTS in authorized_clients:
139
+ authorized_clients.remove(ALL_CLIENTS_RIGHTS)
140
+ clients_query = datastore_client.query()
141
+ clients_query.kind = CLIENTS_KIND
142
+ clients_query.keys_only()
143
+ authorized_clients = [client_entity.key.name for client_entity in clients_query.fetch()]
144
+
145
+ kwargs[CLIENTS_PARAM] = sorted(set(authorized_clients))
146
+
147
+ try:
148
+ if asyncio.iscoroutinefunction(job_func):
149
+ func_response = await asyncio.wait_for(job_func(*args, **kwargs), timeout=timeout)
150
+ else:
151
+ # Synchronous function - wrap in executor for timeout handling
152
+ if timeout is not None:
153
+ loop = asyncio.get_event_loop()
154
+ func_response = await asyncio.wait_for(
155
+ loop.run_in_executor(None, lambda: job_func(*args, **kwargs)),
156
+ timeout=timeout
157
+ )
158
+ else:
159
+ func_response = job_func(*args, **kwargs)
160
+ return func_response
161
+ except asyncio.TimeoutError:
162
+ return {'detail': 'Request Timeout'}, 504
163
+ except Exception as e:
164
+ return {'detail': f'Server Error: {str(e)}'}, 500
165
+
166
+ # Run the async execution in a new event loop
167
+ func_response = asyncio.run(async_execution())
147
168
 
148
169
  end = time.time()
149
170
 
@@ -7,7 +7,7 @@ from typing import Any, Callable, Literal, Optional
7
7
  from flask import g, has_request_context, request
8
8
  from flask_log_request_id import current_request_id
9
9
 
10
- STANDARD_FORMAT = '{"severity": "%(levelname)s", "logging.googleapis.com/trace": "%(cloud_trace)s", "logging.googleapis.com/spanId": "%(span_id)s", "component": "arbitrary", "message": "%(message)s", "logging.googleapis.com/labels": %(labels)s}'
10
+ STANDARD_FORMAT = '{"severity": "%(levelname)s", "logging.googleapis.com/trace": "%(cloud_trace)s", "logging.googleapis.com/spanId": "%(span_id)s", "component": "arbitrary", "message": %(message)s, "logging.googleapis.com/labels": %(labels)s}'
11
11
 
12
12
 
13
13
  class LabelFilter(logging.Filter):
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "arcane-flask"
3
3
 
4
- version = "2.2.1"
4
+ version = "3.0.0"
5
5
 
6
6
 
7
7
  description = "Utility functions for flask apps."
File without changes