launchpy 0.4.3.post1__tar.gz → 0.4.3.post3__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.3.post1/launchpy.egg-info → launchpy-0.4.3.post3}/PKG-INFO +1 -1
  2. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/docs/getstarted.md +9 -1
  3. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/docs/property.md +10 -1
  4. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/docs/releases.md +3 -1
  5. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/docs/synchronizer.md +51 -9
  6. launchpy-0.4.3.post3/launchpy/__version__.py +1 -0
  7. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy/connector.py +5 -1
  8. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy/library.py +55 -1
  9. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy/property.py +44 -1
  10. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy/synchronizer.py +53 -25
  11. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3/launchpy.egg-info}/PKG-INFO +1 -1
  12. launchpy-0.4.3.post1/launchpy/__version__.py +0 -1
  13. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/LICENSE +0 -0
  14. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/README.md +0 -0
  15. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/docs/admin.md +0 -0
  16. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/docs/library.md +0 -0
  17. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/docs/main.md +0 -0
  18. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/docs/translator.md +0 -0
  19. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy/__init__.py +0 -0
  20. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy/admin.py +0 -0
  21. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy/config.py +0 -0
  22. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy/configs.py +0 -0
  23. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy/launchpy.py +0 -0
  24. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy.egg-info/SOURCES.txt +0 -0
  25. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy.egg-info/dependency_links.txt +0 -0
  26. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy.egg-info/requires.txt +0 -0
  27. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/launchpy.egg-info/top_level.txt +0 -0
  28. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/pyproject.toml +0 -0
  29. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/requirements.txt +0 -0
  30. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/setup.cfg +0 -0
  31. {launchpy-0.4.3.post1 → launchpy-0.4.3.post3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: launchpy
3
- Version: 0.4.3.post1
3
+ Version: 0.4.3.post3
4
4
  Summary: Adobe Launch API wrapper
5
5
  Home-page: https://github.com/pitchmuc/launchpy
6
6
  Author: Julien Piccini
@@ -26,7 +26,15 @@ lp.createConfigFile()
26
26
  This will create a JSON and you will need to fill it with the information available in your adobe io account.
27
27
 
28
28
  **NOTE**: scope in the config file has evolved over time so you can change it. Following this [link](https://www.adobe.io/authentication/auth-methods.html#!AdobeDocs/adobeio-auth/master/JWT/Scopes.md) to see the different scope possible.\
29
- By default, I will test the dev scope if the scope provided fails.
29
+ You can change the `scope` attribute for one of these values: `"https://ims-na1.adobelogin.com/s/ent_reactor_admin_sdk"` or `"https://ims-na1.adobelogin.com/s/ent_reactor_sdk"`
30
+ By default, I will test the dev scope if the scope provided fails. So normally nothing you need to do.
31
+
32
+ **NOTE 2**: There is a possibility till end of 2024 to use the JWT.\
33
+ launchpy supports both `jwt` and `oauthV2` authentication.\
34
+ By default the latest version will try to use the `oauthV2`.\
35
+ You can specify the keys generated in the config file by using the `auth_type` parameter.\
36
+ Possible values : `oauthV2` (default) or `jwt`
37
+
30
38
 
31
39
  ## 4. Import the configuration file
32
40
 
@@ -22,7 +22,7 @@ Here are shortlinks to the different methods explanation.
22
22
 
23
23
  ## Get methods
24
24
 
25
- When you have your property instance created, the methods that you may want to call first are the get methods. They never take any argument, they don’t actually need any argument.
25
+ When you have your property instance created, the methods that you may want to call first are the get methods. They never take any argument, they don't actually need any argument.
26
26
  The only thing important to know is that you must run the getRules() method before the getRuleComponents() method.
27
27
 
28
28
  The different get methods are the following :
@@ -82,6 +82,10 @@ The different get methods are the following :
82
82
  - created_at : it will be greater that this date ('2022-12-12T10:19:20.867Z')
83
83
  - updated_at : it will be greater that this date ('2022-12-12T10:19:20.867Z')
84
84
 
85
+ * **getLibrary** : Retrieve a specific library.
86
+ Argument:
87
+ * library : REQUIRED : Either the library name or the library ID
88
+
85
89
  * **getNotes** : Retrieve the note associated with the object pass to the method. Returns list.
86
90
  Arguments:
87
91
  * data: OPTIONAL : object that is associated with a Note (rule, data element, etc...)
@@ -191,6 +195,11 @@ The different delete methods are:
191
195
  Arguments:
192
196
  * dataElement_id : REQUIRED : Data Element ID that needs to be deleted
193
197
 
198
+ * **deleteLibrary** : Delete a specific library. It only works for library in development state.
199
+ Arguments:
200
+ * library : REQUIRED : Either the library ID or the library Name
201
+ * components : OPTIONAL : Boolean, if set to `True`, it will try to delete the different components in that library afterwards. Only rules and data elements would be deleted, no extension.
202
+
194
203
  ## Update methods
195
204
 
196
205
  As you can imagine the update methods enable you to update the different elements.\
@@ -4,7 +4,9 @@ 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
6
  ## 0.4.3
7
- * Refactor `Translator` class for removing `pandas` dependency
7
+ * Refactor `Translator` class for removing `pandas` dependency\
8
+ Patch:
9
+ * Fix an issue with dependency on `target_configs` attribute in synchronizor.
8
10
 
9
11
  ## 0.4.2
10
12
  * Supporting the Oauth Token V2 authentication
@@ -38,6 +38,8 @@ synchronizor = lp.Synchronizer(base='Prop1',targets=['Prop2'])
38
38
 
39
39
  ```
40
40
 
41
+ 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
+
41
43
  ## SyncComponent Method
42
44
 
43
45
  The `syncComponent` method can take 3 arguments.
@@ -166,18 +168,58 @@ An example on how the data element looks like:
166
168
  ```
167
169
  **NOTE** : The JSON representation should be flawless and the regular expression used as well, otherwise the script can break trying to decrypting it.
168
170
 
169
- ### Usage of Dynamic Component filter
171
+ ### Dynamic Component Filtering options
172
+
173
+ #### Data Elements
174
+
175
+ You can pass a Data Element name during the instanciation of the `Synchronizer`. This data element needs to resides in the base property and it will contain a valid JSON that will describe the different rules that will apply during the synchronization.\
176
+ The format of the JSON representation for the data element is [explained above](#dynamic-component-filter)\
177
+ The data element name needs to be passed in the `dynamicRuleComponent` parameter.
178
+
179
+ Hence the code would look like this for instantiation:
180
+ ```python
181
+
182
+ import launchpy as lp
183
+
184
+ lp.importConfigFile('config.json')
185
+ synchronizor = lp.Synchronizer(base='Prop1',targets=['Prop2'],dynamicRuleComponent='dataElementName')
186
+
187
+ ```
188
+
189
+ #### Code
190
+
191
+ You can also provide the JSON representation of the filter used for synchronization via a dictionary, directly in your notebook.\
192
+ A method called `dynamicFiltering` allows you to load a dynamic filter directly within the python application and do not require the creation of data element for it.
193
+
194
+ So after instantiation, you can now pass the rule such as:
195
+
196
+ ```python
197
+ import launchpy as lp
198
+
199
+ lp.importConfigFile('config.json')
200
+ synchronizor = lp.Synchronizer(base='Prop1',targets=['Prop2'])
201
+ myRules = [
202
+ {
203
+ 'name':'myName',
204
+ 'targetProperties':'.+ some condition .+ ',
205
+ 'exclComponents':[
206
+ 'DE - My Data Element',
207
+ 'RL - My Rule'
208
+ ],
209
+ 'inclComponents':[
210
+ 'somecomponent name'
211
+ ]
212
+ }
213
+ ]
170
214
 
171
- You would use that data element name during the instantiation of the synchronizer such as:
215
+ synchronizor.dynamicFiltering(myRules)
172
216
 
173
- ```python
174
- synchronizor = lp.Synchronizer(base='BaseProperty',targets=['Target Property 1',
175
- 'Target Property 2',
176
- 'Target Property 3'],
177
- dynamicRuleComponent='syncFilter')
178
217
  ```
179
- In this example above, `syncFilter` is the name of the data element containing that configuration.\
180
- Once your Synchronizer class has been instanciated, you can access 2 attributes in it to verify the correct setup.
218
+
219
+
220
+ ### Dynamic Component filter check
221
+
222
+ Once you have loaded your dynamic component rules in your Synchronizer class, you can access 2 attributes to verify the correct setup.
181
223
 
182
224
  * `dict_config`
183
225
  The `dict_config` attribute will be a copy of your data element setup, but a dictionary instead of an array where all of the target properties rule will be a key.\
@@ -0,0 +1 @@
1
+ __version__ = "0.4.3-3"
@@ -283,7 +283,7 @@ class AdobeRequest:
283
283
  status_code = {'error': 'Request Error'}
284
284
  return status_code
285
285
 
286
- def deleteData(self, endpoint: str, params: dict = None, headers: dict = None, *args, **kwargs):
286
+ def deleteData(self, endpoint: str, params: dict = None, data=None, headers: dict = None, *args, **kwargs):
287
287
  """
288
288
  Abstraction for deleting data
289
289
  """
@@ -294,6 +294,10 @@ class AdobeRequest:
294
294
  res = requests.delete(endpoint, headers=headers)
295
295
  elif params is not None:
296
296
  res = requests.delete(endpoint, headers=headers, params=params)
297
+ elif params is None and data is not None:
298
+ res = requests.delete(endpoint, headers=headers, data=json.dumps(data))
299
+ elif params is not None and data is not None:
300
+ res = requests.delete(endpoint, headers=headers, params=params, data=json.dumps(data))
297
301
  try:
298
302
  status_code = res.status_code
299
303
  except:
@@ -176,6 +176,24 @@ class Library:
176
176
  res = self.connector.patchData(self.endpoint +url, data=obj)
177
177
  return res
178
178
 
179
+ def removeDataElements(self,data_element_ids:list)->dict:
180
+ """
181
+ Take a list of data elements and remove them from the library.
182
+ Arguments:
183
+ data_element_ids: REQUIRED : list of data elements id
184
+ """
185
+ if self.state != 'development':
186
+ print('State is not development, cannot update relationships')
187
+ return None
188
+ obj = {'data': []}
189
+ if type(data_element_ids) == str:
190
+ data_element_ids = data_element_ids.split(' ')
191
+ for ids in data_element_ids:
192
+ obj['data'].append({"id": ids,"type": "data_elements"})
193
+ url = f'/libraries/{self.id}/relationships/data_elements'
194
+ print(obj)
195
+ res = self.connector.deleteData(self.endpoint +url, data=obj)
196
+ return res
179
197
 
180
198
  def addRules(self, rules_ids: list)->dict:
181
199
  """
@@ -216,6 +234,25 @@ class Library:
216
234
  res = self.connector.patchData(self.endpoint + url, data=obj)
217
235
  return res
218
236
 
237
+ def removeRules(self,rules_ids:list)->dict:
238
+ """
239
+ Remove the rules that are passed.
240
+ Arguments:
241
+ rules_ids: REQUIRED : list of rules id
242
+ """
243
+ if self.state != 'development':
244
+ print('State is not development, cannot add relationships')
245
+ return None
246
+ obj = {'data': []}
247
+ if type(rules_ids) == str:
248
+ rules_ids = rules_ids.split(' ')
249
+ for ids in rules_ids:
250
+ obj['data'].append({"id": ids, "type": "rules"})
251
+ url = f'/libraries/{self.id}/relationships/rules'
252
+ res = self.connector.deleteData(self.endpoint + url, data=obj)
253
+ return res
254
+
255
+
219
256
  def addExtensions(self, extensions_ids: list)->object:
220
257
  """
221
258
  Take a list of extension id and attach them to the library.
@@ -291,9 +328,11 @@ class Library:
291
328
  new_env = self.connector.getData(self.endpoint+path)
292
329
  return new_env
293
330
 
294
- def updateLibrary(self)->dict:
331
+ def updateLibrary(self,empty:bool=False)->dict:
295
332
  """
296
333
  Update the library
334
+ Arguments:
335
+ empty : OPTIONAL : You want to empty the library.
297
336
  """
298
337
  path = f'/libraries/{self.id}'
299
338
  data = {
@@ -305,6 +344,21 @@ class Library:
305
344
  }
306
345
  }
307
346
  }
347
+ if True:
348
+ data['relationships'] = {
349
+ "data_elements": {
350
+ "data": []
351
+ },
352
+ "rules": {
353
+ "data": []
354
+ },
355
+ "extensions": {
356
+ "data": []
357
+ },
358
+ "environment":{
359
+ "data":{},
360
+ }
361
+ }
308
362
  res = self.connector.patchData(self.endpoint+path,data=data)
309
363
  return res
310
364
 
@@ -48,7 +48,7 @@ class Property:
48
48
  self._Rules = data['links']['rules']
49
49
  self._RuleComponents = 'https://reactor.adobe.io/properties/' + \
50
50
  self.id + '/rule_components'
51
- self._Host = 'https://reactor.adobe.io//properties/' + \
51
+ self._Host = 'https://reactor.adobe.io/properties/' + \
52
52
  data['id']+'/hosts'
53
53
  self._Note = 'https://reactor.adobe.io/notes/'
54
54
  self._Environments = data['links']['environments']
@@ -507,6 +507,20 @@ class Property:
507
507
  data = data + append_data
508
508
  return data
509
509
 
510
+ def getLibrary(self,libraryId:str=None)->dict:
511
+ """
512
+ get a library based on its ID.
513
+ Arguments:
514
+ libraryId : REQUIRED : Library ID to be retrieved
515
+ """
516
+ if libraryId is None:
517
+ raise ValueError("Require a library ID")
518
+ path = f"/libraries/{libraryId}"
519
+ res = self.connector.getData(self.endpoint+path)
520
+ if 'data' in res.keys():
521
+ return res['data']
522
+ return res
523
+
510
524
  def getNotes(self, data: object)->list:
511
525
  """
512
526
  Retrieve the note associated with the object pass to the method. Returns list.
@@ -1145,6 +1159,35 @@ class Property:
1145
1159
  'https://reactor.adobe.io/environments/'+env_id)
1146
1160
  return data
1147
1161
 
1162
+ def deleteLibrary(self,library:str=None,components:bool=False)->str:
1163
+ """
1164
+ Delete a Library based on its name or ID.
1165
+ Arguments:
1166
+ library : REQUIRED : Either the ID of the library or the name of the library.
1167
+ components : OPTIONAL : If set to True, it will try to delete all components inside that library.
1168
+ """
1169
+ if library is None:
1170
+ raise ValueError("Require at least library ID")
1171
+ libraries = self.getLibraries()
1172
+ librariesIds = [lib['id'] for lib in libraries]
1173
+ librariesNameId = {lib['attributes']['name']:lib['id'] for lib in libraries}
1174
+ if library in librariesIds:
1175
+ libraryId = library
1176
+ else:
1177
+ libraryId = librariesNameId[library]
1178
+ path = f"/libraries/{libraryId}"
1179
+ if components==True:
1180
+ myLib = self.getLibrary(libraryId)
1181
+ libClass = Library(myLib)
1182
+ rules = libClass.getRules()
1183
+ dataelements = libClass.getDataElements()
1184
+ res = self.connector.deleteData('https://reactor.adobe.io/'+path)
1185
+ for rule in rules:
1186
+ self.deleteRule(rule['id'])
1187
+ for de in dataelements:
1188
+ self.deleteDataElement(de['id'])
1189
+ return res
1190
+
1148
1191
  def extensionsInfo(data: list)->dict:
1149
1192
  """
1150
1193
  Return a dictionary from the list provided from the extensions request.
@@ -44,21 +44,8 @@ class Synchronizer:
44
44
  self.translator = Translator()
45
45
  self.translator.setBaseExtensions(self.base['extensions'],self.base['name'])
46
46
  self.translator.setBaseRules(self.base['rules'],self.base['name'])
47
- self.dict_config = {}
48
- if kwargs.get("dynamicRuleComponent",None) is not None:
49
- configRules = [de for de in self.base['dataElements'] if de['attributes']['name'] == kwargs.get("dynamicRuleComponent",None)]
50
- if len(configRules)==1:
51
- configRules = configRules[0]
52
- else:
53
- raise ValueError("The dynamicRuleComponent is using a value that does not match any data element.")
54
- codeConfig:list = json.loads(json.loads(configRules['attributes']['settings'])['source'])## list expected from the code
55
- for rule in codeConfig:
56
- self.dict_config[rule['targetProperties']] = {'inclComponents':[],'exclComponents':[]}
57
- if 'inclComponents' in rule.keys():
58
- self.dict_config[rule['targetProperties']]['inclComponents'] += rule['inclComponents']
59
- if 'exclComponents' in rule.keys():
60
- self.dict_config[rule['targetProperties']]['exclComponents'] += rule['exclComponents']
61
47
  self.targets = {}
48
+ self.target_configs = {}
62
49
  for target in targets:
63
50
  tmp_target = [prop for prop in properties if prop['attributes']['name'] == target]
64
51
  if len(tmp_target) == 0:
@@ -73,19 +60,60 @@ class Synchronizer:
73
60
  self.translator.extendRules(self.targets[target]['rules'],self.targets[target]['name'])
74
61
  else:
75
62
  self.translator.rules[self.targets[target]['name']] = None
76
- self.target_configs = {}
77
- if len(self.dict_config)>0: ## verify that there are rules
78
- for rule in self.dict_config.keys():
79
- for target in self.targets.keys():
80
- if re.search(rule,target) is not None:
81
- if target in self.target_configs.keys():
63
+ if kwargs.get("dynamicRuleComponent",None) is not None:
64
+ configRules = [de for de in self.base['dataElements'] if de['attributes']['name'] == kwargs.get("dynamicRuleComponent",None)]
65
+ if len(configRules)==1:
66
+ configRules = configRules[0]
67
+ else:
68
+ raise ValueError("The dynamicRuleComponent is using a value that does not match any data element.")
69
+ codeConfig:list = json.loads(json.loads(configRules['attributes']['settings'])['source'])## list expected from the code
70
+ self.dynamicFiltering(codeConfig)
71
+
72
+ def dynamicFiltering(self,dynamicFilterJSON:dict,override:bool=True)->None:
73
+ """
74
+ Building the dynamic rule filtering for each of the target properties.
75
+ Base on this code format
76
+ [
77
+ {
78
+ 'name':'myName',
79
+ 'targetProperties':'.+ some condition .+ ',
80
+ 'exclComponents':[
81
+ 'DE - My Data Element',
82
+ 'RL - My Rule'
83
+ ],
84
+ 'inclComponents':[
85
+ 'somecomponent name'
86
+ ]
87
+ }
88
+ ]
89
+ Arguments:
90
+ dynamicFilterJSON : REQUIRED : The JSON representation of your rules.
91
+ override : OPTIONAL : Replace the existing dynamic filtering. Default True.
92
+ If set to False, apply the logic on top of the existing one loaded before.
93
+ """
94
+ if override == True:
95
+ self.dict_config = {} ## global dynamic rule filtering
96
+ self.target_configs = {} ## dynamic rule filtering apply to target
97
+ for rule in dynamicFilterJSON:
98
+ self.dict_config[rule['targetProperties']] = {'inclComponents':[],'exclComponents':[]}
99
+ if 'inclComponents' in rule.keys():
100
+ self.dict_config[rule['targetProperties']]['inclComponents'] += rule['inclComponents']
101
+ if 'exclComponents' in rule.keys():
102
+ self.dict_config[rule['targetProperties']]['exclComponents'] += rule['exclComponents']
103
+ for rule in self.dict_config.keys():
104
+ for target in self.targets.keys():
105
+ if re.search(rule,target) is not None:
106
+ if target in self.target_configs.keys():
107
+ if self.dict_config[rule]['exclComponents'] not in self.target_configs[target]['exclComponents']:
82
108
  self.target_configs[target]['exclComponents'] += deepcopy(self.dict_config[rule]['exclComponents'])
109
+ if self.dict_config[rule]['inclComponents'] not in self.target_configs[target]['inclComponents']:
83
110
  self.target_configs[target]['inclComponents'] += deepcopy(self.dict_config[rule]['inclComponents'])
84
- else:
85
- self.target_configs[target] = {
86
- 'exclComponents': deepcopy(self.dict_config[rule]['exclComponents']),
87
- 'inclComponents': deepcopy(self.dict_config[rule]['inclComponents'])
88
- }
111
+ else:
112
+ self.target_configs[target] = {
113
+ 'exclComponents': deepcopy(self.dict_config[rule]['exclComponents']),
114
+ 'inclComponents': deepcopy(self.dict_config[rule]['inclComponents'])
115
+ }
116
+
89
117
 
90
118
  def syncComponent(self,componentName:str=None,componentId:str=None,publishedVersion:bool=False,**kwargs)->None:
91
119
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: launchpy
3
- Version: 0.4.3.post1
3
+ Version: 0.4.3.post3
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.3-1"
File without changes
File without changes
File without changes
File without changes