launchpy 0.4.7.post1__tar.gz → 0.4.8__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.
- {launchpy-0.4.7.post1/launchpy.egg-info → launchpy-0.4.8}/PKG-INFO +9 -9
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/docs/cli.md +24 -3
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/docs/releases.md +9 -1
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/docs/synchronizer.md +36 -10
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy/__init__.py +0 -1
- launchpy-0.4.8/launchpy/__version__.py +1 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy/cli/__main__.py +74 -21
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy/config.py +0 -2
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy/configs.py +7 -37
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy/connector.py +0 -2
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy/library.py +24 -7
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy/property.py +10 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy/synchronizer.py +192 -34
- {launchpy-0.4.7.post1 → launchpy-0.4.8/launchpy.egg-info}/PKG-INFO +9 -9
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy.egg-info/requires.txt +1 -2
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/pyproject.toml +9 -9
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/requirements.txt +3 -5
- launchpy-0.4.7.post1/launchpy/__version__.py +0 -1
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/LICENSE +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/README.md +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/docs/admin.md +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/docs/getstarted.md +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/docs/library.md +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/docs/main.md +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/docs/property.md +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/docs/translator.md +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy/admin.py +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy/cli/__init__.py +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy/launchpy.py +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy.egg-info/SOURCES.txt +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy.egg-info/dependency_links.txt +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy.egg-info/entry_points.txt +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/launchpy.egg-info/top_level.txt +0 -0
- {launchpy-0.4.7.post1 → launchpy-0.4.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: launchpy
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.8
|
|
4
4
|
Summary: Adobe Launch API wrapper
|
|
5
5
|
Author-email: Julien Piccini <piccini.julien@gmail.com>
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -8,26 +8,26 @@ Project-URL: homepage, https://github.com/pitchmuc/launchpy
|
|
|
8
8
|
Project-URL: changelog, https://github.com/pitchmuc/launchpy/blob/master/docs/releases.md
|
|
9
9
|
Keywords: adobe,analytics,API,python
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved ::
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python
|
|
14
14
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
20
|
Classifier: Development Status :: 4 - Beta
|
|
20
|
-
Requires-Python: >=3.
|
|
21
|
+
Requires-Python: >=3.10
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
License-File: LICENSE
|
|
23
24
|
Requires-Dist: pandas
|
|
24
25
|
Requires-Dist: requests
|
|
25
|
-
Requires-Dist: PyJWT
|
|
26
|
-
Requires-Dist: PyJWT[crypto]
|
|
27
26
|
Requires-Dist: pathlib2
|
|
28
27
|
Requires-Dist: pathlib
|
|
29
28
|
Requires-Dist: aepp
|
|
30
29
|
Requires-Dist: rich
|
|
30
|
+
Requires-Dist: deprecated
|
|
31
31
|
Dynamic: license-file
|
|
32
32
|
|
|
33
33
|
# Launchpy
|
|
@@ -43,6 +43,7 @@ This document provides an overview of the command line interface (CLI) for `laun
|
|
|
43
43
|
- [get\_base\_libraries](#get_base_libraries)
|
|
44
44
|
- [get\_base\_library](#get_base_library)
|
|
45
45
|
- [sync\_from\_library](#sync_from_library)
|
|
46
|
+
- [Behaviors for sync\_from\_library](#behaviors-for-sync_from_library)
|
|
46
47
|
- [create\_libraries](#create_libraries)
|
|
47
48
|
|
|
48
49
|
|
|
@@ -479,8 +480,23 @@ synchronizer:Base_Property_Name> get_base_library -n "Library Name"
|
|
|
479
480
|
#### sync_from_library
|
|
480
481
|
This command allows you to synchronize a library from the base property to the target properties.\
|
|
481
482
|
**IMPORTANT**:By default it will use the published version of the elements that are part of the library.
|
|
482
|
-
`-n`, `--name`:
|
|
483
|
-
`-id`, `--id`:
|
|
483
|
+
* `-n`, `--name`: Name of the library to sync from. This is a required parameter.
|
|
484
|
+
* `-id`, `--id`: ID of the library to sync from (overrides name if both provided). This is an optional parameter.
|
|
485
|
+
* `-f`, `--force`: Boolean. Create the component if it does not exist. Default True. Possible values: True, False
|
|
486
|
+
* `-ll`, `--library_linked`: Boolean. Whether to use library linked components or not. If set to True, the sync will be done using the library linked components. If set to False, the sync will be done using the component IDs in the library. Default True. Possible values: True, False
|
|
487
|
+
* `-p`, `--published`: Boolean. Sync the latest published version of the component instead of the current version. Default False. Possible values: True, False
|
|
488
|
+
* `-dr`, `--dry_run`: Boolean. If set to True, will only check the sync status of the components without actually syncing them. Default False. Possible values: True, False
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
synchronizer:Base_Property_Name> sync_from_library -id "LibraryId" -ll False -p True -dr True
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
### Behaviors for sync_from_library
|
|
496
|
+
By default, the method will sync the components present in the library as they are in the library.
|
|
497
|
+
If the `published` is set to True, it will try to sync the version that has been published in the base property to the targets.\
|
|
498
|
+
If the `library_linked` is set to `False` and the `published` is set to `False` (default), it will try to sync the latest version of the components present in the base property library to the targets.\
|
|
499
|
+
|
|
484
500
|
|
|
485
501
|
```bash
|
|
486
502
|
synchronizer:Base_Property_Name> sync_from_library -n "Library Name"
|
|
@@ -490,9 +506,14 @@ synchronizer:Base_Property_Name> sync_from_library -n "Library Name"
|
|
|
490
506
|
This command allows you to create a library in the destination properties with the elements you have sync.\
|
|
491
507
|
Arguments:
|
|
492
508
|
`name`: Name for the new library to be created in the destination properties. This is a required parameter.
|
|
493
|
-
`-env`, `--environment`:
|
|
509
|
+
`-env`, `--environment`: String. If set to `True`, it will try to find a free environment and assign it to the library. If none is available, it will not assign any environment to the library. If the name of the environment is provided, it will try to assign it to that library. If the environment is already used in a Library, it will remove that association and assign it to the new library. Default False. Possible values: `True`, `False` or a `string` which is the (partial) name of the environment you want to assign to the library.
|
|
494
510
|
|
|
495
511
|
```bash
|
|
496
512
|
synchronizer:Base_Property_Name> create_library "Library Name" -env True
|
|
497
513
|
```
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
synchronizer:Base_Property_Name> create_library "Library Name" -env "Environment Name
|
|
517
|
+
|
|
518
|
+
```
|
|
498
519
|
|
|
@@ -3,8 +3,16 @@
|
|
|
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.8
|
|
7
|
+
* Adding the `clear` method in CLI
|
|
8
|
+
* Adding `syncFromLibrary` in synchronizer[doc](./synchronizer.md#syncFromLibrary).
|
|
9
|
+
* Adding `sync_from_library` command in CLI[doc](./cli.md#sync_from_library-command).
|
|
10
|
+
* Cleaning the JWT components and requirements
|
|
11
|
+
* Adding the `definition` attribute in the Library class
|
|
12
|
+
* Adding capability to define a specific environment name when creating a library in synchronizer
|
|
13
|
+
|
|
6
14
|
## 0.4.7
|
|
7
|
-
*
|
|
15
|
+
* Modify `getRuleComponents` to `getRulesComponents` as it can return all the rules components if no rule is specified. Kept `getRuleComponents` as an alias for backward compatibility and also provide a single rule components retrieval.
|
|
8
16
|
* Adding the `CLI` layer. [Documentation to CLI](cli.md)
|
|
9
17
|
|
|
10
18
|
## 0.4.6
|
|
@@ -6,6 +6,16 @@ You can recreate a (more advanced) version that supports additional methods or e
|
|
|
6
6
|
|
|
7
7
|
The Synchronizer will allow you to specify a template property and target property **within the same organization** in order to sync these properties rules and data elements.
|
|
8
8
|
|
|
9
|
+
## Menu
|
|
10
|
+
|
|
11
|
+
[MENUI](#menu)
|
|
12
|
+
[Pre Requisite](#pre-requisite)
|
|
13
|
+
[Instantiation](#instantiation)
|
|
14
|
+
[SyncComponent Method](#syncComponent-Method)
|
|
15
|
+
[SyncComponents](#syncComponents)
|
|
16
|
+
[SyncFromLibrary](#syncFromLibrary)
|
|
17
|
+
[createTargetsLibrary](#createTargetsLibrary)
|
|
18
|
+
[renameComponent](#renameComponent)
|
|
9
19
|
|
|
10
20
|
|
|
11
21
|
## PRE REQUISITE
|
|
@@ -85,7 +95,6 @@ synchronizor = lp.Synchronizer(base=base,targets=['another Property Name to sync
|
|
|
85
95
|
|
|
86
96
|
```
|
|
87
97
|
|
|
88
|
-
|
|
89
98
|
### Additional options
|
|
90
99
|
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)
|
|
91
100
|
|
|
@@ -122,7 +131,7 @@ The method will delete all rule components existing in the Target property for t
|
|
|
122
131
|
It will then copy all template rule component existing in that base property to the target properties.\
|
|
123
132
|
No history is saved from the existing rule component, which will make revision check very hard.
|
|
124
133
|
|
|
125
|
-
## SyncComponents
|
|
134
|
+
## SyncComponents
|
|
126
135
|
|
|
127
136
|
You can pass down a list of component names or componentIds.\
|
|
128
137
|
It takes 3 arguments:\
|
|
@@ -131,10 +140,28 @@ Arguments:
|
|
|
131
140
|
* componentsId : REQUIRED : The list of component ID to sync*
|
|
132
141
|
* publishedVersion : OPTIONAL : if you want to take the version that has been published
|
|
133
142
|
|
|
143
|
+
## syncFromLibrary
|
|
144
|
+
|
|
145
|
+
This method allows you to take a library in your base property and sync all of the components in that library to the target properties.
|
|
146
|
+
Arguments:
|
|
147
|
+
* library : REQUIRED : the name or the ID of the library to sync from.
|
|
148
|
+
* state : OPTIONAL : the state of the library to sync from. Default: 'published', possible states: "development", "submitted", "approved", "rejected", "published"
|
|
149
|
+
* force : OPTIONAL : If set to True, it will create the element sync if it does not exist in the target property. Set to `False`, it will prevent that behavior. Default: `True`.
|
|
150
|
+
* libraryLinked : OPTIONAL : If set to True, it will use library linked components for comparison. Default: `True`.
|
|
151
|
+
* publishedVersion : OPTIONAL : If set to True, it will compare the components to the published version in the target properties. Default: `False`.
|
|
152
|
+
* dryRun : OPTIONAL : If set to True, will only check the sync status of the components without actually syncing them. Default `False`. Possible values: `True`, `False`
|
|
153
|
+
|
|
154
|
+
**Note**: A library is not created automatically, so you will need to create a new one via the `createTargetsLibrary` method.
|
|
155
|
+
|
|
156
|
+
### Behaviors for syncFromLibrary
|
|
157
|
+
By default, the method will sync the components present in the library as they are in the library.
|
|
158
|
+
If the `publishedVersion` is set to True, it will try to sync the version that has been published in the base property to the targets.\
|
|
159
|
+
If the `libraryLinked` is set to `False` and the `publishedVersion` is set to `False` (default), it will try to sync the latest version of the components present in the base property library to the targets.\
|
|
160
|
+
|
|
134
161
|
## createTargetsLibrary
|
|
135
162
|
|
|
136
163
|
The synchronizer will automatically track the component that has been updated via the module.\
|
|
137
|
-
When you are done synchronizing your components, you can run this method to create a library, that will contain all
|
|
164
|
+
When you are done synchronizing your components, you can run this method to create a library, that will contain all the elements you synchronized.\
|
|
138
165
|
The library will be available within all of your synch properties.
|
|
139
166
|
|
|
140
167
|
You can pass a name to define the library name that is created.\
|
|
@@ -142,7 +169,10 @@ The name is then used to match if a library already exist with that name, in the
|
|
|
142
169
|
Note that the match looked for is using regular expression (`re.search`) to find a match in the existing libraries name.\
|
|
143
170
|
Arguments:
|
|
144
171
|
* name : REQUIRED : Name of the library to create or to use.
|
|
145
|
-
* assignEnv : OPTIONAL : If you want to automatically assign an environment to the library and build it. Default False.
|
|
172
|
+
* assignEnv : OPTIONAL : If you want to automatically assign an environment to the library and build it. Default False.
|
|
173
|
+
Possible values: `True`, `False` or a `string` which is the name of the environment you want to assign to the library.\
|
|
174
|
+
If `True`, it will try to find a free environment and assign it to the library. If none is available, it will not assign any environment to the library.\
|
|
175
|
+
If the name of the environment is provided, it will try to assign it to that library. If the environment is already used in a Library, it will remove that association and assign it to the new library. (A print statement will be printed to warn about that change of association)
|
|
146
176
|
|
|
147
177
|
## renameComponent
|
|
148
178
|
|
|
@@ -198,14 +228,10 @@ For Data Elements:
|
|
|
198
228
|
|
|
199
229
|
#### Additional elements
|
|
200
230
|
On top of the different properties check, additional information will be provided for the base component:
|
|
201
|
-
* `base-
|
|
231
|
+
* `base-published`: If the current component check is published.\
|
|
202
232
|
**IMPORTANT NOTE**: When you using the `publishedVersion` parameter set to True, most of the element will be set to Published. In reverse, it is not because the component current version is not published that it has never been published. Use the `publishedVersion` to ensure this.
|
|
203
233
|
* `base-enabled`: If the current component check is enabled.
|
|
204
|
-
|
|
205
|
-
* `Latest`: If the component retrieved is the latest latest version
|
|
206
|
-
* `Unknown`: If the component retrieved cannot be evaluated to know if it is the latest.
|
|
207
|
-
* `Draft` : If the component retrieved is not the latest and it has not yet been published
|
|
208
|
-
* `Edited`: If the component retrieved is not the latest and has been published before.
|
|
234
|
+
|
|
209
235
|
|
|
210
236
|
#### Similar vs Same
|
|
211
237
|
The idea of checking the similarity of 2 components is partially done as not everything can be tested during the check.\
|
|
@@ -48,7 +48,6 @@ async def process_all_rules(rules_list, folder, header):
|
|
|
48
48
|
async with httpx.AsyncClient(timeout=timeout, limits=limits) as client:
|
|
49
49
|
# Create a list of "tasks" to run
|
|
50
50
|
tasks = [__extractRuleComponents__(rule, client, folder, header) for rule in rules_list]
|
|
51
|
-
|
|
52
51
|
# 'gather' runs them all concurrently
|
|
53
52
|
await asyncio.gather(*tasks)
|
|
54
53
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.8"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import launchpy
|
|
1
|
+
import launchpy
|
|
3
2
|
import argparse, cmd, shlex, json
|
|
4
3
|
from functools import wraps
|
|
5
4
|
from rich.console import Console
|
|
@@ -10,6 +9,17 @@ import pandas as pd
|
|
|
10
9
|
from datetime import datetime, timedelta
|
|
11
10
|
from typing import Any, Concatenate, ParamSpec, ParamSpecKwargs
|
|
12
11
|
from collections.abc import Callable
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
def str2bool(v):
|
|
15
|
+
if isinstance(v, bool):
|
|
16
|
+
return v
|
|
17
|
+
if v.lower() in ('yes', 'true', 't', 'y', '1'):
|
|
18
|
+
return True
|
|
19
|
+
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
|
|
20
|
+
return False
|
|
21
|
+
else:
|
|
22
|
+
raise argparse.ArgumentTypeError('Boolean value expected.')
|
|
13
23
|
|
|
14
24
|
P = ParamSpec("P")
|
|
15
25
|
|
|
@@ -33,6 +43,11 @@ def login_required(f:Callable[Concatenate["MainShell", P], None]) -> Callable[Co
|
|
|
33
43
|
return f(self, *args, **kwargs)
|
|
34
44
|
return wrapper
|
|
35
45
|
|
|
46
|
+
def clear_terminal():
|
|
47
|
+
"""Clears the terminal screen based on the OS."""
|
|
48
|
+
# os.name is 'nt' for Windows, 'posix' for Mac/Linux
|
|
49
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
50
|
+
|
|
36
51
|
console = Console()
|
|
37
52
|
|
|
38
53
|
class PropertyCLI(cmd.Cmd):
|
|
@@ -48,7 +63,7 @@ class PropertyCLI(cmd.Cmd):
|
|
|
48
63
|
def do_get_extensions(self, arg):
|
|
49
64
|
"""Get all extensions in the property and list them."""
|
|
50
65
|
parser = argparse.ArgumentParser(prog='get_extensions', add_help=True)
|
|
51
|
-
parser.add_argument("-s", "--save", help="Boolean. Save extensions to a CSV file. Default False. Possible values: True, False", type=
|
|
66
|
+
parser.add_argument("-s", "--save", help="Boolean. Save extensions to a CSV file. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
52
67
|
try:
|
|
53
68
|
args = parser.parse_args(shlex.split(arg))
|
|
54
69
|
extensions = self.property.getExtensions()
|
|
@@ -82,7 +97,7 @@ class PropertyCLI(cmd.Cmd):
|
|
|
82
97
|
"""Get details for a specific extension by name."""
|
|
83
98
|
parser = argparse.ArgumentParser(prog='get_extension', add_help=True)
|
|
84
99
|
parser.add_argument("name", help="Name of the extension to get details for", type=str)
|
|
85
|
-
parser.add_argument("-s", "--save", help="Boolean. Save extension details to a JSON file. Default False. Possible values: True, False", type=
|
|
100
|
+
parser.add_argument("-s", "--save", help="Boolean. Save extension details to a JSON file. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
86
101
|
try:
|
|
87
102
|
args = parser.parse_args(shlex.split(args))
|
|
88
103
|
extensions = self.property.getExtensions()
|
|
@@ -107,7 +122,7 @@ class PropertyCLI(cmd.Cmd):
|
|
|
107
122
|
"""Get all rules for the property."""
|
|
108
123
|
parser = argparse.ArgumentParser(prog='get_rules', add_help=True)
|
|
109
124
|
parser.add_argument("-n", "--name", help="Filter rules by name (partial match, non-case sensitive)", type=str, default=None)
|
|
110
|
-
parser.add_argument("-s", "--save", help="Boolean. Save rules to a CSV file. Default False. Possible values: True, False", type=
|
|
125
|
+
parser.add_argument("-s", "--save", help="Boolean. Save rules to a CSV file. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
111
126
|
try:
|
|
112
127
|
args = parser.parse_args(shlex.split(arg))
|
|
113
128
|
rules = self.property.getRules()
|
|
@@ -143,7 +158,7 @@ class PropertyCLI(cmd.Cmd):
|
|
|
143
158
|
parser = argparse.ArgumentParser(prog='get_rule', add_help=True)
|
|
144
159
|
parser.add_argument("-n", "--name", help="Name of the rule to get details for", type=str)
|
|
145
160
|
parser.add_argument("-id", "--id", help="ID of the rule to get details for (overrides name if both provided)", type=str)
|
|
146
|
-
parser.add_argument("-s", "--save", help="Boolean. Save rule details to a JSON file. Default False. Possible values: True, False", type=
|
|
161
|
+
parser.add_argument("-s", "--save", help="Boolean. Save rule details to a JSON file. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
147
162
|
try:
|
|
148
163
|
args = parser.parse_args(shlex.split(arg))
|
|
149
164
|
if self.rules is None:
|
|
@@ -180,7 +195,7 @@ class PropertyCLI(cmd.Cmd):
|
|
|
180
195
|
parser = argparse.ArgumentParser(prog='get_rules_components', add_help=True)
|
|
181
196
|
parser.add_argument("-rn", "--rule_name", help="(Partial) Name of the rule to get components for", type=str,default=None)
|
|
182
197
|
parser.add_argument("-rid", "--rule_id", help="ID of the rule to get components for (overrides rule_name if both provided)", type=str, default=None)
|
|
183
|
-
parser.add_argument("-s", "--save", help="Boolean. Save components to a CSV file. Default False. Possible values: True, False", type=
|
|
198
|
+
parser.add_argument("-s", "--save", help="Boolean. Save components to a CSV file. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
184
199
|
try:
|
|
185
200
|
args = parser.parse_args(shlex.split(arg))
|
|
186
201
|
if self.rules_components is None:
|
|
@@ -244,7 +259,7 @@ class PropertyCLI(cmd.Cmd):
|
|
|
244
259
|
parser = argparse.ArgumentParser(prog='get_rule_components', add_help=True)
|
|
245
260
|
parser.add_argument("-rn", "--rule_name", help="(Partial) Name of the rule to get components for", type=str, default=None)
|
|
246
261
|
parser.add_argument("-rid", "--rule_id", help="ID of the rule to get components for (overrides rule_name if both provided)", type=str, default=None)
|
|
247
|
-
parser.add_argument("-s", "--save", help="Boolean. Save components to a JSON file. Default False. Possible values: True, False", type=
|
|
262
|
+
parser.add_argument("-s", "--save", help="Boolean. Save components to a JSON file. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
248
263
|
try:
|
|
249
264
|
args = parser.parse_args(shlex.split(arg))
|
|
250
265
|
rule = None ## fallback
|
|
@@ -285,7 +300,7 @@ class PropertyCLI(cmd.Cmd):
|
|
|
285
300
|
"""Get all data elements, or only the ones matching a name filter, for the property. Can save them in CSV file."""
|
|
286
301
|
parser = argparse.ArgumentParser(prog='get_data_elements', add_help=True)
|
|
287
302
|
parser.add_argument("-n", "--name", help="Filter data elements by name (partial match, non-case sensitive)", type=str, default=None)
|
|
288
|
-
parser.add_argument("-s", "--save", help="Boolean. Save data elements to a CSV file. Default False. Possible values: True, False", type=
|
|
303
|
+
parser.add_argument("-s", "--save", help="Boolean. Save data elements to a CSV file. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
289
304
|
try:
|
|
290
305
|
args = parser.parse_args(shlex.split(args))
|
|
291
306
|
des = self.property.getDataElements()
|
|
@@ -320,7 +335,7 @@ class PropertyCLI(cmd.Cmd):
|
|
|
320
335
|
"""Get details for a specific data element by name."""
|
|
321
336
|
parser = argparse.ArgumentParser(prog='get_data_element', add_help=True)
|
|
322
337
|
parser.add_argument("name", help="Name of the data element to get details for", type=str)
|
|
323
|
-
parser.add_argument("-s", "--save", help="Boolean. Save data element details to a JSON file. Default False. Possible values: True, False", type=
|
|
338
|
+
parser.add_argument("-s", "--save", help="Boolean. Save data element details to a JSON file. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
324
339
|
try:
|
|
325
340
|
args = parser.parse_args(shlex.split(args))
|
|
326
341
|
if self.data_elements is None:
|
|
@@ -349,7 +364,7 @@ class PropertyCLI(cmd.Cmd):
|
|
|
349
364
|
parser = argparse.ArgumentParser(prog='get_latest_published_version', add_help=True)
|
|
350
365
|
parser.add_argument("-n", "--name", help="Name of the component to get the latest published version for", type=str, default=None)
|
|
351
366
|
parser.add_argument("-t", "--type", help="Type of the component (e.g. 'rule', 'data_element', 'extension') to get the latest published version for. Default None", type=str, default=None)
|
|
352
|
-
parser.add_argument("-s", "--save", help="Boolean. Save the latest published version details to a JSON file. Default False. Possible values: True, False", type=
|
|
367
|
+
parser.add_argument("-s", "--save", help="Boolean. Save the latest published version details to a JSON file. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
353
368
|
try:
|
|
354
369
|
args = parser.parse_args(shlex.split(arg))
|
|
355
370
|
if args.type and args.name is None:
|
|
@@ -419,7 +434,7 @@ class PropertyCLI(cmd.Cmd):
|
|
|
419
434
|
def do_get_libraries(self, arg: Any):
|
|
420
435
|
"""Get all libraries for the property."""
|
|
421
436
|
parser = argparse.ArgumentParser(prog='get_libraries', add_help=True)
|
|
422
|
-
parser.add_argument("-s", "--save", help="Boolean. Save libraries to a CSV file. Default False. Possible values: True, False", type=
|
|
437
|
+
parser.add_argument("-s", "--save", help="Boolean. Save libraries to a CSV file. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
423
438
|
parser.add_argument("-st", "--state", help="Filter by library state. Possible values: 'development' (default), 'submitted', 'approved', 'rejected', 'published'", type=str, default="development")
|
|
424
439
|
try:
|
|
425
440
|
args = parser.parse_args(shlex.split(arg))
|
|
@@ -507,6 +522,10 @@ class PropertyCLI(cmd.Cmd):
|
|
|
507
522
|
except SystemExit:
|
|
508
523
|
return
|
|
509
524
|
|
|
525
|
+
def do_clear(self, arg) -> None:
|
|
526
|
+
"""Clear the terminal screen."""
|
|
527
|
+
clear_terminal()
|
|
528
|
+
|
|
510
529
|
def do_exit(self, arg):
|
|
511
530
|
"""Return to the main menu."""
|
|
512
531
|
console.print(Panel("Returning to main menu..."),style="blue")
|
|
@@ -529,7 +548,7 @@ class SynchronizerCLI(cmd.Cmd):
|
|
|
529
548
|
parser = argparse.ArgumentParser(prog='check_component', add_help=True)
|
|
530
549
|
parser.add_argument("-n", "--name", help="Name of the component to check", type=str)
|
|
531
550
|
parser.add_argument("-id", "--id", help="ID of the component to check (overrides name if both provided)", type=str)
|
|
532
|
-
parser.add_argument("-p", "--published", help="Boolean. Check the latest published version of the component instead of the current version. Default False. Possible values: True, False", type=
|
|
551
|
+
parser.add_argument("-p", "--published", help="Boolean. Check the latest published version of the component instead of the current version. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
533
552
|
try:
|
|
534
553
|
args = parser.parse_args(shlex.split(ars))
|
|
535
554
|
if args.id is not None:
|
|
@@ -548,8 +567,8 @@ class SynchronizerCLI(cmd.Cmd):
|
|
|
548
567
|
parser = argparse.ArgumentParser(prog='sync', add_help=True)
|
|
549
568
|
parser.add_argument("-n", "--name", help="Name of the component to sync", type=str)
|
|
550
569
|
parser.add_argument("-id", "--id", help="ID of the component to sync (overrides name if both provided)", type=str)
|
|
551
|
-
parser.add_argument("-p", "--published", help="Boolean. Sync the latest published version of the component instead of the current version. Default False. Possible values: True, False", type=
|
|
552
|
-
parser.add_argument("-f", "--force", help="Boolean. Create the component if it does not exist. Default True. Possible values: True, False", type=
|
|
570
|
+
parser.add_argument("-p", "--published", help="Boolean. Sync the latest published version of the component instead of the current version. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
571
|
+
parser.add_argument("-f", "--force", help="Boolean. Create the component if it does not exist. Default True. Possible values: True, False", type=str2bool, default=True)
|
|
553
572
|
try:
|
|
554
573
|
args = parser.parse_args(shlex.split(args))
|
|
555
574
|
if args.id is not None:
|
|
@@ -741,12 +760,22 @@ class SynchronizerCLI(cmd.Cmd):
|
|
|
741
760
|
return
|
|
742
761
|
|
|
743
762
|
def do_sync_from_library(self,args:Any):
|
|
744
|
-
"""Sync all components from a base library in the source property to the destination properties.
|
|
763
|
+
"""Sync all components from a base library in the source property to the destination properties.
|
|
764
|
+
Rules and data elements will be the library version by default. Can be changed with --ll parameter."""
|
|
745
765
|
parser = argparse.ArgumentParser(prog='sync_from_library', add_help=True)
|
|
746
766
|
parser.add_argument("-n", "--name", help="Name of the library to sync from", type=str)
|
|
747
767
|
parser.add_argument("-id", "--id", help="ID of the library to sync from (overrides name if both provided)", type=str)
|
|
768
|
+
parser.add_argument("-f", "--force", help="Boolean. Create the component if it does not exist. Default True. Possible values: True, False", type=str2bool, default=True)
|
|
769
|
+
parser.add_argument("-ll", "--library_linked", help="Boolean. Whether to use library linked components or not. If set to True, the sync will be done using the library linked components. If set to False, the sync will be done using the component IDs in the library. Default True. Possible values: True, False", type=str, default=True)
|
|
770
|
+
parser.add_argument("-p", "--published", help="Boolean. Sync the latest published version of the component instead of the current version. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False,)
|
|
771
|
+
parser.add_argument("-dr", "--dry_run", help="Boolean. If set to True, will only check the sync status of the components without actually syncing them. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False,)
|
|
748
772
|
try:
|
|
749
773
|
args = parser.parse_args(shlex.split(args))
|
|
774
|
+
publishedVersion = args.published # default False
|
|
775
|
+
libraryLinked = args.library_linked # default True
|
|
776
|
+
if args.published:
|
|
777
|
+
publishedVersion = True
|
|
778
|
+
libraryLinked = False
|
|
750
779
|
if args.id is not None:
|
|
751
780
|
lib = self.synchronizer.base["api"].getLibrary(args.id, return_class=True)
|
|
752
781
|
else:
|
|
@@ -756,6 +785,10 @@ class SynchronizerCLI(cmd.Cmd):
|
|
|
756
785
|
console.print(f"Library '{args.name}' not found in this property.", style="red")
|
|
757
786
|
return
|
|
758
787
|
lib = self.synchronizer.base["api"].getLibrary(matching_libs[0]['id'], return_class=True)
|
|
788
|
+
if args.dry_run:
|
|
789
|
+
results = self.synchronizer.__checkLibrarySync__(library=lib.id, state=lib.state, excludeSimilar=False,libraryLinked=libraryLinked,publishedVersion=publishedVersion)
|
|
790
|
+
console.print_json(json.dumps(results, indent=4))
|
|
791
|
+
return
|
|
759
792
|
rules = lib.getRules()
|
|
760
793
|
dataelements = lib.getDataElements()
|
|
761
794
|
extensions = lib.getExtensions()
|
|
@@ -771,7 +804,11 @@ class SynchronizerCLI(cmd.Cmd):
|
|
|
771
804
|
console.print(f"Syncing {len(rules)} rule{'s' if len(rules) > 1 else ''} from library '{lib.name}'...", style="blue")
|
|
772
805
|
for rule in rules:
|
|
773
806
|
try:
|
|
774
|
-
|
|
807
|
+
if args.library_linked:
|
|
808
|
+
ruleId = rule['links']['self'].split('/').pop()
|
|
809
|
+
else:
|
|
810
|
+
ruleId = rule['id']
|
|
811
|
+
self.synchronizer.syncComponent(componentId=ruleId, publishedVersion=publishedVersion, forceCreation=args.force,libraryLinked=libraryLinked)
|
|
775
812
|
console.print(f"Rule '{rule['attributes']['name']}' synced successfully.", style="green")
|
|
776
813
|
except Exception as e:
|
|
777
814
|
console.print(f"(!) Error syncing rule '{rule['attributes']['name']}': {str(e)}", style="red")
|
|
@@ -779,7 +816,11 @@ class SynchronizerCLI(cmd.Cmd):
|
|
|
779
816
|
console.print(f"Syncing {len(dataelements)} data element{'s' if len(dataelements) > 1 else ''} from library '{lib.name}'...", style="blue")
|
|
780
817
|
for de in dataelements:
|
|
781
818
|
try:
|
|
782
|
-
|
|
819
|
+
if args.library_linked:
|
|
820
|
+
deId = de['links']['self'].split('/').pop()
|
|
821
|
+
else:
|
|
822
|
+
deId = de['id']
|
|
823
|
+
self.synchronizer.syncComponent(componentId=deId, publishedVersion=publishedVersion, forceCreation=args.force,libraryLinked=libraryLinked)
|
|
783
824
|
console.print(f"Data element '{de['attributes']['name']}' synced successfully.", style="green")
|
|
784
825
|
except Exception as e:
|
|
785
826
|
console.print(f"(!) Error syncing data element '{de['attributes']['name']}': {str(e)}", style="red")
|
|
@@ -793,9 +834,13 @@ class SynchronizerCLI(cmd.Cmd):
|
|
|
793
834
|
"""Create libraries in the destination property based on the libraries in the source property that are selected for synchronization."""
|
|
794
835
|
parser = argparse.ArgumentParser(prog='create_libraries', add_help=True)
|
|
795
836
|
parser.add_argument("name",help="Name for the new library to be created in the destination properties", type=str)
|
|
796
|
-
parser.add_argument("-env",'--environment',help='
|
|
837
|
+
parser.add_argument("-env",'--environment',help='Set to True to try to assign an available environment. Set a string with name of an environment to set a specific environment. Default False',type=str,default=False)
|
|
797
838
|
try:
|
|
798
839
|
args = parser.parse_args(shlex.split(args))
|
|
840
|
+
if args.environment == "True":
|
|
841
|
+
args.environment = True
|
|
842
|
+
if args.environment == "False":
|
|
843
|
+
args.environment = False
|
|
799
844
|
res = self.synchronizer.createTargetsLibrary(name=args.name,assignEnv=args.environment)
|
|
800
845
|
console.print_json(json.dumps(res, indent=4))
|
|
801
846
|
except Exception as e:
|
|
@@ -804,6 +849,10 @@ class SynchronizerCLI(cmd.Cmd):
|
|
|
804
849
|
except SystemExit:
|
|
805
850
|
return
|
|
806
851
|
|
|
852
|
+
def do_clear(self, arg) -> None:
|
|
853
|
+
"""Clear the terminal screen."""
|
|
854
|
+
clear_terminal()
|
|
855
|
+
|
|
807
856
|
def do_exit(self, arg):
|
|
808
857
|
"""Return to the main menu."""
|
|
809
858
|
console.print(Panel("Returning to main menu...", style="blue"))
|
|
@@ -965,7 +1014,7 @@ class MainShell(cmd.Cmd):
|
|
|
965
1014
|
"""List all properties available in the connected AEP instance."""
|
|
966
1015
|
parser = argparse.ArgumentParser(prog='get_properties', add_help=True)
|
|
967
1016
|
parser.add_argument("-n", "--name", help="Filter properties by name (partial match, non-case sensitive)", type=str, default=None)
|
|
968
|
-
parser.add_argument("-s", "--save", help="Boolean. Save properties to a CSV file. Default False. Possible values: True, False", type=
|
|
1017
|
+
parser.add_argument("-s", "--save", help="Boolean. Save properties to a CSV file. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
969
1018
|
try:
|
|
970
1019
|
args = parser.parse_args(shlex.split(arg))
|
|
971
1020
|
properties = self.admin.getProperties(self.cid)
|
|
@@ -1094,7 +1143,7 @@ class MainShell(cmd.Cmd):
|
|
|
1094
1143
|
def do_get_extensions(self, arg:Any) -> None:
|
|
1095
1144
|
"""List all available extensions in the connected Adobe Launch organization."""
|
|
1096
1145
|
parser = argparse.ArgumentParser(prog='get_extensions', add_help=True)
|
|
1097
|
-
parser.add_argument("-s", "--save", help="Boolean. Save extensions to a JSON file. Default False. Possible values: True, False", type=
|
|
1146
|
+
parser.add_argument("-s", "--save", help="Boolean. Save extensions to a JSON file. Default False. Possible values: True, False", type=str2bool, nargs="?", const=True, default=False)
|
|
1098
1147
|
try:
|
|
1099
1148
|
args = parser.parse_args(shlex.split(arg))
|
|
1100
1149
|
extensions = self.admin.getExtensionsCatalogue()
|
|
@@ -1144,6 +1193,10 @@ class MainShell(cmd.Cmd):
|
|
|
1144
1193
|
except SystemExit:
|
|
1145
1194
|
return
|
|
1146
1195
|
|
|
1196
|
+
def do_clear(self, arg:Any) -> None:
|
|
1197
|
+
"""Clear the terminal screen."""
|
|
1198
|
+
clear_terminal()
|
|
1199
|
+
|
|
1147
1200
|
def do_exit(self, arg:Any) -> bool:
|
|
1148
1201
|
"""Exit the application."""
|
|
1149
1202
|
console.print(Panel("Exiting...", style="blue"))
|
|
@@ -3,9 +3,7 @@ token = ""
|
|
|
3
3
|
config_object = {
|
|
4
4
|
"org_id": "",
|
|
5
5
|
"api_key": "",
|
|
6
|
-
"pathToKey": "",
|
|
7
6
|
"secret": "",
|
|
8
|
-
"jwtTokenEndpoint" : "https://ims-na1.adobelogin.com/ims/exchange/jwt",
|
|
9
7
|
"oauthTokenEndpointV2" : "https://ims-na1.adobelogin.com/ims/token/v2",
|
|
10
8
|
"date_limit" : 0,
|
|
11
9
|
"scope_admin" : "https://ims-na1.adobelogin.com/s/ent_reactor_admin_sdk",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json, os
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Optional
|
|
4
|
-
from .config import config_object, header
|
|
4
|
+
from launchpy.config import config_object, header
|
|
5
5
|
|
|
6
6
|
def find_path(path: str) -> Optional[Path]:
|
|
7
7
|
"""Checks if the file denoted by the specified `path` exists and returns the Path object
|
|
@@ -30,7 +30,7 @@ def createConfigFile(filename:str='config_launch',auth_type: str = "oauthV2", sc
|
|
|
30
30
|
scope="https://ims-na1.adobelogin.com/s/ent_reactor_admin_sdk"
|
|
31
31
|
or
|
|
32
32
|
scope="https://ims-na1.adobelogin.com/s/ent_reactor_sdk"
|
|
33
|
-
auth_type : OPTIONAL : The type of Oauth type you want to use for your config file. Possible value: "
|
|
33
|
+
auth_type : OPTIONAL : The type of Oauth type you want to use for your config file. Possible value: "oauthV2"
|
|
34
34
|
"""
|
|
35
35
|
json_data = {
|
|
36
36
|
'org_id': '<orgID>',
|
|
@@ -39,10 +39,6 @@ def createConfigFile(filename:str='config_launch',auth_type: str = "oauthV2", sc
|
|
|
39
39
|
}
|
|
40
40
|
if auth_type == 'oauthV2':
|
|
41
41
|
json_data['scopes'] = "<scopes>"
|
|
42
|
-
elif auth_type == 'jwt':
|
|
43
|
-
json_data["tech_id"] = "<something>@techacct.adobe.com"
|
|
44
|
-
json_data["pathToKey"] = "<path/to/your/privatekey.key>"
|
|
45
|
-
json_data['scope'] = scope
|
|
46
42
|
if '.json' not in filename:
|
|
47
43
|
filename = f"{filename}.json"
|
|
48
44
|
with open(filename, 'w') as cf:
|
|
@@ -84,8 +80,6 @@ def importConfigFile(path: str = None,auth_type:str=None) -> None:
|
|
|
84
80
|
if auth_type is None:
|
|
85
81
|
if 'scopes' in provided_keys:
|
|
86
82
|
auth_type = 'oauthV2'
|
|
87
|
-
elif 'tech_id' in provided_keys and "pathToKey" in provided_keys:
|
|
88
|
-
auth_type = 'jwt'
|
|
89
83
|
args = {
|
|
90
84
|
"org_id" : provided_config['org_id'],
|
|
91
85
|
"secret" : provided_config['secret'],
|
|
@@ -94,9 +88,6 @@ def importConfigFile(path: str = None,auth_type:str=None) -> None:
|
|
|
94
88
|
}
|
|
95
89
|
if auth_type == 'oauthV2':
|
|
96
90
|
args["scopes"] = provided_config["scopes"].replace(' ','')
|
|
97
|
-
if auth_type == 'jwt':
|
|
98
|
-
args["tech_id"] = provided_config["tech_id"]
|
|
99
|
-
args["path_to_key"] = provided_config["pathToKey"]
|
|
100
91
|
configure(**args)
|
|
101
92
|
|
|
102
93
|
|
|
@@ -129,19 +120,17 @@ def configure(org_id: str = None,
|
|
|
129
120
|
tech_id: str = None,
|
|
130
121
|
secret: str = None,
|
|
131
122
|
client_id: str = None,
|
|
132
|
-
path_to_key: str=None,
|
|
133
|
-
private_key: str = None,
|
|
134
123
|
scopes : str= None,
|
|
135
124
|
scope: str="https://ims-na1.adobelogin.com/s/ent_reactor_admin_sdk",
|
|
125
|
+
**kwargs
|
|
136
126
|
):
|
|
137
127
|
"""Performs programmatic configuration of the API using provided values.
|
|
138
128
|
Arguments:
|
|
139
129
|
org_id : REQUIRED : Organization ID
|
|
140
130
|
tech_id : REQUIRED : Technical Account ID
|
|
141
131
|
secret : REQUIRED : secret generated for your connection
|
|
142
|
-
client_id : REQUIRED : The client_id (old api_key) provided by the
|
|
143
|
-
|
|
144
|
-
private_key : REQUIRED : If you do not use a file but pass a variable directly.
|
|
132
|
+
client_id : REQUIRED : The client_id (old api_key) provided by the Adobe Project.
|
|
133
|
+
scopes : REQUIRED : The scopes required for the Oauth connection
|
|
145
134
|
scope : OPTIONAL : Scope that is needed for JWT auth.
|
|
146
135
|
Possible scope: https://www.adobe.io/authentication/auth-methods.html#!AdobeDocs/adobeio-auth/master/JWT/Scopes.md
|
|
147
136
|
"""
|
|
@@ -149,37 +138,18 @@ def configure(org_id: str = None,
|
|
|
149
138
|
raise ValueError("`org_id` must be specified in the configuration.")
|
|
150
139
|
if not client_id:
|
|
151
140
|
raise ValueError("`client_id` must be specified in the configuration.")
|
|
152
|
-
if not
|
|
153
|
-
raise ValueError("`
|
|
141
|
+
if not scopes:
|
|
142
|
+
raise ValueError("`scopes` must be specified in the configuration.")
|
|
154
143
|
if not secret:
|
|
155
144
|
raise ValueError("`secret` must be specified in the configuration.")
|
|
156
|
-
if not path_to_key and not private_key and not scopes:
|
|
157
|
-
raise ValueError("`scopes` must be specified if Oauth setup.\n `pathToKey` or `private_key` must be specified in the configuration if JWT setup.")
|
|
158
145
|
config_object["org_id"] = org_id
|
|
159
146
|
header["x-gw-ims-org-id"] = org_id
|
|
160
147
|
config_object["client_id"] = client_id
|
|
161
148
|
header["x-api-key"] = client_id
|
|
162
149
|
config_object["tech_id"] = tech_id
|
|
163
150
|
config_object["secret"] = secret
|
|
164
|
-
config_object["pathToKey"] = path_to_key
|
|
165
|
-
config_object["private_key"] = private_key
|
|
166
151
|
config_object["official_scope"] = scope
|
|
167
152
|
config_object["scopes"] = scopes
|
|
168
153
|
# ensure the reset of the state by overwriting possible values from previous import.
|
|
169
154
|
config_object["date_limit"] = 0
|
|
170
155
|
config_object["token"] = ""
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def get_private_key_from_config(config: dict) -> str:
|
|
174
|
-
"""
|
|
175
|
-
Returns the private key directly or read a file to return the private key.
|
|
176
|
-
"""
|
|
177
|
-
private_key = config.get('private_key')
|
|
178
|
-
if private_key is not None:
|
|
179
|
-
return private_key
|
|
180
|
-
private_key_path = find_path(config['pathToKey'])
|
|
181
|
-
if private_key_path is None:
|
|
182
|
-
raise FileNotFoundError(f'Unable to find the private key under path `{config["pathToKey"]}`.')
|
|
183
|
-
with open(Path(private_key_path), 'r') as f:
|
|
184
|
-
private_key = f.read()
|
|
185
|
-
return private_key
|
|
@@ -85,8 +85,6 @@ class AdobeRequest:
|
|
|
85
85
|
if now > self.config['date_limit']:
|
|
86
86
|
if self.connectionType =='oauthV2':
|
|
87
87
|
token_and_expiry = self.get_oauth_token_and_expiry_for_config(config=self.config)
|
|
88
|
-
elif self.connectionType == 'jwt':
|
|
89
|
-
token_and_expiry = self.get_jwt_token_and_expiry_for_config(config=self.config)
|
|
90
88
|
token = token_and_expiry['token']
|
|
91
89
|
self.config['token'] = deepcopy(token)
|
|
92
90
|
self.config['date_limit'] = deepcopy(time.time() + token_and_expiry['expiry'] - 500)
|
|
@@ -2,6 +2,8 @@ import time
|
|
|
2
2
|
# Non standard libraries
|
|
3
3
|
from launchpy import config, connector
|
|
4
4
|
from typing import Union
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
import json
|
|
5
7
|
|
|
6
8
|
class Library:
|
|
7
9
|
"""
|
|
@@ -26,6 +28,7 @@ class Library:
|
|
|
26
28
|
self.name = data['attributes']['name']
|
|
27
29
|
self.state = data['attributes']['state']
|
|
28
30
|
self.build_required = data['attributes']['build_required']
|
|
31
|
+
self.definition = deepcopy(data)
|
|
29
32
|
self._DataElements = config.endpoints['global'] + \
|
|
30
33
|
'/libraries/'+data['id']+'/data_elements'
|
|
31
34
|
self._Extensions = config.endpoints['global'] + \
|
|
@@ -40,6 +43,12 @@ class Library:
|
|
|
40
43
|
self.relationships = {}
|
|
41
44
|
self._environments = {}
|
|
42
45
|
self._dev_env = ''
|
|
46
|
+
|
|
47
|
+
def __str__(self):
|
|
48
|
+
return json.dumps(self.definition, indent=4)
|
|
49
|
+
|
|
50
|
+
def __repr__(self):
|
|
51
|
+
return json.dumps(self.definition, indent=4)
|
|
43
52
|
|
|
44
53
|
def getDataElements(self,page:int=0,pageSize:int=50,origin:bool=True)->list:
|
|
45
54
|
"""
|
|
@@ -355,13 +364,24 @@ class Library:
|
|
|
355
364
|
res = self._setEnvironment(obj,verbose=verbose)
|
|
356
365
|
return res
|
|
357
366
|
|
|
358
|
-
def
|
|
367
|
+
def removeEnvironment(self)->None:
|
|
359
368
|
"""
|
|
360
369
|
Remove environment
|
|
361
370
|
"""
|
|
362
|
-
path = f'/libraries/{self.id}
|
|
363
|
-
|
|
364
|
-
|
|
371
|
+
path = f'/libraries/{self.id}'
|
|
372
|
+
data = {
|
|
373
|
+
"data": {
|
|
374
|
+
"id": self.id,
|
|
375
|
+
"type": "libraries",
|
|
376
|
+
"relationships": {
|
|
377
|
+
"environment": {
|
|
378
|
+
"data": None
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
res = self.connector.patchData(self.endpoint+path, data=data)
|
|
384
|
+
return res
|
|
365
385
|
|
|
366
386
|
def updateLibrary(self,empty:bool=False)->dict:
|
|
367
387
|
"""
|
|
@@ -418,7 +438,6 @@ class Library:
|
|
|
418
438
|
"type": "environments"
|
|
419
439
|
}
|
|
420
440
|
}
|
|
421
|
-
self._removeEnvironment()
|
|
422
441
|
status = self._setEnvironment(obj,verbose=verbose)
|
|
423
442
|
elif self.state == 'submitted':
|
|
424
443
|
env = 'staging'
|
|
@@ -428,7 +447,6 @@ class Library:
|
|
|
428
447
|
"type": "environments"
|
|
429
448
|
}
|
|
430
449
|
}
|
|
431
|
-
self._removeEnvironment()
|
|
432
450
|
status = self._setEnvironment(obj,verbose=verbose)
|
|
433
451
|
elif self.state == 'approved':
|
|
434
452
|
env = 'production'
|
|
@@ -438,7 +456,6 @@ class Library:
|
|
|
438
456
|
"type": "environments"
|
|
439
457
|
}
|
|
440
458
|
}
|
|
441
|
-
self._removeEnvironment()
|
|
442
459
|
status = self._setEnvironment(obj,verbose=verbose)
|
|
443
460
|
if 'error' in status.keys():
|
|
444
461
|
raise SystemExit('Issue setting environment')
|
|
@@ -1334,6 +1334,16 @@ class Property:
|
|
|
1334
1334
|
envs = self.getEnvironments()
|
|
1335
1335
|
staging = [env for env in envs if env['attributes']['stage'] == 'staging'][0]
|
|
1336
1336
|
return staging['meta']['script_sources'][0]["license_path"]
|
|
1337
|
+
|
|
1338
|
+
def getEnvironmentLibrary(self,env_id:str)->dict:
|
|
1339
|
+
"""
|
|
1340
|
+
Returns the library URL to use on the website for a specific environment.
|
|
1341
|
+
Arguments:
|
|
1342
|
+
env_id : REQUIRED : Environment ID
|
|
1343
|
+
"""
|
|
1344
|
+
path = f"/environments/{env_id}/library"
|
|
1345
|
+
res = self.connector.getData('https://reactor.adobe.io'+path)
|
|
1346
|
+
return res.get('data',{})
|
|
1337
1347
|
|
|
1338
1348
|
def extensionsInfo(data: list)->dict:
|
|
1339
1349
|
"""
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import re,json
|
|
2
2
|
# Non standard libraries
|
|
3
|
-
from .admin import Admin
|
|
4
|
-
from .property import Property
|
|
5
|
-
from .library import Library
|
|
6
|
-
from .launchpy import Translator, copySettings
|
|
7
|
-
from collections import defaultdict
|
|
3
|
+
from launchpy.admin import Admin
|
|
4
|
+
from launchpy.property import Property
|
|
5
|
+
from launchpy.library import Library
|
|
6
|
+
from launchpy.launchpy import Translator, copySettings
|
|
8
7
|
from copy import deepcopy
|
|
9
8
|
from aepp import som
|
|
10
9
|
|
|
@@ -126,31 +125,52 @@ class Synchronizer:
|
|
|
126
125
|
'inclComponents': deepcopy(self.dict_config[rule]['inclComponents'])
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
def __prepareBaseComponent__(self,componentName:str=None,componentId:str=None,publishedVersion:bool=False)->dict:
|
|
128
|
+
def __prepareBaseComponent__(self,componentName:str=None,componentId:str=None,publishedVersion:bool=False,**kwargs)->dict:
|
|
130
129
|
"""
|
|
131
130
|
Prepare the base component to be used in the syncComponent method.
|
|
132
131
|
Arguments:
|
|
133
132
|
componentName : REQUIRED : the name of the component to sync
|
|
134
133
|
componentID : REQUIRED : the id of the component to sync
|
|
135
134
|
publishedVersion : OPTIONAL : if you want to take the version that has been published
|
|
135
|
+
kwargs:
|
|
136
|
+
libraryLinked : OPTIONAL : If you want to pass ID that are associated to a specific library. Default: False
|
|
136
137
|
"""
|
|
137
138
|
cmp_base=None
|
|
139
|
+
componentBase = None
|
|
138
140
|
if componentId is not None:
|
|
139
141
|
if componentId.startswith('DE'): ## if data element
|
|
142
|
+
if kwargs.get('libraryLinked',False):
|
|
143
|
+
lib_cmp_base = self.base['api'].getDataElement(componentId)
|
|
144
|
+
if lib_cmp_base is None:
|
|
145
|
+
raise KeyError("Component ID cannot be found")
|
|
146
|
+
componentId = lib_cmp_base['links']['origin'].split('/').pop()
|
|
140
147
|
componentBase = [de for de in self.base['dataElements'] if de['id'] == componentId]
|
|
141
148
|
if len(componentBase)==0:
|
|
142
149
|
raise KeyError("Component ID cannot be found")
|
|
143
|
-
|
|
150
|
+
if type(componentBase) == list and len(componentBase)>0:
|
|
151
|
+
cmp_base = componentBase[0]
|
|
144
152
|
elif componentId.startswith('RL'): ## if a rule
|
|
153
|
+
if kwargs.get('libraryLinked',False):
|
|
154
|
+
lib_cmp_base = self.base['api'].getRule(componentId)
|
|
155
|
+
if lib_cmp_base is None:
|
|
156
|
+
raise KeyError("Component ID cannot be found")
|
|
157
|
+
componentId = lib_cmp_base['links']['origin'].split('/').pop()
|
|
145
158
|
componentBase = [de for de in self.base['rules'] if de['id'] == componentId]
|
|
146
159
|
if len(componentBase)==0:
|
|
147
160
|
raise KeyError("Component ID cannot be found")
|
|
148
|
-
|
|
161
|
+
if type(componentBase) == list and len(componentBase)>0:
|
|
162
|
+
cmp_base = componentBase[0]
|
|
149
163
|
elif componentId.startswith('EX'): ## if an extension
|
|
164
|
+
if kwargs.get('libraryLinked',False):
|
|
165
|
+
lib_cmp_base = self.base['api'].getExtension(componentId)
|
|
166
|
+
if lib_cmp_base is None:
|
|
167
|
+
raise KeyError("Component ID cannot be found")
|
|
168
|
+
componentId = lib_cmp_base['links']['origin'].split('/').pop()
|
|
150
169
|
componentBase = [ext for ext in self.base['extensions'] if ext['id'] == componentId]
|
|
151
170
|
if len(componentBase)==0:
|
|
152
|
-
|
|
153
|
-
|
|
171
|
+
raise KeyError("Component ID cannot be found")
|
|
172
|
+
if type(componentBase) == list and len(componentBase)>0:
|
|
173
|
+
cmp_base = componentBase[0]
|
|
154
174
|
if componentId is None and componentName is not None: ## If only componentName
|
|
155
175
|
for rule in self.base['rules']:
|
|
156
176
|
if componentName == rule['attributes']['name']:
|
|
@@ -174,7 +194,7 @@ class Synchronizer:
|
|
|
174
194
|
data = self.base['api'].getRevisions(cmp_baseDict['component'])
|
|
175
195
|
publishedVersion = self.base['api'].getLatestPublishedVersion(data)
|
|
176
196
|
if publishedVersion['attributes']['name'] != cmp_baseDict['name']:
|
|
177
|
-
## Updating mapping table with old name when published version name diff than last
|
|
197
|
+
## Updating mapping table with old name when published version name diff than last version name.
|
|
178
198
|
if cmp_baseDict['component']['type'] == 'rules':
|
|
179
199
|
self.translator.extendBaseRules(
|
|
180
200
|
ruleName=publishedVersion['attributes']['name'],
|
|
@@ -183,10 +203,18 @@ class Synchronizer:
|
|
|
183
203
|
self.base['rules'].append(publishedVersion)
|
|
184
204
|
if cmp_baseDict['component']['type'] == 'data_elements':
|
|
185
205
|
self.base['dataElements'].append(publishedVersion)
|
|
186
|
-
cmp_baseDict['id']
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
206
|
+
cmp_baseDict = {'id':publishedVersion['id'],'name':publishedVersion['attributes']['name'],'component':publishedVersion,'copy':copySettings(publishedVersion)}
|
|
207
|
+
if kwargs.get('libraryLinked',False) and cmp_baseDict['component']['type'] in ['rules','data_elements']:
|
|
208
|
+
if lib_cmp_base['attributes']['name'] != cmp_baseDict['name']:
|
|
209
|
+
if cmp_baseDict['component']['type'] == 'rules':
|
|
210
|
+
self.translator.extendBaseRules(
|
|
211
|
+
ruleName=lib_cmp_base['attributes']['name'],
|
|
212
|
+
ruleId=lib_cmp_base['id'],
|
|
213
|
+
property_name=self.base["name"])
|
|
214
|
+
self.base['rules'].append(lib_cmp_base)
|
|
215
|
+
if cmp_baseDict['component']['type'] == 'data_elements':
|
|
216
|
+
self.base['dataElements'].append(lib_cmp_base)
|
|
217
|
+
cmp_baseDict = {'id':lib_cmp_base['id'],'name':lib_cmp_base['attributes']['name'],'component':lib_cmp_base,'copy':copySettings(lib_cmp_base)}
|
|
190
218
|
return cmp_baseDict
|
|
191
219
|
|
|
192
220
|
def syncComponent(self,componentName:str=None,componentId:str=None,publishedVersion:bool=False,forceCreation:bool=True,**kwargs)->None:
|
|
@@ -202,11 +230,13 @@ class Synchronizer:
|
|
|
202
230
|
forceCreation : OPTIONAL : If the component does not exist in the target property, create it. Default: True
|
|
203
231
|
possible kwargs:
|
|
204
232
|
timeout : OPTIONAL : The timeout to be used for the rule component. If not provided, the existing timeout will be used.
|
|
233
|
+
libraryLinked : OPTIONAL : If you want to take a component ID that are associated to a specific library. Default: False. If set to True, the method will look for the component in the library and take the version linked to the library instead of the latest version in the property. Do not work for Extensions.
|
|
205
234
|
"""
|
|
206
235
|
timeout = kwargs.get('timeout',None)
|
|
236
|
+
libraryLinked = kwargs.get('libraryLinked',False)
|
|
207
237
|
if componentName is None and componentId is None:
|
|
208
238
|
raise ValueError('Require a component Name of a component ID')
|
|
209
|
-
cmp_baseDict = self.__prepareBaseComponent__(componentName=componentName,componentId=componentId,publishedVersion=publishedVersion)
|
|
239
|
+
cmp_baseDict = self.__prepareBaseComponent__(componentName=componentName,componentId=componentId,publishedVersion=publishedVersion,libraryLinked=libraryLinked)
|
|
210
240
|
## handling the data element
|
|
211
241
|
if cmp_baseDict['component']['type'] == 'data_elements':
|
|
212
242
|
for target in list(self.targets.keys()):
|
|
@@ -369,14 +399,15 @@ class Synchronizer:
|
|
|
369
399
|
for component in componentsId:
|
|
370
400
|
self.syncComponent(componentId=component,publishedVersion=publishedVersion)
|
|
371
401
|
|
|
372
|
-
def createTargetsLibrary(self,name:str="syncComponents",assignEnv:bool=False)->None:
|
|
402
|
+
def createTargetsLibrary(self,name:str="syncComponents",assignEnv:bool|str=False)->None:
|
|
373
403
|
"""
|
|
374
404
|
This method will create or update a Library in all of the target properties to gather all elements changed.
|
|
375
405
|
If a library exists and **contains** the same name, it will be used.
|
|
376
406
|
Argument:
|
|
377
407
|
name : REQUIRED : The name of the library to create. Default : "syncComponents"
|
|
378
|
-
assignEnv : REQUIRED : If you want to assign a library to an environment.
|
|
408
|
+
assignEnv : REQUIRED : If you want to assign a library to an environment.
|
|
379
409
|
If set to True, the library will be assigned to an available environment in the Target Property if available and build it.
|
|
410
|
+
If set to a string (regex supported), the library will be assigned to the environment with the name provided if it exists, and build it. If that environment is used in a Library, it will remove that environment from the library and assign it to the new one. Default: False
|
|
380
411
|
"""
|
|
381
412
|
response = {target:{'libraryName':name} for target in self.targets.keys()}
|
|
382
413
|
for target in list(self.targets.keys()):
|
|
@@ -410,8 +441,14 @@ class Synchronizer:
|
|
|
410
441
|
self.targets[target]['library'].updateExtensions(existingExtensions)
|
|
411
442
|
if len(newExtensions)>0:
|
|
412
443
|
self.targets[target]['library'].addExtensions(newExtensions)
|
|
413
|
-
if assignEnv:
|
|
444
|
+
if type(assignEnv) == bool and assignEnv == True:
|
|
414
445
|
envs = self.targets[target]['api'].getEnvironments()
|
|
446
|
+
prod = [env for env in envs if env['attributes']['stage'].lower() == 'production']
|
|
447
|
+
if len(prod) == 0:
|
|
448
|
+
prod_script = "https://assets.adobedtm.com/" ## default script source when no production environment exist, with a random id to avoid any conflict with an existing environment script source.
|
|
449
|
+
else:
|
|
450
|
+
prod = prod[0]
|
|
451
|
+
prod_script = prod['meta'].get('script_sources',[{}])[0].get('minified')
|
|
415
452
|
found_free_env = False
|
|
416
453
|
if len(envs)>0:
|
|
417
454
|
for env in envs:
|
|
@@ -426,10 +463,48 @@ class Synchronizer:
|
|
|
426
463
|
self.targets[target]['library'].setEnvironment(envId)
|
|
427
464
|
self.targets[target]['library'].build()
|
|
428
465
|
response[target]['environment'] = envName
|
|
429
|
-
response[target]['script'] =
|
|
466
|
+
response[target]['script'] = {f"{target} - {self.targets[target]['library'].name}" : {
|
|
467
|
+
"replace": prod_script,
|
|
468
|
+
"with": scriptSource
|
|
469
|
+
}}
|
|
470
|
+
response[target]['build_status'] = self.targets[target]['library'].build_status
|
|
430
471
|
else:
|
|
431
472
|
response[target]['environment'] = "No free environment to assign"
|
|
432
473
|
response[target]['script'] = None
|
|
474
|
+
elif type(assignEnv) == str:
|
|
475
|
+
envs = self.targets[target]['api'].getEnvironments()
|
|
476
|
+
prod = [env for env in envs if env['attributes']['stage'].lower() == 'production']
|
|
477
|
+
if len(prod) == 0:
|
|
478
|
+
prod_script = "https://assets.adobedtm.com/" ## default script source when no production environment exist, with a random id to avoid any conflict with an existing environment script source.
|
|
479
|
+
else:
|
|
480
|
+
prod = prod[0]
|
|
481
|
+
prod_script = prod['meta'].get('script_sources',[{}])[0].get('minified')
|
|
482
|
+
env = [env for env in envs if re.search(assignEnv, env['attributes']['name'], re.IGNORECASE)]
|
|
483
|
+
if len(env) == 0:
|
|
484
|
+
response[target]['environment'] = f"No environment with the name {assignEnv} to assign"
|
|
485
|
+
response[target]['script'] = None
|
|
486
|
+
elif len(env) == 1:
|
|
487
|
+
env = env[0]
|
|
488
|
+
envId = env['id']
|
|
489
|
+
envName = env['attributes']['name']
|
|
490
|
+
scriptSource = env['meta'].get('script_sources',[{}])[0].get('minified')
|
|
491
|
+
libraryUsed = self.targets[target]["api"].getEnvironmentLibrary(envId)
|
|
492
|
+
if libraryUsed is not None:
|
|
493
|
+
print(f'Environment is already used by library "{libraryUsed["attributes"]["name"]}" in "{target}" property. Removing the environment from that library.')
|
|
494
|
+
libraryUsedId = libraryUsed['id']
|
|
495
|
+
tmp_lib = Library(libraryUsedId,config_object=self.targets[target]['api'].connector.config,header=self.targets[target]['api'].connector.header)
|
|
496
|
+
tmp_lib.removeEnvironment()
|
|
497
|
+
self.targets[target]['library'].setEnvironment(envId)
|
|
498
|
+
self.targets[target]['library'].build()
|
|
499
|
+
response[target]['environment'] = envName
|
|
500
|
+
response[target]['script'] = {f"{target} - {self.targets[target]['library'].name}" : {
|
|
501
|
+
"replace": prod_script,
|
|
502
|
+
"with": scriptSource
|
|
503
|
+
}}
|
|
504
|
+
response[target]['build_status'] = self.targets[target]['library'].build_status
|
|
505
|
+
else:
|
|
506
|
+
response[target]['environment'] = f"Multiple environment with the name {assignEnv} to assign"
|
|
507
|
+
response[target]['script'] = None
|
|
433
508
|
return response
|
|
434
509
|
|
|
435
510
|
|
|
@@ -514,25 +589,14 @@ class Synchronizer:
|
|
|
514
589
|
action_setting_path : [str,list] : The dot notation of the paths you want to verify for the settings object. ex: ["code","customAttributes"]. If not provided, the complete settings are compared.
|
|
515
590
|
condition_setting_path : [str,list] : The dot notation of the paths you want to verify for the setting object . ex: ["id",""]. If not provided, the complete settings are compared
|
|
516
591
|
event_setting_path : [str,list] : The dot notation of the paths you want to verify for the setting object . ex: ["id",""]. If not provided, the complete settings are compared
|
|
592
|
+
libraryLinked : bool : If you want to pass ID that are associated to a specific library. Default: False
|
|
517
593
|
"""
|
|
518
594
|
if componentName is None and componentId is None:
|
|
519
595
|
raise ValueError('Require a component Name of a component ID')
|
|
520
|
-
cmp_baseDict = self.__prepareBaseComponent__(componentName=componentName,componentId=componentId,publishedVersion=publishedVersion)
|
|
596
|
+
cmp_baseDict = self.__prepareBaseComponent__(componentName=componentName,componentId=componentId,publishedVersion=publishedVersion,libraryLinked=kwargs.get('libraryLinked',False))
|
|
521
597
|
dict_result = {tar:"" for tar in self.targets.keys()}
|
|
522
598
|
dict_result['base-enabled'] = cmp_baseDict['component']['attributes'].get('enabled',False)
|
|
523
599
|
dict_result['base-published'] = cmp_baseDict['component']['attributes'].get('published',False)
|
|
524
|
-
if cmp_baseDict['component']['attributes'].get('latest',False):
|
|
525
|
-
dict_result['base-state'] = "Latest"
|
|
526
|
-
else:
|
|
527
|
-
if cmp_baseDict['component']['attributes'].get('latest','unknown') == 'unknown':
|
|
528
|
-
if dict_result['base-enabled']:
|
|
529
|
-
dict_result['base-state'] = "Draft"
|
|
530
|
-
else:
|
|
531
|
-
dict_result['base-state'] = "Unknown"
|
|
532
|
-
elif dict_result['base-published'] == False:
|
|
533
|
-
dict_result['base-state'] = "Draft"
|
|
534
|
-
else:
|
|
535
|
-
dict_result['base-state'] = "Edited"
|
|
536
600
|
if cmp_baseDict['component']['type'] == 'data_elements':
|
|
537
601
|
for target in list(self.targets.keys()):
|
|
538
602
|
## if it does not exist
|
|
@@ -675,4 +739,98 @@ class Synchronizer:
|
|
|
675
739
|
else:
|
|
676
740
|
if not excludeSimilar:
|
|
677
741
|
dict_result[target] = 'Similar'+issue_pub
|
|
678
|
-
return dict_result
|
|
742
|
+
return dict_result
|
|
743
|
+
|
|
744
|
+
def __checkLibrarySync__(self,library:str=None,state='published',excludeSimilar:bool=False,libraryLinked:bool=True,publishedVersion:bool=False)->dict:
|
|
745
|
+
"""
|
|
746
|
+
Check if the components in a library, from the base property, is synced to the different target properties.
|
|
747
|
+
By default, it will compare the version of the library to the latest version in the target properties.
|
|
748
|
+
It will return a dictionary with the key being the component name and the value being the result of the evaluation, such as {componentName: {'targetProperty': 'similar'}} when library have same settings.
|
|
749
|
+
Arguments:
|
|
750
|
+
library : REQUIRED : the library name or the library ID to get the components.
|
|
751
|
+
state : OPTIONAL : the state of the library to compare. Default: 'published', possible states: "development", "submitted", "approved", "rejected", "published"
|
|
752
|
+
excludeSimilar : OPTIONAL : If you do not want to see the result if the comparison provide a "similar" result. Similar means that the elements are about the same. Default: False.
|
|
753
|
+
libraryLinked : OPTIONAL : If set to True, it will use library linked components for comparison. Default: True.
|
|
754
|
+
publishedVersion : OPTIONAL : If set to True, it will compare the components to the published version in the target properties. Default: False.
|
|
755
|
+
"""
|
|
756
|
+
if library is None:
|
|
757
|
+
raise ValueError('Require a library Name of a library ID')
|
|
758
|
+
libraryLink = True
|
|
759
|
+
base_libraries = self.base['api'].getLibraries(state=state)
|
|
760
|
+
if library in [lib['attributes']['name'] for lib in base_libraries]:
|
|
761
|
+
base_library = [lib for lib in base_libraries if lib['attributes']['name'] == library][0]
|
|
762
|
+
elif library in [lib['id'] for lib in base_libraries]:
|
|
763
|
+
base_library = [lib for lib in base_libraries if lib['id'] == library][0]
|
|
764
|
+
else:
|
|
765
|
+
raise ValueError('The library name or ID provided does not exist in the base property')
|
|
766
|
+
myLib = Library(base_library['id'],config_object=self.base['api'].connector.config,header=self.base['api'].connector.header)
|
|
767
|
+
components = myLib.getFullLibrary()
|
|
768
|
+
rules = components['rules']
|
|
769
|
+
dataElements = components['data_elements']
|
|
770
|
+
extensions = components['extensions']
|
|
771
|
+
elements = rules + dataElements + extensions
|
|
772
|
+
if publishedVersion == True:
|
|
773
|
+
element_ids = {elem['id']: elem['attributes']['name'] for elem in elements}
|
|
774
|
+
libraryLink = False
|
|
775
|
+
elif libraryLinked == True:
|
|
776
|
+
element_ids ={elem['links']['self'].split('/').pop():elem['attributes']['name'] for elem in elements}
|
|
777
|
+
else:
|
|
778
|
+
element_ids = {elem['id']: elem['attributes']['name'] for elem in elements}
|
|
779
|
+
dict_check = {}
|
|
780
|
+
for element_id, element_name in element_ids.items():
|
|
781
|
+
dict_check[element_name] = self.checkComponentSync(componentId=element_id, excludeSimilar=excludeSimilar,publishedVersion=publishedVersion,libraryLinked=libraryLink)
|
|
782
|
+
return dict_check
|
|
783
|
+
|
|
784
|
+
def syncFromLibrary(self,library:str=None,state='published',force:bool=True,libraryLinked:bool=True,publishedVersion:bool=False,dryRun:bool=False)->None:
|
|
785
|
+
"""
|
|
786
|
+
Sync the components in a library, from the base property, to the different target properties by using the createTargetsLibrary method.
|
|
787
|
+
Arguments:
|
|
788
|
+
library : REQUIRED : the library name or the library ID to get the components.
|
|
789
|
+
state : OPTIONAL : the state of the library to compare. Default: 'published', possible states: "development", "submitted", "approved", "rejected", "published"
|
|
790
|
+
force : OPTIONAL : If set to True, it will sync the components even if they do not exist in the target properties. If set to False, it will only sync the components that already exist in the target properties. Default: True.
|
|
791
|
+
libraryLinked : OPTIONAL : If set to True, it will sync the components using the library version. Default: False.
|
|
792
|
+
publishedVersion : OPTIONAL : if you want to sync the version of the library that has been published in your base vs the published version of your target. Default: False.
|
|
793
|
+
dryRun : OPTIONAL : If set to True, it will not actually sync the components but will return a dictionary with the components that would be synced and the target properties they would be synced to. Default: False.
|
|
794
|
+
"""
|
|
795
|
+
if library is None:
|
|
796
|
+
raise ValueError('Require a library Name of a library ID')
|
|
797
|
+
libs = self.base['api'].getLibraries(state=state)
|
|
798
|
+
if library in [lib['attributes']['name'] for lib in libs]:
|
|
799
|
+
libraryId = [lib for lib in libs if lib['attributes']['name'] == library][0]['id']
|
|
800
|
+
elif library in [lib['id'] for lib in libs]:
|
|
801
|
+
libraryId = [lib for lib in libs if lib['id'] == library][0]['id']
|
|
802
|
+
else:
|
|
803
|
+
raise ValueError('The library name or ID provided does not exist in the base property')
|
|
804
|
+
if dryRun:
|
|
805
|
+
return self.__checkLibrarySync__(library=library,state=state,excludeSimilar=False,libraryLinked=libraryLinked,publishedVersion=publishedVersion)
|
|
806
|
+
myLib = self.base['api'].getLibrary(libraryId,return_class=True)
|
|
807
|
+
rules = myLib.getRules()
|
|
808
|
+
dataelements = myLib.getDataElements()
|
|
809
|
+
extensions = myLib.getExtensions()
|
|
810
|
+
if len(extensions)>0:
|
|
811
|
+
for ext in extensions:
|
|
812
|
+
try:
|
|
813
|
+
self.upgradeTargetExtension(extensionName=ext['attributes']['name'])
|
|
814
|
+
except:
|
|
815
|
+
print(f'Extension {ext["attributes"]["name"]} could not be updated in the target properties. Please check if the extension exist and if there is an update available.')
|
|
816
|
+
if len(rules)>0:
|
|
817
|
+
for rule in rules:
|
|
818
|
+
try:
|
|
819
|
+
if libraryLinked:
|
|
820
|
+
ruleId = rule['links']['self'].split('/').pop()
|
|
821
|
+
else:
|
|
822
|
+
ruleId = rule['id']
|
|
823
|
+
self.syncComponent(componentId=ruleId,publishedVersion=publishedVersion, forceCreation=force,libraryLinked=libraryLinked)
|
|
824
|
+
except:
|
|
825
|
+
print(f'Rule {rule["attributes"]["name"]} could not be updated in the target properties. Please check if the rule exist.')
|
|
826
|
+
if len(dataelements)>0:
|
|
827
|
+
for de in dataelements:
|
|
828
|
+
try:
|
|
829
|
+
if libraryLinked:
|
|
830
|
+
deId = de['links']['self'].split('/').pop()
|
|
831
|
+
else:
|
|
832
|
+
deId = de['id']
|
|
833
|
+
self.syncComponent(componentId=deId,publishedVersion=publishedVersion,forceCreation=force,libraryLinked=libraryLinked)
|
|
834
|
+
except:
|
|
835
|
+
print(f'Data Element {de["attributes"]["name"]} could not be updated in the target properties. Please check if the data element exist.')
|
|
836
|
+
return
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: launchpy
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.8
|
|
4
4
|
Summary: Adobe Launch API wrapper
|
|
5
5
|
Author-email: Julien Piccini <piccini.julien@gmail.com>
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -8,26 +8,26 @@ Project-URL: homepage, https://github.com/pitchmuc/launchpy
|
|
|
8
8
|
Project-URL: changelog, https://github.com/pitchmuc/launchpy/blob/master/docs/releases.md
|
|
9
9
|
Keywords: adobe,analytics,API,python
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved ::
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python
|
|
14
14
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
20
|
Classifier: Development Status :: 4 - Beta
|
|
20
|
-
Requires-Python: >=3.
|
|
21
|
+
Requires-Python: >=3.10
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
License-File: LICENSE
|
|
23
24
|
Requires-Dist: pandas
|
|
24
25
|
Requires-Dist: requests
|
|
25
|
-
Requires-Dist: PyJWT
|
|
26
|
-
Requires-Dist: PyJWT[crypto]
|
|
27
26
|
Requires-Dist: pathlib2
|
|
28
27
|
Requires-Dist: pathlib
|
|
29
28
|
Requires-Dist: aepp
|
|
30
29
|
Requires-Dist: rich
|
|
30
|
+
Requires-Dist: deprecated
|
|
31
31
|
Dynamic: license-file
|
|
32
32
|
|
|
33
33
|
# Launchpy
|
|
@@ -10,29 +10,29 @@ authors = [
|
|
|
10
10
|
description = "Adobe Launch API wrapper"
|
|
11
11
|
dynamic = ["version"]
|
|
12
12
|
readme = "README.md"
|
|
13
|
-
requires-python = ">=3.
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
14
|
keywords = ["adobe", "analytics", "API", "python"]
|
|
15
15
|
license = {text = "Apache License 2.0"}
|
|
16
16
|
classifiers = [
|
|
17
17
|
"Intended Audience :: Developers",
|
|
18
|
-
"License :: OSI Approved ::
|
|
18
|
+
"License :: OSI Approved :: Apache Software License",
|
|
19
19
|
"Operating System :: OS Independent",
|
|
20
20
|
"Programming Language :: Python",
|
|
21
21
|
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
22
|
-
"Programming Language :: Python :: 3.
|
|
23
|
-
"Programming Language :: Python :: 3.
|
|
24
|
-
"Programming Language :: Python :: 3.
|
|
25
|
-
"Programming Language :: Python :: 3.
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
26
27
|
"Development Status :: 4 - Beta"
|
|
27
28
|
]
|
|
28
29
|
dependencies = ['pandas',
|
|
29
30
|
"requests",
|
|
30
|
-
"PyJWT",
|
|
31
|
-
"PyJWT[crypto]",
|
|
32
31
|
"pathlib2",
|
|
33
32
|
"pathlib",
|
|
34
33
|
"aepp",
|
|
35
|
-
"rich",
|
|
34
|
+
"rich",
|
|
35
|
+
"deprecated"]
|
|
36
36
|
|
|
37
37
|
[project.urls]
|
|
38
38
|
homepage = "https://github.com/pitchmuc/launchpy"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.7-1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|