logos-sdk 0.0.25.dev9__tar.gz → 0.0.25.dev11__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.dev11}/PKG-INFO +10 -2
  2. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/services/CampaignManager.py +105 -33
  3. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/services/DV360.py +99 -24
  4. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/services/Facebook.py +28 -1
  5. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/services/GoogleAds.py +87 -18
  6. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/services/MerchantCenter.py +42 -0
  7. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/services/MicrosoftAdvertising.py +26 -0
  8. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/services/Sklik.py +113 -36
  9. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk.egg-info/PKG-INFO +10 -2
  10. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/LICENSE +0 -0
  11. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/README.md +0 -0
  12. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/__init__.py +0 -0
  13. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/big_query/BigQuery.py +0 -0
  14. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/big_query/__init__.py +0 -0
  15. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/logging/LogosLogger.py +0 -0
  16. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/logging/__init__.py +0 -0
  17. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/services/Collabim.py +0 -0
  18. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/services/GoogleSheets.py +0 -0
  19. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/services/MarketMiner.py +0 -0
  20. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk/services/__init__.py +0 -0
  21. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk.egg-info/SOURCES.txt +0 -0
  22. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk.egg-info/dependency_links.txt +0 -0
  23. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk.egg-info/requires.txt +0 -0
  24. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/logos_sdk.egg-info/top_level.txt +0 -0
  25. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/setup.cfg +0 -0
  26. {logos_sdk-0.0.25.dev9 → logos_sdk-0.0.25.dev11}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: logos-sdk
3
- Version: 0.0.25.dev9
3
+ Version: 0.0.25.dev11
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
@@ -19,6 +19,14 @@ Requires-Dist: httplib2
19
19
  Requires-Dist: pandas
20
20
  Requires-Dist: db-dtypes
21
21
  Requires-Dist: numpy
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: requires-dist
29
+ Dynamic: summary
22
30
 
23
31
  # Logos Software Development Kit
24
32
 
@@ -30,17 +30,20 @@ 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"
35
+ self._GET_ACCOUNT_ACCESSIBILITY = self._URL + "/get-account-accessibility"
33
36
 
34
37
  def create_report(
35
- self,
36
- account_id: str,
37
- name: str,
38
- start_date: str,
39
- end_date: str,
40
- dimensions: list,
41
- metrics_names: list,
42
- dimension_filters: list,
43
- secret_id: str,
38
+ self,
39
+ account_id: str,
40
+ name: str,
41
+ start_date: str,
42
+ end_date: str,
43
+ dimensions: list,
44
+ metrics_names: list,
45
+ dimension_filters: list,
46
+ secret_id: str,
44
47
  ) -> Dict:
45
48
  """
46
49
  Method for creating a report in API and returning a dict containing its ID for further use
@@ -179,7 +182,7 @@ class CampaignManagerService:
179
182
  raise CampaignManagerServiceException(response.content)
180
183
 
181
184
  def check_report_ready_with_exponential_backoff(
182
- self, report_id: str, file_id: str, secret_id: str, backoff_attempts: int = 10
185
+ self, report_id: str, file_id: str, secret_id: str, backoff_attempts: int = 10
183
186
  ) -> bool:
184
187
  """
185
188
  Implements exponential backoff for pooling the API for readiness of the report, suggested in
@@ -194,7 +197,7 @@ class CampaignManagerService:
194
197
  if self.check_report_ready(report_id, file_id, secret_id):
195
198
  return True
196
199
  else:
197
- time.sleep((2**attempt) + randint(1, 20))
200
+ time.sleep((2 ** attempt) + randint(1, 20))
198
201
 
199
202
  return False
200
203
 
@@ -288,14 +291,14 @@ class CampaignManagerService:
288
291
  raise CampaignManagerServiceException(response.content)
289
292
 
290
293
  def query_dimension_values(
291
- self,
292
- account_id: str,
293
- secret_id: str,
294
- dimension_name: str,
295
- filters: List,
296
- start_date: str,
297
- end_date: int,
298
- max_results: int = 100,
294
+ self,
295
+ account_id: str,
296
+ secret_id: str,
297
+ dimension_name: str,
298
+ filters: List,
299
+ start_date: str,
300
+ end_date: int,
301
+ max_results: int = 100,
299
302
  ) -> List[Dict]:
300
303
  """
301
304
  Retrieves values of selected dimensions for data filtered according to rules set in list of filters
@@ -346,16 +349,16 @@ class CampaignManagerService:
346
349
  yield service_response["data"]["items"]
347
350
 
348
351
  def create_and_get_report_results(
349
- self,
350
- account_id: str,
351
- name: str,
352
- start_date: str,
353
- end_date: str,
354
- dimensions: list,
355
- metrics_names: list,
356
- dimension_filters: list,
357
- secret_id: str,
358
- backoff_attempts: int,
352
+ self,
353
+ account_id: str,
354
+ name: str,
355
+ start_date: str,
356
+ end_date: str,
357
+ dimensions: list,
358
+ metrics_names: list,
359
+ dimension_filters: list,
360
+ secret_id: str,
361
+ backoff_attempts: int,
359
362
  ) -> Dict:
360
363
  """
361
364
  Method to create report, run it and fetch results in one go
@@ -378,10 +381,10 @@ class CampaignManagerService:
378
381
  secret_id=secret_id,
379
382
  )
380
383
  if self.check_report_ready_with_exponential_backoff(
381
- report_id=report["id"],
382
- file_id=run_report["id"],
383
- secret_id=secret_id,
384
- backoff_attempts=backoff_attempts,
384
+ report_id=report["id"],
385
+ file_id=run_report["id"],
386
+ secret_id=secret_id,
387
+ backoff_attempts=backoff_attempts,
385
388
  ):
386
389
  return self.get_report_results(
387
390
  report_id=report["id"],
@@ -397,3 +400,72 @@ class CampaignManagerService:
397
400
  report_id=report["id"],
398
401
  secret_id=secret_id,
399
402
  )
403
+
404
+ def get_accessible_parent_accounts(self, secret_id):
405
+ """
406
+ Returns accessible parent accounts
407
+ :param secret_id: The ID of the secret in secret manager
408
+ :return: list of parent accounts
409
+ """
410
+
411
+ header = get_headers(self._GET_ACCESSIBLE_PARENT_ACCOUNTS)
412
+ body = {"secret_id": secret_id}
413
+
414
+ response = request(
415
+ "post", url=self._GET_ACCESSIBLE_PARENT_ACCOUNTS, json=body, headers=header
416
+ )
417
+
418
+ if response.status_code == HTTPStatus.OK:
419
+ service_response = response.json()
420
+ return service_response["data"]
421
+ else:
422
+ raise CampaignManagerServiceException(response.content)
423
+
424
+ def get_accessible_advertisers(self, secret_id):
425
+ """
426
+ Returns accessible advertisers
427
+ :param secret_id: The ID of the secret in secret manager
428
+ :return: list of advertisers
429
+ """
430
+
431
+ header = get_headers(self._GET_ACCESSIBLE_ADVERTISERS)
432
+ body = {
433
+ "secret_id": secret_id,
434
+ "filters": {
435
+ "hide_inactive": True,
436
+ },
437
+ }
438
+
439
+ response = request(
440
+ "post", url=self._GET_ACCESSIBLE_ADVERTISERS, json=body, headers=header
441
+ )
442
+
443
+ if response.status_code == HTTPStatus.OK:
444
+ service_response = response.json()
445
+ return service_response["data"]
446
+ else:
447
+ raise CampaignManagerServiceException(response.content)
448
+
449
+ def get_account_accessibility(self, secret_id: str,account_id, advertiser_id: str) -> bool:
450
+ """
451
+ Gets account accessibility
452
+ :param secret_id: The ID of the secret in secret manager
453
+ :param account_id: Account ID
454
+ :param advertiser_id: The ID of the advertiser.
455
+ :return: True if account has access
456
+ """
457
+ body = {
458
+ "secret_id": secret_id,
459
+ "account_id": account_id,
460
+ "advertiser_id": advertiser_id
461
+ }
462
+ header = get_headers(self._GET_ACCOUNT_ACCESSIBILITY)
463
+ response = request(
464
+ "post", url=self._GET_ACCOUNT_ACCESSIBILITY, json=body, headers=header
465
+ )
466
+
467
+ if response.status_code == HTTPStatus.OK:
468
+ service_response = response.json()
469
+ return service_response["data"]
470
+ else:
471
+ raise CampaignManagerServiceException(response.content)
@@ -20,10 +20,10 @@ class DV360Service:
20
20
  self._URL = url or os.environ.get("DV360_SERVICE_PATH")
21
21
  self._LIST_LINE_ITEMS = self._URL + "/line-items"
22
22
  self._BULK_LIST_LINE_ITEM_ASSIGNED_TARGETING_OPTIONS = (
23
- self._URL + "/bulk-list-line-item-assigned-targeting-options"
23
+ self._URL + "/bulk-list-line-item-assigned-targeting-options"
24
24
  )
25
25
  self._BULK_EDIT_LINE_ITEM_ASSIGNED_TARGETING_OPTIONS = (
26
- self._URL + "/bulk-edit-line-item-assigned-targeting-options"
26
+ self._URL + "/bulk-edit-line-item-assigned-targeting-options"
27
27
  )
28
28
  self._CREATE_CHANNEL = self._URL + "/create-channel"
29
29
  self._LIST_CHANNELS = self._URL + "/list-channels"
@@ -34,6 +34,9 @@ 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"
39
+ self._GET_ACCOUNT_ACCESSIBILITY = self._URL + "/get-account-accessibility"
37
40
 
38
41
  def list_line_items(self, advertiser_id, secret_id, filter_string=None):
39
42
  """
@@ -58,11 +61,11 @@ class DV360Service:
58
61
  raise DV360ServiceException(response.content)
59
62
 
60
63
  def bulk_list_line_item_assigned_targeting_options(
61
- self,
62
- advertiser_id,
63
- secret_id,
64
- line_item_ids,
65
- filter_string,
64
+ self,
65
+ advertiser_id,
66
+ secret_id,
67
+ line_item_ids,
68
+ filter_string,
66
69
  ):
67
70
  """
68
71
  Bulk lists targeting options under multiple line items
@@ -94,12 +97,12 @@ class DV360Service:
94
97
  raise DV360ServiceException(response.content)
95
98
 
96
99
  def bulk_edit_line_item_assigned_targeting_options(
97
- self,
98
- advertiser_id,
99
- secret_id,
100
- line_item_ids,
101
- delete_requests=None,
102
- create_requests=None,
100
+ self,
101
+ advertiser_id,
102
+ secret_id,
103
+ line_item_ids,
104
+ delete_requests=None,
105
+ create_requests=None,
103
106
  ):
104
107
  """
105
108
  Bulk edits targeting options under multiple line items
@@ -180,7 +183,7 @@ class DV360Service:
180
183
  raise DV360ServiceException(response.content)
181
184
 
182
185
  def list_channel_sites(
183
- self, advertiser_id, secret_id, channel_id, filter_string=None
186
+ self, advertiser_id, secret_id, channel_id, filter_string=None
184
187
  ):
185
188
  """
186
189
  Lists channels for advertiser
@@ -211,12 +214,12 @@ class DV360Service:
211
214
  raise DV360ServiceException(response.content)
212
215
 
213
216
  def bulk_edit_channels_sites(
214
- self,
215
- advertiser_id,
216
- secret_id,
217
- channel_id,
218
- deleted_sites=None,
219
- created_sites=None,
217
+ self,
218
+ advertiser_id,
219
+ secret_id,
220
+ channel_id,
221
+ deleted_sites=None,
222
+ created_sites=None,
220
223
  ):
221
224
  """
222
225
  Bulk edits sites under a single channel
@@ -301,8 +304,80 @@ class DV360Service:
301
304
  else:
302
305
  raise DV360ServiceException(response.content)
303
306
 
307
+ def get_accessible_partners(self, secret_id):
308
+ """
309
+ Returns accessible partners
310
+ :param secret_id: The ID of the secret in secret manager
311
+ :return: list of partners
312
+ """
313
+
314
+ header = get_headers(self._GET_ACCESSIBLE_PARTNERS)
315
+ body = {
316
+ "secret_id": secret_id,
317
+ "filters": {
318
+ "hide_inactive": True,
319
+ },
320
+ }
321
+
322
+ response = request(
323
+ "post", url=self._GET_ACCESSIBLE_PARTNERS, json=body, headers=header
324
+ )
325
+
326
+ if response.status_code == HTTPStatus.OK:
327
+ service_response = response.json()
328
+ return service_response["data"]
329
+ else:
330
+ raise DV360ServiceException(response.content)
331
+
332
+ def get_accessible_advertisers(self, secret_id):
333
+ """
334
+ Returns accessible advertisers
335
+ :param secret_id: The ID of the secret in secret manager
336
+ :return: list of advertisers
337
+ """
338
+
339
+ header = get_headers(self._GET_ACCESSIBLE_ADVERTISERS)
340
+ body = {
341
+ "secret_id": secret_id,
342
+ "filters": {
343
+ "hide_inactive": True,
344
+ },
345
+ }
346
+
347
+ response = request(
348
+ "post", url=self._GET_ACCESSIBLE_ADVERTISERS, json=body, headers=header
349
+ )
350
+
351
+ if response.status_code == HTTPStatus.OK:
352
+ service_response = response.json()
353
+ return service_response["data"]
354
+ else:
355
+ raise DV360ServiceException(response.content)
356
+
357
+ def get_account_accessibility(self, secret_id: str, advertiser_id: str) -> bool:
358
+ """
359
+ Gets account accessibility
360
+ :param secret_id: The ID of the secret in secret manager
361
+ :param advertiser_id: The ID of the advertiser.
362
+ :return: True if account has access
363
+ """
364
+ body = {
365
+ "secret_id": secret_id,
366
+ "advertiser_id": advertiser_id
367
+ }
368
+ header = get_headers(self._GET_ACCOUNT_ACCESSIBILITY)
369
+ response = request(
370
+ "post", url=self._GET_ACCOUNT_ACCESSIBILITY, json=body, headers=header
371
+ )
372
+
373
+ if response.status_code == HTTPStatus.OK:
374
+ service_response = response.json()
375
+ return service_response["data"]
376
+ else:
377
+ raise DV360ServiceException(response.content)
378
+
304
379
  def _check_report_ready_with_exponential_backoff(
305
- self, secret_id: str, query_id: str, report_id: str, backoff_attempts: int = 10
380
+ self, secret_id: str, query_id: str, report_id: str, backoff_attempts: int = 10
306
381
  ) -> bool:
307
382
  """
308
383
  Implements exponential backoff for pooling the API for readiness of the report, suggested in
@@ -318,7 +393,7 @@ class DV360Service:
318
393
  elif state == "FAILED":
319
394
  raise DV360ServiceException("Report failed to generate")
320
395
  else:
321
- sleep((2**attempt) + randint(1, 20))
396
+ sleep((2 ** attempt) + randint(1, 20))
322
397
 
323
398
  return False
324
399
 
@@ -352,7 +427,7 @@ class DV360Service:
352
427
  :return: The results
353
428
  """
354
429
  if not self._check_report_ready_with_exponential_backoff(
355
- secret_id, query_id, report_id
430
+ secret_id, query_id, report_id
356
431
  ):
357
432
  raise DV360ServiceException("Report did not generate in time")
358
433
 
@@ -393,5 +468,5 @@ class DV360Service:
393
468
 
394
469
  if response.status_code == HTTPStatus.OK:
395
470
  return True
396
- print("Query was not deleted!")
471
+
397
472
  return False
@@ -27,6 +27,7 @@ class FacebookService:
27
27
  self._FEED_ERRORS = self._URL + "/feed-errors"
28
28
  self._FEED_ERRORS_REPORT_STATUS = self._URL + "/feed-errors-report-status"
29
29
  self._FEED_ERRORS_REPORT = self._URL + "/feed-errors-report"
30
+ self._GET_ACCOUNT_ACCESSIBILITY = self._URL + "/get-account-accessibility"
30
31
 
31
32
  def get_accessible_accounts(
32
33
  self, secret_id: str, timeout: int = None
@@ -38,7 +39,12 @@ class FacebookService:
38
39
  :return: all accessible accounts List(Dict)
39
40
  """
40
41
 
41
- body = {"secret_id": secret_id}
42
+ body = {
43
+ "secret_id": secret_id,
44
+ "filters": {
45
+ "hide_inactive": True,
46
+ }
47
+ }
42
48
 
43
49
  header = get_headers(self._ACCESSIBLE_ACCOUNTS)
44
50
 
@@ -56,6 +62,27 @@ class FacebookService:
56
62
  else:
57
63
  raise FacebookServiceException(response.content)
58
64
 
65
+ def get_account_accessibility(self, secret_id: str, account_id: str) -> bool:
66
+ """
67
+ Gets account accessibility
68
+ :param secret_id: The ID of the secret in secret manager
69
+ :param account_id: The ID of the account.
70
+ :return: True if account has access
71
+ """
72
+ body = {
73
+ "secret_id": secret_id,
74
+ "account_id": account_id
75
+ }
76
+ header = get_headers(self._GET_ACCOUNT_ACCESSIBILITY)
77
+ response = request(
78
+ "post", url=self._GET_ACCOUNT_ACCESSIBILITY, json=body, headers=header
79
+ )
80
+
81
+ if response.status_code == HTTPStatus.OK:
82
+ service_response = response.json()
83
+ return service_response["data"]
84
+ else:
85
+ raise FacebookServiceException(response.content)
59
86
  def get_insights(
60
87
  self,
61
88
  account_id: str,
@@ -21,6 +21,8 @@ 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"
25
+ self._GET_ACCOUNT_ACCESSIBILITY = self._URL + "/get-account-accessibility"
24
26
 
25
27
  @staticmethod
26
28
  def fetch_with_retry_on_timeout(url, json, headers):
@@ -29,16 +31,18 @@ class GoogleAdsService:
29
31
  return request("post", url, json=json, headers=headers, timeout=25)
30
32
  except Timeout:
31
33
  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")
34
+ print(
35
+ f"there was a timeout when contacting the service, going to sleep for {delay} seconds"
36
+ )
33
37
  time.sleep(delay)
34
38
 
35
39
  raise Exception("The service is not able to reply within 30 seconds.")
36
40
 
37
41
  def search_stream(
38
- self,
39
- query: str,
40
- queried_account_id: str,
41
- secret_id: str,
42
+ self,
43
+ query: str,
44
+ queried_account_id: str,
45
+ secret_id: str,
42
46
  ) -> List[Union[List, Dict]]:
43
47
  """
44
48
  :param query Sql query for google ads. Best way to build it is https://developers.google.com/google-ads/api/fields/v14/accessible_bidding_strategy_query_builder
@@ -53,7 +57,9 @@ class GoogleAdsService:
53
57
  }
54
58
 
55
59
  header = get_headers(self._SEARCH_STREAM)
56
- response = self.fetch_with_retry_on_timeout(url=self._SEARCH_STREAM, json=body, headers=header)
60
+ response = self.fetch_with_retry_on_timeout(
61
+ url=self._SEARCH_STREAM, json=body, headers=header
62
+ )
57
63
 
58
64
  if response.status_code == HTTPStatus.OK:
59
65
  service_response = response.json()
@@ -62,11 +68,11 @@ class GoogleAdsService:
62
68
  raise GoogleAdsServiceException(response.content)
63
69
 
64
70
  def search(
65
- self,
66
- query: str,
67
- queried_account_id: str,
68
- secret_id: str,
69
- page_size: int,
71
+ self,
72
+ query: str,
73
+ queried_account_id: str,
74
+ secret_id: str,
75
+ page_size: int,
70
76
  ) -> List[Dict]:
71
77
  """
72
78
  :param query Sql query for google ads. Best way to build it is https://developers.google.com/google-ads/api/fields/v14/accessible_bidding_strategy_query_builder
@@ -84,7 +90,9 @@ class GoogleAdsService:
84
90
  }
85
91
 
86
92
  header = get_headers(self._SEARCH)
87
- response = self.fetch_with_retry_on_timeout(url=self._SEARCH, json=body, headers=header)
93
+ response = self.fetch_with_retry_on_timeout(
94
+ url=self._SEARCH, json=body, headers=header
95
+ )
88
96
 
89
97
  if response.status_code != HTTPStatus.OK:
90
98
  raise GoogleAdsServiceException(response.content)
@@ -104,7 +112,7 @@ class GoogleAdsService:
104
112
  yield service_response["data"]["results"]
105
113
 
106
114
  def exclude_criterion_for_account(
107
- self, client_id: str, exclusion_raw: List[str], mode: str, secret_id: str = None
115
+ self, client_id: str, exclusion_raw: List[str], mode: str, secret_id: str = None
108
116
  ) -> None:
109
117
  """
110
118
  Excludes list of unwanted urls/YouTube channels for account with client_id
@@ -133,11 +141,11 @@ class GoogleAdsService:
133
141
  raise GoogleAdsServiceException(response.content)
134
142
 
135
143
  def exclude_criterion_for_ad_group(
136
- self,
137
- client_id: str,
138
- ad_group_id: str,
139
- exclusion_raw: List[str],
140
- secret_id: str = None,
144
+ self,
145
+ client_id: str,
146
+ ad_group_id: str,
147
+ exclusion_raw: List[str],
148
+ secret_id: str = None,
141
149
  ) -> None:
142
150
  """
143
151
  Excludes list of unwanted urls/YouTube channels for given ad_group with client_id
@@ -163,3 +171,64 @@ class GoogleAdsService:
163
171
  return
164
172
  else:
165
173
  raise GoogleAdsServiceException(response.content)
174
+
175
+ def get_accessible_accounts(
176
+ self,
177
+ secret_id: str,
178
+ page_size: int = 1000,
179
+ ) -> List[Dict]:
180
+ body = {
181
+ "secret_id": secret_id,
182
+ "filters": {
183
+ "hide_inactive": True,
184
+ },
185
+ "page_token": None,
186
+ "page_size": page_size,
187
+ }
188
+
189
+ header = get_headers(self._GET_ACCESSIBLE_ACCOUNTS)
190
+ response = self.fetch_with_retry_on_timeout(
191
+ url=self._GET_ACCESSIBLE_ACCOUNTS, json=body, headers=header
192
+ )
193
+
194
+ if response.status_code != HTTPStatus.OK:
195
+ raise GoogleAdsServiceException(response.content)
196
+
197
+ service_response = response.json()
198
+ all_results = service_response["data"]["results"]
199
+
200
+ while service_response["data"]["next_page_token"]:
201
+ body["page_token"] = service_response["data"]["next_page_token"]
202
+ response = request(
203
+ "post", url=self._GET_ACCESSIBLE_ACCOUNTS, json=body, headers=header
204
+ )
205
+
206
+ if response.status_code != HTTPStatus.OK:
207
+ raise GoogleAdsServiceException(response.content)
208
+
209
+ service_response = response.json()
210
+ all_results.extend(service_response["data"]["results"])
211
+
212
+ return all_results
213
+
214
+ def get_account_accessibility(self, secret_id: str, account_id: str) -> bool:
215
+ """
216
+ Gets account accessibility
217
+ :param secret_id: The ID of the secret in secret manager
218
+ :param account_id: The ID of the account.
219
+ :return: True if account has access
220
+ """
221
+ body = {
222
+ "secret_id": secret_id,
223
+ "account_id": account_id
224
+ }
225
+ header = get_headers(self._GET_ACCOUNT_ACCESSIBILITY)
226
+ response = request(
227
+ "post", url=self._GET_ACCOUNT_ACCESSIBILITY, json=body, headers=header
228
+ )
229
+
230
+ if response.status_code == HTTPStatus.OK:
231
+ service_response = response.json()
232
+ return service_response["data"]
233
+ else:
234
+ raise GoogleAdsServiceException(response.content)
@@ -14,6 +14,8 @@ 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"
18
+ self._GET_ACCOUNT_ACCESSIBILITY = self._URL + "/account-service/get-account-accessibility"
17
19
  self._LIST_ACCOUNT_STATUSES = self._URL + "/account-service/account-statuses"
18
20
  self._LIST_PRODUCTS = self._URL + "/product-service/products"
19
21
  self._LIST_PRODUCT_STATUSES = self._URL + "/product-service/product-statuses"
@@ -36,6 +38,22 @@ class MerchantCenterService:
36
38
  else:
37
39
  raise MerchantServiceException(response.content)
38
40
 
41
+ def list_accessible_accounts(self, secret_id: str):
42
+ """
43
+ Lists accessible accounts in Merchant Center
44
+ :param secret_id: The ID of the secret in secret manager
45
+ :return: List[Dict]
46
+ """
47
+ body = {"secret_id": secret_id}
48
+ header = get_headers(self._LIST_ACCESSIBLE_ACCOUNTS)
49
+ response = request("post", url=self._LIST_ACCESSIBLE_ACCOUNTS, json=body, headers=header)
50
+
51
+ if response.status_code == HTTPStatus.OK:
52
+ service_response = response.json()
53
+ return service_response["data"]
54
+ else:
55
+ raise MerchantServiceException(response.content)
56
+
39
57
  def list_account_statuses(
40
58
  self, merchant_account_id: str, account_id: str, secret_id: str
41
59
  ):
@@ -182,3 +200,27 @@ class MerchantCenterService:
182
200
 
183
201
  service_response = response.json()
184
202
  yield service_response["data"]["results"]
203
+
204
+ def get_account_accessibility(self, secret_id: str,merchant_account_id:str, account_id: str) -> bool:
205
+ """
206
+ Gets account accessibility
207
+ :param secret_id: The ID of the secret in secret manager
208
+ :param merchant_account_id: The ID of the managing account. This must be a multi-client account.
209
+ :param account_id: The ID of the account.
210
+ :return: True if account has access
211
+ """
212
+ body = {
213
+ "secret_id": secret_id,
214
+ "merchant_account_id": merchant_account_id,
215
+ "account_id": account_id
216
+ }
217
+ header = get_headers(self._GET_ACCOUNT_ACCESSIBILITY)
218
+ response = request(
219
+ "post", url=self._GET_ACCOUNT_ACCESSIBILITY, json=body, headers=header
220
+ )
221
+
222
+ if response.status_code == HTTPStatus.OK:
223
+ service_response = response.json()
224
+ return service_response["data"]
225
+ else:
226
+ raise MerchantServiceException(response.content)
@@ -23,6 +23,7 @@ class MicrosoftAdvertising:
23
23
  load_dotenv()
24
24
  self._URL = url or os.environ.get("MICROSOFT_ADVERTISING_PATH")
25
25
  self._GET_ACCESSIBLE_ACCOUNTS = self._URL + "/accessible-accounts"
26
+ self._GET_ACCOUNT_ACCESSIBILITY = self._URL + "/get-account-accessibility"
26
27
  self._GET_DESTINATION_URL_REPORT = self._URL + "/destination-url-report"
27
28
  self._GET_CAMPAIGN_PERFORMANCE_REPORT = self._URL + "/campaign-performance-report"
28
29
  self._GET_GEOGRAPHIC_PERFORMANCE_REPORT = self._URL + "/geographic-performance-report"
@@ -212,6 +213,9 @@ class MicrosoftAdvertising:
212
213
  """
213
214
  body = {
214
215
  "secret_id": secret_id,
216
+ "filters": {
217
+ "hide_inactive": True
218
+ }
215
219
  }
216
220
  header = get_headers(self._GET_ACCESSIBLE_ACCOUNTS)
217
221
  response = request(
@@ -223,3 +227,25 @@ class MicrosoftAdvertising:
223
227
  return service_response["data"]
224
228
  else:
225
229
  raise MicrosoftAdvertisingException(response.content)
230
+
231
+ def get_account_accessibility(self, secret_id: str, account_id: str) -> bool:
232
+ """
233
+ Gets account accessibility
234
+ :param secret_id: The ID of the secret in secret manager
235
+ :param account_id: The ID of the account in Microsoft Advertising
236
+ :return: True if account has access
237
+ """
238
+ body = {
239
+ "secret_id": secret_id,
240
+ "account_id": account_id
241
+ }
242
+ header = get_headers(self._GET_ACCOUNT_ACCESSIBILITY)
243
+ response = request(
244
+ "post", url=self._GET_ACCOUNT_ACCESSIBILITY, json=body, headers=header
245
+ )
246
+
247
+ if response.status_code == HTTPStatus.OK:
248
+ service_response = response.json()
249
+ return service_response["data"]
250
+ else:
251
+ raise MicrosoftAdvertisingException(response.content)
@@ -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,19 @@ 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)
437
+
438
+ def get_account_accessibility(self, secret_id: str, account_email: str) -> bool:
439
+ """
440
+ Gets account accessibility
441
+ :param secret_id: The ID of the secret in secret manager
442
+ :param account_email: Account email to refers to Sklik accountId
443
+ :return: True if account has access
444
+ """
445
+ body = {"account_email": account_email, "secret_id": secret_id}
446
+ response = execute_request("post", url=self._GET_SESSION, json=body)
447
+ if response.status_code == 200:
448
+ return True
449
+ if response.status_code == 401 or response.status_code == 403 or response.status_code == 404:
450
+ return False
451
+ raise SklikServiceException(response.content)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: logos-sdk
3
- Version: 0.0.25.dev9
3
+ Version: 0.0.25.dev11
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
@@ -19,6 +19,14 @@ Requires-Dist: httplib2
19
19
  Requires-Dist: pandas
20
20
  Requires-Dist: db-dtypes
21
21
  Requires-Dist: numpy
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: requires-dist
29
+ Dynamic: summary
22
30
 
23
31
  # Logos Software Development Kit
24
32