brynq-sdk-brynq 2.0.3__tar.gz → 2.1.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.

Potentially problematic release.


This version of brynq-sdk-brynq might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: brynq_sdk_brynq
3
- Version: 2.0.3
3
+ Version: 2.1.0
4
4
  Summary: BrynQ SDK for the BrynQ.com platform
5
5
  Home-page: UNKNOWN
6
6
  Author: BrynQ
@@ -0,0 +1 @@
1
+ from .brynq import BrynQ
@@ -0,0 +1,351 @@
1
+ import os
2
+ import requests
3
+ import json
4
+ import pandas as pd
5
+ from typing import Union, Literal, Optional, List
6
+
7
+
8
+ class BrynQ:
9
+ def __init__(self, subdomain: str = None, api_token: str = None, staging: str = 'prod'):
10
+ self.subdomain = os.getenv("BRYNQ_SUBDOMAIN", subdomain)
11
+ self.api_token = os.getenv("BRYNQ_API_TOKEN", api_token)
12
+ self.environment = os.getenv("BRYNQ_ENVIRONMENT", staging)
13
+
14
+ if any([self.subdomain is None, self.api_token is None]):
15
+ raise ValueError("Set the subdomain, api_token either in your .env file or provide the subdomain and api_token parameters")
16
+
17
+ possible_environments = ['dev', 'prod']
18
+ if self.environment not in possible_environments:
19
+ raise ValueError(f"Environment should be in {','.join(possible_environments)}")
20
+
21
+ self.url = 'https://app.brynq-staging.com/api/v2/' if self.environment == 'dev' else 'https://app.brynq.com/api/v2/'
22
+
23
+ def _get_headers(self):
24
+ return {
25
+ 'Authorization': f'Bearer {self.api_token}',
26
+ 'Domain': self.subdomain
27
+ }
28
+
29
+ def _get_mappings(self, data_interface_id: int) -> dict:
30
+ """
31
+ Get the mappings from the task in BrynQ
32
+ :param data_interface_id: The id of the data_interface in BrynQ. this does not have to be the task id of the current task
33
+ :return: A dictionary with the following structure: {mapping_title: {tuple(input): output}}
34
+ """
35
+ response = requests.get(url=f'{self.url}interfaces/{data_interface_id}/data-mapping', headers=self._get_headers())
36
+ if response.status_code != 200:
37
+ raise ValueError(f'Error occurred while fetching mappings: {response.text}. Please always check if you have added BRYNQ_SUBDOMAIN to your .env file')
38
+
39
+ return response.json()
40
+
41
+ def get_mapping(self, data_interface_id: int, mapping: str, return_format: Literal['input_as_key', 'columns_names_as_keys', 'nested_input_output'] = 'input_as_key') -> dict:
42
+ """
43
+ Get the mapping json from the mappings
44
+ :param data_interface_id: The id of the task in BrynQ. this does not have to be the task id of the current task
45
+ :param mapping: The name of the mapping
46
+ :param return_format: Determines how the mapping should be returned. Options are 'input_as_key' (Default, the input column is the key, the output columns are the values), 'columns_names_as_keys', 'nested_input_output'
47
+ :return: The json of the mapping
48
+ """
49
+ # Find the mapping for the given sheet name
50
+ mappings = self._get_mappings(data_interface_id=data_interface_id)
51
+ mapping_data = next((item for item in mappings if item['name'] == mapping), None)
52
+ if not mapping_data:
53
+ raise ValueError(f"Mapping named '{mapping}' not found")
54
+
55
+ # If the user want to get the column names back as keys, transform the data accordingly and return
56
+ if return_format == 'columns_names_as_keys':
57
+ final_mapping = []
58
+ for row in mapping_data['values']:
59
+ combined_dict = {}
60
+ combined_dict.update(row['input'])
61
+ combined_dict.update(row['output'])
62
+ final_mapping.append(combined_dict)
63
+ elif return_format == 'nested_input_output':
64
+ final_mapping = mapping_data
65
+ else:
66
+ final_mapping = {}
67
+ for value in mapping_data['values']:
68
+ input_values = []
69
+ output_values = []
70
+ for _, val in value['input'].items():
71
+ input_values.append(val)
72
+ for _, val in value['output'].items():
73
+ output_values.append(val)
74
+ # Detect if there are multiple input or output columns and concatenate them
75
+ if len(value['input'].items()) > 1 or len(value['output'].items()) > 1:
76
+ concatenated_input = ','.join(input_values)
77
+ concatenated_output = ','.join(output_values)
78
+ final_mapping[concatenated_input] = concatenated_output
79
+ else: # Default to assuming there's only one key-value pair if not concatenating
80
+ if output_values:
81
+ final_mapping[input_values[0]] = output_values[0]
82
+ return final_mapping
83
+
84
+ def get_mapping_as_dataframe(self, data_interface_id: int, mapping: str, prefix: bool = False):
85
+ """
86
+ Get the mapping dataframe from the mappings
87
+ :param mapping: The name of the mapping
88
+ :param prefix: A boolean to indicate if the keys should be prefixed with 'input.' and 'output.'
89
+ :return: The dataframe of the mapping
90
+ """
91
+ # Find the mapping for the given sheet name
92
+ mappings = self._get_mappings(data_interface_id=data_interface_id)
93
+ mapping_data = next((item for item in mappings if item['name'] == mapping), None)
94
+ if not mapping_data:
95
+ raise ValueError(f"Mapping named '{mapping}' not found")
96
+
97
+ # Extract the values which contain the input-output mappings
98
+ values = mapping_data['values']
99
+
100
+ # Create a list to hold all row data
101
+ rows = []
102
+ for value in values:
103
+ # Check if prefix is needed and adjust keys accordingly
104
+ if prefix:
105
+ input_data = {f'input.{key}': val for key, val in value['input'].items()}
106
+ output_data = {f'output.{key}': val for key, val in value['output'].items()}
107
+ else:
108
+ input_data = value['input']
109
+ output_data = value['output']
110
+
111
+ # Combine 'input' and 'output' dictionaries
112
+ row_data = {**input_data, **output_data}
113
+ rows.append(row_data)
114
+
115
+ # Create DataFrame from rows
116
+ df = pd.DataFrame(rows)
117
+
118
+ return df
119
+
120
+ def get_system_credential(self, system: str, label: Union[str, list], test_environment: bool = False) -> json:
121
+ """
122
+ This method retrieves authentication credentials from BrynQ.
123
+ It returns the json data if the request does not return an error code
124
+ :param system: specifies which token is used. (lowercase)
125
+ :param label: reference to the used label
126
+ :param test_environment: boolean if the test environment is used
127
+ :return json response from BrynQ
128
+ """
129
+ response = requests.get(url=f'{self.url}apps/{system}', headers=self._get_headers())
130
+ response.raise_for_status()
131
+ credentials = response.json()
132
+ # rename parameter for readability
133
+ if isinstance(label, str):
134
+ labels = [label]
135
+ else:
136
+ labels = label
137
+ # filter credentials based on label. All labels specified in label parameter should be present in the credential object
138
+ credentials = [credential for credential in credentials if all(label in credential['labels'] for label in labels)]
139
+ if system == 'profit':
140
+ credentials = [credential for credential in credentials if credential['isTestEnvironment'] is test_environment]
141
+
142
+ if len(credentials) == 0:
143
+ raise ValueError(f'No credentials found for {system}')
144
+ if len(credentials) != 1:
145
+ raise ValueError(f'Multiple credentials found for {system} with the specified labels')
146
+
147
+ return credentials[0]
148
+
149
+ def get_interface_credential(self, interface_id: str, system: str, system_type: Optional[str] = None,
150
+ test_environment: bool = False) -> Union[dict, List[dict]]:
151
+ """
152
+ This method retrieves authentication credentials from BrynQ for a specific interface and system.
153
+ :param interface_id: ID of the interface to get credentials for
154
+ :param system: The app name to search for in credentials (e.g., 'bob', 'profit')
155
+ :param system_type: Optional parameter to specify 'source' or 'target'. If not provided,
156
+ searches in both lists
157
+ :param test_environment: boolean if the test environment is used (only for profit system)
158
+ :return: Credential dictionary or list of credential dictionaries for the specified system
159
+ """
160
+ # Get credentials from interface configuration
161
+ response = requests.get(
162
+ url=f'{self.url}interfaces/{interface_id}/config/auth',
163
+ headers=self._get_headers()
164
+ )
165
+ response.raise_for_status()
166
+ config = response.json()
167
+ matching_credentials = []
168
+ # If system_type is provided, only search in that list
169
+ if system_type:
170
+ if system_type not in ['source', 'target']:
171
+ raise ValueError("system_type must be either 'source' or 'target'")
172
+ credentials_list = config.get(f'{system_type}s', [])
173
+ for cred in credentials_list:
174
+ if cred.get('app') == system:
175
+ # Check test environment for profit system
176
+ if system == 'profit':
177
+ is_test = cred.get('data', {}).get('isTestEnvironment', False)
178
+ if is_test == test_environment:
179
+ matching_credentials.append({'credential': cred, 'type': system_type})
180
+ else:
181
+ matching_credentials.append({'credential': cred, 'type': system_type})
182
+ # If no system_type provided, search in both lists
183
+ else:
184
+ source_credentials = []
185
+ target_credentials = []
186
+ # Check sources
187
+ for source in config.get('sources', []):
188
+ if source.get('app') == system:
189
+ if system == 'profit':
190
+ is_test = source.get('data', {}).get('isTestEnvironment', False)
191
+ if is_test == test_environment:
192
+ source_credentials.append({'credential': source, 'type': 'source'})
193
+ else:
194
+ source_credentials.append({'credential': source, 'type': 'source'})
195
+ # Check targets
196
+ for target in config.get('targets', []):
197
+ if target.get('app') == system:
198
+ if system == 'profit':
199
+ is_test = target.get('data', {}).get('isTestEnvironment', False)
200
+ if is_test == test_environment:
201
+ target_credentials.append({'credential': target, 'type': 'target'})
202
+ else:
203
+ target_credentials.append({'credential': target, 'type': 'target'})
204
+ # Combine matching credentials based on type
205
+ if source_credentials and target_credentials:
206
+ raise ValueError(
207
+ f'Multiple credentials found for system {system} in both source and target. '
208
+ f'Please specify system_type as "source" or "target"'
209
+ )
210
+ matching_credentials = source_credentials or target_credentials
211
+ # Handle results
212
+ if len(matching_credentials) == 0:
213
+ if system == 'profit':
214
+ raise ValueError(f'No credentials found for system {system} with test_environment={test_environment}')
215
+ else:
216
+ raise ValueError(f'No credentials found for system {system}')
217
+ if len(matching_credentials) == 1:
218
+ return matching_credentials[0]['credential']
219
+ if len(matching_credentials) > 1:
220
+ warning_msg = f'Multiple credentials found for system {system}'
221
+ if system_type:
222
+ warning_msg += f' in {system_type}'
223
+ warnings.warn(warning_msg)
224
+ return [cred['credential'] for cred in matching_credentials]
225
+
226
+ def refresh_system_credential(self, system: str, system_id: int) -> json:
227
+ """
228
+ This method refreshes Oauth authentication credentials in BrynQ.
229
+ It returns the json data if the request does not return an error code
230
+ :param system: specifies which token is used. (lowercase)
231
+ :param system_id: system id in BrynQ
232
+ :return json response from BrynQ
233
+ """
234
+ response = requests.post(url=f'{self.url}apps/{system}/{system_id}/refresh', headers=self._get_headers())
235
+ response.raise_for_status()
236
+ credentials = response.json()
237
+
238
+ return credentials
239
+
240
+ def get_user_data(self):
241
+ """
242
+ Get all users from BrynQ
243
+ :return: A list of users
244
+ """
245
+ return requests.get(url=f'{self.url}users', headers=self._get_headers())
246
+
247
+ def get_user_authorization_qlik_app(self,dashboard_id):
248
+ """
249
+ Get all users from BrynQ who have access to a qlik dashboard with their entities
250
+ :return: A list of users and entities
251
+ """
252
+ return requests.get(url=f'{self.url}/qlik/{dashboard_id}/users', headers=self._get_headers())
253
+
254
+ def get_role_data(self):
255
+ """
256
+ Get all roles from BrynQ
257
+ :return: A list of roles
258
+ """
259
+ return requests.get(url=f'{self.url}roles', headers=self._get_headers())
260
+
261
+ def create_user(self, user_data: dict) -> requests.Response:
262
+ """
263
+ Create a user in BrynQ
264
+ :param user_data: A dictionary with the following structure:
265
+ {
266
+ "name": "string",
267
+ "username": "string",
268
+ "email": "string",
269
+ "language": "string",
270
+ "salure_connect": bool,
271
+ "qlik_sense_analyzer": bool,
272
+ "qlik_sense_professional": bool
273
+ }
274
+ :return: A response object
275
+ """
276
+ data = {
277
+ "name": user_data['name'],
278
+ "username": user_data['username'],
279
+ "email": user_data['email'],
280
+ "language": user_data['language'],
281
+ "products": {
282
+ "qlikSenseAnalyzer": user_data['qlik_sense_analyzer'],
283
+ "qlikSenseProfessional": user_data['qlik_sense_professional']
284
+ }
285
+ }
286
+
287
+ return requests.post(url=f'{self.url}users', headers=self._get_headers(), json=data)
288
+
289
+ def update_user(self, user_id: str, user_data: dict) -> requests.Response:
290
+ """
291
+ Update a user in BrynQ
292
+ :param user_id: The id of the user in BrynQ
293
+ :param user_data: A dictionary with the following structure:
294
+ {
295
+ "id": "string",
296
+ "name": "string",
297
+ "language": "string",
298
+ "salureconnect": bool,
299
+ "qlik sense analyser": bool,
300
+ "qlik sense professional": false
301
+ }
302
+ :return: A response object
303
+ """
304
+ data = {
305
+ "name": user_data['name'],
306
+ "username": user_data['username'],
307
+ "email": user_data['email'],
308
+ "language": user_data['language'],
309
+ "products": {
310
+ "qlikSenseAnalyzer": user_data['qlik_sense_analyzer'],
311
+ "qlikSenseProfessional": user_data['qlik_sense_professional']
312
+ }
313
+ }
314
+
315
+ return requests.put(url=f'{self.url}users/{user_id}', headers=self._get_headers(), json=data)
316
+
317
+ def delete_user(self, user_id: str) -> requests.Response:
318
+ """
319
+ Delete a user in BrynQ
320
+ :param user_id: The id of the user in BrynQ
321
+ :return: A response object
322
+ """
323
+ return requests.delete(url=f'{self.url}users/{user_id}', headers=self._get_headers())
324
+
325
+ def overwrite_user_roles(self, user_id: int, roles: list) -> requests.Response:
326
+ """
327
+ Overwrite the roles of a user in BrynQ
328
+ :param user_id: The id of the user in BrynQ
329
+ :param roles: A list of role ids
330
+ :return: A response object
331
+ """
332
+ data = {
333
+ "roles": roles
334
+ }
335
+
336
+ return requests.put(url=f'{self.url}users/{user_id}/roles', headers=self._get_headers(), json=data)
337
+
338
+ def get_source_system_entities(self, system: str) -> requests.Response:
339
+ """
340
+ Get all entities from a source system in BrynQ
341
+ :param system: The name of the source system
342
+ :return: A response object
343
+ """
344
+ return requests.get(url=f'{self.url}source-systems/{system}/entities', headers=self._get_headers())
345
+
346
+ def get_layers(self) -> requests.Response:
347
+ """
348
+ Get all layers from a source system in BrynQ
349
+ :return: A response object
350
+ """
351
+ return requests.get(url=f'{self.url}organization-chart/layers', headers=self._get_headers())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: brynq-sdk-brynq
3
- Version: 2.0.3
3
+ Version: 2.1.0
4
4
  Summary: BrynQ SDK for the BrynQ.com platform
5
5
  Home-page: UNKNOWN
6
6
  Author: BrynQ
@@ -1,4 +1,6 @@
1
1
  setup.py
2
+ brynq_sdk_brynq/__init__.py
3
+ brynq_sdk_brynq/brynq.py
2
4
  brynq_sdk_brynq.egg-info/PKG-INFO
3
5
  brynq_sdk_brynq.egg-info/SOURCES.txt
4
6
  brynq_sdk_brynq.egg-info/dependency_links.txt
@@ -0,0 +1 @@
1
+ brynq_sdk_brynq
@@ -2,7 +2,7 @@ from setuptools import setup, find_namespace_packages
2
2
 
3
3
  setup(
4
4
  name='brynq_sdk_brynq',
5
- version='2.0.3',
5
+ version='2.1.0',
6
6
  description='BrynQ SDK for the BrynQ.com platform',
7
7
  long_description='BrynQ SDK for the BrynQ.com platform',
8
8
  author='BrynQ',