launchpy 0.4.5.post5__tar.gz → 0.4.6__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 (31) hide show
  1. {launchpy-0.4.5.post5/launchpy.egg-info → launchpy-0.4.6}/PKG-INFO +1 -1
  2. {launchpy-0.4.5.post5 → launchpy-0.4.6}/docs/releases.md +6 -0
  3. {launchpy-0.4.5.post5 → launchpy-0.4.6}/docs/synchronizer.md +52 -3
  4. launchpy-0.4.6/launchpy/__version__.py +1 -0
  5. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy/connector.py +6 -6
  6. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy/launchpy.py +20 -4
  7. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy/library.py +22 -5
  8. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy/property.py +12 -6
  9. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy/synchronizer.py +23 -12
  10. {launchpy-0.4.5.post5 → launchpy-0.4.6/launchpy.egg-info}/PKG-INFO +1 -1
  11. launchpy-0.4.5.post5/launchpy/__version__.py +0 -1
  12. {launchpy-0.4.5.post5 → launchpy-0.4.6}/LICENSE +0 -0
  13. {launchpy-0.4.5.post5 → launchpy-0.4.6}/README.md +0 -0
  14. {launchpy-0.4.5.post5 → launchpy-0.4.6}/docs/admin.md +0 -0
  15. {launchpy-0.4.5.post5 → launchpy-0.4.6}/docs/getstarted.md +0 -0
  16. {launchpy-0.4.5.post5 → launchpy-0.4.6}/docs/library.md +0 -0
  17. {launchpy-0.4.5.post5 → launchpy-0.4.6}/docs/main.md +0 -0
  18. {launchpy-0.4.5.post5 → launchpy-0.4.6}/docs/property.md +0 -0
  19. {launchpy-0.4.5.post5 → launchpy-0.4.6}/docs/translator.md +0 -0
  20. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy/__init__.py +0 -0
  21. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy/admin.py +0 -0
  22. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy/config.py +0 -0
  23. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy/configs.py +0 -0
  24. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy.egg-info/SOURCES.txt +0 -0
  25. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy.egg-info/dependency_links.txt +0 -0
  26. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy.egg-info/requires.txt +0 -0
  27. {launchpy-0.4.5.post5 → launchpy-0.4.6}/launchpy.egg-info/top_level.txt +0 -0
  28. {launchpy-0.4.5.post5 → launchpy-0.4.6}/pyproject.toml +0 -0
  29. {launchpy-0.4.5.post5 → launchpy-0.4.6}/requirements.txt +0 -0
  30. {launchpy-0.4.5.post5 → launchpy-0.4.6}/setup.cfg +0 -0
  31. {launchpy-0.4.5.post5 → launchpy-0.4.6}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: launchpy
3
- Version: 0.4.5.post5
3
+ Version: 0.4.6
4
4
  Summary: Adobe Launch API wrapper
5
5
  Home-page: https://github.com/pitchmuc/launchpy
6
6
  Author: Julien Piccini
@@ -3,6 +3,9 @@
3
3
  This page gathered the changes made between version of the launchpy module.\
4
4
  This has been started after the 0.3.0 release.\
5
5
 
6
+ ## 0.4.6
7
+ * Allow Synchronizer to be used in multi IMS organization setup.
8
+
6
9
  ## 0.4.5
7
10
  * adding `getExtension` in `Property` class
8
11
  * improve `upgradeExtension` in `Property` class
@@ -15,6 +18,9 @@ Patch:
15
18
  * Support of extension added in `createTargetsLibrary`
16
19
  * Fixing dependency to `aepp` `som` module
17
20
  * Fixing renaming path to use `renameComponent` and using `syncComponent`
21
+ * Adding capability to set environment to a lib in `Library` instance via `setEnvironment`
22
+ * Capability to get a `Library` class from the `getLibrary` method in `Property` instance
23
+ * Fixing code in `deleteLibrary`
18
24
 
19
25
  ## 0.4.4
20
26
  * adding a `getBuilds` method on the `Library` class instance
@@ -13,7 +13,6 @@ The Synchronizer will allow you to specify a template property and target proper
13
13
  This class is currently requiring the following elements on your side:
14
14
 
15
15
  * You have imported the config file (via `importConfigFile`) or configure your module via `configure` method.
16
- * Your template property and target properties are located in the same organization
17
16
  * You have unique name for your rule and data elements
18
17
  * You have the same extension installed in both the template and target properties.\
19
18
  The Synchronizer will not sync the extensions configuration.
@@ -24,8 +23,8 @@ If a rule and a data element have the same name, by passing that name in the `sy
24
23
 
25
24
  In order to instantiate the `Synchronizer` class, you will need to provide 2 elements:
26
25
 
27
- * The name of the base property (as string)
28
- * The names of the target properties (as a list of strings)
26
+ * The name of the base property, as `string` or `launchpy.Property` instance
27
+ * The names of the target properties (as a list of `strings`, or `launchpy.Property` instances)
29
28
 
30
29
  Example:
31
30
 
@@ -38,6 +37,56 @@ synchronizor = lp.Synchronizer(base='Prop1',targets=['Prop2'])
38
37
 
39
38
  ```
40
39
 
40
+ ### Multi Organization environments
41
+
42
+ For some clients, they may want to synchronize the rules and data elements across organization setup.\
43
+ That is possible, but the requirements are still needed regarding the following elements:
44
+ * You have unique name for your rule and data elements
45
+ * You have the same extension installed in both the template and target properties.
46
+
47
+ The synchronizer will use the environment credential lastly loaded in the system.\
48
+ We would recommend to load first the target organization that you want to sync to, and then the base organization.\
49
+ You will be able to use the Property instance of the target organization in order to sync the element afterwards.
50
+
51
+ Example of setup
52
+
53
+ ```py
54
+ import launchpy as lp
55
+
56
+
57
+ ### targeted org
58
+ lp.importConfigFile('config_target.json')
59
+ admin_target = lp.Admin()
60
+ targetId = admin_target.getCompanyId()
61
+ target_props = admin_target.getProperties(targetId)
62
+
63
+ target_prop = lp.Property(target_props[1]) ## selecting one of the property
64
+
65
+ ### Source org for base
66
+ lp.importConfigFile('config_source.json')
67
+ admin = lp.Admin()
68
+ sourceId = admin_target.getCompanyId()
69
+ source_props = admin.getProperties(sourceId)
70
+
71
+ base = lp.Property(source_props[0]) ### selecting the first one as base - it can be any
72
+
73
+ synchronizor = lp.Synchronizer(base=base,targets=['another Property Name to sync',target_prop])
74
+
75
+ ### rest is as usual
76
+ ```
77
+
78
+ If you have private extension that are not released between organization, you can pass a mapping to the synchronizer to tell that these extensions are to be treated as one.\
79
+ You would need to pass the target extension name and the base extension name so that it is mapped as a single one.
80
+
81
+ Example:
82
+ ```py
83
+
84
+ synchronizor = lp.Synchronizer(base=base,targets=['another Property Name to sync',target_prop],mapping_extensions={'extension_name_target':'extension_name_base'})
85
+
86
+ ```
87
+
88
+
89
+ ### Additional options
41
90
  There is a possibility to setup some rules so you can filter when migrating from one property to another. See [dynamic Filtering](#dynamic-component-filter)
42
91
 
43
92
  ## SyncComponent Method
@@ -0,0 +1 @@
1
+ __version__ = "0.4.6"
@@ -31,7 +31,7 @@ class AdobeRequest:
31
31
  raise Exception(
32
32
  'You have to upload the configuration file with importConfigFile method.')
33
33
  self.config = deepcopy(config_object)
34
- self.header = header
34
+ self.header = deepcopy(header)
35
35
  self.retry = retry
36
36
  if self.config['token'] == '' or time.time() > self.config['date_limit']:
37
37
  if 'scopes' in self.config.keys() and self.config.get('scopes',None) is not None:
@@ -42,9 +42,9 @@ class AdobeRequest:
42
42
  token_and_expiry = self.get_jwt_token_and_expiry_for_config(config=self.config, verbose=verbose)
43
43
  token = token_and_expiry['token']
44
44
  expiry = token_and_expiry['expiry']
45
- self.token = token
46
- self.config['token'] = token
47
- self.config['date_limit'] = time.time() + expiry - 500
45
+ self.token = deepcopy(token)
46
+ self.config['token'] = deepcopy(token)
47
+ self.config['date_limit'] = deepcopy(time.time() + expiry - 500)
48
48
  self.header.update({'Authorization': f'Bearer {token}'})
49
49
 
50
50
  def get_jwt_token_and_expiry_for_config(self,config: dict, verbose: bool = False, save: bool = False, *args, **kwargs) -> Dict[str, str]:
@@ -166,8 +166,8 @@ class AdobeRequest:
166
166
  elif self.connectionType == 'jwt':
167
167
  token_and_expiry = self.get_jwt_token_and_expiry_for_config(config=self.config)
168
168
  token = token_and_expiry['token']
169
- self.config['token'] = token
170
- self.config['date_limit'] = time.time() + token_and_expiry['expiry'] - 500
169
+ self.config['token'] = deepcopy(token)
170
+ self.config['date_limit'] = deepcopy(time.time() + token_and_expiry['expiry'] - 500)
171
171
  self.header.update({'Authorization': f'Bearer {token}'})
172
172
 
173
173
  def getData(self, endpoint: str, params: dict = None, data: dict = None, headers: dict = None, *args, **kwargs):
@@ -405,14 +405,23 @@ class Translator:
405
405
  --> You can use the translate method to translate Data Element settings
406
406
  3. setBaseRules
407
407
  4. extendRules
408
- --> You can use the translate method to translate Rule Components settings
408
+ --> You can use the translate method to translate Rule Components settings
409
+
410
+ Possible arguments:
411
+ mapping_extensions : dictionary of extension names that should be replaced.
412
+ Such as: {'old_name':'new_name'}
409
413
  """
410
414
 
411
- def __init__(self):
415
+ def __init__(self,**kwargs):
412
416
  self.rules = {}
413
417
  self.baseRuleIdName = {}
414
418
  self.extensions = {}
415
419
  self.baseExtensionIdName = {}
420
+ self.mapping_extension = {}
421
+ if kwargs.get('mapping_extensions') is not None:
422
+ if type(kwargs.get('mapping_extensions')) == dict:
423
+ for key, value in kwargs.get('mapping_extensions').items():
424
+ self.mapping_extension[key] = value
416
425
 
417
426
  def setBaseExtensions(self, base_property_extensions: list, property_name: str):
418
427
  """
@@ -422,8 +431,12 @@ class Translator:
422
431
  property_name : REQUIRED : name of your base property.
423
432
  """
424
433
  self.baseExtensionIdName = {ext['id'] : ext['attributes']['name'] for ext in base_property_extensions}
434
+ for extId, extName in self.baseExtensionIdName.items():
435
+ if extName in self.mapping_extension.keys():
436
+ self.baseExtensionIdName[extId] = self.mapping_extension[extName]
425
437
  self.extensions = {ext['attributes']['name']:{property_name:ext['id']} for ext in base_property_extensions}
426
438
 
439
+
427
440
  def extendExtensions(self, new_property_extensions: list, new_prop_name: str)-> None:
428
441
  """
429
442
  Add the extensions id from a target property.
@@ -432,8 +445,11 @@ class Translator:
432
445
  new_prop_name : REQUIRED : target property name.
433
446
  """
434
447
  for ext in new_property_extensions:
435
- if ext['attributes']['name'] in list(self.extensions.keys()):
436
- self.extensions[ext['attributes']['name']][new_prop_name] = ext['id']
448
+ extName = ext['attributes']['name']
449
+ if extName in self.mapping_extension.keys():
450
+ extName = self.mapping_extension[extName]
451
+ if extName in list(self.extensions.keys()):
452
+ self.extensions[extName][new_prop_name] = ext['id']
437
453
  return self.extensions
438
454
 
439
455
  def setBaseRules(self, base_property_rules: list, property_name: str):
@@ -313,7 +313,7 @@ class Library:
313
313
 
314
314
  def setEnvironments(self, environments_list: list, dev_name: str = None)->None:
315
315
  """
316
- Save the different environments ids available.
316
+ Save the different environments ids available in a config variable.
317
317
  It is required to use the library class.
318
318
  Arguments :
319
319
  environments_list : REQUIRED : list of environment retrieved by the getEnvironments method
@@ -340,6 +340,21 @@ class Library:
340
340
  res = new_env
341
341
  return res
342
342
 
343
+ def setEnvironment(self, env_id: str,verbose:bool=False)->None:
344
+ """
345
+ Set the environment of the library.
346
+ Arguments:
347
+ env_id : REQUIRED : The environment id you want to set.
348
+ """
349
+ obj = {
350
+ "data": {
351
+ "id": env_id,
352
+ "type": "environments"
353
+ }
354
+ }
355
+ res = self._setEnvironment(obj,verbose=verbose)
356
+ return res
357
+
343
358
  def _removeEnvironment(self)->None:
344
359
  """
345
360
  Remove environment
@@ -347,7 +362,7 @@ class Library:
347
362
  path = f'/libraries/{self.id}/relationships/environment'
348
363
  new_env = self.connector.getData(self.endpoint+path)
349
364
  return new_env
350
-
365
+
351
366
  def updateLibrary(self,empty:bool=False)->dict:
352
367
  """
353
368
  Update the library
@@ -383,12 +398,14 @@ class Library:
383
398
  return res
384
399
 
385
400
 
386
- def build(self,verbose:bool=False)->dict:
401
+ def build(self,timesleep:int=20, verbose:bool=False)->dict:
387
402
  """
388
403
  Build the library.
389
404
  Part of the code takes care of assigning the right environement before building the library.
390
405
  Returns the build when it is completed (succeed or not).
391
- It will check every 15 seconds for the build status, making sure it is not "pending".
406
+ Arguments:
407
+ timesleep : OPTIONAL : How many seconds to wait between each check of the build status. Default to 20 seconds.
408
+ It will check every 20 seconds for the build status, making sure it is not "pending".
392
409
  """
393
410
  if self.build_required == False and self.state != 'approved':
394
411
  return 'build is not required'
@@ -430,7 +447,7 @@ class Library:
430
447
  build_status = build['data']['attributes']['status']
431
448
  while build_status == 'pending':
432
449
  print('pending...')
433
- time.sleep(20)
450
+ time.sleep(timesleep)
434
451
  # return the json directly
435
452
  build = self.connector.getData(
436
453
  config.endpoints['global']+'/builds/'+str(build_id))
@@ -603,7 +603,7 @@ class Property:
603
603
  data = data + append_data
604
604
  return data
605
605
 
606
- def getLibrary(self,libraryId:str=None)->dict:
606
+ def getLibrary(self,libraryId:str=None,return_class:bool=False)->dict:
607
607
  """
608
608
  get a library based on its ID.
609
609
  Arguments:
@@ -614,6 +614,8 @@ class Property:
614
614
  path = f"/libraries/{libraryId}"
615
615
  res = self.connector.getData(self.endpoint+path)
616
616
  if 'data' in res.keys():
617
+ if return_class:
618
+ return Library(res['data'],config_object=self.connector.config,header=self.header)
617
619
  return res['data']
618
620
  return res
619
621
 
@@ -1278,7 +1280,10 @@ class Property:
1278
1280
  if library in librariesIds:
1279
1281
  libraryId = library
1280
1282
  else:
1281
- libraryId = librariesNameId[library]
1283
+ try:
1284
+ libraryId = librariesNameId[library]
1285
+ except KeyError:
1286
+ raise ValueError("Library name or ID provided not found.")
1282
1287
  path = f"/libraries/{libraryId}"
1283
1288
  if components==True:
1284
1289
  myLib = self.getLibrary(libraryId)
@@ -1286,10 +1291,11 @@ class Property:
1286
1291
  rules = libClass.getRules()
1287
1292
  dataelements = libClass.getDataElements()
1288
1293
  res = self.connector.deleteData('https://reactor.adobe.io/'+path)
1289
- for rule in rules:
1290
- self.deleteRule(rule['id'])
1291
- for de in dataelements:
1292
- self.deleteDataElement(de['id'])
1294
+ if components==True:
1295
+ for rule in rules:
1296
+ self.deleteRule(rule['id'])
1297
+ for de in dataelements:
1298
+ self.deleteDataElement(de['id'])
1293
1299
  return res
1294
1300
 
1295
1301
  def getProductionEndpoint(self)->dict:
@@ -21,37 +21,48 @@ class Synchronizer:
21
21
  It requires that you have imported a configuration file.
22
22
  """
23
23
 
24
- def __init__(self,base:str=None,targets:list=None,**kwargs)->None:
24
+ def __init__(self,base:str | Property = None,targets:list=None,**kwargs)->None:
25
25
  """
26
26
  Instantiating the Synchronizer taking 2 parameters, base property name, target property list.
27
27
  Arguments:
28
- base : REQUIRED : The name of your base/template property
28
+ base : REQUIRED : The name of your base/template property or the instance of Property class
29
29
  targets : REQUIRED : The list of property names that you want to sync with.
30
+ the list of target can be string of property name or Property instance for other organization.
30
31
  possible kwargs:
31
32
  dynamicRuleComponent: A data element name that contains rule for synchronization on the property.
33
+ mapping_extensions : A dictionary of target extension name and base extension name when in different IMS org and 2 private extensions are name differently for the same purpose.
32
34
  """
33
35
  tmp_admin = Admin()
34
36
  cid = tmp_admin.getCompanyId()
35
37
  properties = tmp_admin.getProperties(cid)
36
- base_property = [prop for prop in properties if prop['attributes']['name'] == base]
37
- if len(base_property) ==0:
38
- raise KeyError("The base property name has not been found in your account")
38
+ mapping_extensions = kwargs.get('mapping_extensions',None)
39
39
  self.base = {}
40
- self.base["name"] = base
41
- self.base["api"]:Property = Property(base_property[0])
40
+ if type(base) == str:
41
+ base_property = [prop for prop in properties if prop['attributes']['name'] == base]
42
+ if len(base_property) ==0:
43
+ raise KeyError("The base property name has not been found in your account")
44
+ self.base["name"] = base
45
+ self.base["api"]:Property = Property(base_property[0])
46
+ elif type(base) == Property:
47
+ self.base["name"] = base.name
48
+ self.base["api"]:Property = base
42
49
  self.base['rules'] = self.base['api'].getRules()
43
50
  self.base['dataElements'] = self.base['api'].getDataElements()
44
51
  self.base['extensions'] = self.base['api'].getExtensions()
45
- self.translator = Translator()
52
+ self.translator = Translator(mapping_extensions=mapping_extensions)
46
53
  self.translator.setBaseExtensions(self.base['extensions'],self.base['name'])
47
54
  self.translator.setBaseRules(self.base['rules'],self.base['name'])
48
55
  self.targets = {}
49
56
  self.target_configs = {}
50
57
  for target in targets:
51
- tmp_target = [prop for prop in properties if prop['attributes']['name'] == target]
52
- if len(tmp_target) == 0:
53
- raise KeyError(f"The target property : {target} cannot be found. Please, fix it")
54
- self.targets[target] = {'api' : Property(tmp_target[0]),'name':target}
58
+ if isinstance(target,str):
59
+ tmp_target = [prop for prop in properties if prop['attributes']['name'] == target]
60
+ if len(tmp_target) == 0:
61
+ raise KeyError(f"The target property : {target} cannot be found. Please, fix it")
62
+ self.targets[target] = {'api' : Property(tmp_target[0]),'name':target}
63
+ elif isinstance(target,Property):
64
+ self.targets[target.name] = {'api' : deepcopy(target),'name':target.name}
65
+ target = target.name
55
66
  self.targets[target]['rules'] = self.targets[target]['api'].getRules()
56
67
  self.targets[target]['extensions'] = self.targets[target]['api'].getExtensions()
57
68
  self.targets[target]['dataElements'] = self.targets[target]['api'].getDataElements()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: launchpy
3
- Version: 0.4.5.post5
3
+ Version: 0.4.6
4
4
  Summary: Adobe Launch API wrapper
5
5
  Home-page: https://github.com/pitchmuc/launchpy
6
6
  Author: Julien Piccini
@@ -1 +0,0 @@
1
- __version__ = "0.4.5-5"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes