launchpy 0.4.8__tar.gz → 0.4.9__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.8/launchpy.egg-info → launchpy-0.4.9}/PKG-INFO +2 -1
- {launchpy-0.4.8 → launchpy-0.4.9}/docs/cli.md +4 -4
- {launchpy-0.4.8 → launchpy-0.4.9}/docs/releases.md +7 -1
- {launchpy-0.4.8 → launchpy-0.4.9}/docs/synchronizer.md +1 -1
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy/__init__.py +23 -2
- launchpy-0.4.9/launchpy/__version__.py +1 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy/cli/__main__.py +7 -7
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy/synchronizer.py +10 -9
- {launchpy-0.4.8 → launchpy-0.4.9/launchpy.egg-info}/PKG-INFO +2 -1
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy.egg-info/requires.txt +1 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/pyproject.toml +1 -1
- {launchpy-0.4.8 → launchpy-0.4.9}/requirements.txt +2 -1
- launchpy-0.4.8/launchpy/__version__.py +0 -1
- {launchpy-0.4.8 → launchpy-0.4.9}/LICENSE +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/README.md +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/docs/admin.md +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/docs/getstarted.md +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/docs/library.md +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/docs/main.md +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/docs/property.md +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/docs/translator.md +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy/admin.py +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy/cli/__init__.py +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy/config.py +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy/configs.py +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy/connector.py +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy/launchpy.py +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy/library.py +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy/property.py +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy.egg-info/SOURCES.txt +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy.egg-info/dependency_links.txt +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy.egg-info/entry_points.txt +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/launchpy.egg-info/top_level.txt +0 -0
- {launchpy-0.4.8 → launchpy-0.4.9}/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.9
|
|
4
4
|
Summary: Adobe Launch API wrapper
|
|
5
5
|
Author-email: Julien Piccini <piccini.julien@gmail.com>
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -28,6 +28,7 @@ Requires-Dist: pathlib
|
|
|
28
28
|
Requires-Dist: aepp
|
|
29
29
|
Requires-Dist: rich
|
|
30
30
|
Requires-Dist: deprecated
|
|
31
|
+
Requires-Dist: httpx
|
|
31
32
|
Dynamic: license-file
|
|
32
33
|
|
|
33
34
|
# Launchpy
|
|
@@ -482,10 +482,10 @@ This command allows you to synchronize a library from the base property to the t
|
|
|
482
482
|
**IMPORTANT**:By default it will use the published version of the elements that are part of the library.
|
|
483
483
|
* `-n`, `--name`: Name of the library to sync from. This is a required parameter.
|
|
484
484
|
* `-id`, `--id`: ID of the library to sync from (overrides name if both provided). This is an optional parameter.
|
|
485
|
-
* `-
|
|
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
|
|
487
|
-
* `-p`, `--published`: Boolean. Sync the latest published version of the component instead of the current version. Default 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
|
|
485
|
+
* `-c`, `--create`: Boolean. Create the component if it does not exist. Default `False`. 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
489
|
|
|
490
490
|
```bash
|
|
491
491
|
synchronizer:Base_Property_Name> sync_from_library -id "LibraryId" -ll False -p True -dr True
|
|
@@ -3,13 +3,19 @@
|
|
|
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.9
|
|
7
|
+
* fixing httpx dependency requirement
|
|
8
|
+
* extractProperty will now also extract Data Elements and Extensions configurations in separate folders.
|
|
9
|
+
* fix the argument handling for linkedLibrary in CLI.
|
|
10
|
+
* Change of the `force` parameter to `create` in the synchronizer CLI for better understanding of the action. `create` is now default to `False`. The synchronizer module keeps its long name `forceCreation` for backward compatibility and better understanding of the action. Also turning it to `False` by default.
|
|
11
|
+
|
|
6
12
|
## 0.4.8
|
|
7
13
|
* Adding the `clear` method in CLI
|
|
8
14
|
* Adding `syncFromLibrary` in synchronizer[doc](./synchronizer.md#syncFromLibrary).
|
|
9
15
|
* Adding `sync_from_library` command in CLI[doc](./cli.md#sync_from_library-command).
|
|
10
16
|
* Cleaning the JWT components and requirements
|
|
11
17
|
* Adding the `definition` attribute in the Library class
|
|
12
|
-
* Adding capability to define a specific environment name when creating a library in synchronizer
|
|
18
|
+
* Adding capability to define a specific environment name when creating a library in synchronizer.
|
|
13
19
|
|
|
14
20
|
## 0.4.7
|
|
15
21
|
* 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.
|
|
@@ -146,7 +146,7 @@ This method allows you to take a library in your base property and sync all of t
|
|
|
146
146
|
Arguments:
|
|
147
147
|
* library : REQUIRED : the name or the ID of the library to sync from.
|
|
148
148
|
* state : OPTIONAL : the state of the library to sync from. Default: 'published', possible states: "development", "submitted", "approved", "rejected", "published"
|
|
149
|
-
*
|
|
149
|
+
* create : 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: `False`.
|
|
150
150
|
* libraryLinked : OPTIONAL : If set to True, it will use library linked components for comparison. Default: `True`.
|
|
151
151
|
* publishedVersion : OPTIONAL : If set to True, it will compare the components to the published version in the target properties. Default: `False`.
|
|
152
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`
|
|
@@ -9,7 +9,7 @@ from launchpy.library import Library
|
|
|
9
9
|
from launchpy.admin import Admin
|
|
10
10
|
from launchpy.property import Property
|
|
11
11
|
from launchpy.synchronizer import Synchronizer
|
|
12
|
-
import re
|
|
12
|
+
import re,json
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
import httpx, asyncio
|
|
15
15
|
|
|
@@ -51,6 +51,15 @@ async def process_all_rules(rules_list, folder, header):
|
|
|
51
51
|
# 'gather' runs them all concurrently
|
|
52
52
|
await asyncio.gather(*tasks)
|
|
53
53
|
|
|
54
|
+
|
|
55
|
+
async def write_all_data_elements(data_elements_list, folder, header):
|
|
56
|
+
for data_element in data_elements_list:
|
|
57
|
+
data_element_name = __safe_name__(data_element['attributes']['name'])
|
|
58
|
+
file_path = Path(folder) / f"{data_element_name}.json"
|
|
59
|
+
with open(file_path, "w") as f:
|
|
60
|
+
json.dump(data_element, f, indent=4)
|
|
61
|
+
|
|
62
|
+
|
|
54
63
|
def extractProperty(property: dict | Property):
|
|
55
64
|
if property is None:
|
|
56
65
|
raise ValueError("Property is None")
|
|
@@ -62,4 +71,16 @@ def extractProperty(property: dict | Property):
|
|
|
62
71
|
folder = __safe_name__(property.name)
|
|
63
72
|
Path(folder).mkdir(parents=True, exist_ok=True)
|
|
64
73
|
rules = property.getRules()
|
|
65
|
-
asyncio.run(process_all_rules(rules, folder, property.header))
|
|
74
|
+
asyncio.run(process_all_rules(rules, folder, property.header))
|
|
75
|
+
dataElements = property.getDataElements()
|
|
76
|
+
dataElements_folder = Path(folder) / "data_elements"
|
|
77
|
+
Path(dataElements_folder).mkdir(parents=True, exist_ok=True)
|
|
78
|
+
asyncio.run(write_all_data_elements(dataElements, dataElements_folder, property.header))
|
|
79
|
+
extension_folder = Path(folder) / "extensions"
|
|
80
|
+
extension_folder.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
extensions = property.getExtensions()
|
|
82
|
+
for extension in extensions:
|
|
83
|
+
extension_name = __safe_name__(extension['attributes']['name'])
|
|
84
|
+
file_path = extension_folder / f"{extension_name}.json"
|
|
85
|
+
with open(file_path, "w") as f:
|
|
86
|
+
json.dump(extension, f, indent=4)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.9"
|
|
@@ -568,13 +568,13 @@ class SynchronizerCLI(cmd.Cmd):
|
|
|
568
568
|
parser.add_argument("-n", "--name", help="Name of the component to sync", type=str)
|
|
569
569
|
parser.add_argument("-id", "--id", help="ID of the component to sync (overrides name if both provided)", type=str)
|
|
570
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("-
|
|
571
|
+
parser.add_argument("-c", "--create", help="Boolean. Create the component if it does not exist. Default True. Possible values: True, False", type=str2bool, default=True)
|
|
572
572
|
try:
|
|
573
573
|
args = parser.parse_args(shlex.split(args))
|
|
574
574
|
if args.id is not None:
|
|
575
|
-
result = self.synchronizer.syncComponent(componentId=args.id, publishedVersion=args.published, forceCreation=args.
|
|
575
|
+
result = self.synchronizer.syncComponent(componentId=args.id, publishedVersion=args.published, forceCreation=args.create)
|
|
576
576
|
else:
|
|
577
|
-
result = self.synchronizer.syncComponent(componentName=args.name, publishedVersion=args.published, forceCreation=args.
|
|
577
|
+
result = self.synchronizer.syncComponent(componentName=args.name, publishedVersion=args.published, forceCreation=args.create)
|
|
578
578
|
console.print(f"Component '{args.name or args.id}' synced successfully.", style="green")
|
|
579
579
|
except Exception as e:
|
|
580
580
|
console.print(f"(!) Error: {str(e)}", style="red")
|
|
@@ -765,8 +765,8 @@ class SynchronizerCLI(cmd.Cmd):
|
|
|
765
765
|
parser = argparse.ArgumentParser(prog='sync_from_library', add_help=True)
|
|
766
766
|
parser.add_argument("-n", "--name", help="Name of the library to sync from", type=str)
|
|
767
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("-
|
|
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=
|
|
768
|
+
parser.add_argument("-c", "--create", help="Boolean. Create the component if it does not exist. Default True. Possible values: True, False", type=str2bool, default=False)
|
|
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=str2bool, default=True)
|
|
770
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
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,)
|
|
772
772
|
try:
|
|
@@ -808,7 +808,7 @@ class SynchronizerCLI(cmd.Cmd):
|
|
|
808
808
|
ruleId = rule['links']['self'].split('/').pop()
|
|
809
809
|
else:
|
|
810
810
|
ruleId = rule['id']
|
|
811
|
-
self.synchronizer.syncComponent(componentId=ruleId, publishedVersion=publishedVersion, forceCreation=args.
|
|
811
|
+
self.synchronizer.syncComponent(componentId=ruleId, publishedVersion=publishedVersion, forceCreation=args.create,libraryLinked=libraryLinked)
|
|
812
812
|
console.print(f"Rule '{rule['attributes']['name']}' synced successfully.", style="green")
|
|
813
813
|
except Exception as e:
|
|
814
814
|
console.print(f"(!) Error syncing rule '{rule['attributes']['name']}': {str(e)}", style="red")
|
|
@@ -820,7 +820,7 @@ class SynchronizerCLI(cmd.Cmd):
|
|
|
820
820
|
deId = de['links']['self'].split('/').pop()
|
|
821
821
|
else:
|
|
822
822
|
deId = de['id']
|
|
823
|
-
self.synchronizer.syncComponent(componentId=deId, publishedVersion=publishedVersion, forceCreation=args.
|
|
823
|
+
self.synchronizer.syncComponent(componentId=deId, publishedVersion=publishedVersion, forceCreation=args.create,libraryLinked=libraryLinked)
|
|
824
824
|
console.print(f"Data element '{de['attributes']['name']}' synced successfully.", style="green")
|
|
825
825
|
except Exception as e:
|
|
826
826
|
console.print(f"(!) Error syncing data element '{de['attributes']['name']}': {str(e)}", style="red")
|
|
@@ -217,7 +217,7 @@ class Synchronizer:
|
|
|
217
217
|
cmp_baseDict = {'id':lib_cmp_base['id'],'name':lib_cmp_base['attributes']['name'],'component':lib_cmp_base,'copy':copySettings(lib_cmp_base)}
|
|
218
218
|
return cmp_baseDict
|
|
219
219
|
|
|
220
|
-
def syncComponent(self,componentName:str=None,componentId:str=None,publishedVersion:bool=False,forceCreation:bool=
|
|
220
|
+
def syncComponent(self,componentName:str=None,componentId:str=None,publishedVersion:bool=False,forceCreation:bool=False,**kwargs)->None:
|
|
221
221
|
"""
|
|
222
222
|
Synchronize a component from the base property to the different target properties.
|
|
223
223
|
It will detect the component with the same name in target properties.
|
|
@@ -227,7 +227,7 @@ class Synchronizer:
|
|
|
227
227
|
componentName : REQUIRED : the name of the component to sync
|
|
228
228
|
componentID : REQUIRED : the id of the component to sync
|
|
229
229
|
publishedVersion : OPTIONAL : if you want to take the version that has been published
|
|
230
|
-
forceCreation : OPTIONAL : If the component does not exist in the target property, create it. Default:
|
|
230
|
+
forceCreation : OPTIONAL : If the component does not exist in the target property, create it. Default: False
|
|
231
231
|
possible kwargs:
|
|
232
232
|
timeout : OPTIONAL : The timeout to be used for the rule component. If not provided, the existing timeout will be used.
|
|
233
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.
|
|
@@ -384,20 +384,21 @@ class Synchronizer:
|
|
|
384
384
|
del self.targets[target]['rules'][index]
|
|
385
385
|
self.targets[target]['rules'].append(targetRule)
|
|
386
386
|
|
|
387
|
-
def syncComponents(self,componentsName:list=None,componentsId:list=None,publishedVersion:bool=False)->None:
|
|
387
|
+
def syncComponents(self,componentsName:list=None,componentsId:list=None,publishedVersion:bool=False,forceCreation:bool=False)->None:
|
|
388
388
|
"""
|
|
389
389
|
Sync multiple components by looping through the list of name passed.
|
|
390
390
|
Arguments:
|
|
391
391
|
componentsName : REQUIRED : The list of component names to sync
|
|
392
392
|
componentsId : REQUIRED : The list of component ID to sync
|
|
393
393
|
publishedVersion : OPTIONAL : if you want to take the version that has been published
|
|
394
|
+
forceCreation : 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: False.
|
|
394
395
|
"""
|
|
395
396
|
if componentsName is not None:
|
|
396
397
|
for component in componentsName:
|
|
397
|
-
self.syncComponent(componentName=component,publishedVersion=publishedVersion)
|
|
398
|
+
self.syncComponent(componentName=component,publishedVersion=publishedVersion,forceCreation=forceCreation)
|
|
398
399
|
if componentsId is not None:
|
|
399
400
|
for component in componentsId:
|
|
400
|
-
self.syncComponent(componentId=component,publishedVersion=publishedVersion)
|
|
401
|
+
self.syncComponent(componentId=component,publishedVersion=publishedVersion,forceCreation=forceCreation)
|
|
401
402
|
|
|
402
403
|
def createTargetsLibrary(self,name:str="syncComponents",assignEnv:bool|str=False)->None:
|
|
403
404
|
"""
|
|
@@ -781,13 +782,13 @@ class Synchronizer:
|
|
|
781
782
|
dict_check[element_name] = self.checkComponentSync(componentId=element_id, excludeSimilar=excludeSimilar,publishedVersion=publishedVersion,libraryLinked=libraryLink)
|
|
782
783
|
return dict_check
|
|
783
784
|
|
|
784
|
-
def syncFromLibrary(self,library:str=None,state='published',
|
|
785
|
+
def syncFromLibrary(self,library:str=None,state='published',forceCreation:bool=False,libraryLinked:bool=True,publishedVersion:bool=False,dryRun:bool=False)->None:
|
|
785
786
|
"""
|
|
786
787
|
Sync the components in a library, from the base property, to the different target properties by using the createTargetsLibrary method.
|
|
787
788
|
Arguments:
|
|
788
789
|
library : REQUIRED : the library name or the library ID to get the components.
|
|
789
790
|
state : OPTIONAL : the state of the library to compare. Default: 'published', possible states: "development", "submitted", "approved", "rejected", "published"
|
|
790
|
-
|
|
791
|
+
forceCreation : 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: False.
|
|
791
792
|
libraryLinked : OPTIONAL : If set to True, it will sync the components using the library version. Default: False.
|
|
792
793
|
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
794
|
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.
|
|
@@ -820,7 +821,7 @@ class Synchronizer:
|
|
|
820
821
|
ruleId = rule['links']['self'].split('/').pop()
|
|
821
822
|
else:
|
|
822
823
|
ruleId = rule['id']
|
|
823
|
-
self.syncComponent(componentId=ruleId,publishedVersion=publishedVersion, forceCreation=
|
|
824
|
+
self.syncComponent(componentId=ruleId,publishedVersion=publishedVersion, forceCreation=forceCreation,libraryLinked=libraryLinked)
|
|
824
825
|
except:
|
|
825
826
|
print(f'Rule {rule["attributes"]["name"]} could not be updated in the target properties. Please check if the rule exist.')
|
|
826
827
|
if len(dataelements)>0:
|
|
@@ -830,7 +831,7 @@ class Synchronizer:
|
|
|
830
831
|
deId = de['links']['self'].split('/').pop()
|
|
831
832
|
else:
|
|
832
833
|
deId = de['id']
|
|
833
|
-
self.syncComponent(componentId=deId,publishedVersion=publishedVersion,forceCreation=
|
|
834
|
+
self.syncComponent(componentId=deId,publishedVersion=publishedVersion,forceCreation=forceCreation,libraryLinked=libraryLinked)
|
|
834
835
|
except:
|
|
835
836
|
print(f'Data Element {de["attributes"]["name"]} could not be updated in the target properties. Please check if the data element exist.')
|
|
836
837
|
return
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: launchpy
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.9
|
|
4
4
|
Summary: Adobe Launch API wrapper
|
|
5
5
|
Author-email: Julien Piccini <piccini.julien@gmail.com>
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -28,6 +28,7 @@ Requires-Dist: pathlib
|
|
|
28
28
|
Requires-Dist: aepp
|
|
29
29
|
Requires-Dist: rich
|
|
30
30
|
Requires-Dist: deprecated
|
|
31
|
+
Requires-Dist: httpx
|
|
31
32
|
Dynamic: license-file
|
|
32
33
|
|
|
33
34
|
# Launchpy
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.8"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|