fusion-platform-python-sdk 1.14.7__tar.gz → 1.15.12__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.
- {fusion_platform_python_sdk-1.14.7/fusion_platform_python_sdk.egg-info → fusion_platform_python_sdk-1.15.12}/PKG-INFO +1 -1
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/__init__.py +3 -2
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/base.py +1 -1
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/command.py +232 -29
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/fusion_platform_sdk.pdf +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/model.py +80 -46
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/organisation.py +1 -2
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/process.py +15 -3
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/process_execution.md +1 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/process_execution.py +1 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/service.md +4 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/service.py +17 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/translations.py +46 -14
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12/fusion_platform_python_sdk.egg-info}/PKG-INFO +1 -1
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/setup.py +1 -1
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/tests/test_session.py +3 -5
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/LICENSE.txt +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/MANIFEST.in +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/README.md +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/__main__.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/common/__init__.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/common/raise_thread.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/common/utilities.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/detailed_example.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/documentation.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/glasgow.geojson +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/lake_district.geojson +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/localisations.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/localise.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/__init__.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/credit.md +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/credit.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/data.md +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/data.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/data_file.md +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/data_file.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/fields.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/organisation.md +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/process.md +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/process_service_execution.md +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/process_service_execution.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/process_service_execution_log.md +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/process_service_execution_log.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/user.md +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/models/user.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/quick_example.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/session.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform_python_sdk.egg-info/SOURCES.txt +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform_python_sdk.egg-info/dependency_links.txt +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform_python_sdk.egg-info/entry_points.txt +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform_python_sdk.egg-info/requires.txt +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform_python_sdk.egg-info/top_level.txt +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/setup.cfg +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/tests/test_base.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/tests/test_command.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/tests/test_init.py +0 -0
- {fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/tests/test_localise.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fusion-platform-python-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.15.12
|
|
4
4
|
Summary: Python SDK used to interact with the Fusion Platform(r)
|
|
5
5
|
Home-page: https://github.com/d-cat-support/fusion-platform-python-sdk
|
|
6
6
|
Author: Digital Content Analysis Technology Ltd
|
{fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/__init__.py
RENAMED
|
@@ -11,8 +11,8 @@ API, and this is defined within the corresponding OpenAPI 3.0 specification, whi
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
# Do not modify the following two lines as they are maintained by the version.sh script.
|
|
14
|
-
__version__ = '1.
|
|
15
|
-
__version_date__ = '2024-
|
|
14
|
+
__version__ = '1.15.12'
|
|
15
|
+
__version_date__ = '2024-12-20T11:33:56Z'
|
|
16
16
|
|
|
17
17
|
# Exclude certain sub-modules from documentation.
|
|
18
18
|
# @formatter:off
|
|
@@ -73,6 +73,7 @@ __pdoc__ = {
|
|
|
73
73
|
'models.service.ServiceInputExpressionSchema': False,
|
|
74
74
|
'models.service.ServiceOptionExpressionSchema': False,
|
|
75
75
|
'models.service.ServiceValidationSchema': False,
|
|
76
|
+
'models.service.ServiceOrganisationChargeExpressionSchema': False,
|
|
76
77
|
'models.user.UserSchema.Meta': False,
|
|
77
78
|
'models.user.UserSchema.opts': False,
|
|
78
79
|
'models.user.UserOrganisationSchema': False,
|
{fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/base.py
RENAMED
|
@@ -31,7 +31,7 @@ class Base:
|
|
|
31
31
|
Localise.setup()
|
|
32
32
|
|
|
33
33
|
# Set up logging.
|
|
34
|
-
logging.basicConfig(format='%(asctime)s.%(msecs)03d [%(levelname)s] %(
|
|
34
|
+
logging.basicConfig(format='%(asctime)s.%(msecs)03d [%(levelname)s] %(filename)s:%(funcName)s:%(lineno)d - %(message)s')
|
|
35
35
|
self._logger = logging.getLogger(fusion_platform.FUSION_PLATFORM_LOGGER)
|
|
36
36
|
self._logger.debug('sdk %s (%s): %s', fusion_platform.__version__, fusion_platform.__version_date__, self.__class__.__name__)
|
|
37
37
|
|
{fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/command.py
RENAMED
|
@@ -305,7 +305,7 @@ class Command(Base):
|
|
|
305
305
|
non_aggregator_digits = len(str(non_aggregator_count))
|
|
306
306
|
non_aggregator_chains = process.chains if hasattr(process, Model._FIELD_CHAINS) else []
|
|
307
307
|
|
|
308
|
-
self.__print(logging.INFO, i18n.t('command.define_storage',
|
|
308
|
+
self.__print(logging.INFO, i18n.t('command.define_storage', slices=non_aggregator_count))
|
|
309
309
|
progress_bar = tqdm(total=non_aggregator_count, ncols=100)
|
|
310
310
|
|
|
311
311
|
for group_index in range(1, non_aggregator_count + 1):
|
|
@@ -345,6 +345,60 @@ class Command(Base):
|
|
|
345
345
|
|
|
346
346
|
return process_definition
|
|
347
347
|
|
|
348
|
+
def display_service(self, organisation, service_name):
|
|
349
|
+
"""
|
|
350
|
+
Displays the documentation for the service. If the service does not exist, it will be ignored.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
organisation: The logged in organisation.
|
|
354
|
+
service_name: The name of the service to display.
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
True if a service was displayed.
|
|
358
|
+
"""
|
|
359
|
+
found = False
|
|
360
|
+
|
|
361
|
+
# Try to find any existing service, ignoring any error.
|
|
362
|
+
try:
|
|
363
|
+
service = self.get_service(organisation, service_name)
|
|
364
|
+
found = True
|
|
365
|
+
|
|
366
|
+
self.__print_service(service)
|
|
367
|
+
|
|
368
|
+
except CommandError as e:
|
|
369
|
+
self.__print(logging.DEBUG, str(e))
|
|
370
|
+
|
|
371
|
+
return found
|
|
372
|
+
|
|
373
|
+
def display_process(self, organisation, process_name):
|
|
374
|
+
"""
|
|
375
|
+
Displays the current configuration of the process. If the process does not exist, it will be ignored.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
organisation: The logged in organisation.
|
|
379
|
+
process_name: The name of the process to display.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
True if a process was displayed.
|
|
383
|
+
"""
|
|
384
|
+
found = False
|
|
385
|
+
|
|
386
|
+
# Try to find any existing process, ignoring any error.
|
|
387
|
+
try:
|
|
388
|
+
process, _ = self.get_process_or_execution(organisation, process_name)
|
|
389
|
+
found = True
|
|
390
|
+
|
|
391
|
+
self.__print_process(process)
|
|
392
|
+
|
|
393
|
+
# Display the status of any execution.
|
|
394
|
+
for execution in process.executions:
|
|
395
|
+
self.__print_execution(execution)
|
|
396
|
+
|
|
397
|
+
except CommandError as e:
|
|
398
|
+
self.__print(logging.DEBUG, str(e))
|
|
399
|
+
|
|
400
|
+
return found
|
|
401
|
+
|
|
348
402
|
def __download_execution(self, process, download_inputs, download_outputs, download_storage, download_intermediate, download_components, output_dir, execution):
|
|
349
403
|
"""
|
|
350
404
|
Downloads an execution.
|
|
@@ -756,10 +810,13 @@ class Command(Base):
|
|
|
756
810
|
|
|
757
811
|
Args:
|
|
758
812
|
organisation: The logged in organisation.
|
|
759
|
-
process_name: The name of the process
|
|
813
|
+
process_name: The name of the process. This can also be the id of a process or an execution.
|
|
760
814
|
|
|
761
815
|
Returns:
|
|
762
816
|
The found process and/or execution.
|
|
817
|
+
|
|
818
|
+
Raises:
|
|
819
|
+
CommandError: if a process or execution could not be found.
|
|
763
820
|
"""
|
|
764
821
|
|
|
765
822
|
# Attempt to find the process.
|
|
@@ -782,6 +839,37 @@ class Command(Base):
|
|
|
782
839
|
|
|
783
840
|
return process, execution
|
|
784
841
|
|
|
842
|
+
def get_service(self, organisation, service_name):
|
|
843
|
+
"""
|
|
844
|
+
Attempts to find a service using the given service name, service id or SSD id.
|
|
845
|
+
|
|
846
|
+
Args:
|
|
847
|
+
organisation: The logged in organisation.
|
|
848
|
+
service_name: The name of the service. This can also be the id or SSD id of a service.
|
|
849
|
+
|
|
850
|
+
Returns:
|
|
851
|
+
The found service.
|
|
852
|
+
|
|
853
|
+
Raises:
|
|
854
|
+
CommandError: if a service could not be found.
|
|
855
|
+
"""
|
|
856
|
+
|
|
857
|
+
# Find the service using its case-sensitive name (starts with).
|
|
858
|
+
service, _ = organisation.find_services(name=service_name)
|
|
859
|
+
|
|
860
|
+
# If the service cannot be found, try to find it by its id or SSD id.
|
|
861
|
+
if service is None:
|
|
862
|
+
service = next((item for item in organisation.services if (str(item.id) == service_name) or (str(item.ssd_id) == service_name)), None)
|
|
863
|
+
|
|
864
|
+
# If the service still cannot be found, then try it as a specific service for the organisation.
|
|
865
|
+
if service is None:
|
|
866
|
+
service = next((item for item in organisation.own_services if (str(item.id) == service_name) or (str(item.ssd_id) == service_name)), None)
|
|
867
|
+
|
|
868
|
+
if service is None:
|
|
869
|
+
raise CommandError(i18n.t('command.no_such_service', service=service_name))
|
|
870
|
+
|
|
871
|
+
return service
|
|
872
|
+
|
|
785
873
|
def __get_storage_data_item(self, process, group_index, group_count, ssd_id, chain_index):
|
|
786
874
|
"""
|
|
787
875
|
Gets the storage date item for a process' group and chain index.
|
|
@@ -896,7 +984,8 @@ class Command(Base):
|
|
|
896
984
|
# Change the print and logging to the desired level.
|
|
897
985
|
self._logger.setLevel(logging.CRITICAL)
|
|
898
986
|
|
|
899
|
-
if arguments.
|
|
987
|
+
if arguments.debug:
|
|
988
|
+
self._logger.setLevel(logging.DEBUG)
|
|
900
989
|
self.__print_level = logging.DEBUG
|
|
901
990
|
else:
|
|
902
991
|
self.__print_level = logging.INFO
|
|
@@ -905,8 +994,15 @@ class Command(Base):
|
|
|
905
994
|
# Login to the correct deployment and select the organisation.
|
|
906
995
|
organisation, _, _ = self.login(arguments.deployment, arguments.email, arguments.organisation)
|
|
907
996
|
|
|
908
|
-
# Perform the start, define or download.
|
|
909
|
-
if arguments.command == i18n.t('command.
|
|
997
|
+
# Perform the display, start, define or download.
|
|
998
|
+
if arguments.command == i18n.t('command.display.command'):
|
|
999
|
+
# Display a service or process with the specified name or ids. If a service is displayed, we do not display a process.
|
|
1000
|
+
for process_or_service in arguments.process_or_service:
|
|
1001
|
+
if not self.display_service(organisation, process_or_service):
|
|
1002
|
+
if not self.display_process(organisation, process_or_service):
|
|
1003
|
+
self.__print(logging.WARNING, i18n.t('command.no_process_or_service', process_or_service=process_or_service))
|
|
1004
|
+
|
|
1005
|
+
elif arguments.command == i18n.t('command.start.command'):
|
|
910
1006
|
# Start one or more processes.
|
|
911
1007
|
for service_or_yaml in arguments.service_or_yaml:
|
|
912
1008
|
# Extract the command line or YAML file arguments.
|
|
@@ -979,20 +1075,27 @@ class Command(Base):
|
|
|
979
1075
|
|
|
980
1076
|
# Create the subparsers.
|
|
981
1077
|
subparsers = parser.add_subparsers(help=i18n.t('command.subparser'), dest='command')
|
|
1078
|
+
parser.add_argument(i18n.t('command.version_short'), i18n.t('command.version_long'), help=i18n.t('command.version_help'), action='version',
|
|
1079
|
+
version=i18n.t('command.version_content', version=fusion_platform.__version__, version_date=fusion_platform.__version_date__))
|
|
1080
|
+
|
|
982
1081
|
subparsers.required = True
|
|
1082
|
+
parser_display = subparsers.add_parser(i18n.t('command.display.command'), help=i18n.t('command.display.help'))
|
|
983
1083
|
parser_start = subparsers.add_parser(i18n.t('command.start.command'), help=i18n.t('command.start.help'))
|
|
984
1084
|
parser_define = subparsers.add_parser(i18n.t('command.define.command'), help=i18n.t('command.define.help'))
|
|
985
1085
|
parser_download = subparsers.add_parser(i18n.t('command.download.command'), help=i18n.t('command.download.help'))
|
|
986
1086
|
|
|
987
1087
|
# Add in the common arguments.
|
|
988
|
-
for subparser in [parser_start, parser_define, parser_download]:
|
|
1088
|
+
for subparser in [parser_display, parser_start, parser_define, parser_download]:
|
|
989
1089
|
subparser.add_argument(i18n.t('command.deployment_short'), i18n.t('command.deployment_long'), help=i18n.t('command.deployment_help'),
|
|
990
1090
|
default=Command._DEPLOYMENT_PRODUCTION_IRL)
|
|
991
1091
|
subparser.add_argument(i18n.t('command.email_short'), i18n.t('command.email_long'), help=i18n.t('command.email_help'))
|
|
992
1092
|
subparser.add_argument(i18n.t('command.organisation_short'), i18n.t('command.organisation_long'), help=i18n.t('command.organisation_help'))
|
|
993
|
-
subparser.add_argument(i18n.t('command.
|
|
1093
|
+
subparser.add_argument(i18n.t('command.debug_short'), i18n.t('command.debug_long'), help=i18n.t('command.debug_help'), default=False,
|
|
994
1094
|
action="store_true")
|
|
995
1095
|
|
|
1096
|
+
# Display arguments.
|
|
1097
|
+
parser_display.add_argument(i18n.t('command.display.service_or_process_long'), help=i18n.t('command.display.service_or_process_help'), nargs='+')
|
|
1098
|
+
|
|
996
1099
|
# Start arguments.
|
|
997
1100
|
parser_start.add_argument(i18n.t('command.start.definition_long'), help=i18n.t('command.start.definition_help'), nargs='+')
|
|
998
1101
|
parser_start.add_argument(i18n.t('command.start.input_list_short'), i18n.t('command.start.input_list_long'), help=i18n.t('command.start.input_list_help'),
|
|
@@ -1059,6 +1162,48 @@ class Command(Base):
|
|
|
1059
1162
|
else:
|
|
1060
1163
|
print(message)
|
|
1061
1164
|
|
|
1165
|
+
def __print_execution(self, execution):
|
|
1166
|
+
"""
|
|
1167
|
+
Prints an execution. This just shows the status in a simple format.
|
|
1168
|
+
|
|
1169
|
+
Args:
|
|
1170
|
+
execution: The execution to be logged.
|
|
1171
|
+
"""
|
|
1172
|
+
|
|
1173
|
+
# Form the status of the execution.
|
|
1174
|
+
status = ''
|
|
1175
|
+
group = ''
|
|
1176
|
+
|
|
1177
|
+
if execution.success:
|
|
1178
|
+
status = i18n.t('command.log_execution_success')
|
|
1179
|
+
|
|
1180
|
+
elif execution.stopped:
|
|
1181
|
+
status = i18n.t('command.log_execution_stopped')
|
|
1182
|
+
|
|
1183
|
+
elif execution.progress < 100:
|
|
1184
|
+
status = i18n.t('command.log_execution_progress', progress=execution.progress)
|
|
1185
|
+
|
|
1186
|
+
else:
|
|
1187
|
+
abort_reason = i18n.t('command.log_execution_abort_reason', abort_reason=execution.abort_reason) if hasattr(execution,
|
|
1188
|
+
Model._FIELD_ABORT_REASON) else ''
|
|
1189
|
+
exit_type = i18n.t('command.log_execution_exit_type', exit_type=execution.exit_type) if hasattr(execution, Model._FIELD_EXIT_TYPE) else ''
|
|
1190
|
+
status = i18n.t('command.log_execution_failed', exit_type=exit_type, abort_reason=abort_reason)
|
|
1191
|
+
|
|
1192
|
+
started_at = execution.started_at.strftime('%Y-%m-%d %H:%M:%S')
|
|
1193
|
+
ended_at = i18n.t('command.log_execution_ended_at', ended_at=execution.ended_at.strftime('%Y-%m-%d %H:%M:%S')) if hasattr(execution,
|
|
1194
|
+
Model._FIELD_ENDED_AT) else ''
|
|
1195
|
+
period = i18n.t('command.log_execution_period', started_at=started_at, ended_at=ended_at)
|
|
1196
|
+
duration = i18n.t('command.log_execution_duration', minutes=round((execution.ended_at - execution.started_at).total_seconds() // 60)) if hasattr(execution,
|
|
1197
|
+
Model._FIELD_ENDED_AT) and (
|
|
1198
|
+
execution.ended_at is not None) else ''
|
|
1199
|
+
|
|
1200
|
+
if (hasattr(execution, Model._FIELD_GROUP_ID) and (execution.group_id is not None)) and (
|
|
1201
|
+
hasattr(execution, Model._FIELD_GROUP_INDEX) and (execution.group_index is not None)) and (
|
|
1202
|
+
hasattr(execution, Model._FIELD_GROUP_COUNT) and (execution.group_count is not None)):
|
|
1203
|
+
group = i18n.t('command.log_execution_group', group_id=execution.group_id, group_index=execution.group_index, group_count=execution.group_count)
|
|
1204
|
+
|
|
1205
|
+
self.__print(logging.INFO, i18n.t('command.log_execution', id=execution.id, period=period, status=status, duration=duration, group=group))
|
|
1206
|
+
|
|
1062
1207
|
def __print_process(self, process):
|
|
1063
1208
|
"""
|
|
1064
1209
|
Prints a process.
|
|
@@ -1073,26 +1218,88 @@ class Command(Base):
|
|
|
1073
1218
|
if hasattr(process, field):
|
|
1074
1219
|
self.__print(logging.INFO, i18n.t('command.log_field', field=field, value=getattr(process, field)))
|
|
1075
1220
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1221
|
+
process_inputs = [input for input in process.inputs]
|
|
1222
|
+
|
|
1223
|
+
if len(process_inputs) > 0:
|
|
1224
|
+
self.__print(logging.INFO, i18n.t('command.log_divider'))
|
|
1225
|
+
self.__print(logging.INFO, i18n.t('command.log_inputs'))
|
|
1226
|
+
|
|
1227
|
+
for data_item in process_inputs:
|
|
1228
|
+
self.__print(logging.INFO, str(data_item), format=False)
|
|
1229
|
+
|
|
1230
|
+
process_options = [option for option in process.options]
|
|
1231
|
+
|
|
1232
|
+
if len(process_options) > 0:
|
|
1233
|
+
self.__print(logging.INFO, i18n.t('command.log_divider'))
|
|
1234
|
+
self.__print(logging.INFO, i18n.t('command.log_options'))
|
|
1235
|
+
|
|
1236
|
+
for option in process_options:
|
|
1237
|
+
self.__print(logging.INFO, str(option), format=False)
|
|
1238
|
+
|
|
1239
|
+
process_dispatchers = [dispatcher for dispatcher in process.dispatchers]
|
|
1240
|
+
|
|
1241
|
+
if len(process_dispatchers) > 0:
|
|
1242
|
+
self.__print(logging.INFO, i18n.t('command.log_divider'))
|
|
1243
|
+
self.__print(logging.INFO, i18n.t('command.log_dispatchers'))
|
|
1244
|
+
|
|
1245
|
+
for dispatcher in process_dispatchers:
|
|
1246
|
+
self.__print(logging.INFO, i18n.t('command.log_subdivider'))
|
|
1247
|
+
self.__print(logging.INFO, str(dispatcher), format=False)
|
|
1078
1248
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1249
|
+
for option in dispatcher.options:
|
|
1250
|
+
self.__print(logging.INFO, str(option), format=False)
|
|
1251
|
+
|
|
1252
|
+
self.__print(logging.INFO, i18n.t('command.log_bookend'))
|
|
1253
|
+
|
|
1254
|
+
def __print_service(self, service):
|
|
1255
|
+
"""
|
|
1256
|
+
Prints the documentation for a service.
|
|
1257
|
+
|
|
1258
|
+
Args:
|
|
1259
|
+
service: The service to be logged.
|
|
1260
|
+
"""
|
|
1261
|
+
self.__print(logging.INFO, i18n.t('command.log_bookend'))
|
|
1262
|
+
self.__print(logging.INFO, i18n.t('command.log_service', service=service.name))
|
|
1263
|
+
|
|
1264
|
+
self.__print(logging.INFO, i18n.t('command.log_field', field=Model._FIELD_CATEGORIES, value=service.categories))
|
|
1265
|
+
self.__print(logging.INFO, i18n.t('command.log_field', field=Model._FIELD_KEYWORDS, value=service.keywords))
|
|
1081
1266
|
|
|
1082
1267
|
self.__print(logging.INFO, i18n.t('command.log_divider'))
|
|
1083
|
-
self.__print(logging.INFO, i18n.t('command.
|
|
1268
|
+
self.__print(logging.INFO, i18n.t('command.log_summary'))
|
|
1269
|
+
self.__print(logging.INFO, service.documentation_summary, format=False)
|
|
1084
1270
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1271
|
+
self.__print(logging.INFO, i18n.t('command.log_divider'))
|
|
1272
|
+
self.__print(logging.INFO, i18n.t('command.log_description'))
|
|
1273
|
+
self.__print(logging.INFO, service.documentation_description, format=False)
|
|
1087
1274
|
|
|
1088
1275
|
self.__print(logging.INFO, i18n.t('command.log_divider'))
|
|
1089
|
-
self.__print(logging.INFO, i18n.t('command.
|
|
1276
|
+
self.__print(logging.INFO, i18n.t('command.log_assumptions'))
|
|
1277
|
+
self.__print(logging.INFO, service.documentation_assumptions, format=False)
|
|
1278
|
+
|
|
1279
|
+
if len(service.documentation_actions.strip()) > 0:
|
|
1280
|
+
self.__print(logging.INFO, i18n.t('command.log_divider'))
|
|
1281
|
+
self.__print(logging.INFO, i18n.t('command.log_actions'))
|
|
1282
|
+
self.__print(logging.INFO, service.documentation_actions, format=False)
|
|
1283
|
+
|
|
1284
|
+
if hasattr(service, Model._FIELD_DOCUMENTATION_INPUTS):
|
|
1285
|
+
self.__print(logging.INFO, i18n.t('command.log_divider'))
|
|
1286
|
+
self.__print(logging.INFO, i18n.t('command.log_inputs'))
|
|
1287
|
+
|
|
1288
|
+
for input in service.documentation_inputs:
|
|
1289
|
+
self.__print(logging.INFO, str(input), format=False)
|
|
1090
1290
|
|
|
1091
|
-
|
|
1092
|
-
self.__print(logging.INFO, i18n.t('command.
|
|
1093
|
-
self.__print(logging.INFO,
|
|
1291
|
+
if hasattr(service, Model._FIELD_DOCUMENTATION_OUTPUTS):
|
|
1292
|
+
self.__print(logging.INFO, i18n.t('command.log_divider'))
|
|
1293
|
+
self.__print(logging.INFO, i18n.t('command.log_outputs'))
|
|
1094
1294
|
|
|
1095
|
-
for
|
|
1295
|
+
for output in service.documentation_outputs:
|
|
1296
|
+
self.__print(logging.INFO, str(output), format=False)
|
|
1297
|
+
|
|
1298
|
+
if hasattr(service, Model._FIELD_DOCUMENTATION_OPTIONS):
|
|
1299
|
+
self.__print(logging.INFO, i18n.t('command.log_divider'))
|
|
1300
|
+
self.__print(logging.INFO, i18n.t('command.log_options'))
|
|
1301
|
+
|
|
1302
|
+
for option in service.documentation_options:
|
|
1096
1303
|
self.__print(logging.INFO, str(option), format=False)
|
|
1097
1304
|
|
|
1098
1305
|
self.__print(logging.INFO, i18n.t('command.log_bookend'))
|
|
@@ -1234,16 +1441,8 @@ class Command(Base):
|
|
|
1234
1441
|
else:
|
|
1235
1442
|
data_items = []
|
|
1236
1443
|
|
|
1237
|
-
# Find the service
|
|
1238
|
-
service
|
|
1239
|
-
|
|
1240
|
-
# If the service cannot be found, then try it as a specific service for the organisation.
|
|
1241
|
-
if service is None:
|
|
1242
|
-
service = next((item for item in organisation.own_services if item.name == service_name), None)
|
|
1243
|
-
|
|
1244
|
-
if service is None:
|
|
1245
|
-
raise CommandError(i18n.t('command.no_such_service', service=service_name))
|
|
1246
|
-
|
|
1444
|
+
# Find the service.
|
|
1445
|
+
service = self.get_service(organisation, service_name)
|
|
1247
1446
|
self.__print(logging.DEBUG, i18n.t('command.using_service', service=service.name))
|
|
1248
1447
|
|
|
1249
1448
|
# Create the process with its options and inputs.
|
|
@@ -1416,3 +1615,7 @@ def main():
|
|
|
1416
1615
|
Main entry point for command line.
|
|
1417
1616
|
"""
|
|
1418
1617
|
Command().main()
|
|
1618
|
+
|
|
1619
|
+
|
|
1620
|
+
if __name__ == "__main__":
|
|
1621
|
+
main()
|
|
Binary file
|
|
@@ -35,14 +35,21 @@ class Model(Base):
|
|
|
35
35
|
# Define the standard template model paths. Override these with the correct paths.
|
|
36
36
|
_PATH_CREATE = None
|
|
37
37
|
_PATH_DELETE = None
|
|
38
|
+
_PATH_EDIT = None
|
|
38
39
|
_PATH_GET = None
|
|
39
40
|
_PATH_NEW = None
|
|
40
41
|
_PATH_PATCH = None
|
|
41
42
|
|
|
43
|
+
# Define the expected model and list extras. Override these with the correct values.
|
|
44
|
+
_EXTRAS_MODEL = None
|
|
45
|
+
_EXTRAS_LIST = None
|
|
46
|
+
|
|
42
47
|
# Useful fields and templates.
|
|
48
|
+
_FIELD_ABORT_REASON = 'abort_reason'
|
|
43
49
|
_FIELD_AVAILABLE_DISPATCHERS = 'available_dispatchers'
|
|
44
50
|
_FIELD_BOUNDS = 'bounds'
|
|
45
51
|
_FIELD_CATEGORY = 'category'
|
|
52
|
+
_FIELD_CATEGORIES = 'categories'
|
|
46
53
|
_FIELD_CHAINS = 'chains'
|
|
47
54
|
_FIELD_CHAIN_INDEX = 'chain_index'
|
|
48
55
|
_FIELD_CONSTRAINED_NAMES = 'constrained_names'
|
|
@@ -53,8 +60,13 @@ class Model(Base):
|
|
|
53
60
|
_FIELD_DISPATCHERS = 'dispatchers'
|
|
54
61
|
_FIELD_DOCUMENTATION_SUMMARY = 'documentation_summary'
|
|
55
62
|
_FIELD_DOCUMENTATION_DESCRIPTION = 'documentation_description'
|
|
63
|
+
_FIELD_DOCUMENTATION_INPUTS = 'documentation_inputs'
|
|
64
|
+
_FIELD_DOCUMENTATION_OUTPUTS = 'documentation_outputs'
|
|
65
|
+
_FIELD_DOCUMENTATION_OPTIONS = 'documentation_options'
|
|
66
|
+
_FIELD_ENDED_AT = 'ended_at'
|
|
56
67
|
_FIELD_ERROR = 'error'
|
|
57
68
|
_FIELD_EXECUTIONS = 'executions'
|
|
69
|
+
_FIELD_EXIT_TYPE = 'exit_type'
|
|
58
70
|
_FIELD_EXTENSIONS = 'extensions'
|
|
59
71
|
_FIELD_FILE = 'file'
|
|
60
72
|
_FIELD_FILE_NAME = 'file_name'
|
|
@@ -153,6 +165,27 @@ class Model(Base):
|
|
|
153
165
|
self.__model = None
|
|
154
166
|
self.__persisted = False
|
|
155
167
|
|
|
168
|
+
@classmethod
|
|
169
|
+
def _extract_extras(cls, response, extras=None):
|
|
170
|
+
"""
|
|
171
|
+
Extracts the optional extras from the response. If the extras are not found, they are not loaded.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
response: The response to be modified.
|
|
175
|
+
extras: The optional list of extras as tuples (key, attribute) to extract.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
The extracted extras.
|
|
179
|
+
"""
|
|
180
|
+
extracted_extras = {}
|
|
181
|
+
model_extras = response.get(Model._RESPONSE_KEY_EXTRAS, {})
|
|
182
|
+
|
|
183
|
+
for key, attribute in (extras if extras is not None else []):
|
|
184
|
+
if (key is not None) and (attribute is not None):
|
|
185
|
+
extracted_extras[attribute] = model_extras.get(key)
|
|
186
|
+
|
|
187
|
+
return extracted_extras
|
|
188
|
+
|
|
156
189
|
@property
|
|
157
190
|
def attributes(self):
|
|
158
191
|
"""
|
|
@@ -308,9 +341,9 @@ class Model(Base):
|
|
|
308
341
|
def get(self, **kwargs):
|
|
309
342
|
"""
|
|
310
343
|
Gets the model object by loading it from the Fusion Platform<sup>®</sup>. Uses the model's current id and base model id for the get unless explicit
|
|
311
|
-
values are provided via keyword arguments. This assumes the model is obtained using a GET RESTful request, and that the
|
|
312
|
-
dictionary key within the response. The model is then loaded using the supplied schema to obtain the corresponding
|
|
313
|
-
loading it into the model as a set of read-only attributes.
|
|
344
|
+
values are provided via keyword arguments. Any expected extras are also added. This assumes the model is obtained using a GET RESTful request, and that the
|
|
345
|
+
model data is held with the expected dictionary key within the response. The model is then loaded using the supplied schema to obtain the corresponding
|
|
346
|
+
Python representation of it, before loading it into the model as a set of read-only attributes.
|
|
314
347
|
|
|
315
348
|
Args:
|
|
316
349
|
kwargs: Any explicit ids to be used.
|
|
@@ -431,7 +464,7 @@ class Model(Base):
|
|
|
431
464
|
@classmethod
|
|
432
465
|
def _model_from_api_id(cls, session, **kwargs):
|
|
433
466
|
"""
|
|
434
|
-
Loads a model object with the given ids as keyword arguments from the Fusion Platform<sup>®</sup>.
|
|
467
|
+
Loads a model object with the given ids as keyword arguments from the Fusion Platform<sup>®</sup>. Any required extras are also added.
|
|
435
468
|
|
|
436
469
|
Args:
|
|
437
470
|
session: The linked session object for interfacing with the Fusion Platform<sup>®</sup>.
|
|
@@ -455,7 +488,7 @@ class Model(Base):
|
|
|
455
488
|
@classmethod
|
|
456
489
|
def _models_from_api_ids(cls, session, ids):
|
|
457
490
|
"""
|
|
458
|
-
Generates an iterator through a series of models using their ids. Each model is loaded using an id.
|
|
491
|
+
Generates an iterator through a series of models using their ids. Each model is loaded using an id. Any extras are optionally added.
|
|
459
492
|
|
|
460
493
|
Args:
|
|
461
494
|
session: The linked session object for interfacing with the Fusion Platform<sup>®</sup>.
|
|
@@ -471,10 +504,10 @@ class Model(Base):
|
|
|
471
504
|
return (cls._model_from_api_id(session, **id) for id in ids)
|
|
472
505
|
|
|
473
506
|
@classmethod
|
|
474
|
-
def _models_from_api_path(cls, session, path, items_per_request=24, reverse=False, filter=None, search=None, **kwargs):
|
|
507
|
+
def _models_from_api_path(cls, session, path, items_per_request=24, reverse=False, filter=None, search=None, load_extras=True, **kwargs):
|
|
475
508
|
"""
|
|
476
|
-
Generates an iterator through a series of models using a path which returns a list of objects. Each model is loaded from the list
|
|
477
|
-
paged, the generator takes into account having to get subsequent pages of results.
|
|
509
|
+
Generates an iterator through a series of models using a path which returns a list of objects. Each model is loaded from the list with its expected extras.
|
|
510
|
+
Since API lists are paged, the generator takes into account having to get subsequent pages of results.
|
|
478
511
|
|
|
479
512
|
Use the items per request, reverse and filter parameters to modify the set of results returned. The items per request parameter determines how many results
|
|
480
513
|
are returned by each request to the API for a page of results, up to the maximum number of results available. The reverse parameter can be used to return
|
|
@@ -508,6 +541,7 @@ class Model(Base):
|
|
|
508
541
|
reverse: Whether the list should be reversed or not. Default False.
|
|
509
542
|
filter: The optional filter to be applied to the results. Default is no filter.
|
|
510
543
|
search: The optional search term to be applied to the results. Default to no search term.
|
|
544
|
+
load_extras: Should the model extras be automatically loaded? Default True.
|
|
511
545
|
kwargs: Any additional attributes which are to be set on each model which are not provided in each item from the path.
|
|
512
546
|
|
|
513
547
|
Returns:
|
|
@@ -541,55 +575,40 @@ class Model(Base):
|
|
|
541
575
|
Model._RESPONSE_KEY_LAST) is not None else {}
|
|
542
576
|
finished = len(last) <= 0
|
|
543
577
|
|
|
544
|
-
#
|
|
578
|
+
# Optionally extract the extras.
|
|
579
|
+
extracted_extras = cls._extract_extras(response, extras=cls._EXTRAS_LIST) if load_extras else {}
|
|
580
|
+
|
|
581
|
+
# Build the generator around all of the returned items, which must all have been persisted. Optional extras are added to each model.
|
|
545
582
|
for item in response.get(Model._RESPONSE_KEY_LIST, []):
|
|
583
|
+
# Add the optional extras into the item.
|
|
584
|
+
for key, value in extracted_extras.items():
|
|
585
|
+
item[key] = value
|
|
586
|
+
|
|
587
|
+
# Build the model from the modified item dictionary.
|
|
546
588
|
model = cls(session)
|
|
547
589
|
model._set_model_from_response(item, **kwargs)
|
|
548
590
|
model.__persisted = True
|
|
549
591
|
|
|
550
592
|
yield model
|
|
551
593
|
|
|
552
|
-
def _new(self, query_parameters=None,
|
|
594
|
+
def _new(self, query_parameters=None, **kwargs):
|
|
553
595
|
"""
|
|
554
|
-
Gets a new template model object by loading it from the Fusion Platform<sup>®</sup>.
|
|
555
|
-
argument. This assumes the model is obtained using a GET RESTful request from the corresponding path, and that the
|
|
556
|
-
dictionary key within the response. The template model is then loaded using the supplied schema to obtain the
|
|
557
|
-
before loading it into the model as a set of read-only attributes.
|
|
558
|
-
field in the extras, and attribute is the corresponding model attribute to be set from the field, then each is added to the model.
|
|
596
|
+
Gets a new template model object by loading it from the Fusion Platform<sup>®</sup>. Any expected extras are also added. The explicit base model id
|
|
597
|
+
value should be provided via a keyword argument. This assumes the model is obtained using a GET RESTful request from the corresponding path, and that the
|
|
598
|
+
model data is held with the expected dictionary key within the response. The template model is then loaded using the supplied schema to obtain the
|
|
599
|
+
corresponding Python representation of it, before loading it into the model as a set of read-only attributes.
|
|
559
600
|
|
|
560
601
|
Args:
|
|
561
602
|
query_parameters: The optional query parameters as a dictionary.
|
|
562
|
-
extras: The optional list of extras tuples (key, attribute) which should be added to the model.
|
|
563
603
|
kwargs: Should include the base model id, if needed.
|
|
564
604
|
|
|
565
605
|
Raises:
|
|
566
606
|
RequestError: if the new fails.
|
|
567
607
|
ModelError: if the model could not be loaded or validated from the Fusion Platform<sup>®</sup>.
|
|
568
608
|
"""
|
|
569
|
-
# Get the data.
|
|
570
|
-
response = self._session.request(path=self._get_path(self.__class__._PATH_NEW, **kwargs), query_parameters=query_parameters)
|
|
571
609
|
|
|
572
|
-
#
|
|
573
|
-
|
|
574
|
-
raise ModelError(i18n.t('models.model.failed_model_new_model'))
|
|
575
|
-
|
|
576
|
-
# If any extras are required, extract them.
|
|
577
|
-
if (Model._RESPONSE_KEY_EXTRAS not in response) and (extras is not None):
|
|
578
|
-
raise ModelError(i18n.t('models.model.failed_model_new_extras'))
|
|
579
|
-
|
|
580
|
-
modified_response = response.get(Model._RESPONSE_KEY_MODEL, {})
|
|
581
|
-
model_extras = response.get(Model._RESPONSE_KEY_EXTRAS, {})
|
|
582
|
-
|
|
583
|
-
for key, attribute in (extras if extras is not None else []):
|
|
584
|
-
if (key is not None) and (attribute is not None):
|
|
585
|
-
modified_response[attribute] = model_extras.get(key)
|
|
586
|
-
|
|
587
|
-
# Add in the keyword arguments to the response so that they are set in the model as well.
|
|
588
|
-
for key, value in kwargs.items():
|
|
589
|
-
modified_response[key] = value
|
|
590
|
-
|
|
591
|
-
# Load the response into the model. We ignore missing required fields and those which are None.
|
|
592
|
-
self._set_model_from_response(modified_response, partial=True)
|
|
610
|
+
# Get and load the data, setting the extras as well.
|
|
611
|
+
self._send_and_load(self._get_path(self.__class__._PATH_NEW, **kwargs), partial=True, query_parameters=query_parameters, **kwargs)
|
|
593
612
|
|
|
594
613
|
# The model is not persisted.
|
|
595
614
|
self.__persisted = False
|
|
@@ -604,25 +623,40 @@ class Model(Base):
|
|
|
604
623
|
"""
|
|
605
624
|
return self.__persisted
|
|
606
625
|
|
|
607
|
-
def _send_and_load(self, path, method=Session.METHOD_GET, body=None, key=_RESPONSE_KEY_MODEL):
|
|
626
|
+
def _send_and_load(self, path, method=Session.METHOD_GET, body=None, key=_RESPONSE_KEY_MODEL, partial=False, query_parameters=None, **kwargs):
|
|
608
627
|
"""
|
|
609
|
-
Sends the body to the path using the method and then loads the resulting model.
|
|
628
|
+
Sends the body to the path using the method and then loads the resulting model. Additional query parameters may be specified that should be added to the
|
|
629
|
+
model. Keyword arguments can be used to be added to the model, overriding any values that may have been retrieved.
|
|
610
630
|
|
|
611
631
|
Args:
|
|
612
632
|
path: The path to send the request to.
|
|
613
633
|
method: The optional RESTful method for sending. Default is GET.
|
|
614
634
|
body: The optional body to send. Default is None.
|
|
615
635
|
key: The optional key to load the model from the response. Default is RESPONSE_KEY_MODEL.
|
|
636
|
+
partial: Skip validation of required fields which are missing? Default False.
|
|
637
|
+
query_parameters: The optional query parameters as a dictionary.
|
|
638
|
+
kwargs: Should include the base model id, if needed.
|
|
616
639
|
"""
|
|
617
640
|
# Send the request.
|
|
618
|
-
response = self._session.request(path=path, method=method, body=body)
|
|
641
|
+
response = self._session.request(path=path, query_parameters=query_parameters, method=method, body=body)
|
|
619
642
|
|
|
620
643
|
# Assume that the resulting model is held within the expected key within the resulting dictionary.
|
|
621
644
|
if key not in response:
|
|
622
645
|
raise ModelError(i18n.t('models.model.failed_model_send_and_load'))
|
|
623
646
|
|
|
624
|
-
#
|
|
625
|
-
|
|
647
|
+
# If any extras are required, extract them and add them to the model.
|
|
648
|
+
modified_response = response.get(key, {}) if key is not None else response
|
|
649
|
+
extracted_extras = self.__class__._extract_extras(response, extras=self.__class__._EXTRAS_MODEL)
|
|
650
|
+
|
|
651
|
+
for key, value in extracted_extras.items():
|
|
652
|
+
modified_response[key] = value
|
|
653
|
+
|
|
654
|
+
# Add in the keyword arguments to the response so that they are set in the model as well.
|
|
655
|
+
for key, value in kwargs.items():
|
|
656
|
+
modified_response[key] = value
|
|
657
|
+
|
|
658
|
+
# Load the response into the model. Optionally ignore missing required fields and those which are None.
|
|
659
|
+
self._set_model_from_response(modified_response, partial=partial)
|
|
626
660
|
|
|
627
661
|
def __setattr__(self, key, value):
|
|
628
662
|
"""
|
|
@@ -760,8 +794,8 @@ class Model(Base):
|
|
|
760
794
|
def update(self, **kwargs):
|
|
761
795
|
"""
|
|
762
796
|
Attempts to update the model object with the given values. For models which have not been persisted, the relevant fields are updated without validation,
|
|
763
|
-
which will occur when the model is persisted. For models which have been persisted, the update is made via the Fusion Platform<sup>®</sup>. All current
|
|
764
|
-
attributes are sent to the Fusion Platform<sup>®</sup> which will automatically ignore any which are not allowed or are read-only.
|
|
797
|
+
which will occur when the model is persisted. For models which have been persisted, the update is made via the Fusion Platform<sup>®</sup>. All current
|
|
798
|
+
model attributes are sent to the Fusion Platform<sup>®</sup> which will automatically ignore any which are not allowed or are read-only.
|
|
765
799
|
|
|
766
800
|
Args:
|
|
767
801
|
kwargs: The model attributes which are to be patched.
|
|
@@ -301,8 +301,7 @@ class Organisation(Model):
|
|
|
301
301
|
"""
|
|
302
302
|
# Get a new template for the process model using the service.
|
|
303
303
|
process = Process(self._session)
|
|
304
|
-
process._new(query_parameters={Model._get_id_name(Service.__name__): service.id},
|
|
305
|
-
extras=[(self.__class__._FIELD_DISPATCHERS, self.__class__._FIELD_AVAILABLE_DISPATCHERS)], organisation_id=self.id, name=name)
|
|
304
|
+
process._new(query_parameters={Model._get_id_name(Service.__name__): service.id}, organisation_id=self.id, name=name)
|
|
306
305
|
|
|
307
306
|
return process
|
|
308
307
|
|
|
@@ -228,7 +228,7 @@ class Option(Model):
|
|
|
228
228
|
Returns:
|
|
229
229
|
A string representation of the object.
|
|
230
230
|
"""
|
|
231
|
-
return f"{self.title} ('{self.name}', {self.data_type}{', required' if self.required else ''}) = {self.value}: {self.description}"
|
|
231
|
+
return f"{self.title} ('{self.name}', {self.data_type}{' ' + str(self.constrained_values) if self.data_type == fusion_platform.DATA_TYPE_CONSTRAINED else ''}{', required' if self.required else ''}) = {self.value}: {self.description}"
|
|
232
232
|
|
|
233
233
|
|
|
234
234
|
# Define a model class for an input which overrides the representation to display more relevant information.
|
|
@@ -319,6 +319,7 @@ class ProcessExecutionStatusSchema(Schema):
|
|
|
319
319
|
group_count = fields.Integer(allow_none=True)
|
|
320
320
|
job_status = fields.String(required=True)
|
|
321
321
|
abort_reason = fields.String(allow_none=True)
|
|
322
|
+
exit_type = fields.String(allow_none=True)
|
|
322
323
|
started_at = fields.DateTime(required=True)
|
|
323
324
|
ended_at = fields.DateTime(allow_none=True)
|
|
324
325
|
delete_warning_status = fields.String(required=True)
|
|
@@ -517,6 +518,10 @@ class Process(Model):
|
|
|
517
518
|
_PATH_EXECUTIONS = f"{_PATH_BASE}/executions"
|
|
518
519
|
_PATH_STOP = f"{_PATH_BASE}/stop"
|
|
519
520
|
|
|
521
|
+
# Define the expected model and list extras.
|
|
522
|
+
_EXTRAS_MODEL = [(Model._FIELD_DISPATCHERS, Model._FIELD_AVAILABLE_DISPATCHERS)]
|
|
523
|
+
_EXTRAS_LIST = [(Model._FIELD_DISPATCHERS, Model._FIELD_AVAILABLE_DISPATCHERS)]
|
|
524
|
+
|
|
520
525
|
# Process status values.
|
|
521
526
|
_PROCESS_STATUS_EXECUTE = 'execute'
|
|
522
527
|
_PROCESS_STATUS_STOP = 'stop'
|
|
@@ -631,6 +636,12 @@ class Process(Model):
|
|
|
631
636
|
modified_response = response.get(Model._RESPONSE_KEY_MODEL, {})
|
|
632
637
|
modified_response['organisation_id'] = self.organisation_id
|
|
633
638
|
|
|
639
|
+
# Extract the extras.
|
|
640
|
+
extracted_extras = self.__class__._extract_extras(response, extras=self.__class__._EXTRAS_LIST)
|
|
641
|
+
|
|
642
|
+
for key, value in extracted_extras.items():
|
|
643
|
+
modified_response[key] = value
|
|
644
|
+
|
|
634
645
|
# Load the response into the model. We ignore missing required fields and those which are None.
|
|
635
646
|
process._set_model_from_response(modified_response, partial=True)
|
|
636
647
|
|
|
@@ -745,7 +756,8 @@ class Process(Model):
|
|
|
745
756
|
RequestError: if any get fails.
|
|
746
757
|
ModelError: if a model could not be loaded or validated from the Fusion Platform<sup>®</sup>.
|
|
747
758
|
"""
|
|
748
|
-
return ProcessExecution._models_from_api_path(self._session, self._get_path(self.__class__._PATH_EXECUTIONS), reverse=True
|
|
759
|
+
return ProcessExecution._models_from_api_path(self._session, self._get_path(self.__class__._PATH_EXECUTIONS), reverse=True,
|
|
760
|
+
load_extras=False) # Most recent first.
|
|
749
761
|
|
|
750
762
|
def find_executions(self, id=None, group_id=None):
|
|
751
763
|
"""
|
|
@@ -766,7 +778,7 @@ class Process(Model):
|
|
|
766
778
|
[(self.__class__._FIELD_ID, self.__class__._FILTER_MODIFIER_EQ, id), (self.__class__._FIELD_GROUP_ID, self.__class__._FILTER_MODIFIER_EQ, group_id)])
|
|
767
779
|
|
|
768
780
|
# Build the partial find generator and execute it.
|
|
769
|
-
find = partial(ProcessExecution._models_from_api_path, self._session, self._get_path(self.__class__._PATH_EXECUTIONS), filter=filter)
|
|
781
|
+
find = partial(ProcessExecution._models_from_api_path, self._session, self._get_path(self.__class__._PATH_EXECUTIONS), filter=filter, load_extras=False)
|
|
770
782
|
return self.__class__._first_and_generator(find)
|
|
771
783
|
|
|
772
784
|
@property
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
* **stopped**: Has the execution been stopped by a user?
|
|
34
34
|
* **abort**: Has the execution been aborted?
|
|
35
35
|
* **abort_reason**: The reason for any abort of the execution.
|
|
36
|
+
* **exit_type**: The type of exit experienced by the execution.
|
|
36
37
|
* **success**: Has the execution completed successfully?
|
|
37
38
|
* **progress**: Percentage progress of the execution.
|
|
38
39
|
* **delete_expiry**: When will the execution expire and therefore be deleted?
|
|
@@ -106,6 +106,7 @@ class ProcessExecutionSchema(Schema):
|
|
|
106
106
|
stopped = fields.Boolean(required=True, metadata={'read_only': True}) # Changed to prevent this being updated.
|
|
107
107
|
abort = fields.Boolean(required=True, metadata={'read_only': True}) # Changed to prevent this being updated.
|
|
108
108
|
abort_reason = fields.String(allow_none=True, metadata={'read_only': True}) # Changed to prevent this being updated.
|
|
109
|
+
exit_type = fields.String(allow_none=True, metadata={'read_only': True}) # Changed to prevent this being updated.
|
|
109
110
|
success = fields.Boolean(required=True, metadata={'read_only': True}) # Changed to prevent this being updated.
|
|
110
111
|
progress = fields.Integer(required=True, metadata={'read_only': True}) # Changed to prevent this being updated.
|
|
111
112
|
delete_expiry = fields.DateTime(required=True)
|
|
@@ -68,6 +68,10 @@
|
|
|
68
68
|
* **charge_expression_platform**: The expression used to calculate the charge in FPUs levied for the platform.
|
|
69
69
|
* **charge_expression_owner**: The expression used to calculate the charge in FPUs levied for the owner of the service.
|
|
70
70
|
* **organisations**: The optional whitelist of organisations who can use this service.
|
|
71
|
+
* **organisation_charge_expressions**: For the whitelisted organisations, these provide the optional charge expressions to be applied.
|
|
72
|
+
* **id**: The owning organisation.
|
|
73
|
+
* **platform**: The expression used to calculate the charge in FPUs levied for the platform.
|
|
74
|
+
* **owner**: The expression used to calculate the charge in FPUs levied for the owner of the service.
|
|
71
75
|
* **geographic_regions**: The optional list of geographic regions from which this service can be used.
|
|
72
76
|
* **documentation_summary**: The documentation summary of the service.
|
|
73
77
|
* **documentation_description**: The documentation description of the service.
|
|
@@ -184,6 +184,22 @@ class ServiceValidationSchema(Schema):
|
|
|
184
184
|
ordered = True
|
|
185
185
|
|
|
186
186
|
|
|
187
|
+
class ServiceOrganisationChargeExpressionSchema(Schema):
|
|
188
|
+
"""
|
|
189
|
+
Nested schema class for service organisation.
|
|
190
|
+
"""
|
|
191
|
+
id = fields.UUID(required=True)
|
|
192
|
+
platform = fields.String(allow_none=True)
|
|
193
|
+
owner = fields.String(allow_none=True)
|
|
194
|
+
|
|
195
|
+
class Meta:
|
|
196
|
+
"""
|
|
197
|
+
When loading an object, make sure we exclude any unknown fields, rather than raising an exception, and put fields in their definition order.
|
|
198
|
+
"""
|
|
199
|
+
unknown = EXCLUDE
|
|
200
|
+
ordered = True
|
|
201
|
+
|
|
202
|
+
|
|
187
203
|
class ServiceSchema(Schema):
|
|
188
204
|
"""
|
|
189
205
|
Schema class for service model.
|
|
@@ -230,6 +246,7 @@ class ServiceSchema(Schema):
|
|
|
230
246
|
charge_expression_owner = fields.String(allow_none=True) # Changed to optional.
|
|
231
247
|
|
|
232
248
|
organisations = fields.List(fields.UUID(required=True), allow_none=True)
|
|
249
|
+
organisation_charge_expressions = fields.List(fields.Nested(ServiceOrganisationChargeExpressionSchema()), allow_none=True)
|
|
233
250
|
geographic_regions = fields.List(fields.String(required=True), allow_none=True)
|
|
234
251
|
|
|
235
252
|
# Removed creator.
|
|
@@ -13,19 +13,37 @@ import i18n
|
|
|
13
13
|
# @formatter:off
|
|
14
14
|
i18n.add_translation('command.validate_constrained_error', 'Not one of %{constrained}', 'en')
|
|
15
15
|
i18n.add_translation('command.unknown_command', 'Unknown command \'%{command}\'', 'en')
|
|
16
|
+
i18n.add_translation('command.no_process_or_service', 'No such service, process or execution \'%{process_or_service}\'', 'en')
|
|
16
17
|
i18n.add_translation('command.no_such_service', 'No such service \'%{service}\'', 'en')
|
|
17
18
|
i18n.add_translation('command.no_such_process', 'No such process or execution \'%{process}\'', 'en')
|
|
18
19
|
i18n.add_translation('command.no_such_organisation', 'No such organisation \'%{organisation}\'', 'en')
|
|
19
20
|
i18n.add_translation('command.no_such_input', 'No such input \'%{input_name}\'', 'en')
|
|
20
21
|
i18n.add_translation('command.no_executions', 'No executions for process \'%{process}\'', 'en')
|
|
21
22
|
i18n.add_translation('command.invalid_login_response', 'Incorrect username or password', 'en')
|
|
22
|
-
i18n.add_translation('command.
|
|
23
|
-
i18n.add_translation('command.
|
|
23
|
+
i18n.add_translation('command.log_execution_group', ' (group \'%{group_id}\' %{group_index} of %{group_count})', 'en')
|
|
24
|
+
i18n.add_translation('command.log_execution_duration', ' - %{minutes} minute(s)', 'en')
|
|
25
|
+
i18n.add_translation('command.log_execution_period', ' (%{started_at}%{ended_at})', 'en')
|
|
26
|
+
i18n.add_translation('command.log_execution_ended_at', ' -> %{ended_at}', 'en')
|
|
27
|
+
i18n.add_translation('command.log_execution_failed', 'failed%{exit_type}%{abort_reason}', 'en')
|
|
28
|
+
i18n.add_translation('command.log_execution_exit_type', ' (%{exit_type})', 'en')
|
|
29
|
+
i18n.add_translation('command.log_execution_abort_reason', ': %{abort_reason}', 'en')
|
|
30
|
+
i18n.add_translation('command.log_execution_stopped', 'stopped', 'en')
|
|
31
|
+
i18n.add_translation('command.log_execution_progress', 'processing %{progress}%%', 'en')
|
|
32
|
+
i18n.add_translation('command.log_execution_success', 'success', 'en')
|
|
33
|
+
i18n.add_translation('command.log_actions', 'Actions:', 'en')
|
|
34
|
+
i18n.add_translation('command.log_assumptions', 'Assumptions:', 'en')
|
|
35
|
+
i18n.add_translation('command.log_description', 'Description:', 'en')
|
|
36
|
+
i18n.add_translation('command.log_summary', 'Summary:', 'en')
|
|
37
|
+
i18n.add_translation('command.log_dispatchers', 'Dispatchers:', 'en')
|
|
24
38
|
i18n.add_translation('command.log_options', 'Options:', 'en')
|
|
39
|
+
i18n.add_translation('command.log_outputs', 'Outputs:', 'en')
|
|
25
40
|
i18n.add_translation('command.log_inputs', 'Inputs:', 'en')
|
|
41
|
+
i18n.add_translation('command.log_execution', 'Execution \'%{id}\'%{period}: %{status}%{duration}%{group}', 'en')
|
|
42
|
+
i18n.add_translation('command.log_service', 'Service \'%{service}\'', 'en')
|
|
43
|
+
i18n.add_translation('command.log_process', 'Process \'%{process}\'', 'en')
|
|
26
44
|
i18n.add_translation('command.log_field', '%{field}: %{value}', 'en')
|
|
45
|
+
i18n.add_translation('command.log_subdivider', '................................................................................................', 'en')
|
|
27
46
|
i18n.add_translation('command.log_divider', '------------------------------------------------------------------------------------------------', 'en')
|
|
28
|
-
i18n.add_translation('command.log_dispatchers', 'Dispatchers:', 'en')
|
|
29
47
|
i18n.add_translation('command.log_bookend', '************************************************************************************************', 'en')
|
|
30
48
|
i18n.add_translation('command.wait_for_next', 'Waiting for next execution...', 'en')
|
|
31
49
|
i18n.add_translation('command.using_storage', 'Uploading storage \'%{filename}\' with id %{storage_id} to \'%{storage_name}\'', 'en')
|
|
@@ -48,7 +66,7 @@ i18n.add_translation('command.download_process', 'Downloading process \'%{proces
|
|
|
48
66
|
i18n.add_translation('command.download_files', 'Downloading %{files} file(s)...', 'en')
|
|
49
67
|
i18n.add_translation('command.download_executions', 'Gathering files for %{executions} execution(s)...', 'en')
|
|
50
68
|
i18n.add_translation('command.download_execution', 'Downloading execution %{execution} to %{output}', 'en')
|
|
51
|
-
i18n.add_translation('command.define_storage', 'Defining %{
|
|
69
|
+
i18n.add_translation('command.define_storage', 'Defining storage file(s) for %{slices} slice(s)...', 'en')
|
|
52
70
|
i18n.add_translation('command.define_process', 'Defining \'%{process}\' (inputs %{inputs}, storage %{storage})', 'en')
|
|
53
71
|
i18n.add_translation('command.define_inputs', 'Defining %{files} input file(s)...', 'en')
|
|
54
72
|
i18n.add_translation('command.add_dispatcher', 'Add %{dispatcher} dispatcher?', 'en')
|
|
@@ -97,10 +115,18 @@ i18n.add_translation('command.start.definition_help', 'the names of the service
|
|
|
97
115
|
i18n.add_translation('command.start.definition_long', 'service_or_yaml', 'en')
|
|
98
116
|
i18n.add_translation('command.start.help', 'starts a process', 'en')
|
|
99
117
|
i18n.add_translation('command.start.command', 'start', 'en')
|
|
100
|
-
i18n.add_translation('command.
|
|
101
|
-
i18n.add_translation('command.
|
|
102
|
-
i18n.add_translation('command.
|
|
103
|
-
i18n.add_translation('command.
|
|
118
|
+
i18n.add_translation('command.display.service_or_process_help', 'the names of the service, id, SSD id, process, process id or associated execution id that are to be displayed', 'en')
|
|
119
|
+
i18n.add_translation('command.display.service_or_process_long', 'process_or_service', 'en')
|
|
120
|
+
i18n.add_translation('command.display.help', 'displays service information or a configured process', 'en')
|
|
121
|
+
i18n.add_translation('command.display.command', 'display', 'en')
|
|
122
|
+
i18n.add_translation('command.subparser', 'displays a service or process, or starts, defines or downloads a process', 'en')
|
|
123
|
+
i18n.add_translation('command.version_content', '%%(prog)s %{version} (%{version_date})', 'en')
|
|
124
|
+
i18n.add_translation('command.version_help', 'show the version information and exit', 'en')
|
|
125
|
+
i18n.add_translation('command.version_long', '--version', 'en')
|
|
126
|
+
i18n.add_translation('command.version_short', '-v', 'en')
|
|
127
|
+
i18n.add_translation('command.debug_help', 'show debug output (default \'%%(default)s\')', 'en')
|
|
128
|
+
i18n.add_translation('command.debug_long', '--debug', 'en')
|
|
129
|
+
i18n.add_translation('command.debug_short', '-b', 'en')
|
|
104
130
|
i18n.add_translation('command.organisation_help', 'the organisation to be used', 'en')
|
|
105
131
|
i18n.add_translation('command.organisation_long', '--organisation', 'en')
|
|
106
132
|
i18n.add_translation('command.organisation_short', '-g', 'en')
|
|
@@ -113,18 +139,26 @@ i18n.add_translation('command.deployment_short', '-y', 'en')
|
|
|
113
139
|
i18n.add_translation('command.epilog', '''
|
|
114
140
|
For more detailed options, use:
|
|
115
141
|
|
|
142
|
+
fusion_platform display --help
|
|
116
143
|
fusion_platform start --help
|
|
117
144
|
fusion_platform define --help
|
|
118
145
|
fusion_platform download --help
|
|
119
146
|
|
|
120
147
|
''', 'en')
|
|
121
148
|
i18n.add_translation('command.description', '''
|
|
122
|
-
Use this command to
|
|
123
|
-
defined by a YAML file.
|
|
149
|
+
Use this command to display service information and processes, and to start, download or define processes. Processes can be selected via command line options or
|
|
150
|
+
defined by a YAML file.
|
|
124
151
|
|
|
125
152
|
Usage:
|
|
126
|
-
The following will
|
|
127
|
-
|
|
153
|
+
The following will display information about one or more services or processes from the given \'service_or_process_name\'.
|
|
154
|
+
|
|
155
|
+
fusion_platform display <service_or_process_name> ...
|
|
156
|
+
|
|
157
|
+
For a service, this will display the full service documentation, including its expected inputs and options. For a process, it will show the current process
|
|
158
|
+
configuration.
|
|
159
|
+
|
|
160
|
+
The following will attempt to create one or more processes from the given \'service_name\' to use the inputs (in order) and options and execute them. All
|
|
161
|
+
processes will use the same inputs and options:
|
|
128
162
|
|
|
129
163
|
fusion_platform start <service_name> ... -l <input_file|input_name> ... -p <option=value> ...
|
|
130
164
|
|
|
@@ -237,8 +271,6 @@ i18n.add_translation('models.fields.boolean.invalid', 'Not a valid boolean', 'en
|
|
|
237
271
|
i18n.add_translation('models.model.update_empty_body', 'Update cannot be requested as there are no attributes to be used (read-only attributes have been removed)', 'en')
|
|
238
272
|
i18n.add_translation('models.model.create_empty_body', 'Create cannot be requested as there are no attributes to be used (read-only attributes have been removed)', 'en')
|
|
239
273
|
i18n.add_translation('models.model.failed_model_validation', 'Failed to validate model: %{message}', 'en')
|
|
240
|
-
i18n.add_translation('models.model.failed_model_new_extras', 'Failed to get model extras from response', 'en')
|
|
241
|
-
i18n.add_translation('models.model.failed_model_new_model', 'Failed to get model template from response', 'en')
|
|
242
274
|
i18n.add_translation('models.model.failed_model_send_and_load', 'Failed to request and load model', 'en')
|
|
243
275
|
i18n.add_translation('models.model.no_such_keys', 'No such keys %{keys}', 'en')
|
|
244
276
|
i18n.add_translation('models.model.readonly_property', 'Property %{property} is read-only and cannot be set', 'en')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fusion-platform-python-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.15.12
|
|
4
4
|
Summary: Python SDK used to interact with the Fusion Platform(r)
|
|
5
5
|
Home-page: https://github.com/d-cat-support/fusion-platform-python-sdk
|
|
6
6
|
Author: Digital Content Analysis Technology Ltd
|
|
@@ -19,7 +19,7 @@ PACKAGE_DIR = pathlib.Path(__file__).parent
|
|
|
19
19
|
# @formatter:off
|
|
20
20
|
setup(
|
|
21
21
|
name='fusion-platform-python-sdk',
|
|
22
|
-
version='1.
|
|
22
|
+
version='1.15.12',
|
|
23
23
|
description='Python SDK used to interact with the Fusion Platform(r)',
|
|
24
24
|
long_description=(PACKAGE_DIR / 'README.md').read_text(),
|
|
25
25
|
long_description_content_type='text/markdown',
|
{fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/tests/test_session.py
RENAMED
|
@@ -61,17 +61,15 @@ class TestSession(CustomTestCase):
|
|
|
61
61
|
"""
|
|
62
62
|
|
|
63
63
|
with tempfile.TemporaryDirectory() as dir:
|
|
64
|
-
destination = os.path.join(dir, 'file.
|
|
64
|
+
destination = os.path.join(dir, 'file.html')
|
|
65
65
|
|
|
66
66
|
session = Session()
|
|
67
67
|
|
|
68
68
|
with pytest.raises(RequestError):
|
|
69
|
-
session.download_file('https://www.d-cat.co.uk/
|
|
69
|
+
session.download_file('https://www.d-cat.co.uk/.well-known/acme-challenge/test.html', destination)
|
|
70
70
|
|
|
71
71
|
self.assertFalse(os.path.exists(destination))
|
|
72
|
-
session.download_file(
|
|
73
|
-
'https://www.d-cat.co.uk/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBkUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--8a6deba9596edd3e9f0c341d274ae1317da59bbc/Water%20RA%20Webinar%20IoT%20September%202020.pdf?',
|
|
74
|
-
destination, self.download_callback)
|
|
72
|
+
session.download_file('https://www.d-cat.co.uk/home', destination, self.download_callback)
|
|
75
73
|
self.assertTrue(os.path.exists(destination))
|
|
76
74
|
self.assertIsNotNone(self._download_size)
|
|
77
75
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/__main__.py
RENAMED
|
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
|
{fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/localise.py
RENAMED
|
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
|
{fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/fusion_platform/session.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/tests/test_command.py
RENAMED
|
File without changes
|
|
File without changes
|
{fusion_platform_python_sdk-1.14.7 → fusion_platform_python_sdk-1.15.12}/tests/test_localise.py
RENAMED
|
File without changes
|