logos-sdk 0.0.25.dev9__tar.gz → 0.0.25.dev10__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.
Files changed (26) hide show
  1. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/PKG-INFO +1 -1
  2. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/services/CampaignManager.py +47 -0
  3. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/services/DV360.py +53 -1
  4. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/services/Facebook.py +6 -1
  5. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/services/GoogleAds.py +50 -4
  6. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/services/MerchantCenter.py +17 -0
  7. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/services/MicrosoftAdvertising.py +3 -0
  8. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/services/Sklik.py +98 -36
  9. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk.egg-info/PKG-INFO +1 -1
  10. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/LICENSE +0 -0
  11. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/README.md +0 -0
  12. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/__init__.py +0 -0
  13. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/big_query/BigQuery.py +0 -0
  14. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/big_query/__init__.py +0 -0
  15. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/logging/LogosLogger.py +0 -0
  16. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/logging/__init__.py +0 -0
  17. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/services/Collabim.py +0 -0
  18. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/services/GoogleSheets.py +0 -0
  19. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/services/MarketMiner.py +0 -0
  20. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk/services/__init__.py +0 -0
  21. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk.egg-info/SOURCES.txt +0 -0
  22. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk.egg-info/dependency_links.txt +0 -0
  23. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk.egg-info/requires.txt +0 -0
  24. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/logos_sdk.egg-info/top_level.txt +0 -0
  25. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/setup.cfg +0 -0
  26. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev10}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: logos-sdk
3
- Version: 0.0.25.dev9
3
+ Version: 0.0.25.dev10
4
4
  Summary: SDK for Logos platform
5
5
  Home-page: https://bitbucket.org/databy/logos-sdk-pip/src/master/
6
6
  Author: Databy.io
@@ -30,6 +30,8 @@ class CampaignManagerService:
30
30
  self._GET_FILE = self._URL + "/get-file"
31
31
  self._GET_FILE_MEDIA_REQUEST = self._URL + "/get-file-media-request"
32
32
  self._QUERY_DIMENSION_VALUES = self._URL + "/query-dimension-values"
33
+ self._GET_ACCESSIBLE_PARENT_ACCOUNTS = self._URL + "/get-parent-accounts"
34
+ self._GET_ACCESSIBLE_ADVERTISERS = self._URL + "/get-advertisers"
33
35
 
34
36
  def create_report(
35
37
  self,
@@ -397,3 +399,48 @@ class CampaignManagerService:
397
399
  report_id=report["id"],
398
400
  secret_id=secret_id,
399
401
  )
402
+
403
+ def get_accessible_parent_accounts(self, secret_id):
404
+ """
405
+ Returns accessible parent accounts
406
+ :param secret_id: The ID of the secret in secret manager
407
+ :return: list of parent accounts
408
+ """
409
+
410
+ header = get_headers(self._GET_ACCESSIBLE_PARENT_ACCOUNTS)
411
+ body = {"secret_id": secret_id}
412
+
413
+ response = request(
414
+ "post", url=self._GET_ACCESSIBLE_PARENT_ACCOUNTS, json=body, headers=header
415
+ )
416
+
417
+ if response.status_code == HTTPStatus.OK:
418
+ service_response = response.json()
419
+ return service_response["data"]
420
+ else:
421
+ raise CampaignManagerServiceException(response.content)
422
+
423
+ def get_accessible_advertisers(self, secret_id):
424
+ """
425
+ Returns accessible advertisers
426
+ :param secret_id: The ID of the secret in secret manager
427
+ :return: list of advertisers
428
+ """
429
+
430
+ header = get_headers(self._GET_ACCESSIBLE_ADVERTISERS)
431
+ body = {
432
+ "secret_id": secret_id,
433
+ "filters": {
434
+ "hide_inactive": True,
435
+ },
436
+ }
437
+
438
+ response = request(
439
+ "post", url=self._GET_ACCESSIBLE_ADVERTISERS, json=body, headers=header
440
+ )
441
+
442
+ if response.status_code == HTTPStatus.OK:
443
+ service_response = response.json()
444
+ return service_response["data"]
445
+ else:
446
+ raise CampaignManagerServiceException(response.content)
@@ -34,6 +34,8 @@ class DV360Service:
34
34
  self._GET_QUERY_METADATA = self._URL + "/get-query-metadata"
35
35
  self._RUN_QUERY = self._URL + "/run-query"
36
36
  self._DELETE_QUERY = self._URL + "/delete-query"
37
+ self._GET_ACCESSIBLE_PARTNERS = self._URL + "/get-accessible-partners"
38
+ self._GET_ACCESSIBLE_ADVERTISERS = self._URL + "/get-accessible-advertisers"
37
39
 
38
40
  def list_line_items(self, advertiser_id, secret_id, filter_string=None):
39
41
  """
@@ -301,6 +303,56 @@ class DV360Service:
301
303
  else:
302
304
  raise DV360ServiceException(response.content)
303
305
 
306
+ def get_accessible_partners(self, secret_id):
307
+ """
308
+ Returns accessible partners
309
+ :param secret_id: The ID of the secret in secret manager
310
+ :return: list of partners
311
+ """
312
+
313
+ header = get_headers(self._GET_ACCESSIBLE_PARTNERS)
314
+ body = {
315
+ "secret_id": secret_id,
316
+ "filters": {
317
+ "hide_inactive": True,
318
+ },
319
+ }
320
+
321
+ response = request(
322
+ "post", url=self._GET_ACCESSIBLE_PARTNERS, json=body, headers=header
323
+ )
324
+
325
+ if response.status_code == HTTPStatus.OK:
326
+ service_response = response.json()
327
+ return service_response["data"]
328
+ else:
329
+ raise DV360ServiceException(response.content)
330
+
331
+ def get_accessible_advertisers(self, secret_id):
332
+ """
333
+ Returns accessible advertisers
334
+ :param secret_id: The ID of the secret in secret manager
335
+ :return: list of advertisers
336
+ """
337
+
338
+ header = get_headers(self._GET_ACCESSIBLE_ADVERTISERS)
339
+ body = {
340
+ "secret_id": secret_id,
341
+ "filters": {
342
+ "hide_inactive": True,
343
+ },
344
+ }
345
+
346
+ response = request(
347
+ "post", url=self._GET_ACCESSIBLE_ADVERTISERS, json=body, headers=header
348
+ )
349
+
350
+ if response.status_code == HTTPStatus.OK:
351
+ service_response = response.json()
352
+ return service_response["data"]
353
+ else:
354
+ raise DV360ServiceException(response.content)
355
+
304
356
  def _check_report_ready_with_exponential_backoff(
305
357
  self, secret_id: str, query_id: str, report_id: str, backoff_attempts: int = 10
306
358
  ) -> bool:
@@ -393,5 +445,5 @@ class DV360Service:
393
445
 
394
446
  if response.status_code == HTTPStatus.OK:
395
447
  return True
396
- print("Query was not deleted!")
448
+
397
449
  return False
@@ -38,7 +38,12 @@ class FacebookService:
38
38
  :return: all accessible accounts List(Dict)
39
39
  """
40
40
 
41
- body = {"secret_id": secret_id}
41
+ body = {
42
+ "secret_id": secret_id,
43
+ "filters": {
44
+ "hide_inactive": True,
45
+ }
46
+ }
42
47
 
43
48
  header = get_headers(self._ACCESSIBLE_ACCOUNTS)
44
49
 
@@ -21,6 +21,7 @@ class GoogleAdsService:
21
21
  self._SEARCH = self._URL + "/search"
22
22
  self._EXCLUDE_FOR_ACCOUNT = self._URL + "/exclude-for-account"
23
23
  self._EXCLUDE_FOR_AD_GROUP = self._URL + "/exclude-for-ad-group"
24
+ self._GET_ACCESSIBLE_ACCOUNTS = self._URL + "/list-accessible-accounts"
24
25
 
25
26
  @staticmethod
26
27
  def fetch_with_retry_on_timeout(url, json, headers):
@@ -28,8 +29,10 @@ class GoogleAdsService:
28
29
  try:
29
30
  return request("post", url, json=json, headers=headers, timeout=25)
30
31
  except Timeout:
31
- delay = 2 * (2 ** attempt) + random.randint(0, 9)
32
- print(f"there was a timeout when contacting the service, going to sleep for {delay} seconds")
32
+ delay = 2 * (2**attempt) + random.randint(0, 9)
33
+ print(
34
+ f"there was a timeout when contacting the service, going to sleep for {delay} seconds"
35
+ )
33
36
  time.sleep(delay)
34
37
 
35
38
  raise Exception("The service is not able to reply within 30 seconds.")
@@ -53,7 +56,9 @@ class GoogleAdsService:
53
56
  }
54
57
 
55
58
  header = get_headers(self._SEARCH_STREAM)
56
- response = self.fetch_with_retry_on_timeout(url=self._SEARCH_STREAM, json=body, headers=header)
59
+ response = self.fetch_with_retry_on_timeout(
60
+ url=self._SEARCH_STREAM, json=body, headers=header
61
+ )
57
62
 
58
63
  if response.status_code == HTTPStatus.OK:
59
64
  service_response = response.json()
@@ -84,7 +89,9 @@ class GoogleAdsService:
84
89
  }
85
90
 
86
91
  header = get_headers(self._SEARCH)
87
- response = self.fetch_with_retry_on_timeout(url=self._SEARCH, json=body, headers=header)
92
+ response = self.fetch_with_retry_on_timeout(
93
+ url=self._SEARCH, json=body, headers=header
94
+ )
88
95
 
89
96
  if response.status_code != HTTPStatus.OK:
90
97
  raise GoogleAdsServiceException(response.content)
@@ -163,3 +170,42 @@ class GoogleAdsService:
163
170
  return
164
171
  else:
165
172
  raise GoogleAdsServiceException(response.content)
173
+
174
+ def get_accessible_accounts(
175
+ self,
176
+ secret_id: str,
177
+ page_size: int = 1000,
178
+ ) -> List[Dict]:
179
+ body = {
180
+ "secret_id": secret_id,
181
+ "filters": {
182
+ "hide_inactive": True,
183
+ },
184
+ "page_token": None,
185
+ "page_size": page_size,
186
+ }
187
+
188
+ header = get_headers(self._GET_ACCESSIBLE_ACCOUNTS)
189
+ response = self.fetch_with_retry_on_timeout(
190
+ url=self._GET_ACCESSIBLE_ACCOUNTS, json=body, headers=header
191
+ )
192
+
193
+ if response.status_code != HTTPStatus.OK:
194
+ raise GoogleAdsServiceException(response.content)
195
+
196
+ service_response = response.json()
197
+ all_results = service_response["data"]["results"]
198
+
199
+ while service_response["data"]["next_page_token"]:
200
+ body["page_token"] = service_response["data"]["next_page_token"]
201
+ response = request(
202
+ "post", url=self._GET_ACCESSIBLE_ACCOUNTS, json=body, headers=header
203
+ )
204
+
205
+ if response.status_code != HTTPStatus.OK:
206
+ raise GoogleAdsServiceException(response.content)
207
+
208
+ service_response = response.json()
209
+ all_results.extend(service_response["data"]["results"])
210
+
211
+ return all_results
@@ -14,6 +14,7 @@ class MerchantCenterService:
14
14
  load_dotenv()
15
15
  self._URL = url or os.environ.get("MERCHANT_CENTER_SERVICE_PATH")
16
16
  self._LIST_ACCOUNTS = self._URL + "/account-service/accounts"
17
+ self._LIST_ACCESSIBLE_ACCOUNTS = self._URL + "/account-service/list-accessible-accounts"
17
18
  self._LIST_ACCOUNT_STATUSES = self._URL + "/account-service/account-statuses"
18
19
  self._LIST_PRODUCTS = self._URL + "/product-service/products"
19
20
  self._LIST_PRODUCT_STATUSES = self._URL + "/product-service/product-statuses"
@@ -36,6 +37,22 @@ class MerchantCenterService:
36
37
  else:
37
38
  raise MerchantServiceException(response.content)
38
39
 
40
+ def list_accessible_accounts(self, secret_id: str):
41
+ """
42
+ Lists accessible accounts in Merchant Center
43
+ :param secret_id: The ID of the secret in secret manager
44
+ :return: List[Dict]
45
+ """
46
+ body = {"secret_id": secret_id}
47
+ header = get_headers(self._LIST_ACCESSIBLE_ACCOUNTS)
48
+ response = request("post", url=self._LIST_ACCESSIBLE_ACCOUNTS, json=body, headers=header)
49
+
50
+ if response.status_code == HTTPStatus.OK:
51
+ service_response = response.json()
52
+ return service_response["data"]
53
+ else:
54
+ raise MerchantServiceException(response.content)
55
+
39
56
  def list_account_statuses(
40
57
  self, merchant_account_id: str, account_id: str, secret_id: str
41
58
  ):
@@ -212,6 +212,9 @@ class MicrosoftAdvertising:
212
212
  """
213
213
  body = {
214
214
  "secret_id": secret_id,
215
+ "filters": {
216
+ "hide_inactive": True
217
+ }
215
218
  }
216
219
  header = get_headers(self._GET_ACCESSIBLE_ACCOUNTS)
217
220
  response = request(
@@ -46,13 +46,15 @@ def get_session_if_malformed(wrapped_function):
46
46
 
47
47
  return inner
48
48
 
49
+
49
50
  def get_report_results_if_expired(wrapped_function):
50
51
  @wraps(wrapped_function)
51
52
  def inner(*args, **kwargs):
52
53
  try:
53
54
  return wrapped_function(*args, **kwargs)
54
55
  except SklikServiceException as err:
55
- if json.loads(err.args[0].decode("utf8")).get("detail") == "Requested report has expired. Please create a new report by calling createReport endpoint.":
56
+ if json.loads(err.args[0].decode("utf8")).get(
57
+ "detail") == "Requested report has expired. Please create a new report by calling createReport endpoint.":
56
58
  print("Report expired, creating new report")
57
59
  args[0].get_session(args[0]._secret_id, args[0]._account_email)
58
60
  return wrapped_function(*args, **kwargs)
@@ -74,6 +76,7 @@ class SklikService:
74
76
  self._LOGOUT = self._URL + "/logout"
75
77
  self._API_LIMITS = self._URL + "/api-limits"
76
78
  self._GET_SESSION = self._URL + "/get-session"
79
+ self._GET_SESSION_WITHOUT_ACCOUNT = self._URL + "/get-session-without-account"
77
80
  self.session = None # dict {"sklik_session": "", "user_id": ""}
78
81
  self._secret_id = None
79
82
  self._account_email = None
@@ -99,6 +102,25 @@ class SklikService:
99
102
  else:
100
103
  raise SklikServiceException(response.content)
101
104
 
105
+ def get_session_without_account(self, secret_id: str) -> None:
106
+ """
107
+ Manages initial login of the sessions, returns dict with sklik session string without user id int
108
+ :param secret_id: The ID of the secret in secret manager
109
+ :return: Dict
110
+ """
111
+ body = {"secret_id": secret_id}
112
+ response = execute_request("post", url=self._GET_SESSION_WITHOUT_ACCOUNT, json=body)
113
+
114
+ if response.status_code == HTTPStatus.OK:
115
+ service_response = response.json()
116
+ self.session = {
117
+ "sklik_session": service_response["data"]["sklik_session"],
118
+ "user_id": None,
119
+ }
120
+ self._secret_id = secret_id
121
+ else:
122
+ raise SklikServiceException(response.content)
123
+
102
124
  def logout_of_current_session(self) -> None:
103
125
  """
104
126
  Attempts to log out of the current session, nothing happens if not ok
@@ -113,15 +135,15 @@ class SklikService:
113
135
  @get_session_if_malformed
114
136
  @get_report_results_if_expired
115
137
  def get_report_results(
116
- self,
117
- secret_id: str,
118
- account_email: str,
119
- report_type: str,
120
- date_from: str,
121
- date_to: str,
122
- columns: List[str],
123
- create_params: Dict[str, Union[int, str]] = None,
124
- read_params: Dict[str, Union[int, str]] = None,
138
+ self,
139
+ secret_id: str,
140
+ account_email: str,
141
+ report_type: str,
142
+ date_from: str,
143
+ date_to: str,
144
+ columns: List[str],
145
+ create_params: Dict[str, Union[int, str]] = None,
146
+ read_params: Dict[str, Union[int, str]] = None,
125
147
  ) -> List[Dict]:
126
148
  """
127
149
  It creates sklik report with /create-report call in sklik service and reads sklik report with pagination
@@ -166,14 +188,14 @@ class SklikService:
166
188
 
167
189
  @get_session_if_malformed
168
190
  def get_streamed_report_results(
169
- self,
170
- secret_id: str,
171
- account_email: str,
172
- report_type: str,
173
- date_from: str,
174
- date_to: str,
175
- columns: List[str],
176
- create_params: Dict[str, Union[int, str]] = None,
191
+ self,
192
+ secret_id: str,
193
+ account_email: str,
194
+ report_type: str,
195
+ date_from: str,
196
+ date_to: str,
197
+ columns: List[str],
198
+ create_params: Dict[str, Union[int, str]] = None,
177
199
  ) -> List[Dict]:
178
200
  """
179
201
  It creates sklik report with /create-report call in sklik service and reads sklik report with pagination
@@ -213,13 +235,13 @@ class SklikService:
213
235
  raise SklikServiceException(response.content)
214
236
 
215
237
  def _create_report(
216
- self,
217
- secret_id: str,
218
- account_email: str,
219
- report_type: str,
220
- date_from: str,
221
- date_to: str,
222
- params: Dict[str, Union[int, str]] = None,
238
+ self,
239
+ secret_id: str,
240
+ account_email: str,
241
+ report_type: str,
242
+ date_from: str,
243
+ date_to: str,
244
+ params: Dict[str, Union[int, str]] = None,
223
245
  ) -> Dict:
224
246
  """
225
247
  Function creates sklik report with /create-report call
@@ -256,7 +278,7 @@ class SklikService:
256
278
 
257
279
  @get_session_if_malformed
258
280
  def call_api(
259
- self, secret_id: str, account_email: str, method: str, params: List[Dict]
281
+ self, secret_id: str, account_email: str, method: str, params: List[Dict]
260
282
  ) -> Union[List, Dict]:
261
283
  """
262
284
  Function to call SklikService get-api route
@@ -310,12 +332,52 @@ class SklikService:
310
332
  return service_response["data"]
311
333
  else:
312
334
  raise SklikServiceException(response.content)
335
+
336
+ @get_session_if_malformed
337
+ def get_accessible_accounts(
338
+ self,
339
+ secret_id: str,
340
+ ) -> Union[List, Dict]:
341
+ """
342
+ Function to retrieve accessible accounts
343
+ :param secret_id: The ID of the secret in secret manager
344
+ :return:
345
+ """
346
+
347
+ if self.session is None:
348
+ self.get_session_without_account(secret_id)
349
+
350
+ response = execute_request(
351
+ "post",
352
+ url=self._GET_CLIENT,
353
+ json={
354
+ "sklik_session": self.session["sklik_session"],
355
+ "filters": {
356
+ "hide_inactive": True
357
+ }
358
+ },
359
+ )
360
+
361
+ if response.status_code == HTTPStatus.OK:
362
+ accounts = []
363
+ service_response = response.json()
364
+ data = service_response["data"]
365
+ for account in data["foreignAccounts"]:
366
+ accounts.append({
367
+ "id": account['userId'],
368
+ "name": account['username'],
369
+ "active": account["relationStatus"] == "live"
370
+ })
371
+ return accounts
372
+ else:
373
+ raise SklikServiceException(response.content)
374
+
313
375
  @get_session_if_malformed
314
376
  def check_data_ready(
315
- self,
316
- secret_id: str,
317
- account_email: str,
318
- date: str = None,
377
+ self,
378
+ secret_id: str,
379
+ account_email: str,
380
+ date: str = None,
319
381
  ) -> int:
320
382
  """
321
383
  Checks if data on server are ready
@@ -329,8 +391,8 @@ class SklikService:
329
391
  self.get_session(secret_id, account_email)
330
392
 
331
393
  date = date or (
332
- datetime.now(timezone("UTC")).astimezone(timezone("Europe/Prague"))
333
- - timedelta(days=1)
394
+ datetime.now(timezone("UTC")).astimezone(timezone("Europe/Prague"))
395
+ - timedelta(days=1)
334
396
  ).strftime("%Y-%m-%d")
335
397
 
336
398
  body = {"date": date}
@@ -351,9 +413,9 @@ class SklikService:
351
413
 
352
414
  @get_session_if_malformed
353
415
  def fetch_api_limits(
354
- self,
355
- secret_id: str,
356
- account_email: str,
416
+ self,
417
+ secret_id: str,
418
+ account_email: str,
357
419
  ) -> Union[List, Dict]:
358
420
  """
359
421
  Function to call SklikService api/limits route
@@ -371,4 +433,4 @@ class SklikService:
371
433
  service_response = response.json()
372
434
  return service_response["data"]
373
435
  else:
374
- raise SklikServiceException(response.content)
436
+ raise SklikServiceException(response.content)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: logos-sdk
3
- Version: 0.0.25.dev9
3
+ Version: 0.0.25.dev10
4
4
  Summary: SDK for Logos platform
5
5
  Home-page: https://bitbucket.org/databy/logos-sdk-pip/src/master/
6
6
  Author: Databy.io