diff --git a/python/tk-core/_core_upgrader.py b/python/tk-core/_core_upgrader.py
index f3959670..3fdcb201 100644
--- a/python/tk-core/_core_upgrader.py
+++ b/python/tk-core/_core_upgrader.py
@@ -19,11 +19,11 @@
"""
-import os
-import sys
-import stat
import datetime
+import os
import shutil
+import stat
+import sys
LooseVersion = None
try:
@@ -99,7 +99,7 @@ def __current_version_less_than(log, sgtk_install_root, ver):
"""
log.debug("Checking if the currently installed version is less than %s..." % ver)
- if __is_upgrade(sgtk_install_root) == False:
+ if not __is_upgrade(sgtk_install_root):
# there is no current version. So it is definitely
# not at least version X
log.debug(
diff --git a/python/tk-core/codecov.yml b/python/tk-core/codecov.yml
index 5fe4673e..cf2cef2f 100644
--- a/python/tk-core/codecov.yml
+++ b/python/tk-core/codecov.yml
@@ -1,4 +1,21 @@
+coverage:
+ status:
+ project:
+ default:
+ # Coverage checks are advisory only - never block CI
+ informational: true
+ # Only flag if overall project coverage drops below 80%
+ target: 80%
+ # Ignore drops of 1% or less (noise from small changes)
+ threshold: 1%
+ patch:
+ default:
+ # Coverage checks are advisory only - never block CI
+ informational: true
+ # Only flag if less than 80% of new/changed lines are covered
+ target: 80%
+
ignore:
# ignore generated QT files
- "**tank/authentication/ui/login_dialog.py"
@@ -6,3 +23,11 @@ ignore:
# disable PySide6 code coverage for now
- "**tank/util/pyside6_patcher.py"
+
+ # flowam and other files not covered by unit tests
+ - "**tank/bootstrap/manager.py"
+ - "**tank/flowam/create.py"
+ - "**tank/flowam/host.py"
+ - "**tank/flowam/open.py"
+ - "**tank/flowam/utils.py"
+ - "**tank/platform/engine.py"
diff --git a/python/tk-core/commit_id b/python/tk-core/commit_id
index 321245c5..3f254928 100644
--- a/python/tk-core/commit_id
+++ b/python/tk-core/commit_id
@@ -1 +1 @@
-bc5f5e526d45baae95e2233f625550a31ccec92d
+2fd64cd3f4f3f7f363c51ddf40f031605087107d
diff --git a/python/tk-core/developer/README.md b/python/tk-core/developer/README.md
index 27bd1f16..d1992e52 100644
--- a/python/tk-core/developer/README.md
+++ b/python/tk-core/developer/README.md
@@ -27,6 +27,14 @@ The `requirements/update_python_packages.py` script automates the creation and m
- Generate the `frozen_requirements.txt` file for consistency.
3. Validate that the `pkgs.zip` file contains all necessary packages and matches the updated requirements.
+### Shared (Python-version-independent) vendor zips
+
+In addition to the per-version `pkgs.zip`, `requirements/any/` holds pure-Python
+packages that are safe to load across every supported Python version (e.g. the
+Autodesk Flow Data Beta SDK). Each zip is auto-discovered by
+`tank_vendor/__init__.py` and contains the importable package plus its
+`.dist-info/` directory at the zip's root.
+
## How to upgrade ruamel.yaml
Until version `0.10.10`, the contents of the library was located at `tank_vendor/ruamel_yaml`.
diff --git a/python/tk-core/developer/bake_config.py b/python/tk-core/developer/bake_config.py
index 6c16ecfd..9375ce77 100644
--- a/python/tk-core/developer/bake_config.py
+++ b/python/tk-core/developer/bake_config.py
@@ -15,8 +15,8 @@
# system imports
import os
-import sys
import shutil
+import sys
# add sgtk API
this_folder = os.path.abspath(os.path.dirname(__file__))
@@ -25,23 +25,27 @@
# any installed version in site-packages
sys.path.insert(0, python_folder)
+import functools
+
# sgtk imports
from tank import LogManager
-from tank.util import filesystem
-from tank.descriptor import Descriptor, descriptor_uri_to_dict
-from tank.descriptor import create_descriptor, is_descriptor_version_missing
-from tank.descriptor.errors import TankDescriptorError
from tank.bootstrap import constants as bootstrap_constants
-import functools
-
+from tank.descriptor import (
+ Descriptor,
+ create_descriptor,
+ descriptor_uri_to_dict,
+ is_descriptor_version_missing,
+)
+from tank.descriptor.errors import TankDescriptorError
+from tank.util import filesystem
from utils import (
- cache_apps,
- authenticate,
- add_authentication_options,
OptionParserLineBreakingEpilog,
+ add_authentication_options,
+ authenticate,
+ automated_setup_documentation,
+ cache_apps,
cleanup_bundle_cache,
wipe_folder,
- automated_setup_documentation,
)
# Set up logging
diff --git a/python/tk-core/developer/build_plugin.py b/python/tk-core/developer/build_plugin.py
index fcf7abd1..c42be482 100644
--- a/python/tk-core/developer/build_plugin.py
+++ b/python/tk-core/developer/build_plugin.py
@@ -16,11 +16,12 @@
a primed bundle cache.
"""
+import datetime
+
# system imports
import os
-import sys
import shutil
-import datetime
+import sys
# add sgtk API
this_folder = os.path.abspath(os.path.dirname(__file__))
@@ -31,22 +32,27 @@
# sgtk imports
from tank import LogManager
-from tank.util import filesystem, sgre as re
-from tank.errors import TankError
-from tank.descriptor import Descriptor, descriptor_uri_to_dict, descriptor_dict_to_uri
-from tank.descriptor import create_descriptor, is_descriptor_version_missing
-from tank.bootstrap.baked_configuration import BakedConfiguration
from tank.bootstrap import constants as bootstrap_constants
+from tank.bootstrap.baked_configuration import BakedConfiguration
+from tank.descriptor import (
+ Descriptor,
+ create_descriptor,
+ descriptor_dict_to_uri,
+ descriptor_uri_to_dict,
+ is_descriptor_version_missing,
+)
+from tank.errors import TankError
+from tank.util import filesystem
+from tank.util import sgre as re
from tank_vendor import yaml
-
from utils import (
- cache_apps,
- authenticate,
- add_authentication_options,
OptionParserLineBreakingEpilog,
+ add_authentication_options,
+ authenticate,
+ automated_setup_documentation,
+ cache_apps,
cleanup_bundle_cache,
wipe_folder,
- automated_setup_documentation,
)
# set up logging
diff --git a/python/tk-core/developer/find_flow_project.py b/python/tk-core/developer/find_flow_project.py
new file mode 100644
index 00000000..804ed05f
--- /dev/null
+++ b/python/tk-core/developer/find_flow_project.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python3
+# Copyright (c) 2026 Shotgun Software Inc.
+# CONFIDENTIAL AND PROPRIETARY
+"""
+Standalone CLI to find your Flow project ID using the Flow Data SDK.
+
+Use this script to retrieve the Flow project ID for a given FPT project,
+so it can be linked in the FPT site settings.
+
+Defaults to production values. Override any individual setting via flags.
+
+Usage:
+ # Production (default):
+ python scripts/find_flow_project.py
+
+ # Override just the endpoint (e.g. for staging):
+ python scripts/find_flow_project.py --endpoint
+
+ # Override multiple values:
+ python scripts/find_flow_project.py --endpoint --application-id --auth-base-url
+
+ # Filter by project name (case-insensitive):
+ python scripts/find_flow_project.py --name "My Project"
+"""
+
+from __future__ import annotations
+
+import argparse
+import os
+import sys
+
+# Set up sys.path so tk-core python packages are importable
+_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+_REPO_ROOT = os.path.dirname(_SCRIPT_DIR)
+sys.path.insert(0, os.path.join(_REPO_ROOT, "python"))
+
+from tank.authentication.flow_auth._constants import (
+ DEFAULT_AUTH_APPLICATION_ID,
+ DEFAULT_AUTH_BASE_URL,
+ DEFAULT_AUTH_CALLBACK_URL,
+ REQUIRED_SCOPES,
+)
+from tank.util import LocalFileStorageManager
+from tank_vendor.adsk_auth import AuthConfig, get_access_token
+from tank_vendor.flow_data_sdk import GQLClient
+from tank_vendor.flow_data_sdk.base import model as flow_model
+from tank_vendor.flow_data_sdk.base.client import AuthenticationHandlerBase
+
+DEFAULT_ENDPOINT = "https://medm-v2.medata-p-ue1.cloudos.autodesk.com/api/v2/graphql"
+
+
+class TokenAuthHandler(AuthenticationHandlerBase):
+ """Auth handler that returns a pre-fetched token."""
+
+ def __init__(self, token: str) -> None:
+ self._token = token
+
+ def get_authentication_token(self) -> str:
+ return self._token
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(
+ description="List Flow collections/projects to find project IDs for FPT linking.",
+ )
+ parser.add_argument(
+ "--endpoint",
+ default=DEFAULT_ENDPOINT,
+ help=f"Flow GraphQL endpoint URL (default: {DEFAULT_ENDPOINT})",
+ )
+ parser.add_argument(
+ "--application-id",
+ default=DEFAULT_AUTH_APPLICATION_ID,
+ help="APS application ID (default: prod value)",
+ )
+ parser.add_argument(
+ "--auth-base-url",
+ default=DEFAULT_AUTH_BASE_URL,
+ help=f"APS auth base URL (default: {DEFAULT_AUTH_BASE_URL})",
+ )
+ parser.add_argument(
+ "--auth-callback-url",
+ default=DEFAULT_AUTH_CALLBACK_URL,
+ help=f"OAuth callback URL (default: {DEFAULT_AUTH_CALLBACK_URL})",
+ )
+ parser.add_argument(
+ "--name",
+ help="Only show projects whose name contains this string (case-insensitive)",
+ )
+ parser.add_argument(
+ "--force-reauth",
+ action="store_true",
+ help="Ignore cached tokens and force browser login",
+ )
+ parser.add_argument(
+ "--time-out",
+ type=float,
+ default=120.0,
+ help="Browser auth timeout in seconds (default: 120)",
+ )
+ args = parser.parse_args()
+
+ print(f"Endpoint : {args.endpoint}")
+ print("Authenticating... (browser may open if no valid cached token)")
+
+ auth_config = AuthConfig(
+ application_id=args.application_id,
+ base_url=args.auth_base_url,
+ callback_url=args.auth_callback_url,
+ description="Flow find project CLI",
+ required_application_scopes=REQUIRED_SCOPES,
+ storage_dir=LocalFileStorageManager.get_global_root(
+ LocalFileStorageManager.CACHE
+ ),
+ )
+ token = get_access_token(
+ auth_config,
+ force_reauthentication=args.force_reauth,
+ time_out=args.time_out,
+ )
+
+ client = GQLClient(
+ endpoint=args.endpoint,
+ auth_handler=TokenAuthHandler(token),
+ )
+
+ name_filter = (args.name or "").strip().lower()
+ collections_resp = client.service_collection.collections(
+ flow_model.CollectionsInput()
+ ).call()
+
+ collections = getattr(collections_resp, "collections", None) or []
+ if not collections:
+ print("No collections found for your account.")
+ sys.exit(0)
+
+ found_any = False
+ for col in collections:
+ col_id = getattr(col, "id", None) or ""
+ col_name = getattr(col, "name", None) or "(no name)"
+ projects_resp = client.service_project.projects_by_collection_id(
+ flow_model.ProjectsByCollectionIdInput(collection_id=col_id)
+ ).call()
+ projects = getattr(projects_resp, "projects", None) or []
+ for proj in projects:
+ proj_name = getattr(proj, "name", None) or "(no name)"
+ proj_id = getattr(proj, "id", None) or ""
+ if name_filter and name_filter not in proj_name.lower():
+ continue
+ if not found_any:
+ print("\nCollection -> Project")
+ print("-" * 60)
+ found_any = True
+ print(f" {col_name} -> {proj_name}")
+ print(f" Project ID: {proj_id}")
+ print()
+ if not projects and not name_filter:
+ print(f" {col_name} -> (no projects)")
+
+ if not found_any and name_filter:
+ print(f"No project matching '{args.name}' found.")
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/tk-core/developer/populate_bundle_cache.py b/python/tk-core/developer/populate_bundle_cache.py
index 6ac83b6d..30062379 100644
--- a/python/tk-core/developer/populate_bundle_cache.py
+++ b/python/tk-core/developer/populate_bundle_cache.py
@@ -18,7 +18,6 @@
# system imports
import os
-
import sys
# add sgtk API
@@ -30,16 +29,15 @@
# sgtk imports
from sgtk import LogManager
-from sgtk.util import filesystem
from sgtk.descriptor import Descriptor, create_descriptor, is_descriptor_version_missing
-
+from sgtk.util import filesystem
from utils import (
- cache_apps,
- authenticate,
- add_authentication_options,
OptionParserLineBreakingEpilog,
- cleanup_bundle_cache,
+ add_authentication_options,
+ authenticate,
automated_setup_documentation,
+ cache_apps,
+ cleanup_bundle_cache,
)
# set up logging
diff --git a/python/tk-core/developer/utils/__init__.py b/python/tk-core/developer/utils/__init__.py
index 15eeef6d..9d6c3285 100644
--- a/python/tk-core/developer/utils/__init__.py
+++ b/python/tk-core/developer/utils/__init__.py
@@ -1,7 +1,7 @@
-from .caching import cache_apps, cleanup_bundle_cache, wipe_folder # noqa
-from .option_parser import OptionParserLineBreakingEpilog # noqa
-from .authentication import (
+from .authentication import ( # noqa
add_authentication_options,
authenticate,
automated_setup_documentation,
-) # noqa
+)
+from .caching import cache_apps, cleanup_bundle_cache, wipe_folder # noqa
+from .option_parser import OptionParserLineBreakingEpilog # noqa
diff --git a/python/tk-core/developer/utils/caching.py b/python/tk-core/developer/utils/caching.py
index da8ae723..17ca8a92 100644
--- a/python/tk-core/developer/utils/caching.py
+++ b/python/tk-core/developer/utils/caching.py
@@ -8,16 +8,15 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import os
import glob
+import os
import shutil
import stat
-from tank.util import filesystem
-from tank.platform import environment
-from tank.descriptor import Descriptor, create_descriptor
-
from tank import LogManager
+from tank.descriptor import Descriptor, create_descriptor
+from tank.platform import environment
+from tank.util import filesystem
logger = LogManager.get_logger("utils.caching")
diff --git a/python/tk-core/hooks/cache_location.py b/python/tk-core/hooks/cache_location.py
index 1578a9d7..988dfebb 100644
--- a/python/tk-core/hooks/cache_location.py
+++ b/python/tk-core/hooks/cache_location.py
@@ -12,9 +12,10 @@
Hook to control path cache and bundle cache folder creation.
"""
-import sgtk
import os
-from sgtk.util import filesystem, LocalFileStorageManager
+
+import sgtk
+from sgtk.util import LocalFileStorageManager, filesystem
HookBaseClass = sgtk.get_hook_baseclass()
log = sgtk.LogManager.get_logger(__name__)
diff --git a/python/tk-core/hooks/ensure_folder_exists.py b/python/tk-core/hooks/ensure_folder_exists.py
index 1b8573e3..ce56404a 100644
--- a/python/tk-core/hooks/ensure_folder_exists.py
+++ b/python/tk-core/hooks/ensure_folder_exists.py
@@ -13,8 +13,8 @@
:class:`~sgtk.platform.Application.ensure_folder_exists` method is called.
"""
-from sgtk.util import filesystem
from sgtk import Hook
+from sgtk.util import filesystem
class EnsureFolderExists(Hook):
diff --git a/python/tk-core/hooks/get_current_login.py b/python/tk-core/hooks/get_current_login.py
index 78870b5a..1197e9f7 100644
--- a/python/tk-core/hooks/get_current_login.py
+++ b/python/tk-core/hooks/get_current_login.py
@@ -27,8 +27,9 @@
current user.
"""
-from tank import Hook
import os
+
+from tank import Hook
from tank.util import is_windows
@@ -54,5 +55,5 @@ def execute(self, **kwargs):
pwd_entry = pwd.getpwuid(os.geteuid())
return pwd_entry[0]
- except:
+ except Exception:
return None
diff --git a/python/tk-core/hooks/process_folder_creation.py b/python/tk-core/hooks/process_folder_creation.py
index f6c0a83b..d0f18ddc 100644
--- a/python/tk-core/hooks/process_folder_creation.py
+++ b/python/tk-core/hooks/process_folder_creation.py
@@ -13,9 +13,10 @@
called.
"""
-from tank import Hook
import os
import shutil
+
+from tank import Hook
from tank.util import is_windows
diff --git a/python/tk-core/hooks/process_folder_name.py b/python/tk-core/hooks/process_folder_name.py
index b9d4e654..cc38edc6 100644
--- a/python/tk-core/hooks/process_folder_name.py
+++ b/python/tk-core/hooks/process_folder_name.py
@@ -24,9 +24,10 @@
raise TankError("Shot names cannot start with AA!")
"""
-from tank import Hook
import re
+from tank import Hook
+
class ProcessFolderName(Hook):
def execute(self, entity_type, entity_id, field_name, value, **kwargs):
diff --git a/python/tk-core/info.yml b/python/tk-core/info.yml
index 42d7d155..e62d7ec9 100644
--- a/python/tk-core/info.yml
+++ b/python/tk-core/info.yml
@@ -12,9 +12,10 @@
# This is automatically replaced with appropriate values
# as the core bundle is uploaded to the app store.
-version: "v0.23.8"
+version: "v0.23.10"
documentation_url: "https://help.autodesk.com/view/SGDEV/ENU/?contextId=SA_INTEGRATIONS_USER_GUIDE"
requires_shotgun_version: "v5.0.0"
+minimum_python_version: "3.9"
features:
# lean_config history
diff --git a/python/tk-core/python/sgtk/__init__.py b/python/tk-core/python/sgtk/__init__.py
index 0df56282..0b1f2684 100644
--- a/python/tk-core/python/sgtk/__init__.py
+++ b/python/tk-core/python/sgtk/__init__.py
@@ -12,12 +12,12 @@
ALT_API_NAME = "tank"
THIS_MODULE_NAME = "sgtk"
-# first import our alternative API
-import tank
-
# now go through and duplicate all entries in sys.modules
import sys
+# first import our alternative API
+import tank # noqa: F401
+
# Generate a list of keys to iterate over,
# since we'll be mutating the dict as we iterate.
for x in list(sys.modules.keys()):
diff --git a/python/tk-core/python/tank/__init__.py b/python/tk-core/python/tank/__init__.py
index 2e553bd8..f720d254 100644
--- a/python/tk-core/python/tank/__init__.py
+++ b/python/tk-core/python/tank/__init__.py
@@ -35,34 +35,19 @@
import uuid
import warnings
-if sys.version_info < (3, 7):
+if sys.version_info < (3, 9):
if os.environ.get("SHOTGUN_ALLOW_OLD_PYTHON", "0") != "1":
- # This is our preferred default behavior when using an old
- # unsupported Python version.
- # This way, we can control where the exception is raised, and it provides a
- # comprehensive error message rather than having users facing a random
- # Python traceback and trying to understand this is due to using an
- # unsupported Python version.
-
- raise RuntimeError("This module requires Python version 3.7 or higher.")
+ raise RuntimeError("This module requires Python version 3.9 or higher.")
warnings.warn(
- "Python versions older than 3.7 are no longer supported as of January "
- "2023. Since the SHOTGUN_ALLOW_OLD_PYTHON variable is enabled, this "
+ "Python versions older than 3.9 are no longer supported as of March "
+ "2025. Since the SHOTGUN_ALLOW_OLD_PYTHON variable is enabled, this "
"module is raising a warning instead of an exception. "
"However, it is very likely that this module will not be able to work "
"on this Python version.",
RuntimeWarning,
stacklevel=2,
)
-elif sys.version_info < (3, 9):
- warnings.warn(
- "Python versions older than 3.9 are no longer supported as of March "
- "2025 and compatibility will be discontinued after March 2026. "
- "Please update to Python 3.11 or any other supported version.",
- DeprecationWarning,
- stacklevel=2
- )
def __fix_tank_vendor():
@@ -134,57 +119,58 @@ def __fix_tank_vendor():
########################################################################
# first import the log manager since a lot of modules require this.
-from .log import LogManager
+from .log import LogManager # isort: skip
# make sure that all sub-modules are imported at the same as the main module
-from . import authentication
-from . import descriptor
-from . import bootstrap
-from . import commands
-from . import deploy
-from . import folder
-from . import platform
-from . import util
+from . import (
+ authentication,
+ bootstrap,
+ commands,
+ deploy,
+ descriptor,
+ folder,
+ platform,
+ util,
+)
# core functionality
from .api import (
+ Sgtk,
Tank,
- tank_from_path,
- tank_from_entity,
- set_authenticated_user,
get_authenticated_user,
+ set_authenticated_user,
+ sgtk_from_entity,
+ sgtk_from_path,
+ tank_from_entity,
+ tank_from_path,
)
-from .api import Sgtk, sgtk_from_path, sgtk_from_entity
-from .pipelineconfig_utils import (
- get_python_interpreter_for_config,
- get_core_python_path_for_config,
- get_sgtk_module_path,
-)
+from .authentication.flow_auth import get_flow_access_token, get_flow_client
+from .commands import CommandInteraction, SgtkSystemCommand, get_command, list_commands
+# expose the support url
+from .constants import (
+ DEFAULT_STORAGE_ROOT_HOOK_NAME,
+)
+from .constants import SUPPORT_URL as support_url
from .context import Context
-
from .errors import (
TankError,
TankErrorProjectIsSetup,
- TankHookMethodDoesNotExistError,
TankFileDoesNotExistError,
- TankUnreadableFileError,
- TankInvalidInterpreterLocationError,
+ TankHookMethodDoesNotExistError,
TankInvalidCoreLocationError,
+ TankInvalidInterpreterLocationError,
TankNotPipelineConfigurationError,
+ TankUnreadableFileError,
+)
+from .hook import Hook, get_hook_baseclass
+from .pipelineconfig_utils import (
+ get_core_python_path_for_config,
+ get_python_interpreter_for_config,
+ get_sgtk_module_path,
)
# note: TankEngineInitError used to reside in .errors but was moved into platform.errors
from .platform.errors import TankEngineInitError
from .template import Template, TemplatePath, TemplateString
-from .hook import Hook, get_hook_baseclass
-
-from .commands import list_commands, get_command, SgtkSystemCommand, CommandInteraction
-
-from .templatekey import TemplateKey, SequenceKey, IntegerKey, StringKey, TimestampKey
-
-# expose the support url
-from .constants import (
- DEFAULT_STORAGE_ROOT_HOOK_NAME,
- SUPPORT_URL as support_url,
-)
+from .templatekey import IntegerKey, SequenceKey, StringKey, TemplateKey, TimestampKey
diff --git a/python/tk-core/python/tank/api.py b/python/tk-core/python/tank/api.py
index cca816e1..03478832 100644
--- a/python/tk-core/python/tank/api.py
+++ b/python/tk-core/python/tank/api.py
@@ -12,20 +12,22 @@
Classes for the main Sgtk API.
"""
-import os
import glob
+import os
-from . import folder
-from . import context
-from .util import shotgun, yaml_cache
+from . import (
+ LogManager,
+ constants,
+ context,
+ folder,
+ pipelineconfig,
+ pipelineconfig_factory,
+ pipelineconfig_utils,
+)
from .errors import TankError, TankMultipleMatchingTemplatesError
from .path_cache import PathCache
from .template import read_templates
-from . import constants
-from . import pipelineconfig
-from . import pipelineconfig_utils
-from . import pipelineconfig_factory
-from . import LogManager
+from .util import shotgun, yaml_cache
log = LogManager.get_logger(__name__)
@@ -38,7 +40,7 @@ class Sgtk(object):
manipulation and the Toolkit template system.
"""
- (DEFAULT, CENTRALIZED, DISTRIBUTED) = range(3)
+ DEFAULT, CENTRALIZED, DISTRIBUTED = range(3)
def __init__(self, project_path):
"""
@@ -1032,7 +1034,6 @@ def get_authenticated_user():
:returns: A :class:`~sgtk.authentication.ShotgunUser` derived object if set,
None otherwise.
"""
- global _authenticated_user
return _authenticated_user
diff --git a/python/tk-core/python/tank/authentication/__init__.py b/python/tk-core/python/tank/authentication/__init__.py
index f9125528..f669b96d 100644
--- a/python/tk-core/python/tank/authentication/__init__.py
+++ b/python/tk-core/python/tank/authentication/__init__.py
@@ -18,27 +18,28 @@
credentials are reused if available.
"""
+from .core_defaults_manager import CoreDefaultsManager
+from .defaults_manager import DefaultsManager
+from .errors import ConsoleLoginWithSSONotSupportedError # For backward compatibility.
from .errors import ( # noqa
AuthenticationCancelled,
AuthenticationError,
- ConsoleLoginWithSSONotSupportedError, # For backward compatibility.
ConsoleLoginNotSupportedError,
IncompleteCredentials,
ShotgunAuthenticationError,
UnresolvableHumanUser,
UnresolvableScriptUser,
)
-from .web_login_support import (
- get_shotgun_authenticator_support_web_login,
- set_shotgun_authenticator_support_web_login,
-)
+from .flow_auth import FlowAuthenticationHandler, get_flow_access_token, get_flow_client
from .shotgun_authenticator import ShotgunAuthenticator
-from .defaults_manager import DefaultsManager
-from .core_defaults_manager import CoreDefaultsManager
from .user import ( # noqa
- deserialize_user,
- serialize_user,
ShotgunSamlUser,
ShotgunUser,
ShotgunWebUser,
+ deserialize_user,
+ serialize_user,
+)
+from .web_login_support import (
+ get_shotgun_authenticator_support_web_login,
+ set_shotgun_authenticator_support_web_login,
)
diff --git a/python/tk-core/python/tank/authentication/app_session_launcher.py b/python/tk-core/python/tank/authentication/app_session_launcher.py
index 0e7762db..c48d260e 100644
--- a/python/tk-core/python/tank/authentication/app_session_launcher.py
+++ b/python/tk-core/python/tank/authentication/app_session_launcher.py
@@ -89,7 +89,7 @@ def process(
url_handlers.append(
urllib.request.HTTPSHandler(
- context = ssl.create_default_context(
+ context=ssl.create_default_context(
cafile=ca_certs,
),
),
@@ -255,10 +255,12 @@ def process(
elif response_code_major == 3:
location = response.headers.get("location", None)
- logger.debug("Request redirected: http code: {code}; redirect to: {location}".format(
- code=response.code,
- location=location,
- ))
+ logger.debug(
+ "Request redirected: http code: {code}; redirect to: {location}".format(
+ code=response.code,
+ location=location,
+ )
+ )
raise AuthenticationError(
"Request redirected",
@@ -286,9 +288,11 @@ def process(
)
elif response.code != http.client.OK:
- logger.debug("Request denied: http code is: {code}".format(
- code=response.code,
- ))
+ logger.debug(
+ "Request denied: http code is: {code}".format(
+ code=response.code,
+ )
+ )
raise AuthenticationError(
"Request denied",
payload=getattr(response, "json", response),
@@ -503,9 +507,11 @@ def build_user_agent():
lh = logging.StreamHandler()
lh.setLevel(logging.DEBUG)
- lh.setFormatter(logging.Formatter(
- "%(asctime)s - %(levelname)s - %(message)s",
- ))
+ lh.setFormatter(
+ logging.Formatter(
+ "%(asctime)s - %(levelname)s - %(message)s",
+ )
+ )
logger.addHandler(lh)
print()
diff --git a/python/tk-core/python/tank/authentication/console_authentication.py b/python/tk-core/python/tank/authentication/console_authentication.py
index cde2ddc7..cebcb995 100644
--- a/python/tk-core/python/tank/authentication/console_authentication.py
+++ b/python/tk-core/python/tank/authentication/console_authentication.py
@@ -19,23 +19,21 @@
--------------------------------------------------------------------------------
"""
-from . import constants
-from . import session_cache
+import webbrowser
+from getpass import getpass
+
+from tank_vendor.shotgun_api3 import MissingTwoFactorAuthenticationFault
+
from .. import LogManager
+from ..util import metrics_cache
+from ..util.metrics import EventMetric
+from ..util.shotgun.connection import sanitize_url
+from . import app_session_launcher, constants, session_cache, site_info
from .errors import (
- AuthenticationError,
AuthenticationCancelled,
+ AuthenticationError,
ConsoleLoginNotSupportedError,
)
-from tank_vendor.shotgun_api3 import MissingTwoFactorAuthenticationFault
-from . import site_info
-from . import app_session_launcher
-from ..util import metrics_cache
-from ..util.metrics import EventMetric
-from ..util.shotgun.connection import sanitize_url
-
-from getpass import getpass
-import webbrowser
logger = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/authentication/constants.py b/python/tk-core/python/tank/authentication/constants.py
index b3bf7194..28972b34 100644
--- a/python/tk-core/python/tank/authentication/constants.py
+++ b/python/tk-core/python/tank/authentication/constants.py
@@ -20,8 +20,6 @@
def method_resolve_reverse(m):
- global method_resolve
-
- for (k, v) in method_resolve.items():
+ for k, v in method_resolve.items():
if v == m:
return k
diff --git a/python/tk-core/python/tank/authentication/core_defaults_manager.py b/python/tk-core/python/tank/authentication/core_defaults_manager.py
index 0f3c2f0e..aa7c0db9 100644
--- a/python/tk-core/python/tank/authentication/core_defaults_manager.py
+++ b/python/tk-core/python/tank/authentication/core_defaults_manager.py
@@ -14,8 +14,8 @@
been configured with the core, its credentials will also be provided.
"""
-from .defaults_manager import DefaultsManager
from ..util import shotgun
+from .defaults_manager import DefaultsManager
class CoreDefaultsManager(DefaultsManager):
diff --git a/python/tk-core/python/tank/authentication/defaults_manager.py b/python/tk-core/python/tank/authentication/defaults_manager.py
index 886b5c11..d23026f6 100644
--- a/python/tk-core/python/tank/authentication/defaults_manager.py
+++ b/python/tk-core/python/tank/authentication/defaults_manager.py
@@ -8,9 +8,9 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from . import session_cache
-from ..util.user_settings import UserSettings
from ..util.system_settings import SystemSettings
+from ..util.user_settings import UserSettings
+from . import session_cache
class DefaultsManager(object):
diff --git a/python/tk-core/python/tank/authentication/errors.py b/python/tk-core/python/tank/authentication/errors.py
index b7fe9330..8bee559c 100644
--- a/python/tk-core/python/tank/authentication/errors.py
+++ b/python/tk-core/python/tank/authentication/errors.py
@@ -73,9 +73,7 @@ def __init__(self, login):
"""
:param str login: ``login`` field value of the ``HumanUser`` that could not be resolved.
"""
- super().__init__(
- "person", "HumanUser", "login", login
- )
+ super().__init__("person", "HumanUser", "login", login)
class UnresolvableScriptUser(UnresolvableUser):
@@ -87,9 +85,7 @@ def __init__(self, script_name):
"""
:param str script_name: ``firstname`` field value of the ``ApiUser`` that could not be resolved.
"""
- super().__init__(
- "script", "ApiUser", "firstname", script_name
- )
+ super().__init__("script", "ApiUser", "firstname", script_name)
class ConsoleLoginNotSupportedError(ShotgunAuthenticationError):
diff --git a/python/tk-core/python/tank/authentication/flow_auth/__init__.py b/python/tk-core/python/tank/authentication/flow_auth/__init__.py
new file mode 100644
index 00000000..a244922e
--- /dev/null
+++ b/python/tk-core/python/tank/authentication/flow_auth/__init__.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+"""
+Flow / MEDM authentication for Toolkit bootstrap.
+
+Triggered during bootstrap when a project is "AM-ready". Obtains an APS
+access token via PKCE; the token is cached in a local file store for reuse.
+"""
+
+from ._authentication import (
+ check_token_expiry,
+ get_access_token,
+ get_flow_access_token,
+ init_authentication,
+)
+from ._client import FlowAuthenticationHandler, get_flow_client
+from ._constants import AM_READY_PROJECT_FIELD
+from ._settings import FlowAuthSettings, resolve_flow_auth_settings
+from .errors import FlowAuthConfigurationError, FlowAuthError
+
+__all__ = [
+ "init_authentication",
+ "get_access_token",
+ "get_flow_access_token",
+ "check_token_expiry",
+ "FlowAuthenticationHandler",
+ "get_flow_client",
+ "FlowAuthSettings",
+ "resolve_flow_auth_settings",
+ "FlowAuthError",
+ "FlowAuthConfigurationError",
+ "AM_READY_PROJECT_FIELD",
+]
diff --git a/python/tk-core/python/tank/authentication/flow_auth/_authentication.py b/python/tk-core/python/tank/authentication/flow_auth/_authentication.py
new file mode 100644
index 00000000..1ee18349
--- /dev/null
+++ b/python/tk-core/python/tank/authentication/flow_auth/_authentication.py
@@ -0,0 +1,193 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+"""
+This module contains utilities for authenticating into Flow.
+"""
+
+from __future__ import annotations
+
+import base64
+import json
+import time
+
+from tank_vendor.adsk_auth import (
+ AuthConfig,
+)
+from tank_vendor.adsk_auth import get_access_token as get_access_token_from_adsk_auth
+
+from ... import LogManager
+from ...util import LocalFileStorageManager
+from ._constants import REQUIRED_SCOPES
+from .errors import FlowAuthConfigurationError
+
+logger = LogManager.get_logger(__name__)
+
+_aps_configuration = None
+
+
+def init_authentication(settings):
+ """Initialize authentication configuration with configured settings.
+
+ Args:
+ settings: FlowAuthSettings (or duck-typed equivalent) containing
+ auth_application_id, auth_base_url, auth_callback_url.
+
+ Raises:
+ FlowAuthConfigurationError: If required authentication settings are missing or invalid.
+ """
+ global _aps_configuration
+
+ auth_application_id = settings.auth_application_id
+ auth_base_url = settings.auth_base_url
+ auth_callback_url = settings.auth_callback_url
+
+ if not auth_application_id:
+ raise FlowAuthConfigurationError(
+ details="Required setting 'auth_application_id' is not configured."
+ )
+ if not auth_base_url:
+ raise FlowAuthConfigurationError(
+ details="Required setting 'auth_base_url' is not configured."
+ )
+ if not auth_callback_url:
+ raise FlowAuthConfigurationError(
+ details="Required setting 'auth_callback_url' is not configured."
+ )
+
+ _aps_configuration = AuthConfig(
+ application_id=auth_application_id,
+ base_url=auth_base_url,
+ callback_url=auth_callback_url,
+ description="Autodesk Toolkit",
+ required_application_scopes=REQUIRED_SCOPES,
+ storage_dir=LocalFileStorageManager.get_global_root(
+ LocalFileStorageManager.CACHE
+ ),
+ )
+
+
+def _get_aps_configuration():
+ """Get the APS configuration.
+
+ Returns:
+ tank_vendor.adsk_auth.AuthConfig: The initialized auth configuration.
+
+ Raises:
+ RuntimeError: If authentication has not been initialized.
+ """
+ if _aps_configuration is None:
+ raise RuntimeError(
+ "Authentication not initialized. Call init_authentication() first."
+ )
+ return _aps_configuration
+
+
+def check_token_expiry(token: str, buffer_seconds: int = 300) -> bool:
+ """
+ Check if the given token is expiring soon (within the buffer period).
+
+ The Flow API can fail if the token is not yet expired but will expire soon,
+ so we proactively refresh when within the buffer.
+
+ Args:
+ token: The access token to check.
+ buffer_seconds: Seconds before expiry to consider the token "expiring soon".
+ Defaults to 300 (5 minutes).
+
+ Returns:
+ True if the token is expiring soon or invalid, False if it has
+ sufficient validity remaining.
+ """
+ try:
+ payload = _decode_token_payload(token)
+ except Exception as e:
+ logger.error("Error decoding token: %s", e)
+ return True
+
+ exp_timestamp = payload.get("exp") if payload else None
+
+ if not exp_timestamp:
+ logger.warning(
+ "Token does not contain 'exp' claim. Treating token as expiring soon."
+ )
+ return True
+
+ current_timestamp = time.time()
+ time_remaining = exp_timestamp - current_timestamp
+ if time_remaining < buffer_seconds:
+ logger.debug(
+ "Token will expire in %.0f seconds, less than buffer of %s seconds.",
+ time_remaining,
+ buffer_seconds,
+ )
+ return True
+
+ return False
+
+
+def _decode_token_payload(token: str):
+ """Decode JWT payload without verification (only used to read exp)."""
+ try:
+ parts = token.split(".")
+ if len(parts) != 3:
+ return None
+ payload_b64 = parts[1]
+ padding = (4 - len(payload_b64) % 4) % 4
+ payload_b64 += "=" * padding
+ payload_bytes = base64.urlsafe_b64decode(payload_b64)
+ return json.loads(payload_bytes)
+ except Exception:
+ return None
+
+
+def _auth_options_from_kwargs(kwargs):
+ """Extract adsk_auth get_access_token options from kwargs (e.g. authentication_options)."""
+ opts = kwargs.get("authentication_options")
+ if opts is None:
+ return {}
+ return {
+ "profile": getattr(opts, "user_profile", None),
+ "force_refresh": getattr(opts, "force_refresh", False),
+ "force_reauthentication": getattr(opts, "force_reauthentication", False),
+ "time_out": getattr(opts, "time_out", 30.0),
+ "browser": getattr(opts, "browser", None),
+ }
+
+
+def get_access_token(*args, **kwargs) -> str:
+ """Get access token from file store or web (PKCE).
+
+ Ensures the returned token has at least 5 minutes of validity remaining,
+ since the Flow API can fail when the token is about to expire.
+ """
+ config = _get_aps_configuration()
+ options = _auth_options_from_kwargs(kwargs)
+ token = get_access_token_from_adsk_auth(config, **options)
+
+ if check_token_expiry(token):
+ logger.info("Access token is expiring within 5 minutes. Forcing a refresh.")
+ options["force_refresh"] = True
+ token = get_access_token_from_adsk_auth(config, **options)
+
+ return token
+
+
+def get_flow_access_token(**kwargs) -> str:
+ """Get a Flow access token, lazy-initialising auth if needed.
+
+ Safe to call without a prior ``init_authentication()`` — if bootstrap has
+ not already initialised the APS configuration, settings are resolved from
+ environment variables and initialisation is performed automatically.
+ """
+ if _aps_configuration is None:
+ from ._settings import resolve_flow_auth_settings
+
+ init_authentication(resolve_flow_auth_settings())
+ return get_access_token(**kwargs)
diff --git a/python/tk-core/python/tank/authentication/flow_auth/_client.py b/python/tk-core/python/tank/authentication/flow_auth/_client.py
new file mode 100644
index 00000000..db00aff2
--- /dev/null
+++ b/python/tk-core/python/tank/authentication/flow_auth/_client.py
@@ -0,0 +1,49 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+"""
+Flow GQL SDK client factory with authentication wired in.
+"""
+
+from ._authentication import get_flow_access_token
+
+
+class FlowAuthenticationHandler:
+ """Auth adapter for the Flow GQL SDK client.
+
+ Satisfies the SDK's ``auth_handler`` interface: the client calls
+ ``get_authentication_token()`` on every request so that short-lived tokens
+ are transparently refreshed without recreating the client.
+ """
+
+ def get_authentication_token(self) -> str:
+ return get_flow_access_token()
+
+
+# TODO: replace with proper client entry point
+def get_flow_client(endpoint_url=None):
+ """Return a ready-to-use Flow GQL SDK client with authentication wired in.
+
+ Lazy-initialises APS auth if bootstrap has not already done so. Uses the
+ SDK default endpoint (``tank_vendor.flow_data_sdk.config.DEFAULT_ENDPOINT``)
+ when ``endpoint_url`` is not supplied.
+
+ :param endpoint_url: Override the GraphQL endpoint. Defaults to the SDK's
+ production endpoint.
+ :type endpoint_url: str or None
+ :returns: Initialised ``GQLClient`` instance.
+ """
+ from tank_vendor.flow_data_sdk import GQLClient
+ from tank_vendor.flow_data_sdk.config import DEFAULT_ENDPOINT
+
+ return GQLClient(
+ endpoint=endpoint_url or DEFAULT_ENDPOINT,
+ auth_handler=FlowAuthenticationHandler(),
+ )
diff --git a/python/tk-core/python/tank/authentication/flow_auth/_constants.py b/python/tk-core/python/tank/authentication/flow_auth/_constants.py
new file mode 100644
index 00000000..b4c0b540
--- /dev/null
+++ b/python/tk-core/python/tank/authentication/flow_auth/_constants.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+"""Constants for Flow / MEDM authentication."""
+
+AM_READY_PROJECT_FIELD = "sg_flow_am_id"
+
+DEFAULT_AUTH_APPLICATION_ID = "8QyoQKXZ7HDuQFmptJGrzsp2GwpATmyV"
+DEFAULT_AUTH_BASE_URL = "https://api.aps.usa.autodesk.com"
+DEFAULT_AUTH_CALLBACK_URL = "http://localhost:4201/auth/callback"
+
+# Previously "openid" was also requested but is not used or required for
+# authentication to Flow, and exceeded Windows Credential Manager's 1280-char
+# limit. Safe to exclude.
+REQUIRED_SCOPES = [
+ "data:read",
+ "data:write",
+ "data:create",
+]
diff --git a/python/tk-core/python/tank/authentication/flow_auth/_settings.py b/python/tk-core/python/tank/authentication/flow_auth/_settings.py
new file mode 100644
index 00000000..1a276ce9
--- /dev/null
+++ b/python/tk-core/python/tank/authentication/flow_auth/_settings.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+"""Settings resolver for Flow / MEDM authentication."""
+
+from __future__ import annotations
+
+import os
+from dataclasses import dataclass
+
+from ._constants import (
+ DEFAULT_AUTH_APPLICATION_ID,
+ DEFAULT_AUTH_BASE_URL,
+ DEFAULT_AUTH_CALLBACK_URL,
+)
+
+
+@dataclass
+class FlowAuthSettings:
+ """APS credentials required to perform PKCE authentication."""
+
+ auth_application_id: str
+ auth_base_url: str
+ auth_callback_url: str
+
+
+def resolve_flow_auth_settings() -> FlowAuthSettings:
+ """
+ Resolve APS auth settings: env-var overrides, falling back to hardcoded defaults.
+
+ Override env vars:
+ TK_FLOW_AUTH_APPLICATION_ID
+ TK_FLOW_AUTH_BASE_URL
+ TK_FLOW_AUTH_CALLBACK_URL
+ """
+ return FlowAuthSettings(
+ auth_application_id=os.environ.get(
+ "TK_FLOW_AUTH_APPLICATION_ID", DEFAULT_AUTH_APPLICATION_ID
+ ),
+ auth_base_url=os.environ.get("TK_FLOW_AUTH_BASE_URL", DEFAULT_AUTH_BASE_URL),
+ auth_callback_url=os.environ.get(
+ "TK_FLOW_AUTH_CALLBACK_URL", DEFAULT_AUTH_CALLBACK_URL
+ ),
+ )
diff --git a/python/tk-core/python/tank/authentication/flow_auth/errors.py b/python/tk-core/python/tank/authentication/flow_auth/errors.py
new file mode 100644
index 00000000..bc9c3f6c
--- /dev/null
+++ b/python/tk-core/python/tank/authentication/flow_auth/errors.py
@@ -0,0 +1,25 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+"""Errors for Flow / MEDM authentication."""
+
+from ...errors import TankError
+
+
+class FlowAuthError(TankError):
+ """Base exception for Flow / MEDM authentication errors."""
+
+
+class FlowAuthConfigurationError(FlowAuthError):
+ """Raised when Flow auth settings are missing or invalid."""
+
+ def __init__(self, details: str = ""):
+ super().__init__(details)
+ self.details = details
diff --git a/python/tk-core/python/tank/authentication/interactive_authentication.py b/python/tk-core/python/tank/authentication/interactive_authentication.py
index d5ca7a94..d68a267c 100644
--- a/python/tk-core/python/tank/authentication/interactive_authentication.py
+++ b/python/tk-core/python/tank/authentication/interactive_authentication.py
@@ -24,15 +24,14 @@
# Using "with" with the lock to make sure it is always released.
-from .errors import AuthenticationCancelled
-from .console_authentication import ConsoleLoginHandler, ConsoleRenewSessionHandler
-
-from .. import LogManager
-
-import threading
import os
+import threading
+
from tank.util import is_windows
+from .. import LogManager
+from .console_authentication import ConsoleLoginHandler, ConsoleRenewSessionHandler
+from .errors import AuthenticationCancelled
logger = LogManager.get_logger(__name__)
@@ -56,7 +55,7 @@ def _get_current_os_user():
pwd_entry = pwd.getpwuid(os.geteuid())
return pwd_entry[0]
- except:
+ except Exception:
return None
@@ -78,6 +77,7 @@ def _get_ui_state() -> bool:
return bool(QtGui and QtGui.QApplication.instance())
+
class SessionRenewal(object):
"""
Handles multi-threaded session renewal. This class handles the use case when
@@ -220,6 +220,7 @@ def renew_session(user):
# If we have a gui, we need gui based authentication
if has_ui:
from .ui_authentication import UiAuthenticationHandler
+
authenticator = UiAuthenticationHandler(
is_session_renewal=True, session_metadata=user.get_session_metadata()
)
diff --git a/python/tk-core/python/tank/authentication/login_dialog.py b/python/tk-core/python/tank/authentication/login_dialog.py
index 46311c8d..1b7542f3 100644
--- a/python/tk-core/python/tank/authentication/login_dialog.py
+++ b/python/tk-core/python/tank/authentication/login_dialog.py
@@ -17,37 +17,34 @@
at any point.
--------------------------------------------------------------------------------
"""
+
import os
import sys
+
from tank_vendor import shotgun_api3
-from .. import constants
-from .web_login_support import get_shotgun_authenticator_support_web_login
-from .ui import resources_rc # noqa
-from .ui import login_dialog
-from . import constants as auth_constants
-from . import session_cache
+
+from .. import LogManager, constants
+from ..util import LocalFileStorageManager, login, metrics_cache
from ..util.metrics import EventMetric
from ..util.shotgun import connection
-from ..util import login
-from ..util import LocalFileStorageManager
-from ..util import metrics_cache
-from .errors import AuthenticationError
-from .ui.qt_abstraction import (
- QtGui,
- QtCore,
- QtNetwork,
- QtWebEngineWidgets,
- qt_version_tuple,
-)
from . import app_session_launcher
-from . import site_info
+from . import constants as auth_constants
+from . import session_cache, site_info
+from .errors import AuthenticationError
from .sso_saml2 import (
SsoSaml2IncompletePySide2,
SsoSaml2MissingQtModuleError,
)
from .sso_saml2.sso_saml2_toolkit import SsoSaml2Toolkit
-
-from .. import LogManager
+from .ui import resources_rc # noqa
+from .ui import login_dialog
+from .ui.qt_abstraction import (
+ QtCore,
+ QtGui,
+ QtNetwork,
+ QtWebEngineWidgets,
+)
+from .web_login_support import get_shotgun_authenticator_support_web_login
logger = LogManager.get_logger(__name__)
@@ -109,6 +106,7 @@ def run(self):
"""
self._site_info.reload(self._url_to_test, self._http_proxy)
+
class LoginDialog(QtGui.QDialog):
"""
Dialog for getting user credentials.
@@ -315,6 +313,16 @@ def __init__(
% self._get_current_site()
)
+ # QThread.finished is delivered through the Qt event queue and requires
+ # the event loop to be running. Since we are still in the constructor,
+ # the _toggle_web slot has not been called yet even though wait()
+ # returned and the thread is done. Call it directly here so the dialog
+ # opens with the correct UI (e.g. Identity/SSO sites must not flash the
+ # 3-fields credentials form before switching to the web-login view).
+ # The guard inside _toggle_web makes the subsequent signal delivery a
+ # no-op, so this is safe.
+ self._toggle_web()
+
# Initialize exit confirm message box
self.confirm_box = QtGui.QMessageBox(
QtGui.QMessageBox.Question,
@@ -484,15 +492,21 @@ def _toggle_web(self, method_selected=None):
# - they need to use the legacy login / passphrase to use a PAT with
# Autodesk Identity authentication
if os.environ.get("SGTK_FORCE_STANDARD_LOGIN_DIALOG"):
- logger.info("Using the standard login dialog with the Flow Production Tracking")
+ logger.info(
+ "Using the standard login dialog with the Flow Production Tracking"
+ )
else:
if _is_running_in_desktop():
- can_use_web = can_use_web or self.site_info.autodesk_identity_enabled
+ can_use_web = (
+ can_use_web or self.site_info.autodesk_identity_enabled
+ )
# If we have full support for Web-based login, or if we enable it in our
# environment, use the Unified Login Flow for all authentication modes.
if get_shotgun_authenticator_support_web_login():
- can_use_web = can_use_web or self.site_info.unified_login_flow_enabled
+ can_use_web = (
+ can_use_web or self.site_info.unified_login_flow_enabled
+ )
if method_selected:
# Selecting requested mode (credentials, qt_web_login or app_session_launcher)
@@ -504,9 +518,7 @@ def _toggle_web(self, method_selected=None):
method_selected = session_cache.get_preferred_method(site)
# Make sure that the method_selected is currently supported
- if (
- method_selected == auth_constants.METHOD_WEB_LOGIN and not can_use_web
- ) or (
+ if (method_selected == auth_constants.METHOD_WEB_LOGIN and not can_use_web) or (
method_selected == auth_constants.METHOD_ASL and not can_use_asl
):
method_selected = None
@@ -518,9 +530,7 @@ def _toggle_web(self, method_selected=None):
)
# Make sure that the method_selected is currently supported
- if (
- method_selected == auth_constants.METHOD_WEB_LOGIN and not can_use_web
- ) or (
+ if (method_selected == auth_constants.METHOD_WEB_LOGIN and not can_use_web) or (
method_selected == auth_constants.METHOD_ASL and not can_use_asl
):
method_selected = None
@@ -691,7 +701,9 @@ def result(self):
"Logged In",
properties={
"authentication_method": self.site_info.user_authentication_method,
- "authentication_experience": auth_constants.method_resolve.get(self.method_selected),
+ "authentication_experience": auth_constants.method_resolve.get(
+ self.method_selected
+ ),
"authentication_interface": "qt_dialog",
"authentication_renewal": self._is_session_renewal,
},
@@ -760,7 +772,10 @@ def _ok_pressed(self):
# Cleanup the URL and update the GUI.
if self.method_selected != auth_constants.METHOD_BASIC:
- if site.startswith("http://") and "SGTK_AUTH_ALLOW_NO_HTTPS" not in os.environ:
+ if (
+ site.startswith("http://")
+ and "SGTK_AUTH_ALLOW_NO_HTTPS" not in os.environ
+ ):
site = "https" + site[4:]
self.ui.site.setEditText(site)
diff --git a/python/tk-core/python/tank/authentication/session_cache.py b/python/tk-core/python/tank/authentication/session_cache.py
index 7f10e1e9..7b66ec6a 100644
--- a/python/tk-core/python/tank/authentication/session_cache.py
+++ b/python/tk-core/python/tank/authentication/session_cache.py
@@ -21,19 +21,21 @@
import os
import socket
+
+from tank_vendor import yaml
from tank_vendor.shotgun_api3 import (
- Shotgun,
AuthenticationFault,
- ProtocolError,
MissingTwoFactorAuthenticationFault,
+ ProtocolError,
+ Shotgun,
)
from tank_vendor.shotgun_api3.lib import httplib2
-from tank_vendor import yaml
-from . import constants
-from .errors import AuthenticationError
+
from .. import LogManager
-from ..util.shotgun import connection
from ..util import LocalFileStorageManager
+from ..util.shotgun import connection
+from . import constants
+from .errors import AuthenticationError
logger = LogManager.get_logger(__name__)
@@ -222,7 +224,7 @@ def _try_load_site_authentication_file(file_path):
content.setdefault(_RECENT_USERS, [])
if content.get(_PREFERRED_METHOD, "not null") is None:
- del(content[_PREFERRED_METHOD])
+ del content[_PREFERRED_METHOD]
for user in content[_USERS]:
user[_LOGIN] = user[_LOGIN].strip()
diff --git a/python/tk-core/python/tank/authentication/shotgun_authenticator.py b/python/tk-core/python/tank/authentication/shotgun_authenticator.py
index 7987650d..5f15685c 100644
--- a/python/tk-core/python/tank/authentication/shotgun_authenticator.py
+++ b/python/tk-core/python/tank/authentication/shotgun_authenticator.py
@@ -10,14 +10,11 @@
"""PTR Authenticator."""
-from .sso_saml2 import has_sso_info_in_cookies, has_unified_login_flow_info_in_cookies
-from . import interactive_authentication
-from . import user
-from . import user_impl
-from . import session_cache
-from .errors import IncompleteCredentials
-from .defaults_manager import DefaultsManager
from .. import LogManager
+from . import interactive_authentication, session_cache, user, user_impl
+from .defaults_manager import DefaultsManager
+from .errors import IncompleteCredentials
+from .sso_saml2 import has_sso_info_in_cookies, has_unified_login_flow_info_in_cookies
logger = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/authentication/shotgun_wrapper.py b/python/tk-core/python/tank/authentication/shotgun_wrapper.py
index 965b2bc6..a6a17d62 100644
--- a/python/tk-core/python/tank/authentication/shotgun_wrapper.py
+++ b/python/tk-core/python/tank/authentication/shotgun_wrapper.py
@@ -15,12 +15,14 @@
at any point.
--------------------------------------------------------------------------------
"""
-import http.client
-from tank_vendor.shotgun_api3 import Shotgun, AuthenticationFault
+import http.client
from xmlrpc.client import ProtocolError
-from . import interactive_authentication, session_cache
+
+from tank_vendor.shotgun_api3 import AuthenticationFault, Shotgun
+
from .. import LogManager
+from . import interactive_authentication, session_cache
logger = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/authentication/site_info.py b/python/tk-core/python/tank/authentication/site_info.py
index 1689c1b2..7b45d2e0 100644
--- a/python/tk-core/python/tank/authentication/site_info.py
+++ b/python/tk-core/python/tank/authentication/site_info.py
@@ -11,11 +11,10 @@
import time
-from . import utils
-
from tank_vendor import shotgun_api3
from .. import LogManager
+from . import utils
logger = LogManager.get_logger(__name__)
@@ -53,7 +52,9 @@ def _get_site_infos(url, http_proxy=None):
# require authentication.
http_proxy = utils.sanitize_http_proxy(http_proxy).netloc
if http_proxy:
- logger.debug("Using HTTP proxy to connect to the PTR server: %s", http_proxy)
+ logger.debug(
+ "Using HTTP proxy to connect to the PTR server: %s", http_proxy
+ )
logger.info("Infos for site '%s' not in cache or expired", url)
sg = shotgun_api3.Shotgun(
diff --git a/python/tk-core/python/tank/authentication/sso_saml2/__init__.py b/python/tk-core/python/tank/authentication/sso_saml2/__init__.py
index f329b56d..bf460261 100644
--- a/python/tk-core/python/tank/authentication/sso_saml2/__init__.py
+++ b/python/tk-core/python/tank/authentication/sso_saml2/__init__.py
@@ -21,6 +21,10 @@
SsoSaml2MissingQtNetwork,
SsoSaml2MultiSessionNotSupportedError,
)
+from .core.utils import ( # noqa
+ get_logger,
+ set_logger_parent,
+)
# Functions
from .utils import ( # noqa
@@ -28,8 +32,3 @@
has_sso_info_in_cookies,
has_unified_login_flow_info_in_cookies,
)
-
-from .core.utils import ( # noqa
- get_logger,
- set_logger_parent,
-)
diff --git a/python/tk-core/python/tank/authentication/sso_saml2/core/__init__.py b/python/tk-core/python/tank/authentication/sso_saml2/core/__init__.py
index 3b92ed9b..a31a8fb4 100644
--- a/python/tk-core/python/tank/authentication/sso_saml2/core/__init__.py
+++ b/python/tk-core/python/tank/authentication/sso_saml2/core/__init__.py
@@ -11,6 +11,7 @@
This module contains files which are shared between RV and Toolkit.
"""
+
def __getattr__(name):
"""
Retro compatibility - SG-40049 - Temporary workaround for compatibility for
@@ -22,7 +23,7 @@ def __getattr__(name):
https://github.com/shotgunsoftware/tk-nuke/blob/v0.16.0/engine.py#L396
This was removed in https://github.com/shotgunsoftware/tk-nuke/pull/125.
- TODO: Remove this after 2026-07.
+ TODO: Remove this after 2026-07.
"""
try:
@@ -33,6 +34,7 @@ def __getattr__(name):
try:
import inspect
+
frame = inspect.currentframe().f_back
caller_mod = frame.f_globals.get("__name__", "")
is_import = caller_mod.startswith("importlib.") or caller_mod == "builtins"
@@ -50,6 +52,7 @@ def __getattr__(name):
)
import warnings
+
warnings.warn(
deprecation_message,
DeprecationWarning,
@@ -58,10 +61,12 @@ def __getattr__(name):
try:
import tank
+
logger = tank.LogManager.get_logger(__name__)
logger.warning(deprecation_message)
except:
- pass # nosec
+ pass # nosec
import importlib
+
return importlib.import_module(f"{__name__}.{name}")
diff --git a/python/tk-core/python/tank/authentication/sso_saml2/core/sso_saml2_core.py b/python/tk-core/python/tank/authentication/sso_saml2/core/sso_saml2_core.py
index 4c5d9d72..c42e72e8 100644
--- a/python/tk-core/python/tank/authentication/sso_saml2/core/sso_saml2_core.py
+++ b/python/tk-core/python/tank/authentication/sso_saml2/core/sso_saml2_core.py
@@ -21,6 +21,7 @@
import sys
import time
+from ...utils import sanitize_http_proxy
from .authentication_session_data import AuthenticationSessionData
from .errors import (
SsoSaml2IncompletePySide2,
@@ -39,7 +40,6 @@
get_session_id,
get_user_name,
)
-from ...utils import sanitize_http_proxy
try:
from .username_password_dialog import UsernamePasswordDialog
@@ -129,7 +129,9 @@ def get_renew_path(session, logger):
# When this variable is set, it is passed to Autodesk Identity's login.
tk_shotgun_default_login = os.getenv("TK_SHOTGRID_DEFAULT_LOGIN")
# When this variable is set for a SSO domain, skip the initial login page.
- tk_shotgun_sso_domain = sanitize_http_proxy(os.getenv("TK_SHOTGRID_SSO_DOMAIN")).netloc
+ tk_shotgun_sso_domain = sanitize_http_proxy(
+ os.getenv("TK_SHOTGRID_SSO_DOMAIN")
+ ).netloc
# Flow Production Tracking's renew endpoint supports some useful
# Autodesk Identity params.
@@ -210,6 +212,7 @@ def __init__(self, window_title="Web Login", qt_modules=None):
)
if QtNetwork and not hasattr(QtNetwork, "QSslConfiguration"):
raise SsoSaml2IncompletePySide2("Missing class QtNetwork.QSslConfiguration")
+
class TKWebPageQtWebEngine(QtWebEngineWidgets.QWebEnginePage):
"""
Wrapper class to better control the behaviour when clicking on links
diff --git a/python/tk-core/python/tank/authentication/sso_saml2/core/username_password_dialog.py b/python/tk-core/python/tank/authentication/sso_saml2/core/username_password_dialog.py
index 963957ba..80d27e3f 100644
--- a/python/tk-core/python/tank/authentication/sso_saml2/core/username_password_dialog.py
+++ b/python/tk-core/python/tank/authentication/sso_saml2/core/username_password_dialog.py
@@ -75,8 +75,7 @@ def __init__(self, window_title=None, message=None):
buttons.button(QtGui.QDialogButtonBox.Cancel).clicked.connect(self.close)
# On Qt4, this sets the look-and-feel to that of the toolkit.
- self.setStyleSheet(
- """QWidget
+ self.setStyleSheet("""QWidget
{
background-color: rgb(36, 39, 42);
color: rgb(192, 193, 195);
@@ -98,8 +97,7 @@ def __init__(self, window_title=None, message=None):
color: rgb(248, 248, 248);
background-color: rgb(35, 165, 225);
}
- """
- )
+ """)
@property
def username(self):
diff --git a/python/tk-core/python/tank/authentication/sso_saml2/core/utils.py b/python/tk-core/python/tank/authentication/sso_saml2/core/utils.py
index b5509e4e..492864b6 100644
--- a/python/tk-core/python/tank/authentication/sso_saml2/core/utils.py
+++ b/python/tk-core/python/tank/authentication/sso_saml2/core/utils.py
@@ -13,13 +13,12 @@
# pylint: disable=line-too-long
-import sys
import base64
import binascii
import logging
-from urllib.parse import unquote_plus
+import sys
from http.cookies import SimpleCookie
-
+from urllib.parse import unquote_plus
from .errors import SsoSaml2MultiSessionNotSupportedError
diff --git a/python/tk-core/python/tank/authentication/sso_saml2/sso_saml2.py b/python/tk-core/python/tank/authentication/sso_saml2/sso_saml2.py
index af198d48..d1c7b0f6 100644
--- a/python/tk-core/python/tank/authentication/sso_saml2/sso_saml2.py
+++ b/python/tk-core/python/tank/authentication/sso_saml2/sso_saml2.py
@@ -17,7 +17,6 @@
# pylint: disable=unused-import
from .core.sso_saml2_core import SsoSaml2Core # noqa
-
from .core.utils import ( # noqa
set_logger_parent,
)
diff --git a/python/tk-core/python/tank/authentication/sso_saml2/utils.py b/python/tk-core/python/tank/authentication/sso_saml2/utils.py
index ef485fc6..bea8ef01 100644
--- a/python/tk-core/python/tank/authentication/sso_saml2/utils.py
+++ b/python/tk-core/python/tank/authentication/sso_saml2/utils.py
@@ -14,10 +14,10 @@
# pylint: disable=unused-import
from .core.utils import ( # noqa
+ _decode_cookies,
+ _get_shotgun_user_id,
get_saml_claims_expiration,
get_session_expiration,
- _decode_cookies,
- _get_shotgun_user_id
)
diff --git a/python/tk-core/python/tank/authentication/ui/aspect_preserving_label.py b/python/tk-core/python/tank/authentication/ui/aspect_preserving_label.py
index 5e6db348..de53e3d4 100644
--- a/python/tk-core/python/tank/authentication/ui/aspect_preserving_label.py
+++ b/python/tk-core/python/tank/authentication/ui/aspect_preserving_label.py
@@ -17,6 +17,7 @@ class AspectPreservingLabel(QtGui.QLabel):
Label that displays a scaled down version of an image if it is bigger
than the label.
"""
+
def __init__(self, parent=None):
"""
Constructor
@@ -35,7 +36,8 @@ def setPixmap(self, pixmap):
"""
self._pix = pixmap
scaled_pixmap = self._pix.scaled(
- self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
+ self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation
+ )
QtGui.QLabel.setPixmap(self, scaled_pixmap)
def heightForWidth(self, width):
@@ -67,6 +69,7 @@ def resizeEvent(self, e):
return
scaled_pixmap = self._pix.scaled(
- self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
+ self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation
+ )
QtGui.QLabel.setPixmap(self, scaled_pixmap)
QtGui.QApplication.instance().processEvents()
diff --git a/python/tk-core/python/tank/authentication/ui/completion_filter_proxy.py b/python/tk-core/python/tank/authentication/ui/completion_filter_proxy.py
index de6462e9..e1e6b876 100644
--- a/python/tk-core/python/tank/authentication/ui/completion_filter_proxy.py
+++ b/python/tk-core/python/tank/authentication/ui/completion_filter_proxy.py
@@ -23,13 +23,14 @@
from .qt_abstraction import QtGui
-class FuzzyMatcher():
+class FuzzyMatcher:
"""
Implement an algorithm to rank strings via fuzzy matching.
Based on the analysis at
http://crossplatform.net/sublime-text-ctrl-p-fuzzy-matching-in-python
"""
+
def __init__(self, pattern):
# construct a pattern that matches the letters in order
# for example "aad" turns into "(a).*?(a).*?(d)".
@@ -51,6 +52,7 @@ class CompletionFilterProxy(QtGui.QSortFilterProxyModel):
"""
Filters rows based on fuzzy matching and sorts them based on their score.
"""
+
def __init__(self, parent):
super().__init__(parent)
self.set_filter("")
diff --git a/python/tk-core/python/tank/authentication/ui/login_dialog.py b/python/tk-core/python/tank/authentication/ui/login_dialog.py
index 8cf2f9fc..2b80db05 100644
--- a/python/tk-core/python/tank/authentication/ui/login_dialog.py
+++ b/python/tk-core/python/tank/authentication/ui/login_dialog.py
@@ -9,166 +9,175 @@
################################################################################
from .qt_abstraction import QtCore
+
for name, cls in QtCore.__dict__.items():
- if isinstance(cls, type): globals()[name] = cls
+ if isinstance(cls, type):
+ globals()[name] = cls
from .qt_abstraction import QtGui
+
for name, cls in QtGui.__dict__.items():
- if isinstance(cls, type): globals()[name] = cls
+ if isinstance(cls, type):
+ globals()[name] = cls
from .recent_box import RecentBox
from .aspect_preserving_label import AspectPreservingLabel
-from . import resources_rc
+from . import resources_rc
+
class Ui_LoginDialog(object):
def setupUi(self, LoginDialog):
if not LoginDialog.objectName():
- LoginDialog.setObjectName(u"LoginDialog")
+ LoginDialog.setObjectName("LoginDialog")
LoginDialog.setWindowModality(Qt.NonModal)
LoginDialog.resize(424, 304)
LoginDialog.setMinimumSize(QSize(424, 304))
- LoginDialog.setStyleSheet(u"\n"
-"QWidget\n"
-"{\n"
-" background-color: rgb(36, 39, 42);\n"
-" color: rgb(192, 192, 192);\n"
-" selection-background-color: rgb(168, 123, 43);\n"
-" selection-color: rgb(230, 230, 230);\n"
-" font-size: 11px;\n"
-"}\n"
-"\n"
-"QPushButton\n"
-"{\n"
-" background-color: transparent;\n"
-" border: 1px solid transparent;\n"
-" border-radius: 2px;\n"
-" padding: 8px;\n"
-" padding-left: 15px;\n"
-" padding-right: 15px;\n"
-"}\n"
-"\n"
-"QPushButton::menu-indicator {\n"
-" subcontrol-position: right center;\n"
-"}\n"
-"\n"
-"QPushButton QMenu::item {\n"
-" padding: 15px;\n"
-" border: 1px solid transparent;\n"
-"}\n"
-"\n"
-"QPushButton QMenu::item:disabled {\n"
-" color: rgb(160, 160, 160);\n"
-" font-style: italic;\n"
-"}\n"
-"\n"
-"QPushButton QMenu::item:selected {\n"
-" border-color: rgb(54, 60, 66);\n"
-"}\n"
-"\n"
-"QPushButton QMenu::item:pressed\n"
-"{\n"
-" border-color: rgb(192, 192, 192);\n"
-"}\n"
-"\n"
-"QLineEdit, QComboBox\n"
-"{\n"
-" background-color: rgb(29, 31, 34);\n"
-" bord"
- "er: 1px solid rgb(54, 60, 66);\n"
-" border-radius: 2px;\n"
-" padding: 5px;\n"
-" font-size: 12px;\n"
-"}\n"
-"\n"
-"QComboBox\n"
-"{\n"
-" margin-left: 3px;\n"
-" margin-right: 3px;\n"
-"}\n"
-"\n"
-"QPushButton:focus\n"
-"{\n"
-" border-color: rgb(48, 167, 227);\n"
-" outline: none;\n"
-"}\n"
-"\n"
-"QPushButton:hover {\n"
-" border-color: rgb(54, 60, 66);\n"
-"}\n"
-"\n"
-"QPushButton:pressed\n"
-"{\n"
-" border-color: rgb(192, 192, 192);\n"
-"}\n"
-"\n"
-"QComboBox:focus, QLineEdit:focus\n"
-"{\n"
-" border: 1px solid rgb(48, 167, 227);\n"
-"}\n"
-"\n"
-"QComboBox:drop-down:button {\n"
-" border: 1px solid rgb(54, 60, 66);\n"
-"}\n"
-"\n"
-"QComboBox:down-arrow {\n"
-" image: url(:/shotgun_authentication/down-arrow.png);\n"
-"}\n"
-"\n"
-"QLineEdit:disabled {\n"
-" background-color: rgb(60, 60, 60);\n"
-" color: rgb(160, 160, 160);\n"
-"}\n"
-"\n"
-"QComboBox::drop-down:disabled {\n"
-" border-width: 0px;\n"
-"}\n"
-"\n"
-"QComboBox::down-arrow:disabled {\n"
-" image: url(noimg); border-width: 0px;\n"
-"}\n"
-""
- "\n"
-"QComboBox::disabled {\n"
-" background-color: rgb(60, 60, 60);\n"
-" color: rgb(160, 160, 160);\n"
-"}\n"
-"\n"
-"QPushButton.main\n"
-"{\n"
-" background-color: rgb(35, 165, 225);\n"
-" border-color: rgb(36, 39, 42);\n"
-" color: rgb(248, 248, 248);\n"
-"}\n"
-"\n"
-"QPushButton.main:focus, QPushButton.main:hover\n"
-"{\n"
-" border-color: rgb(54, 60, 66);\n"
-"}\n"
-"\n"
-"QPushButton.main:pressed\n"
-"{\n"
-" border-color: rgb(248, 248, 248);\n"
-"}\n"
-"")
+ LoginDialog.setStyleSheet(
+ "\n"
+ "QWidget\n"
+ "{\n"
+ " background-color: rgb(36, 39, 42);\n"
+ " color: rgb(192, 192, 192);\n"
+ " selection-background-color: rgb(168, 123, 43);\n"
+ " selection-color: rgb(230, 230, 230);\n"
+ " font-size: 11px;\n"
+ "}\n"
+ "\n"
+ "QPushButton\n"
+ "{\n"
+ " background-color: transparent;\n"
+ " border: 1px solid transparent;\n"
+ " border-radius: 2px;\n"
+ " padding: 8px;\n"
+ " padding-left: 15px;\n"
+ " padding-right: 15px;\n"
+ "}\n"
+ "\n"
+ "QPushButton::menu-indicator {\n"
+ " subcontrol-position: right center;\n"
+ "}\n"
+ "\n"
+ "QPushButton QMenu::item {\n"
+ " padding: 15px;\n"
+ " border: 1px solid transparent;\n"
+ "}\n"
+ "\n"
+ "QPushButton QMenu::item:disabled {\n"
+ " color: rgb(160, 160, 160);\n"
+ " font-style: italic;\n"
+ "}\n"
+ "\n"
+ "QPushButton QMenu::item:selected {\n"
+ " border-color: rgb(54, 60, 66);\n"
+ "}\n"
+ "\n"
+ "QPushButton QMenu::item:pressed\n"
+ "{\n"
+ " border-color: rgb(192, 192, 192);\n"
+ "}\n"
+ "\n"
+ "QLineEdit, QComboBox\n"
+ "{\n"
+ " background-color: rgb(29, 31, 34);\n"
+ " bord"
+ "er: 1px solid rgb(54, 60, 66);\n"
+ " border-radius: 2px;\n"
+ " padding: 5px;\n"
+ " font-size: 12px;\n"
+ "}\n"
+ "\n"
+ "QComboBox\n"
+ "{\n"
+ " margin-left: 3px;\n"
+ " margin-right: 3px;\n"
+ "}\n"
+ "\n"
+ "QPushButton:focus\n"
+ "{\n"
+ " border-color: rgb(48, 167, 227);\n"
+ " outline: none;\n"
+ "}\n"
+ "\n"
+ "QPushButton:hover {\n"
+ " border-color: rgb(54, 60, 66);\n"
+ "}\n"
+ "\n"
+ "QPushButton:pressed\n"
+ "{\n"
+ " border-color: rgb(192, 192, 192);\n"
+ "}\n"
+ "\n"
+ "QComboBox:focus, QLineEdit:focus\n"
+ "{\n"
+ " border: 1px solid rgb(48, 167, 227);\n"
+ "}\n"
+ "\n"
+ "QComboBox:drop-down:button {\n"
+ " border: 1px solid rgb(54, 60, 66);\n"
+ "}\n"
+ "\n"
+ "QComboBox:down-arrow {\n"
+ " image: url(:/shotgun_authentication/down-arrow.png);\n"
+ "}\n"
+ "\n"
+ "QLineEdit:disabled {\n"
+ " background-color: rgb(60, 60, 60);\n"
+ " color: rgb(160, 160, 160);\n"
+ "}\n"
+ "\n"
+ "QComboBox::drop-down:disabled {\n"
+ " border-width: 0px;\n"
+ "}\n"
+ "\n"
+ "QComboBox::down-arrow:disabled {\n"
+ " image: url(noimg); border-width: 0px;\n"
+ "}\n"
+ ""
+ "\n"
+ "QComboBox::disabled {\n"
+ " background-color: rgb(60, 60, 60);\n"
+ " color: rgb(160, 160, 160);\n"
+ "}\n"
+ "\n"
+ "QPushButton.main\n"
+ "{\n"
+ " background-color: rgb(35, 165, 225);\n"
+ " border-color: rgb(36, 39, 42);\n"
+ " color: rgb(248, 248, 248);\n"
+ "}\n"
+ "\n"
+ "QPushButton.main:focus, QPushButton.main:hover\n"
+ "{\n"
+ " border-color: rgb(54, 60, 66);\n"
+ "}\n"
+ "\n"
+ "QPushButton.main:pressed\n"
+ "{\n"
+ " border-color: rgb(248, 248, 248);\n"
+ "}\n"
+ ""
+ )
LoginDialog.setModal(True)
self.verticalLayout_2 = QVBoxLayout(LoginDialog)
self.verticalLayout_2.setContentsMargins(20, 20, 20, 20)
- self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setSpacing(0)
- self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalLayout.setObjectName("horizontalLayout")
self.horizontalLayout.setSizeConstraint(QLayout.SetMinAndMaxSize)
self.logo = AspectPreservingLabel(LoginDialog)
- self.logo.setObjectName(u"logo")
+ self.logo.setObjectName("logo")
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.logo.sizePolicy().hasHeightForWidth())
self.logo.setSizePolicy(sizePolicy)
self.logo.setMaximumSize(QSize(320, 72))
- self.logo.setPixmap(QPixmap(u":/shotgun_authentication/shotgun_logo_light_medium.png"))
+ self.logo.setPixmap(
+ QPixmap(":/shotgun_authentication/shotgun_logo_light_medium.png")
+ )
self.logo.setAlignment(Qt.AlignCenter)
self.logo.setTextInteractionFlags(Qt.NoTextInteraction)
@@ -177,52 +186,54 @@ def setupUi(self, LoginDialog):
self.verticalLayout_2.addLayout(self.horizontalLayout)
self.stackedWidget = QStackedWidget(LoginDialog)
- self.stackedWidget.setObjectName(u"stackedWidget")
+ self.stackedWidget.setObjectName("stackedWidget")
self.stackedWidget.setMinimumSize(QSize(324, 172))
self.login_page = QWidget()
- self.login_page.setObjectName(u"login_page")
+ self.login_page.setObjectName("login_page")
self.verticalLayout_3 = QVBoxLayout(self.login_page)
self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
- self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.verticalLayout_3.setObjectName("verticalLayout_3")
self.credentials = QWidget(self.login_page)
- self.credentials.setObjectName(u"credentials")
+ self.credentials.setObjectName("credentials")
self.credentials.setMinimumSize(QSize(0, 126))
self.verticalLayout_7 = QVBoxLayout(self.credentials)
self.verticalLayout_7.setContentsMargins(0, 0, 0, 0)
- self.verticalLayout_7.setObjectName(u"verticalLayout_7")
+ self.verticalLayout_7.setObjectName("verticalLayout_7")
self.site = RecentBox(self.credentials)
- self.site.setObjectName(u"site")
+ self.site.setObjectName("site")
self.verticalLayout_7.addWidget(self.site)
self.login = RecentBox(self.credentials)
- self.login.setObjectName(u"login")
+ self.login.setObjectName("login")
self.verticalLayout_7.addWidget(self.login)
self.password = QLineEdit(self.credentials)
- self.password.setObjectName(u"password")
+ self.password.setObjectName("password")
self.password.setMinimumSize(QSize(308, 0))
self.password.setEchoMode(QLineEdit.Password)
self.verticalLayout_7.addWidget(self.password)
self.message = QLabel(self.credentials)
- self.message.setObjectName(u"message")
+ self.message.setObjectName("message")
sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.message.sizePolicy().hasHeightForWidth())
self.message.setSizePolicy(sizePolicy1)
self.message.setTextFormat(Qt.RichText)
- self.message.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.message.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter)
self.message.setWordWrap(True)
self.message.setMargin(4)
self.message.setOpenExternalLinks(True)
self.verticalLayout_7.addWidget(self.message)
- self.verticalSpacer_3 = QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer_3 = QSpacerItem(
+ 20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding
+ )
self.verticalLayout_7.addItem(self.verticalSpacer_3)
@@ -230,19 +241,19 @@ def setupUi(self, LoginDialog):
self.button_layout = QHBoxLayout()
self.button_layout.setSpacing(10)
- self.button_layout.setObjectName(u"button_layout")
+ self.button_layout.setObjectName("button_layout")
self.button_layout.setContentsMargins(0, -1, -1, -1)
self.button_options = QPushButton(self.login_page)
- self.button_options.setObjectName(u"button_options")
+ self.button_options.setObjectName("button_options")
self.button_options.setAutoDefault(False)
self.button_options.setFlat(True)
self.button_layout.addWidget(self.button_options)
self.links = QVBoxLayout()
- self.links.setObjectName(u"links")
+ self.links.setObjectName("links")
self.forgot_password_link = QLabel(self.login_page)
- self.forgot_password_link.setObjectName(u"forgot_password_link")
+ self.forgot_password_link.setObjectName("forgot_password_link")
self.forgot_password_link.setCursor(QCursor(Qt.PointingHandCursor))
self.forgot_password_link.setTextFormat(Qt.RichText)
self.forgot_password_link.setMargin(4)
@@ -252,12 +263,14 @@ def setupUi(self, LoginDialog):
self.button_layout.addLayout(self.links)
- self.sign_in_hspacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.sign_in_hspacer = QSpacerItem(
+ 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
+ )
self.button_layout.addItem(self.sign_in_hspacer)
self.sign_in = QPushButton(self.login_page)
- self.sign_in.setObjectName(u"sign_in")
+ self.sign_in.setObjectName("sign_in")
self.sign_in.setAutoDefault(True)
self.sign_in.setFlat(True)
@@ -269,54 +282,60 @@ def setupUi(self, LoginDialog):
self.stackedWidget.addWidget(self.login_page)
self._2fa_page = QWidget()
- self._2fa_page.setObjectName(u"_2fa_page")
+ self._2fa_page.setObjectName("_2fa_page")
self.verticalLayout_4 = QVBoxLayout(self._2fa_page)
self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
- self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+ self.verticalLayout_4.setObjectName("verticalLayout_4")
self.credentials_2 = QWidget(self._2fa_page)
- self.credentials_2.setObjectName(u"credentials_2")
+ self.credentials_2.setObjectName("credentials_2")
self.credentials_2.setMinimumSize(QSize(0, 133))
self.horizontalLayout_2 = QHBoxLayout(self.credentials_2)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
- self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.label = QLabel(self.credentials_2)
- self.label.setObjectName(u"label")
+ self.label.setObjectName("label")
self.label.setMinimumSize(QSize(86, 0))
- self.label.setPixmap(QPixmap(u":/google_authenticator/google_authenticator.png"))
- self.label.setAlignment(Qt.AlignHCenter|Qt.AlignTop)
+ self.label.setPixmap(QPixmap(":/google_authenticator/google_authenticator.png"))
+ self.label.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
self.horizontalLayout_2.addWidget(self.label)
self.widget_2 = QWidget(self.credentials_2)
- self.widget_2.setObjectName(u"widget_2")
+ self.widget_2.setObjectName("widget_2")
self.verticalLayout = QVBoxLayout(self.widget_2)
- self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setObjectName("verticalLayout")
self._2fa_message = QLabel(self.widget_2)
- self._2fa_message.setObjectName(u"_2fa_message")
+ self._2fa_message.setObjectName("_2fa_message")
sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
- sizePolicy2.setHeightForWidth(self._2fa_message.sizePolicy().hasHeightForWidth())
+ sizePolicy2.setHeightForWidth(
+ self._2fa_message.sizePolicy().hasHeightForWidth()
+ )
self._2fa_message.setSizePolicy(sizePolicy2)
- self._2fa_message.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self._2fa_message.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter)
self._2fa_message.setWordWrap(True)
self._2fa_message.setMargin(0)
self.verticalLayout.addWidget(self._2fa_message)
self._2fa_code = QLineEdit(self.widget_2)
- self._2fa_code.setObjectName(u"_2fa_code")
+ self._2fa_code.setObjectName("_2fa_code")
self.verticalLayout.addWidget(self._2fa_code)
self.invalid_code = QLabel(self.widget_2)
- self.invalid_code.setObjectName(u"invalid_code")
- sizePolicy2.setHeightForWidth(self.invalid_code.sizePolicy().hasHeightForWidth())
+ self.invalid_code.setObjectName("invalid_code")
+ sizePolicy2.setHeightForWidth(
+ self.invalid_code.sizePolicy().hasHeightForWidth()
+ )
self.invalid_code.setSizePolicy(sizePolicy2)
self.verticalLayout.addWidget(self.invalid_code)
- self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer_2 = QSpacerItem(
+ 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding
+ )
self.verticalLayout.addItem(self.verticalSpacer_2)
@@ -326,20 +345,22 @@ def setupUi(self, LoginDialog):
self.button_layout_2 = QHBoxLayout()
self.button_layout_2.setSpacing(10)
- self.button_layout_2.setObjectName(u"button_layout_2")
+ self.button_layout_2.setObjectName("button_layout_2")
self.button_layout_2.setContentsMargins(0, -1, -1, -1)
self.use_backup = QPushButton(self._2fa_page)
- self.use_backup.setObjectName(u"use_backup")
+ self.use_backup.setObjectName("use_backup")
self.use_backup.setFlat(True)
self.button_layout_2.addWidget(self.use_backup)
- self._2fa_hspacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self._2fa_hspacer = QSpacerItem(
+ 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
+ )
self.button_layout_2.addItem(self._2fa_hspacer)
self.verify_2fa = QPushButton(self._2fa_page)
- self.verify_2fa.setObjectName(u"verify_2fa")
+ self.verify_2fa.setObjectName("verify_2fa")
self.verify_2fa.setMinimumSize(QSize(65, 0))
self.verify_2fa.setAutoDefault(False)
self.verify_2fa.setFlat(True)
@@ -350,50 +371,58 @@ def setupUi(self, LoginDialog):
self.stackedWidget.addWidget(self._2fa_page)
self.backup_page = QWidget()
- self.backup_page.setObjectName(u"backup_page")
+ self.backup_page.setObjectName("backup_page")
self.verticalLayout_6 = QVBoxLayout(self.backup_page)
self.verticalLayout_6.setContentsMargins(0, 0, 0, 0)
- self.verticalLayout_6.setObjectName(u"verticalLayout_6")
+ self.verticalLayout_6.setObjectName("verticalLayout_6")
self.credentials_3 = QWidget(self.backup_page)
- self.credentials_3.setObjectName(u"credentials_3")
+ self.credentials_3.setObjectName("credentials_3")
self.credentials_3.setMinimumSize(QSize(0, 133))
self.horizontalLayout_3 = QHBoxLayout(self.credentials_3)
self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
- self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.label_2 = QLabel(self.credentials_3)
- self.label_2.setObjectName(u"label_2")
- self.label_2.setPixmap(QPixmap(u":/backup_codes/backup_codes_light_bg.png"))
- self.label_2.setAlignment(Qt.AlignHCenter|Qt.AlignTop)
+ self.label_2.setObjectName("label_2")
+ self.label_2.setPixmap(QPixmap(":/backup_codes/backup_codes_light_bg.png"))
+ self.label_2.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
self.horizontalLayout_3.addWidget(self.label_2)
self.widget_4 = QWidget(self.credentials_3)
- self.widget_4.setObjectName(u"widget_4")
+ self.widget_4.setObjectName("widget_4")
self.verticalLayout_5 = QVBoxLayout(self.widget_4)
- self.verticalLayout_5.setObjectName(u"verticalLayout_5")
+ self.verticalLayout_5.setObjectName("verticalLayout_5")
self._2fa_message_2 = QLabel(self.widget_4)
- self._2fa_message_2.setObjectName(u"_2fa_message_2")
- sizePolicy2.setHeightForWidth(self._2fa_message_2.sizePolicy().hasHeightForWidth())
+ self._2fa_message_2.setObjectName("_2fa_message_2")
+ sizePolicy2.setHeightForWidth(
+ self._2fa_message_2.sizePolicy().hasHeightForWidth()
+ )
self._2fa_message_2.setSizePolicy(sizePolicy2)
- self._2fa_message_2.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self._2fa_message_2.setAlignment(
+ Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter
+ )
self._2fa_message_2.setWordWrap(True)
self._2fa_message_2.setMargin(0)
self.verticalLayout_5.addWidget(self._2fa_message_2)
self.backup_code = QLineEdit(self.widget_4)
- self.backup_code.setObjectName(u"backup_code")
+ self.backup_code.setObjectName("backup_code")
self.verticalLayout_5.addWidget(self.backup_code)
self.invalid_backup_code = QLabel(self.widget_4)
- self.invalid_backup_code.setObjectName(u"invalid_backup_code")
- sizePolicy2.setHeightForWidth(self.invalid_backup_code.sizePolicy().hasHeightForWidth())
+ self.invalid_backup_code.setObjectName("invalid_backup_code")
+ sizePolicy2.setHeightForWidth(
+ self.invalid_backup_code.sizePolicy().hasHeightForWidth()
+ )
self.invalid_backup_code.setSizePolicy(sizePolicy2)
self.verticalLayout_5.addWidget(self.invalid_backup_code)
- self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer = QSpacerItem(
+ 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding
+ )
self.verticalLayout_5.addItem(self.verticalSpacer)
@@ -403,21 +432,23 @@ def setupUi(self, LoginDialog):
self.button_layout_3 = QHBoxLayout()
self.button_layout_3.setSpacing(10)
- self.button_layout_3.setObjectName(u"button_layout_3")
+ self.button_layout_3.setObjectName("button_layout_3")
self.button_layout_3.setContentsMargins(0, -1, -1, -1)
self.use_app = QPushButton(self.backup_page)
- self.use_app.setObjectName(u"use_app")
+ self.use_app.setObjectName("use_app")
self.use_app.setAutoDefault(False)
self.use_app.setFlat(True)
self.button_layout_3.addWidget(self.use_app)
- self.backup_hspacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.backup_hspacer = QSpacerItem(
+ 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
+ )
self.button_layout_3.addItem(self.backup_hspacer)
self.verify_backup = QPushButton(self.backup_page)
- self.verify_backup.setObjectName(u"verify_backup")
+ self.verify_backup.setObjectName("verify_backup")
self.verify_backup.setMinimumSize(QSize(65, 0))
self.verify_backup.setAutoDefault(True)
self.verify_backup.setFlat(True)
@@ -428,33 +459,37 @@ def setupUi(self, LoginDialog):
self.stackedWidget.addWidget(self.backup_page)
self.asl_page = QWidget()
- self.asl_page.setObjectName(u"asl_page")
+ self.asl_page.setObjectName("asl_page")
self.verticalLayout_21 = QVBoxLayout(self.asl_page)
self.verticalLayout_21.setContentsMargins(20, 20, 20, 20)
- self.verticalLayout_21.setObjectName(u"verticalLayout_21")
+ self.verticalLayout_21.setObjectName("verticalLayout_21")
self.asl_msg = QLabel(self.asl_page)
- self.asl_msg.setObjectName(u"asl_msg")
+ self.asl_msg.setObjectName("asl_msg")
sizePolicy1.setHeightForWidth(self.asl_msg.sizePolicy().hasHeightForWidth())
self.asl_msg.setSizePolicy(sizePolicy1)
- self.asl_msg.setStyleSheet(u"padding-left: 40px; padding-left: 40px;padding-right: 40px;")
+ self.asl_msg.setStyleSheet(
+ "padding-left: 40px; padding-left: 40px;padding-right: 40px;"
+ )
self.asl_msg.setAlignment(Qt.AlignCenter)
self.asl_msg.setWordWrap(True)
self.verticalLayout_21.addWidget(self.asl_msg)
self.asl_msg_back = QLabel(self.asl_page)
- self.asl_msg_back.setObjectName(u"asl_msg_back")
+ self.asl_msg_back.setObjectName("asl_msg_back")
self.asl_msg_back.setAlignment(Qt.AlignCenter)
self.asl_msg_back.setWordWrap(True)
self.verticalLayout_21.addWidget(self.asl_msg_back)
- self.asl_spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.asl_spacer = QSpacerItem(
+ 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding
+ )
self.verticalLayout_21.addItem(self.asl_spacer)
self.asl_msg_help = QLabel(self.asl_page)
- self.asl_msg_help.setObjectName(u"asl_msg_help")
+ self.asl_msg_help.setObjectName("asl_msg_help")
self.asl_msg_help.setAlignment(Qt.AlignCenter)
self.asl_msg_help.setWordWrap(True)
@@ -474,48 +509,124 @@ def setupUi(self, LoginDialog):
self.verify_backup.setDefault(True)
QMetaObject.connectSlotsByName(LoginDialog)
+
# setupUi
def retranslateUi(self, LoginDialog):
- LoginDialog.setWindowTitle(QCoreApplication.translate("LoginDialog", u"Flow Production Tracking Login", None))
+ LoginDialog.setWindowTitle(
+ QCoreApplication.translate(
+ "LoginDialog", "Flow Production Tracking Login", None
+ )
+ )
self.logo.setText("")
-#if QT_CONFIG(accessibility)
- self.site.setAccessibleName(QCoreApplication.translate("LoginDialog", u"site", None))
-#endif // QT_CONFIG(accessibility)
-#if QT_CONFIG(accessibility)
- self.login.setAccessibleName(QCoreApplication.translate("LoginDialog", u"login", None))
-#endif // QT_CONFIG(accessibility)
-#if QT_CONFIG(accessibility)
- self.password.setAccessibleName(QCoreApplication.translate("LoginDialog", u"password", None))
-#endif // QT_CONFIG(accessibility)
- self.password.setPlaceholderText(QCoreApplication.translate("LoginDialog", u"password", None))
- self.message.setText(QCoreApplication.translate("LoginDialog", u"Please enter your credentials.", None))
- self.button_options.setText(QCoreApplication.translate("LoginDialog", u"See other options", None))
- self.forgot_password_link.setText(QCoreApplication.translate("LoginDialog", u"Forgot your password?
", None))
- self.sign_in.setText(QCoreApplication.translate("LoginDialog", u"Sign In", None))
- self.sign_in.setProperty("class", QCoreApplication.translate("LoginDialog", u"main", None))
+ # if QT_CONFIG(accessibility)
+ self.site.setAccessibleName(
+ QCoreApplication.translate("LoginDialog", "site", None)
+ )
+ # endif // QT_CONFIG(accessibility)
+ # if QT_CONFIG(accessibility)
+ self.login.setAccessibleName(
+ QCoreApplication.translate("LoginDialog", "login", None)
+ )
+ # endif // QT_CONFIG(accessibility)
+ # if QT_CONFIG(accessibility)
+ self.password.setAccessibleName(
+ QCoreApplication.translate("LoginDialog", "password", None)
+ )
+ # endif // QT_CONFIG(accessibility)
+ self.password.setPlaceholderText(
+ QCoreApplication.translate("LoginDialog", "password", None)
+ )
+ self.message.setText(
+ QCoreApplication.translate(
+ "LoginDialog", "Please enter your credentials.", None
+ )
+ )
+ self.button_options.setText(
+ QCoreApplication.translate("LoginDialog", "See other options", None)
+ )
+ self.forgot_password_link.setText(
+ QCoreApplication.translate(
+ "LoginDialog",
+ 'Forgot your password?
',
+ None,
+ )
+ )
+ self.sign_in.setText(QCoreApplication.translate("LoginDialog", "Sign In", None))
+ self.sign_in.setProperty(
+ "class", QCoreApplication.translate("LoginDialog", "main", None)
+ )
self.label.setText("")
- self._2fa_message.setText(QCoreApplication.translate("LoginDialog", u"Enter the code generated by the Google Authenticator or Duo Mobile app.", None))
-#if QT_CONFIG(accessibility)
- self._2fa_code.setAccessibleName(QCoreApplication.translate("LoginDialog", u"2fa code", None))
-#endif // QT_CONFIG(accessibility)
- self._2fa_code.setPlaceholderText(QCoreApplication.translate("LoginDialog", u"Enter code", None))
+ self._2fa_message.setText(
+ QCoreApplication.translate(
+ "LoginDialog",
+ "Enter the code generated by the Google Authenticator or Duo Mobile app.",
+ None,
+ )
+ )
+ # if QT_CONFIG(accessibility)
+ self._2fa_code.setAccessibleName(
+ QCoreApplication.translate("LoginDialog", "2fa code", None)
+ )
+ # endif // QT_CONFIG(accessibility)
+ self._2fa_code.setPlaceholderText(
+ QCoreApplication.translate("LoginDialog", "Enter code", None)
+ )
self.invalid_code.setText("")
- self.use_backup.setText(QCoreApplication.translate("LoginDialog", u"Use backup code", None))
- self.verify_2fa.setText(QCoreApplication.translate("LoginDialog", u"Verify", None))
- self.verify_2fa.setProperty("class", QCoreApplication.translate("LoginDialog", u"main", None))
+ self.use_backup.setText(
+ QCoreApplication.translate("LoginDialog", "Use backup code", None)
+ )
+ self.verify_2fa.setText(
+ QCoreApplication.translate("LoginDialog", "Verify", None)
+ )
+ self.verify_2fa.setProperty(
+ "class", QCoreApplication.translate("LoginDialog", "main", None)
+ )
self.label_2.setText("")
- self._2fa_message_2.setText(QCoreApplication.translate("LoginDialog", u"Please enter one of your backup codes.", None))
-#if QT_CONFIG(accessibility)
- self.backup_code.setAccessibleName(QCoreApplication.translate("LoginDialog", u"backup code", None))
-#endif // QT_CONFIG(accessibility)
+ self._2fa_message_2.setText(
+ QCoreApplication.translate(
+ "LoginDialog", "Please enter one of your backup codes.", None
+ )
+ )
+ # if QT_CONFIG(accessibility)
+ self.backup_code.setAccessibleName(
+ QCoreApplication.translate("LoginDialog", "backup code", None)
+ )
+ # endif // QT_CONFIG(accessibility)
self.backup_code.setText("")
- self.backup_code.setPlaceholderText(QCoreApplication.translate("LoginDialog", u"Enter backup code", None))
+ self.backup_code.setPlaceholderText(
+ QCoreApplication.translate("LoginDialog", "Enter backup code", None)
+ )
self.invalid_backup_code.setText("")
- self.use_app.setText(QCoreApplication.translate("LoginDialog", u"Use Google App", None))
- self.verify_backup.setText(QCoreApplication.translate("LoginDialog", u"Verify", None))
- self.verify_backup.setProperty("class", QCoreApplication.translate("LoginDialog", u"main", None))
- self.asl_msg.setText(QCoreApplication.translate("LoginDialog", u"Check your default web browser to continue logging in.", None))
- self.asl_msg_back.setText(QCoreApplication.translate("LoginDialog", u"Cancel & return to the login page
", None))
- self.asl_msg_help.setText(QCoreApplication.translate("LoginDialog", u"
If you are having trouble logging in with the browser, select this support link
", None))
+ self.use_app.setText(
+ QCoreApplication.translate("LoginDialog", "Use Google App", None)
+ )
+ self.verify_backup.setText(
+ QCoreApplication.translate("LoginDialog", "Verify", None)
+ )
+ self.verify_backup.setProperty(
+ "class", QCoreApplication.translate("LoginDialog", "main", None)
+ )
+ self.asl_msg.setText(
+ QCoreApplication.translate(
+ "LoginDialog",
+ "Check your default web browser to continue logging in.",
+ None,
+ )
+ )
+ self.asl_msg_back.setText(
+ QCoreApplication.translate(
+ "LoginDialog",
+ 'Cancel & return to the login page
',
+ None,
+ )
+ )
+ self.asl_msg_help.setText(
+ QCoreApplication.translate(
+ "LoginDialog",
+ 'If you are having trouble logging in with the browser, select this support link
',
+ None,
+ )
+ )
+
# retranslateUi
diff --git a/python/tk-core/python/tank/authentication/ui/resources_rc.py b/python/tk-core/python/tank/authentication/ui/resources_rc.py
index 95c08531..8163a110 100644
--- a/python/tk-core/python/tank/authentication/ui/resources_rc.py
+++ b/python/tk-core/python/tank/authentication/ui/resources_rc.py
@@ -3715,10 +3715,17 @@
\x00\x00\x01\x8f05\xd1\xf5\
"
+
def qInitResources():
- QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+ QtCore.qRegisterResourceData(
+ 0x03, qt_resource_struct, qt_resource_name, qt_resource_data
+ )
+
def qCleanupResources():
- QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+ QtCore.qUnregisterResourceData(
+ 0x03, qt_resource_struct, qt_resource_name, qt_resource_data
+ )
+
qInitResources()
diff --git a/python/tk-core/python/tank/authentication/ui_authentication.py b/python/tk-core/python/tank/authentication/ui_authentication.py
index 486262e6..8158e2c1 100644
--- a/python/tk-core/python/tank/authentication/ui_authentication.py
+++ b/python/tk-core/python/tank/authentication/ui_authentication.py
@@ -18,9 +18,9 @@
--------------------------------------------------------------------------------
"""
-from .errors import AuthenticationCancelled, ShotgunAuthenticationError
-from . import invoker
from .. import LogManager
+from . import invoker
+from .errors import AuthenticationCancelled, ShotgunAuthenticationError
logger = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/authentication/user.py b/python/tk-core/python/tank/authentication/user.py
index 37c00350..56d20846 100644
--- a/python/tk-core/python/tank/authentication/user.py
+++ b/python/tk-core/python/tank/authentication/user.py
@@ -12,13 +12,10 @@
import threading
import time
-from . import sso_saml2
-from . import interactive_authentication
-from . import user_impl
from .. import LogManager
+from . import interactive_authentication, sso_saml2, user_impl
from .errors import AuthenticationCancelled
-
logger = LogManager.get_logger(__name__)
# Ensure that the SSO-related logging will be merged in our loggin.
diff --git a/python/tk-core/python/tank/authentication/user_impl.py b/python/tk-core/python/tank/authentication/user_impl.py
index 3cd32e60..7d434043 100644
--- a/python/tk-core/python/tank/authentication/user_impl.py
+++ b/python/tk-core/python/tank/authentication/user_impl.py
@@ -18,16 +18,17 @@
at any point.
--------------------------------------------------------------------------------
"""
-import json
+
import http.client
+import json
-from .shotgun_wrapper import ShotgunWrapper
-from tank_vendor.shotgun_api3 import Shotgun, AuthenticationFault, ProtocolError
+from tank_vendor.shotgun_api3 import AuthenticationFault, ProtocolError, Shotgun
-from . import session_cache
-from .errors import IncompleteCredentials, UnresolvableHumanUser, UnresolvableScriptUser
from .. import LogManager
from ..util import pickle
+from . import session_cache
+from .errors import IncompleteCredentials, UnresolvableHumanUser, UnresolvableScriptUser
+from .shotgun_wrapper import ShotgunWrapper
# Indirection to create ShotgunWrapper instances. Great for unit testing.
_shotgun_instance_factory = ShotgunWrapper
@@ -606,7 +607,6 @@ def deserialize_user(payload):
user_dict = pickle.loads(payload)
# Find which user type we have
- global __factories
factory = __factories.get(user_dict.get("type"))
# Unknown type, something is wrong. Maybe backward compatible code broke?
if not factory:
diff --git a/python/tk-core/python/tank/authentication/web_login_support.py b/python/tk-core/python/tank/authentication/web_login_support.py
index a3a93e4a..21abf161 100644
--- a/python/tk-core/python/tank/authentication/web_login_support.py
+++ b/python/tk-core/python/tank/authentication/web_login_support.py
@@ -59,5 +59,4 @@ def get_shotgun_authenticator_support_web_login():
:returns: Bool indicating support for the Unified Login Flow.
"""
- global shotgun_authenticator_support_web_login
return shotgun_authenticator_support_web_login
diff --git a/python/tk-core/python/tank/bootstrap/__init__.py b/python/tk-core/python/tank/bootstrap/__init__.py
index ada6fd37..e3edeaa0 100644
--- a/python/tk-core/python/tank/bootstrap/__init__.py
+++ b/python/tk-core/python/tank/bootstrap/__init__.py
@@ -8,5 +8,5 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from .manager import ToolkitManager
from .errors import TankBootstrapError, TankMissingTankNameError
+from .manager import ToolkitManager
diff --git a/python/tk-core/python/tank/bootstrap/baked_configuration.py b/python/tk-core/python/tank/bootstrap/baked_configuration.py
index 21b2d81b..7ec5c64c 100644
--- a/python/tk-core/python/tank/bootstrap/baked_configuration.py
+++ b/python/tk-core/python/tank/bootstrap/baked_configuration.py
@@ -10,16 +10,12 @@
import os
-from .configuration import Configuration
-from .configuration_writer import ConfigurationWriter
-
-from .. import LogManager
-from .. import constants
-
+from .. import LogManager, constants, pipelineconfig_utils
+from ..errors import TankFileDoesNotExistError
from ..util import ShotgunPath
from ..util.pickle import store_env_var_pickled
-from ..errors import TankFileDoesNotExistError
-from .. import pipelineconfig_utils
+from .configuration import Configuration
+from .configuration_writer import ConfigurationWriter
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/bootstrap/bundle_downloader.py b/python/tk-core/python/tank/bootstrap/bundle_downloader.py
index 42a391fc..8a5c955a 100644
--- a/python/tk-core/python/tank/bootstrap/bundle_downloader.py
+++ b/python/tk-core/python/tank/bootstrap/bundle_downloader.py
@@ -9,8 +9,8 @@
# not expressly granted therein are reserved by Shotgun Software Inc.
import os
-from sgtk import hook
-from sgtk import LogManager
+
+from sgtk import LogManager, hook
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/bootstrap/cached_configuration.py b/python/tk-core/python/tank/bootstrap/cached_configuration.py
index a8425398..2dc41623 100644
--- a/python/tk-core/python/tank/bootstrap/cached_configuration.py
+++ b/python/tk-core/python/tank/bootstrap/cached_configuration.py
@@ -9,21 +9,18 @@
# not expressly granted therein are reserved by Shotgun Software Inc.
import os
-import sys
-import traceback
import pprint
-
-from . import constants
-
-from ..descriptor import create_descriptor, Descriptor
-from .errors import TankBootstrapError, TankMissingTankNameError
-
-from ..util import filesystem, version
+import traceback
from tank_vendor import yaml
+
+from .. import LogManager
+from ..descriptor import Descriptor, create_descriptor
+from ..util import filesystem
+from . import constants
from .configuration import Configuration
from .configuration_writer import ConfigurationWriter
-from .. import LogManager
+from .errors import TankBootstrapError, TankMissingTankNameError
log = LogManager.get_logger(__name__)
@@ -140,7 +137,7 @@ def verify_required_shotgun_fields(self):
% (storage_roots.roots_file, storage_roots.required_roots)
)
- (_, unmapped_roots) = storage_roots.get_local_storages(self._sg_connection)
+ _, unmapped_roots = storage_roots.get_local_storages(self._sg_connection)
# get a list of all defined storage roots without a corresponding PTR
# local storage defined
@@ -258,10 +255,10 @@ def update_configuration(self):
try:
# Move to backup needs to undo changes when failing because we need to put the configuration
# in a usable state.
- (config_backup_path, core_backup_path) = self._config_writer.move_to_backup(
+ config_backup_path, core_backup_path = self._config_writer.move_to_backup(
undo_on_error=True
)
- except Exception as e:
+ except Exception:
log.exception(
"Unexpected error while making a backup of the configuration. Toolkit will use the "
"original configuration."
diff --git a/python/tk-core/python/tank/bootstrap/configuration.py b/python/tk-core/python/tank/bootstrap/configuration.py
index cb58e162..8c0debdb 100644
--- a/python/tk-core/python/tank/bootstrap/configuration.py
+++ b/python/tk-core/python/tank/bootstrap/configuration.py
@@ -11,16 +11,14 @@
import inspect
import os
-from .import_handler import CoreImportHandler
-
-from ..log import LogManager
-from .. import pipelineconfig_utils
-from .. import constants
+from .. import constants, pipelineconfig_utils
from ..authentication import (
+ ShotgunSamlUser,
get_shotgun_authenticator_support_web_login,
serialize_user,
- ShotgunSamlUser,
)
+from ..log import LogManager
+from .import_handler import CoreImportHandler
log = LogManager.get_logger(__name__)
@@ -178,8 +176,7 @@ def _tank_from_path(self, path):
"""
# Perform a local import here to make sure we are getting
# the newly swapped in core code, if it was swapped
- from .. import api
- from .. import pipelineconfig
+ from .. import api, pipelineconfig
log.debug("Executing tank_from_path('%s')" % path)
@@ -267,6 +264,7 @@ def _set_authenticated_user(
ShotgunAuthenticator,
deserialize_user,
)
+
from ..util import CoreDefaultsManager
except ImportError:
log.debug("Using pre-0.16 core, no authenticated user will be set.")
diff --git a/python/tk-core/python/tank/bootstrap/configuration_writer.py b/python/tk-core/python/tank/bootstrap/configuration_writer.py
index 3e95a306..17f33aa4 100644
--- a/python/tk-core/python/tank/bootstrap/configuration_writer.py
+++ b/python/tk-core/python/tank/bootstrap/configuration_writer.py
@@ -8,23 +8,17 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
+import datetime
import os
import sys
-import datetime
-
-from . import constants
-
-from ..descriptor import Descriptor, create_descriptor, is_descriptor_version_missing
-
-from ..util import filesystem
-from ..util import StorageRoots
-from ..util.shotgun import connection
-from ..util.move_guard import MoveGuard
-from ..util import is_macos, is_windows
from tank_vendor import yaml
from .. import LogManager
+from ..util import StorageRoots, filesystem, is_macos, is_windows
+from ..util.move_guard import MoveGuard
+from ..util.shotgun import connection
+from . import constants
log = LogManager.get_logger(__name__)
@@ -265,7 +259,9 @@ def create_tank_command(self, executable=sys.executable, prefix=sys.prefix):
if current_interpreter:
log.debug("Current OS interpreter will be %s.", current_interpreter)
else:
- log.debug("Current OS interpreter will be the default PTR desktop app location.")
+ log.debug(
+ "Current OS interpreter will be the default PTR desktop app location."
+ )
config_root_path = self._path.current_os
@@ -339,7 +335,9 @@ def write_config_info_file(self, config_descriptor):
with filesystem.auto_created_yml(config_info_file) as fh:
fh.write("# This file contains metadata describing what exact version\n")
- fh.write("# Of the config that was downloaded from Flow Production Tracking\n")
+ fh.write(
+ "# Of the config that was downloaded from Flow Production Tracking\n"
+ )
fh.write("\n")
fh.write("# Below follows details for the sg attachment that is\n")
fh.write("# reflected within this local configuration.\n")
diff --git a/python/tk-core/python/tank/bootstrap/import_handler.py b/python/tk-core/python/tank/bootstrap/import_handler.py
index 715d61e1..2e16ad25 100644
--- a/python/tk-core/python/tank/bootstrap/import_handler.py
+++ b/python/tk-core/python/tank/bootstrap/import_handler.py
@@ -102,7 +102,7 @@ def swap_core(cls, core_path):
try:
if prev_log_file:
tank.LogManager().initialize_base_file_handler_from_path(prev_log_file)
- except AttributeError as e:
+ except AttributeError:
# older versions of the API may not have this defined.
log.debug(
"Switching to a version of the core API that doesn't "
@@ -289,20 +289,14 @@ def find_spec(self, module_fullname, package_path=None, target=None):
# later raise FileNotFoundError instead of the expected ImportError
# when the module doesn't exist on disk.
if os.path.isdir(os.path.join(package_path[0], module_name)):
- module_file = os.path.join(
- package_path[0], module_name, "__init__.py"
- )
+ module_file = os.path.join(package_path[0], module_name, "__init__.py")
else:
- module_file = os.path.join(
- package_path[0], module_name + ".py"
- )
+ module_file = os.path.join(package_path[0], module_name + ".py")
if not os.path.isfile(module_file):
return None
- loader = importlib.machinery.SourceFileLoader(
- module_fullname, module_file
- )
+ loader = importlib.machinery.SourceFileLoader(module_fullname, module_file)
spec = importlib.util.spec_from_loader(loader.name, loader)
self._module_info[module_fullname] = spec
except ImportError:
@@ -328,9 +322,7 @@ def load_module(self, module_fullname):
try:
spec.loader.exec_module(module)
except FileNotFoundError as e:
- raise ImportError(
- f"No module named '{module_fullname}'"
- ) from e
+ raise ImportError(f"No module named '{module_fullname}'") from e
# the module needs to know the loader so that reload() works
module.__loader__ = self
diff --git a/python/tk-core/python/tank/bootstrap/installed_configuration.py b/python/tk-core/python/tank/bootstrap/installed_configuration.py
index fa5f5334..4de4ffd7 100644
--- a/python/tk-core/python/tank/bootstrap/installed_configuration.py
+++ b/python/tk-core/python/tank/bootstrap/installed_configuration.py
@@ -10,9 +10,9 @@
import os
-from .errors import TankBootstrapError
-from .configuration import Configuration
from .. import LogManager
+from .configuration import Configuration
+from .errors import TankBootstrapError
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/bootstrap/manager.py b/python/tk-core/python/tank/bootstrap/manager.py
index b058b6e8..7efc505d 100644
--- a/python/tk-core/python/tank/bootstrap/manager.py
+++ b/python/tk-core/python/tank/bootstrap/manager.py
@@ -8,18 +8,20 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import os
import inspect
+import os
-from . import constants
-from .errors import TankBootstrapError
-from .configuration import Configuration
-from .resolver import ConfigurationResolver
-from ..authentication import ShotgunAuthenticator
-from ..pipelineconfig import PipelineConfiguration
from .. import LogManager
+from ..authentication import ShotgunAuthenticator, flow_auth
from ..errors import TankError
+from ..flowam import constants as flow_const
+from ..flowam import utils as flow_utils
+from ..pipelineconfig import PipelineConfiguration
from ..util import ShotgunPath
+from . import constants
+from .configuration import Configuration
+from .errors import TankBootstrapError
+from .resolver import ConfigurationResolver
log = LogManager.get_logger(__name__)
@@ -33,12 +35,12 @@ class ToolkitManager(object):
# Constants used to make the manager bootstrapping:
# - download and cache the config dependencies needed to run the engine being started in a specific environment.
# - download and cache all the config dependencies needed to run the engine in any environment.
- (CACHE_SPARSE, CACHE_FULL) = range(2)
+ CACHE_SPARSE, CACHE_FULL = range(2)
# Constants used to indicate that the manager is:
# - bootstrapping the toolkit (with method bootstrap_toolkit),
# - starting up the engine (with method _start_engine).
- (TOOLKIT_BOOTSTRAP_PHASE, ENGINE_STARTUP_PHASE) = range(2)
+ TOOLKIT_BOOTSTRAP_PHASE, ENGINE_STARTUP_PHASE = range(2)
# List of constants representing the status of the progress bar when these event occurs during bootstrap.
_RESOLVING_PROJECT_RATE = 0.0
@@ -84,6 +86,10 @@ def __init__(self, sg_user=None):
self._plugin_id = None
self._allow_config_overrides = True
+ # flow fields
+ self._flow_project_id = None
+ self._flow_schema_version = None
+
# look for the standard env var SHOTGUN_PIPELINE_CONFIGURATION_ID
# and in case this is set, use it as a default
if constants.PIPELINE_CONFIG_ID_ENV_VAR in os.environ:
@@ -914,6 +920,84 @@ def _log_startup_message(self, engine_name, entity):
log.debug("Bootstrapping engine %s." % engine_name)
log.debug("-----------------------------------------------------------------")
+ def _resolve_project_id(self, entity):
+ """
+ Resolve a Shotgun project id from a target entity.
+
+ :param entity: ``None``, a Project dict, an entity with a ``project``
+ link, or any entity that can be looked up in Shotgun
+ to find its parent project.
+ :type entity: dict or None
+ :returns: Integer project id, or ``None`` for the site context.
+ :rtype: int or None
+ """
+ if entity is None:
+ return None
+
+ if entity.get("type") == "Project":
+ return entity["id"]
+
+ if "project" in entity and entity["project"].get("type") == "Project":
+ return entity["project"]["id"]
+
+ data = self._sg_connection.find_one(
+ entity["type"], [["id", "is", entity["id"]]], ["project"]
+ )
+ if not data or not data.get("project"):
+ raise TankBootstrapError("Cannot resolve project for %s" % entity)
+ return data["project"]["id"]
+
+ def _trigger_am_auth(self, pipeline_config, entity, progress_callback):
+ """
+ Proactively obtain a Flow/MEDM access token.
+ Silent path (file store -> refresh) is tried first; falls
+ back to opening a browser for PKCE if no usable cached/refresh token
+ exists.
+
+ Configuration errors raise ``TankBootstrapError`` (deployment bug).
+ Runtime auth failures are logged and swallowed unless the
+ ``TK_FLOW_AUTH_REQUIRED`` env var is set to ``"1"``, in which case
+ they raise ``TankBootstrapError``.
+
+ :param pipeline_config: PipelineConfiguration object.
+ :param entity: Shotgun entity used to resolve a project context.
+ :type entity: dict or None
+ :param progress_callback: Callback function that reports back on the toolkit bootstrap progress.
+ Set to ``None`` to use the default callback function.
+ :rtype: None
+ """
+ if entity is None:
+ return
+
+ try:
+ log.info("Triggering Flow authentication...")
+ overrides = flow_utils.get_config_flow_settings(pipeline_config)
+ # Set authentication overrides in reserved env vars so they will persist
+ # across toolkit engine sessions
+ if flow_const.FLOW_AUTH_APP_ID in overrides:
+ os.environ["TK_FLOW_AUTH_APPLICATION_ID"] = overrides[
+ flow_const.FLOW_AUTH_APP_ID
+ ]
+ if flow_const.FLOW_AUTH_BASE_URL in overrides:
+ os.environ["TK_FLOW_AUTH_BASE_URL"] = overrides[
+ flow_const.FLOW_AUTH_BASE_URL
+ ]
+ if flow_const.FLOW_AUTH_CALLBACK_URL in overrides:
+ os.environ["TK_FLOW_AUTH_CALLBACK_URL"] = overrides[
+ flow_const.FLOW_AUTH_CALLBACK_URL
+ ]
+ settings = flow_auth.resolve_flow_auth_settings()
+ flow_auth.init_authentication(settings)
+ # Token is intentionally discarded here; it now sits in the file
+ # store and adsk_auth's in-memory cache for the next consumer.
+ flow_auth.get_access_token()
+ except flow_auth.FlowAuthConfigurationError as e:
+ raise TankBootstrapError(
+ "MEDM auth misconfigured for AM-ready project: %s" % e
+ )
+ except Exception as e:
+ raise TankBootstrapError("MEDM auth failed for AM-ready project: %s" % e)
+
def _get_configuration(self, entity, progress_callback):
"""
Resolves the configuration to use without creating it on disk.
@@ -928,25 +1012,7 @@ def _get_configuration(self, entity, progress_callback):
self._report_progress(
progress_callback, self._RESOLVING_PROJECT_RATE, "Resolving project..."
)
- if entity is None:
- project_id = None
-
- elif entity.get("type") == "Project":
- project_id = entity["id"]
-
- elif "project" in entity and entity["project"].get("type") == "Project":
- # user passed a project link
- project_id = entity["project"]["id"]
-
- else:
- # resolve from shotgun
- data = self._sg_connection.find_one(
- entity["type"], [["id", "is", entity["id"]]], ["project"]
- )
-
- if not data or not data.get("project"):
- raise TankBootstrapError("Cannot resolve project for %s" % entity)
- project_id = data["project"]["id"]
+ project_id = self._resolve_project_id(entity)
# get an object to represent the business logic for
# how a configuration location is being determined
@@ -997,7 +1063,9 @@ def _get_configuration(self, entity, progress_callback):
elif self._do_shotgun_config_lookup:
# do the full resolve where we connect to shotgun etc.
- log.debug("Checking for pipeline configuration overrides in Flow Production Tracking.")
+ log.debug(
+ "Checking for pipeline configuration overrides in Flow Production Tracking."
+ )
log.debug(
"In order to turn this off, set do_shotgun_config_lookup to False"
)
@@ -1063,6 +1131,36 @@ def _get_updated_configuration(self, entity, progress_callback):
else:
raise TankBootstrapError("Unknown configuration update status!")
+ if entity is not None:
+ project_id = self._resolve_project_id(entity)
+ if project_id:
+ sg_project = self._sg_connection.find_one(
+ "Project",
+ [["id", "is", project_id]],
+ [
+ flow_auth.AM_READY_PROJECT_FIELD,
+ flow_const.FLOW_SCHEMA_VERSION_FIELD,
+ ],
+ )
+ if sg_project and sg_project.get(flow_auth.AM_READY_PROJECT_FIELD):
+ # Store flow fields temporarily to avoid re-querying.
+ # They will be cached on the context object after the context object has been rebuilt.
+ self._flow_project_id = sg_project.get(
+ flow_auth.AM_READY_PROJECT_FIELD
+ )
+ self._flow_schema_version = sg_project.get(
+ flow_const.FLOW_SCHEMA_VERSION_FIELD
+ )
+ log.info(
+ f"Current SG project is associated with a Flow project: {self._flow_project_id} with schema version {self._flow_schema_version}."
+ )
+
+ tk, _ = config.get_tk_instance(self._sg_user)
+ # Authenticate into Flow AM
+ self._trigger_am_auth(
+ tk.pipeline_configuration, entity, progress_callback
+ )
+
return config
def _bootstrap_sgtk(self, engine_name, entity, progress_callback=None):
@@ -1114,6 +1212,11 @@ def _start_engine(self, tk, engine_name, entity, progress_callback=None):
If entity is None, the method will bootstrap into the site config.
+ For Flow-enabled projects, the Flow project id and schema version
+ cached during ``_get_updated_configuration()`` are injected onto
+ ``ctx.project`` so the engine can read them via
+ ``context.flow_project_id`` and ``context.flow_schema_version``.
+
Please note that the API version of the tk instance that hosts
the engine may not be the same as the API version that was
executed during the bootstrap.
@@ -1140,6 +1243,13 @@ def _start_engine(self, tk, engine_name, entity, progress_callback=None):
else:
ctx = tk.context_from_entity_dictionary(entity)
+ # Inject flow fields to context if current project is related to a Flow project.
+ if ctx.project and self._flow_project_id is not None:
+ ctx.project[flow_auth.AM_READY_PROJECT_FIELD] = self._flow_project_id
+ ctx.project[flow_const.FLOW_SCHEMA_VERSION_FIELD] = (
+ self._flow_schema_version
+ )
+
self._report_progress(
progress_callback, self._LAUNCHING_ENGINE_RATE, "Launching Engine..."
)
diff --git a/python/tk-core/python/tank/bootstrap/resolver.py b/python/tk-core/python/tank/bootstrap/resolver.py
index d7f19204..e4990373 100644
--- a/python/tk-core/python/tank/bootstrap/resolver.py
+++ b/python/tk-core/python/tank/bootstrap/resolver.py
@@ -13,27 +13,25 @@
on disk.
"""
-import sys
-import os
import fnmatch
+import os
import pprint
+import sys
+from .. import LogManager
from ..descriptor import (
Descriptor,
create_descriptor,
descriptor_uri_to_dict,
is_descriptor_version_missing,
)
-from .errors import TankBootstrapError, TankBootstrapInvalidPipelineConfigurationError
+from ..descriptor.descriptor_installed_config import InstalledConfigDescriptor
+from ..util import LocalFileStorageManager, ShotgunPath, filesystem
+from . import constants
from .baked_configuration import BakedConfiguration
from .cached_configuration import CachedConfiguration
+from .errors import TankBootstrapError, TankBootstrapInvalidPipelineConfigurationError
from .installed_configuration import InstalledConfiguration
-from ..descriptor.descriptor_installed_config import InstalledConfigDescriptor
-from ..util import filesystem
-from ..util import ShotgunPath
-from ..util import LocalFileStorageManager
-from .. import LogManager
-from . import constants
log = LogManager.get_logger(__name__)
@@ -376,9 +374,9 @@ def _get_pipeline_configurations_for_project(
# field. Note that this may be None if for example the pipeline configuration
# is defined for another operating system.
try:
- pipeline_config[
- "config_descriptor"
- ] = self._create_config_descriptor(sg_connection, pipeline_config)
+ pipeline_config["config_descriptor"] = (
+ self._create_config_descriptor(sg_connection, pipeline_config)
+ )
yield pipeline_config
except TankBootstrapInvalidPipelineConfigurationError as e:
@@ -841,7 +839,9 @@ def resolve_shotgun_configuration(
"Will use pipeline configuration id '%s'" % pipeline_config_identifier
)
- log.debug("Requesting pipeline configuration data from Flow Production Tracking...")
+ log.debug(
+ "Requesting pipeline configuration data from Flow Production Tracking..."
+ )
# Fetch the one and only config that matches this id.
pipeline_config = sg_connection.find_one(
@@ -869,7 +869,9 @@ def resolve_shotgun_configuration(
# We couldn't resolve anything from Shotgun, so we'll resolve the configuration using
# an offline resolve.
- return self.resolve_not_found_sg_configuration(fallback_config_descriptor, sg_connection)
+ return self.resolve_not_found_sg_configuration(
+ fallback_config_descriptor, sg_connection
+ )
else:
# Something was found in Shotgun, which means we've also potentially resolved its
diff --git a/python/tk-core/python/tank/commands/__init__.py b/python/tk-core/python/tank/commands/__init__.py
index 3df351e8..e63da80e 100644
--- a/python/tk-core/python/tank/commands/__init__.py
+++ b/python/tk-core/python/tank/commands/__init__.py
@@ -8,5 +8,5 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from .tank_command import list_commands, get_command, SgtkSystemCommand
from .interaction import CommandInteraction
+from .tank_command import SgtkSystemCommand, get_command, list_commands
diff --git a/python/tk-core/python/tank/commands/app_info.py b/python/tk-core/python/tank/commands/app_info.py
index 03de1802..2f1c214d 100644
--- a/python/tk-core/python/tank/commands/app_info.py
+++ b/python/tk-core/python/tank/commands/app_info.py
@@ -8,8 +8,8 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from .action_base import Action
from ..errors import TankError
+from .action_base import Action
class AppInfoAction(Action):
@@ -108,7 +108,7 @@ def _env_breakdown(self, log, env_name):
descriptor = env.get_app_descriptor(eng, app)
log.info("App %s" % app)
log.info("-" * (4 + len(app)))
- for (k, v) in descriptor.get_dict().items():
+ for k, v in descriptor.get_dict().items():
log.info(" %s: %s" % (k.capitalize(), v))
log.info(" Docs: %s" % descriptor.documentation_url)
log.info("")
diff --git a/python/tk-core/python/tank/commands/cache_apps.py b/python/tk-core/python/tank/commands/cache_apps.py
index d6dc0d04..30ecaf35 100644
--- a/python/tk-core/python/tank/commands/cache_apps.py
+++ b/python/tk-core/python/tank/commands/cache_apps.py
@@ -8,8 +8,8 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from .action_base import Action
from ..errors import TankError
+from .action_base import Action
class CacheAppsAction(Action):
diff --git a/python/tk-core/python/tank/commands/cache_yaml.py b/python/tk-core/python/tank/commands/cache_yaml.py
index b299f4c0..3628aab4 100644
--- a/python/tk-core/python/tank/commands/cache_yaml.py
+++ b/python/tk-core/python/tank/commands/cache_yaml.py
@@ -8,13 +8,12 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import os
import fnmatch
+import os
-
-from .action_base import Action
from ..errors import TankError
-from ..util import yaml_cache, pickle
+from ..util import pickle, yaml_cache
+from .action_base import Action
class CacheYamlAction(Action):
diff --git a/python/tk-core/python/tank/commands/clone_configuration.py b/python/tk-core/python/tank/commands/clone_configuration.py
index bed03b0d..f1115dc1 100644
--- a/python/tk-core/python/tank/commands/clone_configuration.py
+++ b/python/tk-core/python/tank/commands/clone_configuration.py
@@ -8,19 +8,16 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from ..util import ShotgunPath
-from ..errors import TankError
-from . import constants
-from ..util import filesystem
-from ..util import is_linux, is_macos, is_windows
+import os
+import shutil
from tank_vendor import yaml
+from ..errors import TankError
+from ..util import ShotgunPath, filesystem, is_linux, is_macos, is_windows
+from . import constants
from .action_base import Action
-import os
-import shutil
-
class CloneConfigAction(Action):
"""
diff --git a/python/tk-core/python/tank/commands/console_utils.py b/python/tk-core/python/tank/commands/console_utils.py
index b8838566..5d4f21ea 100644
--- a/python/tk-core/python/tank/commands/console_utils.py
+++ b/python/tk-core/python/tank/commands/console_utils.py
@@ -15,12 +15,11 @@
import textwrap
from .. import pipelineconfig_utils
-from ..platform import validation
-from ..errors import TankError, TankNoDefaultValueError
from ..descriptor import CheckVersionConstraintsError
+from ..errors import TankError, TankNoDefaultValueError
+from ..platform import validation
from ..platform.bundle import resolve_default_value
-
##########################################################################################
# displaying of info in the terminal, ascii-graphcics style
@@ -34,7 +33,7 @@ def format_bundle_info(log, descriptor, required_updates=None):
:param required_updates: A list of bundle names that require updating.
"""
# yay we can install! - get release notes
- (summary, url) = descriptor.changelog
+ summary, url = descriptor.changelog
if required_updates:
add_padding = " "
diff --git a/python/tk-core/python/tank/commands/core_localize.py b/python/tk-core/python/tank/commands/core_localize.py
index f29f1385..0eb6b1bd 100644
--- a/python/tk-core/python/tank/commands/core_localize.py
+++ b/python/tk-core/python/tank/commands/core_localize.py
@@ -8,17 +8,16 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
+import datetime
import os
-import sys
import shutil
-import datetime
+import sys
+from .. import pipelineconfig_factory, pipelineconfig_utils
from ..errors import TankError
from ..util import filesystem
from ..util.version import is_version_older
from .action_base import Action
-from .. import pipelineconfig_utils
-from .. import pipelineconfig_factory
# these are the items that need to be copied across
# when a configuration is upgraded to contain a core API
diff --git a/python/tk-core/python/tank/commands/core_upgrade.py b/python/tk-core/python/tank/commands/core_upgrade.py
index 183044c9..0516f2b7 100644
--- a/python/tk-core/python/tank/commands/core_upgrade.py
+++ b/python/tk-core/python/tank/commands/core_upgrade.py
@@ -11,22 +11,21 @@
"""
Tank command allowing to do core updates.
"""
-from ..errors import TankError
-from .action_base import Action
+import copy
+import optparse
import os
import sys
import textwrap
-import optparse
-import copy
-
-from ..util import shotgun
-from .. import pipelineconfig_utils
-from . import console_utils
-from ..util.version import is_version_newer, is_version_head
from tank_vendor import yaml
+from .. import pipelineconfig_utils
+from ..errors import TankError
+from ..util import shotgun
+from ..util.version import is_version_head, is_version_newer
+from .action_base import Action
+
# FIXME: This should be refactored into something that can be used by other commands.
class TkOptParse(optparse.OptionParser):
@@ -197,7 +196,7 @@ def _run(self, log, core_version):
elif status == TankCoreUpdater.UPDATE_POSSIBLE:
- (summary, url) = installer.get_release_notes()
+ summary, url = installer.get_release_notes()
log.info("")
log.info("Newer version %s is available." % new_version)
diff --git a/python/tk-core/python/tank/commands/desktop_migration.py b/python/tk-core/python/tank/commands/desktop_migration.py
index 90f671ed..92ffc3fb 100644
--- a/python/tk-core/python/tank/commands/desktop_migration.py
+++ b/python/tk-core/python/tank/commands/desktop_migration.py
@@ -8,9 +8,8 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from .action_base import Action
-from . import console_utils
from . import constants
+from .action_base import Action
_MESSAGE = (
"This command will migrate the PTR site configuration used by the Desktop app so "
diff --git a/python/tk-core/python/tank/commands/dump_config.py b/python/tk-core/python/tank/commands/dump_config.py
index d0f68b21..12c7f2ab 100644
--- a/python/tk-core/python/tank/commands/dump_config.py
+++ b/python/tk-core/python/tank/commands/dump_config.py
@@ -12,8 +12,8 @@
from io import StringIO
from ..errors import TankError
-from .action_base import Action
from ..util import filesystem
+from .action_base import Action
class DumpConfigAction(Action):
diff --git a/python/tk-core/python/tank/commands/get_entity_commands.py b/python/tk-core/python/tank/commands/get_entity_commands.py
index f22424a4..a18d41dc 100644
--- a/python/tk-core/python/tank/commands/get_entity_commands.py
+++ b/python/tk-core/python/tank/commands/get_entity_commands.py
@@ -7,16 +7,15 @@
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
+import itertools
+import operator
+import os
import sys
-from .action_base import Action
from ..errors import TankError
-from ..util.process import SubprocessCalledProcessError, subprocess_check_output
from ..util import is_linux, is_macos, is_windows
-
-import itertools
-import operator
-import os
+from ..util.process import SubprocessCalledProcessError, subprocess_check_output
+from .action_base import Action
def execute_tank_command(pipeline_config_path, args):
@@ -150,7 +149,7 @@ def run_noninteractive(self, log, parameters):
per_entity_type = itertools.groupby(entities, operator.itemgetter(0))
commands_per_entity = {}
- for (entity_type, entities_of_type) in per_entity_type:
+ for entity_type, entities_of_type in per_entity_type:
# make a list out of the grouped entity tuples
entities_of_type = list(entities_of_type)
try:
@@ -311,7 +310,7 @@ def _parse_cached_commands(self, commands_data):
tokens += [""] * (NUM_EXPECTED_TOKENS - len(tokens))
# extract the information from the tokens
- (name, title, _, _, icon, description) = tuple(tokens)
+ name, title, _, _, icon, description = tuple(tokens)
commands.append(
{"name": name, "title": title, "icon": icon, "description": description}
diff --git a/python/tk-core/python/tank/commands/install.py b/python/tk-core/python/tank/commands/install.py
index 0d1fd957..baf7dfda 100644
--- a/python/tk-core/python/tank/commands/install.py
+++ b/python/tk-core/python/tank/commands/install.py
@@ -9,10 +9,8 @@
# not expressly granted therein are reserved by Shotgun Software Inc.
from ..errors import TankError
-from . import console_utils
+from . import console_utils, constants, util
from .action_base import Action
-from . import util
-from . import constants
class InstallAppAction(Action):
@@ -94,7 +92,7 @@ def run_interactive(self, log, args):
:param log: std python logger
:param args: command line args
"""
- (use_legacy_parser, args) = util.should_use_legacy_yaml_parser(args)
+ use_legacy_parser, args = util.should_use_legacy_yaml_parser(args)
preserve_yaml = not use_legacy_parser
if len(args) != 3:
@@ -411,7 +409,7 @@ def run_interactive(self, log, args):
:param log: std python logger
:param args: command line args
"""
- (use_legacy_parser, args) = util.should_use_legacy_yaml_parser(args)
+ use_legacy_parser, args = util.should_use_legacy_yaml_parser(args)
preserve_yaml = not use_legacy_parser
if len(args) != 2:
@@ -539,8 +537,8 @@ def _run(self, log, env_name, engine_name, preserve_yaml):
# run descriptor factory method
log.info("Connecting to git...")
location = {"type": "git", "path": engine_name}
- engine_descriptor = self.tk.pipeline_configuration.get_latest_engine_descriptor(
- location
+ engine_descriptor = (
+ self.tk.pipeline_configuration.get_latest_engine_descriptor(location)
)
log.info(
"Latest tag in repository '%s' is %s."
@@ -559,8 +557,8 @@ def _run(self, log, env_name, engine_name, preserve_yaml):
# this is an app store app!
log.info("Connecting to the Toolkit App Store...")
location = {"type": "app_store", "name": engine_name}
- engine_descriptor = self.tk.pipeline_configuration.get_latest_engine_descriptor(
- location
+ engine_descriptor = (
+ self.tk.pipeline_configuration.get_latest_engine_descriptor(location)
)
log.info(
"Latest approved App Store Version is %s." % engine_descriptor.version
diff --git a/python/tk-core/python/tank/commands/interaction.py b/python/tk-core/python/tank/commands/interaction.py
index 0fed12b3..5229db4d 100644
--- a/python/tk-core/python/tank/commands/interaction.py
+++ b/python/tk-core/python/tank/commands/interaction.py
@@ -14,7 +14,6 @@
from .. import LogManager
-
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/commands/misc.py b/python/tk-core/python/tank/commands/misc.py
index f4d1b911..5694b686 100644
--- a/python/tk-core/python/tank/commands/misc.py
+++ b/python/tk-core/python/tank/commands/misc.py
@@ -8,12 +8,12 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from ..errors import TankError
-from .action_base import Action
-
import code
-import sys
import os
+import sys
+
+from ..errors import TankError
+from .action_base import Action
class ClearCacheAction(Action):
@@ -70,7 +70,7 @@ def _run(self, log):
log.debug("Deleting cache file %s..." % full_path)
try:
os.remove(full_path)
- except:
+ except Exception:
log.warning("Could not delete cache file '%s'!" % full_path)
log.info("The PTR menu cache has been cleared.")
@@ -128,8 +128,8 @@ def run_interactive(self, log, args):
# attempt install tab command completion
try:
- import rlcompleter
import readline
+ import rlcompleter # noqa: F401
if "libedit" in readline.__doc__:
# macosx, some versions - see
@@ -137,7 +137,7 @@ def run_interactive(self, log, args):
readline.parse_and_bind("bind ^I rl_complete")
else:
readline.parse_and_bind("tab: complete")
- except:
+ except Exception:
pass
code.interact(banner="\n".join(msg), local=tk_locals)
diff --git a/python/tk-core/python/tank/commands/move_pc.py b/python/tk-core/python/tank/commands/move_pc.py
index 24017746..5aac6c9a 100644
--- a/python/tk-core/python/tank/commands/move_pc.py
+++ b/python/tk-core/python/tank/commands/move_pc.py
@@ -8,15 +8,14 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from . import constants
-from ..errors import TankError
-
-from .action_base import Action
-
import os
import shutil
import sys
+from ..errors import TankError
+from . import constants
+from .action_base import Action
+
class MovePCAction(Action):
"""
diff --git a/python/tk-core/python/tank/commands/path_cache.py b/python/tk-core/python/tank/commands/path_cache.py
index bd7554a7..11218f7a 100644
--- a/python/tk-core/python/tank/commands/path_cache.py
+++ b/python/tk-core/python/tank/commands/path_cache.py
@@ -12,10 +12,8 @@
Methods relating to the path cache
"""
+from .. import folder, path_cache
from ..errors import TankError
-from .. import path_cache
-from .. import folder
-
from .action_base import Action
@@ -176,7 +174,9 @@ def run_interactive(self, log, args):
# first load up the current path cache file and make sure
# shotgun has got all those entries present as FilesystemLocations.
log.info("")
- log.info("Phase 1/3: Pushing data from the current path cache to Flow Production Tracking...")
+ log.info(
+ "Phase 1/3: Pushing data from the current path cache to Flow Production Tracking..."
+ )
curr_pc = path_cache.PathCache(self.tk)
try:
curr_pc.ensure_all_entries_are_in_shotgun()
@@ -191,7 +191,9 @@ def run_interactive(self, log, args):
# and synchronize path cache
log.info("")
- log.info("Phase 3/3: Synchronizing your local machine with Flow Production Tracking...")
+ log.info(
+ "Phase 3/3: Synchronizing your local machine with Flow Production Tracking..."
+ )
pc = path_cache.PathCache(self.tk)
try:
pc.synchronize(full_sync=True)
diff --git a/python/tk-core/python/tank/commands/pc_overview.py b/python/tk-core/python/tank/commands/pc_overview.py
index 76b1b48e..d3568441 100644
--- a/python/tk-core/python/tank/commands/pc_overview.py
+++ b/python/tk-core/python/tank/commands/pc_overview.py
@@ -9,14 +9,14 @@
# not expressly granted therein are reserved by Shotgun Software Inc.
+import os
+
from .. import pipelineconfig_utils
+from ..errors import TankError
from ..util import ShotgunPath
from . import constants
-from ..errors import TankError
from .action_base import Action
-import os
-
class PCBreakdownAction(Action):
"""
diff --git a/python/tk-core/python/tank/commands/push_pc.py b/python/tk-core/python/tank/commands/push_pc.py
index aa5f6e47..faa27e7d 100644
--- a/python/tk-core/python/tank/commands/push_pc.py
+++ b/python/tk-core/python/tank/commands/push_pc.py
@@ -8,21 +8,16 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from ..util import filesystem
-from . import constants
+import datetime
+import os
+import shutil
+
from ..errors import TankError
from ..pipelineconfig import PipelineConfiguration
-
-from . import console_utils
-
+from ..util import ShotgunPath, filesystem
+from . import constants
from .action_base import Action
-from ..util import ShotgunPath
-
-import os
-import datetime
-import shutil
-
# Core configuration files which are associated with the core API installation and not
# the pipeline configuration.
CORE_API_FILES = [
@@ -136,7 +131,7 @@ def run_interactive(self, log, args):
raise TankError("Aborted by user.")
try:
target_pc_id = int(answer)
- except:
+ except Exception:
raise TankError("Please enter a number!")
self._run(
diff --git a/python/tk-core/python/tank/commands/setup_project.py b/python/tk-core/python/tank/commands/setup_project.py
index 4ed10d33..44b9184f 100644
--- a/python/tk-core/python/tank/commands/setup_project.py
+++ b/python/tk-core/python/tank/commands/setup_project.py
@@ -13,19 +13,15 @@
import textwrap
import traceback
-from .action_base import Action
-from . import core_localize
-from ..errors import TankError
-from ..util import shotgun
-from ..util import ShotgunPath
-from ..util import is_linux, is_macos, is_windows
-from . import constants
from .. import pipelineconfig_utils
+from ..errors import TankError
+from ..util import ShotgunPath, is_linux, is_macos, is_windows, shotgun
from ..util.filesystem import ensure_folder_exists
-
+from . import constants, core_localize
+from .action_base import Action
+from .interaction import YesToEverythingInteraction
from .setup_project_core import run_project_setup
from .setup_project_params import ProjectSetupParameters
-from .interaction import YesToEverythingInteraction
class SetupProjectAction(Action):
@@ -483,7 +479,7 @@ def _select_project(self, log, sg, show_initialized_projects):
["archived", "is_not", True],
]
- if show_initialized_projects == False:
+ if show_initialized_projects is False:
# not force mode. Only show non-set up projects
filters.append(["tank_name", "is", None])
@@ -545,7 +541,7 @@ def _select_project(self, log, sg, show_initialized_projects):
raise TankError("Aborted by user.")
try:
project_id = int(answer)
- except:
+ except Exception:
raise TankError("Please enter a number!")
if project_id not in [x["id"] for x in projs]:
@@ -687,9 +683,7 @@ def _get_disk_location(self, log, params):
default_config_locations = self._get_default_configuration_location(log, params)
- linux_path = self._ask_location(
- log, default_config_locations["linux"], "Linux"
- )
+ linux_path = self._ask_location(log, default_config_locations["linux"], "Linux")
windows_path = self._ask_location(
log, default_config_locations["win32"], "Windows"
)
@@ -726,7 +720,6 @@ def _get_default_configuration_location(self, log, params):
# location for the installed config.
# Multi-root configurations require a storage named "primary" so we base
# our default on that. If only a single storage is available, we just use it.
- storage_names = params.get_required_storages()
default_storage_name = params.default_storage_name
# There is no default storage name for a config that doesn't use roots, like
@@ -950,7 +943,7 @@ def _map_storages(self, params, config_uri, log, sg):
mapped_roots = []
# loop over required storage roots
- for (root_name, root_info) in required_roots.items():
+ for root_name, root_info in required_roots.items():
log.info("%s" % (root_name,))
log.info("-" * len(root_name))
@@ -1057,7 +1050,7 @@ def _map_storages(self, params, config_uri, log, sg):
# ---- now we've mapped the roots, and they're all valid, we need to
# update the root information on the core wizard
- for (root_name, storage_name) in mapped_roots:
+ for root_name, storage_name in mapped_roots:
root_info = required_roots[root_name]
storage_data = storage_by_name[storage_name.lower()]
diff --git a/python/tk-core/python/tank/commands/setup_project_core.py b/python/tk-core/python/tank/commands/setup_project_core.py
index 38b54395..6f315c19 100644
--- a/python/tk-core/python/tank/commands/setup_project_core.py
+++ b/python/tk-core/python/tank/commands/setup_project_core.py
@@ -8,18 +8,17 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import sys
import os
import shutil
-
-from . import constants
-from ..errors import TankError
-from ..util import StorageRoots
-from ..util import filesystem
-from ..api import sgtk_from_path
+import sys
from tank_vendor import yaml
+from ..api import sgtk_from_path
+from ..errors import TankError
+from ..util import StorageRoots, filesystem
+from . import constants
+
@filesystem.with_cleared_umask
def run_project_setup(log, sg, setup_params):
diff --git a/python/tk-core/python/tank/commands/setup_project_params.py b/python/tk-core/python/tank/commands/setup_project_params.py
index bd9bed67..3f7cc2aa 100644
--- a/python/tk-core/python/tank/commands/setup_project_params.py
+++ b/python/tk-core/python/tank/commands/setup_project_params.py
@@ -9,29 +9,21 @@
# not expressly granted therein are reserved by Shotgun Software Inc.
import os
+import sys
import tempfile
import uuid
-import sys
-from . import constants
-from . import util
+from tank_vendor import yaml
-from ..util import StorageRoots
+from .. import hook, pipelineconfig_utils
+from ..descriptor import Descriptor, create_descriptor
+from ..errors import TankError, TankErrorProjectIsSetup
+from ..util import ShotgunPath, StorageRoots, filesystem
from ..util import sgre as re
from ..util import shotgun
-from ..util import filesystem
-from ..util import is_windows
from ..util.version import is_version_newer
from ..util.zip import unzip_file, zip_file
-
-from .. import hook
-from ..errors import TankError, TankErrorProjectIsSetup
-from .. import pipelineconfig_utils
-from ..descriptor import create_descriptor, Descriptor
-
-from tank_vendor import yaml
-
-from ..util import ShotgunPath
+from . import constants, util
class ProjectSetupParameters(object):
@@ -57,7 +49,7 @@ class ProjectSetupParameters(object):
- run project setup!
"""
- (CENTRALIZED_CONFIG, DISTRIBUTED_CONFIG) = range(2)
+ CENTRALIZED_CONFIG, DISTRIBUTED_CONFIG = range(2)
def __init__(self, log, sg):
"""
@@ -514,8 +506,8 @@ def set_project_id(self, project_id, force=False):
# if force is false then tank_name must be empty
if (
- self.get_auto_path_mode() == False
- and force == False
+ self.get_auto_path_mode() is False
+ and force is False
and proj["tank_name"] is not None
):
raise TankErrorProjectIsSetup()
@@ -1019,7 +1011,7 @@ def __init__(self, config_uri, sg, log):
# now extract the cfg and validate
old_umask = os.umask(0)
try:
- (self._cfg_folder, self._version, self._config_mode) = self._process_config(
+ self._cfg_folder, self._version, self._config_mode = self._process_config(
config_uri
)
finally:
@@ -1272,7 +1264,7 @@ def resolve_storages(self):
storage_info = {}
# do the storage lookup and mapping in PTR
- (local_storage_lookup, unmapped_roots) = self._storage_roots.get_local_storages(
+ local_storage_lookup, unmapped_roots = self._storage_roots.get_local_storages(
self._sg
)
diff --git a/python/tk-core/python/tank/commands/setup_project_wizard.py b/python/tk-core/python/tank/commands/setup_project_wizard.py
index 45f11947..465275d9 100644
--- a/python/tk-core/python/tank/commands/setup_project_wizard.py
+++ b/python/tk-core/python/tank/commands/setup_project_wizard.py
@@ -651,9 +651,9 @@ def get_core_settings(self):
)
return_data["using_runtime"] = False
return_data["pipeline_config"] = data
- return_data[
- "core_path"
- ] = pipelineconfig_utils.resolve_all_os_paths_to_core(core_api_root)
+ return_data["core_path"] = (
+ pipelineconfig_utils.resolve_all_os_paths_to_core(core_api_root)
+ )
# finally, check the logic for localization:
# if this core that we have found and resolved is localized,
diff --git a/python/tk-core/python/tank/commands/switch.py b/python/tk-core/python/tank/commands/switch.py
index b1cbfd3f..d2027995 100644
--- a/python/tk-core/python/tank/commands/switch.py
+++ b/python/tk-core/python/tank/commands/switch.py
@@ -8,14 +8,12 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
+import os
+
from ..errors import TankError
-from . import constants
-from . import util
-from . import console_utils
+from . import console_utils, constants, util
from .action_base import Action
-import os
-
class SwitchAppAction(Action):
"""
@@ -116,7 +114,7 @@ def run_interactive(self, log, args):
log.info("")
return
- (use_legacy_parser, args) = util.should_use_legacy_yaml_parser(args)
+ use_legacy_parser, args = util.should_use_legacy_yaml_parser(args)
preserve_yaml = not use_legacy_parser
# get parameters
@@ -203,13 +201,13 @@ def run_interactive(self, log, args):
log.info("")
log.info("Current version")
log.info("------------------------------------")
- for (k, v) in descriptor.get_dict().items():
+ for k, v in descriptor.get_dict().items():
log.info(" - %s: %s" % (k.capitalize(), v))
log.info("")
log.info("New version")
log.info("------------------------------------")
- for (k, v) in new_descriptor.get_dict().items():
+ for k, v in new_descriptor.get_dict().items():
log.info(" - %s: %s" % (k.capitalize(), v))
log.info("")
@@ -229,9 +227,7 @@ def run_interactive(self, log, args):
# ensure that all required frameworks have been installed
# find the file where our item is being installed
- (_, yml_file) = env.find_location_for_app(
- engine_instance_name, app_instance_name
- )
+ _, yml_file = env.find_location_for_app(engine_instance_name, app_instance_name)
console_utils.ensure_frameworks_installed(
log, self.tk, yml_file, new_descriptor, env, self._interaction_interface
diff --git a/python/tk-core/python/tank/commands/tank_command.py b/python/tk-core/python/tank/commands/tank_command.py
index 8c5171e1..d803b20c 100644
--- a/python/tk-core/python/tank/commands/tank_command.py
+++ b/python/tk-core/python/tank/commands/tank_command.py
@@ -15,38 +15,38 @@
import logging
-from .action_base import Action
-from .interaction import RawInputCommandInteraction, YesToEverythingInteraction
-from . import folders
-from . import misc
-from . import move_pc
-from . import pc_overview
-from . import path_cache
-from . import update
-from . import push_pc
-from . import setup_project
-from . import setup_project_wizard
-from . import dump_config
-from . import validate_config
-from . import cache_apps
-from . import switch
-from . import app_info
-from . import core_upgrade
-from . import core_localize
-from . import install
-from . import clone_configuration
-from . import copy_apps
-from . import unregister_folders
-from . import desktop_migration
-from . import cache_yaml
-from . import get_entity_commands
-from . import constants
-
-
-from .. import constants as constants_global
from .. import LogManager
-from ..platform.engine import start_engine, get_environment_from_context
+from .. import constants as constants_global
from ..errors import TankError
+from ..platform.engine import get_environment_from_context, start_engine
+from . import (
+ app_info,
+ cache_apps,
+ cache_yaml,
+ clone_configuration,
+ constants,
+ copy_apps,
+ core_localize,
+ core_upgrade,
+ desktop_migration,
+ dump_config,
+ folders,
+ get_entity_commands,
+ install,
+ misc,
+ move_pc,
+ path_cache,
+ pc_overview,
+ push_pc,
+ setup_project,
+ setup_project_wizard,
+ switch,
+ unregister_folders,
+ update,
+ validate_config,
+)
+from .action_base import Action
+from .interaction import RawInputCommandInteraction, YesToEverythingInteraction
log = LogManager.get_logger(__name__)
@@ -473,7 +473,7 @@ def run_action(log, tk, ctx, command, args):
found_action = x
break
- if found_action and found_action.wants_running_shell_engine == False:
+ if found_action and found_action.wants_running_shell_engine is False:
log.debug("No need to load up the engine for this command.")
else:
# try to load the engine.
diff --git a/python/tk-core/python/tank/commands/unregister_folders.py b/python/tk-core/python/tank/commands/unregister_folders.py
index 7aeb58e8..8b61967f 100644
--- a/python/tk-core/python/tank/commands/unregister_folders.py
+++ b/python/tk-core/python/tank/commands/unregister_folders.py
@@ -12,10 +12,9 @@
Method to unregister folders from the path cache
"""
-from ..errors import TankError
from .. import path_cache
+from ..errors import TankError
from .action_base import Action
-from ..util.login import get_current_user
class UnregisterFoldersAction(Action):
diff --git a/python/tk-core/python/tank/commands/update.py b/python/tk-core/python/tank/commands/update.py
index 2b8129e5..2f3b1e64 100644
--- a/python/tk-core/python/tank/commands/update.py
+++ b/python/tk-core/python/tank/commands/update.py
@@ -10,14 +10,12 @@
import os
-from .action_base import Action
-from . import console_utils
-from . import util
-from ..platform.environment import WritableEnvironment
-from ..descriptor import CheckVersionConstraintsError
-from . import constants
-from ..util.version import is_version_number, is_version_newer
from .. import pipelineconfig_utils
+from ..descriptor import CheckVersionConstraintsError
+from ..platform.environment import WritableEnvironment
+from ..util.version import is_version_newer, is_version_number
+from . import console_utils, constants, util
+from .action_base import Action
class AppUpdatesAction(Action):
@@ -110,7 +108,7 @@ def run_interactive(self, log, args):
:param log: std python logger
:param args: command line args
"""
- (use_legacy_parser, args) = util.should_use_legacy_yaml_parser(args)
+ use_legacy_parser, args = util.should_use_legacy_yaml_parser(args)
preserve_yaml = not use_legacy_parser
if len(args) == 0:
@@ -368,7 +366,7 @@ def _check_for_updates(
x["new_descriptor"].version,
)
)
- (_, url) = x["new_descriptor"].changelog
+ _, url = x["new_descriptor"].changelog
if url:
summary.append("Change Log: %s" % url)
summary.append("")
@@ -512,11 +510,11 @@ def _update_item(
# ensure that all required frameworks have been installed
# find the file where our item is being installed
if framework_name:
- (_, yml_file) = env.find_location_for_framework(framework_name)
+ _, yml_file = env.find_location_for_framework(framework_name)
elif app_name:
- (_, yml_file) = env.find_location_for_app(engine_name, app_name)
+ _, yml_file = env.find_location_for_app(engine_name, app_name)
else:
- (_, yml_file) = env.find_location_for_engine(engine_name)
+ _, yml_file = env.find_location_for_engine(engine_name)
console_utils.ensure_frameworks_installed(
log, tk, yml_file, new_descriptor, env, self._interaction_interface
@@ -791,7 +789,7 @@ def _check_item_update_status(
out_of_date = latest_desc.version != curr_desc.version
# check deprecation
- (is_dep, dep_msg) = latest_desc.deprecation_status
+ is_dep, dep_msg = latest_desc.deprecation_status
if is_dep:
# we treat deprecation as an out of date that cannot be upgraded!
diff --git a/python/tk-core/python/tank/commands/validate_config.py b/python/tk-core/python/tank/commands/validate_config.py
index ce2d6b80..539ca38c 100644
--- a/python/tk-core/python/tank/commands/validate_config.py
+++ b/python/tk-core/python/tank/commands/validate_config.py
@@ -9,9 +9,10 @@
# not expressly granted therein are reserved by Shotgun Software Inc.
import os
-from .action_base import Action
+
from ..errors import TankError
-from ..platform import validation, bundle
+from ..platform import bundle, validation
+from .action_base import Action
class ValidateConfigAction(Action):
@@ -83,9 +84,7 @@ def _run(self, log, parameters):
log.info("")
log.info("")
- log.info(
- "Welcome to the Flow Production Tracking Configuration validator!"
- )
+ log.info("Welcome to the Flow Production Tracking Configuration validator!")
log.info("")
log.info("Found the following environments:")
diff --git a/python/tk-core/python/tank/context.py b/python/tk-core/python/tank/context.py
index 4fe363ed..8c3757c8 100644
--- a/python/tk-core/python/tank/context.py
+++ b/python/tk-core/python/tank/context.py
@@ -13,22 +13,22 @@
"""
-import os
+from __future__ import annotations # needed for python 3.9 support
+
import copy
import json
+import os
from tank_vendor import yaml
-from . import authentication
-
-from .util import login
-from .util import shotgun_entity
-from .util import shotgun
-from .util import get_sg_entity_name_field
-from .util import pickle
-from . import constants
-from .errors import TankError, TankContextDeserializationError
+from tank_vendor.flow_integration_sdk import sandbox
+
+from . import authentication, constants
+from .authentication import flow_auth
+from .errors import TankContextDeserializationError, TankError
+from .flowam import constants as flow_const
from .path_cache import PathCache
from .template import TemplatePath
+from .util import login, pickle, shotgun, shotgun_entity
class Context(object):
@@ -72,6 +72,7 @@ def __init__(
user=None,
additional_entities=None,
source_entity=None,
+ flow_draft_id=None,
):
"""
Context objects are not constructed by hand but are fabricated by the
@@ -86,6 +87,7 @@ def __init__(
self.__user = user
self.__additional_entities = additional_entities or []
self.__source_entity = source_entity
+ self.__flow_draft_id = flow_draft_id
self._entity_fields_cache = {}
def __repr__(self):
@@ -176,9 +178,9 @@ def _entity_dicts_eq(d1, d2):
:param d2: Second entity dictionary
:returns: True if d1 and d2 are considered equal, otherwise False.
"""
- if d1 == d2 == None:
+ if d1 is None and d2 is None:
return True
- if d1 == None or d2 == None:
+ if d1 is None or d2 is None:
return False
return d1["type"] == d2["type"] and d1["id"] == d2["id"]
@@ -259,6 +261,7 @@ def __deepcopy__(self, memo):
ctx_copy.__user = copy.deepcopy(self.__user, memo)
ctx_copy.__additional_entities = copy.deepcopy(self.__additional_entities, memo)
ctx_copy.__source_entity = copy.deepcopy(self.__source_entity, memo)
+ ctx_copy.__flow_draft_id = copy.deepcopy(self.__flow_draft_id, memo)
# except:
# ctx_copy._entity_fields_cache
@@ -403,6 +406,45 @@ def additional_entities(self):
"""
return self.__additional_entities
+ @property
+ def flow_project_id(self) -> str | None:
+ """
+ The FlowAM project ID for this context, or ``None`` if the project is
+ not FlowAM-enabled or there is no project in this context.
+
+ The value is read from the ``sg_flow_am_id`` field on the project dict.
+ It is populated by the bootstrap manager after it queries ShotGrid.
+
+ :returns: A string containing the FlowAM project ID, or ``None``.
+ :rtype: str or None
+ """
+ if self.project:
+ return self.project.get(flow_auth.AM_READY_PROJECT_FIELD)
+ return None
+
+ @property
+ def flow_draft_id(self) -> str | None:
+ """Current Flow draft context.
+ If not None, this designates the current draft being worked on within the DCC session.
+ """
+ return self.__flow_draft_id
+
+ @property
+ def flow_schema_version(self) -> str | None:
+ """
+ The FlowAM schema version of the project in context, or ``None`` if the project is
+ not FlowAM-enabled or there is no schema created.
+
+ The value is read from the ``sg_flow_schema_config_version`` field on the project dict.
+ It is populated by the bootstrap manager after it queries ShotGrid.
+
+ :returns: A string containing the FlowAM schema version, or ``None``.
+ :rtype: str or None
+ """
+ if self.project:
+ return self.project.get(flow_const.FLOW_SCHEMA_VERSION_FIELD)
+ return None
+
@property
def entity_locations(self):
"""
@@ -708,6 +750,19 @@ def create_copy_for_user(self, user):
ctx_copy.__user = user
return ctx_copy
+ def set_flow_context(self, file_path: str):
+ """Set the current flow asset context based on opened file path.
+
+ Args:
+ file_path: File currently opened in DCC.
+ """
+ self.__flow_draft_id = sandbox.get_draft_context(file_path)
+
+ def clear_flow_context(self):
+ """Clear the current flow asset context."""
+
+ self.__flow_draft_id = None
+
################################################################################################
# serialization
@@ -815,8 +870,8 @@ def deserialize(cls, context_str):
def to_dict(self):
"""
Converts the context into a dictionary with keys ``project``,
- ``entity``, ``user``, ``step``, ``task``, ``additional_entities`` and
- ``source_entity``.
+ ``entity``, ``user``, ``step``, ``task``, ``additional_entities``,
+ ``source_entity``, ``flow_project_id``, ``flow_draft_id`` and ``flow_schema_version``.
.. note ::
Contrary to :meth:`Context.serialize`, this method discards information
@@ -835,6 +890,9 @@ def to_dict(self):
self._cleanup_entity(entity) for entity in self.additional_entities
],
"source_entity": self._cleanup_entity(self.source_entity),
+ "flow_project_id": self.flow_project_id,
+ "flow_draft_id": self.flow_draft_id,
+ "flow_schema_version": self.flow_schema_version,
}
def _cleanup_entity(self, entity):
@@ -894,7 +952,7 @@ def _from_dict(cls, data):
:returns: :class:`Context`
"""
- return Context(
+ ctx = Context(
tk=data.get("tk"),
project=data.get("project"),
entity=data.get("entity"),
@@ -903,7 +961,23 @@ def _from_dict(cls, data):
user=data.get("user"),
additional_entities=data.get("additional_entities"),
source_entity=data.get("source_entity"),
+ flow_draft_id=data.get("flow_draft_id"),
)
+ if (
+ ctx.project is not None
+ and "flow_project_id" in data
+ and flow_auth.AM_READY_PROJECT_FIELD not in ctx.project
+ ):
+ ctx.project[flow_auth.AM_READY_PROJECT_FIELD] = data["flow_project_id"]
+ if (
+ ctx.project is not None
+ and "flow_schema_version" in data
+ and flow_const.FLOW_SCHEMA_VERSION_FIELD not in ctx.project
+ ):
+ ctx.project[flow_const.FLOW_SCHEMA_VERSION_FIELD] = data[
+ "flow_schema_version"
+ ]
+ return ctx
################################################################################################
# private methods
@@ -931,7 +1005,7 @@ def _fields_from_shotgun(self, template, entities, validate):
# this key is a shotgun value that needs fetching!
# ensure that the context actually provides the desired entities
- if not key.shotgun_entity_type in entities:
+ if key.shotgun_entity_type not in entities:
if validate:
raise TankError(
"Key '%s' in template '%s' could not be populated by "
@@ -1305,7 +1379,8 @@ def _from_entity_type_and_id(tk, entity, source_entity=None):
if sg_entity is None:
raise TankError(
- "Entity %s with id %s not found in Flow Production Tracking!" % (entity_type, entity_id)
+ "Entity %s with id %s not found in Flow Production Tracking!"
+ % (entity_type, entity_id)
)
if sg_entity.get("task"):
@@ -1412,7 +1487,6 @@ def _from_entity_dictionary(tk, entity_dictionary, source_entity=None):
}
entity_type = entity_dictionary["type"]
- entity_id = entity_dictionary["id"]
# try to determine the various entities from the entity dictionary:
project = None
@@ -1477,7 +1551,7 @@ def _build_clean_entity(ent):
if "id" not in ent or "type" not in ent:
return None
ent_name = _get_entity_name(ent)
- if ent_name == None:
+ if ent_name is None:
return None
# return a clean dictionary:
return {"type": ent["type"], "id": ent["id"], "name": ent_name}
@@ -1719,7 +1793,7 @@ def context_yaml_representer(dumper, context):
# pipeline config path as part of the dict
context_dict["_pc_path"] = context.tank.pipeline_configuration.get_path()
- return dumper.represent_mapping(u"!TankContext", context_dict)
+ return dumper.represent_mapping("!TankContext", context_dict)
def context_yaml_constructor(loader, node):
@@ -1750,7 +1824,7 @@ def context_yaml_constructor(loader, node):
yaml.add_representer(Context, context_yaml_representer)
-yaml.add_constructor(u"!TankContext", context_yaml_constructor)
+yaml.add_constructor("!TankContext", context_yaml_constructor)
################################################################################################
# utility methods
@@ -1767,7 +1841,7 @@ def _get_entity_name(entity_dictionary):
"""
name_field = shotgun_entity.get_sg_entity_name_field(entity_dictionary["type"])
entity_name = entity_dictionary.get(name_field)
- if entity_name == None:
+ if entity_name is None:
# Also check to see if entity contains 'name':
if name_field != "name":
entity_name = entity_dictionary.get("name")
@@ -1807,7 +1881,9 @@ def _task_from_sg(tk, task_id, additional_fields=None):
"Task", [["id", "is", task_id]], standard_fields + additional_fields
)
if not task:
- raise TankError("Unable to locate Task with id %s in Flow Production Tracking" % task_id)
+ raise TankError(
+ "Unable to locate Task with id %s in Flow Production Tracking" % task_id
+ )
# add task so it can be processed with other shotgun entities
task["task"] = {"type": "Task", "id": task_id, "name": task["content"]}
@@ -1867,7 +1943,8 @@ def _entity_from_sg(tk, entity_type, entity_id):
if not data:
raise TankError(
- "Unable to locate %s with id %s in Flow Production Tracking" % (entity_type, entity_id)
+ "Unable to locate %s with id %s in Flow Production Tracking"
+ % (entity_type, entity_id)
)
# create context
diff --git a/python/tk-core/python/tank/deploy/__init__.py b/python/tk-core/python/tank/deploy/__init__.py
index 7f72de65..b129e819 100644
--- a/python/tk-core/python/tank/deploy/__init__.py
+++ b/python/tk-core/python/tank/deploy/__init__.py
@@ -8,6 +8,4 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from . import descriptor
-from . import dev_descriptor
-from . import util
+from . import descriptor, dev_descriptor, util
diff --git a/python/tk-core/python/tank/deploy/descriptor.py b/python/tk-core/python/tank/deploy/descriptor.py
index b8482eba..c6fcd948 100644
--- a/python/tk-core/python/tank/deploy/descriptor.py
+++ b/python/tk-core/python/tank/deploy/descriptor.py
@@ -14,7 +14,7 @@
This code may be removed at some point in the future.
"""
-from ..descriptor import create_descriptor, Descriptor
+from ..descriptor import Descriptor, create_descriptor
from ..util import shotgun
diff --git a/python/tk-core/python/tank/deploy/util.py b/python/tk-core/python/tank/deploy/util.py
index be85eb0d..9b55d6ba 100644
--- a/python/tk-core/python/tank/deploy/util.py
+++ b/python/tk-core/python/tank/deploy/util.py
@@ -16,4 +16,4 @@
# import methods to ensure that older version of the desktop engine
# will function correctly - the code calls these internal methods
-from ..util.version import is_version_newer, is_version_older
+from ..util.version import is_version_newer, is_version_older # noqa: F401
diff --git a/python/tk-core/python/tank/descriptor/__init__.py b/python/tk-core/python/tank/descriptor/__init__.py
index 2031cc02..b8b82e15 100644
--- a/python/tk-core/python/tank/descriptor/__init__.py
+++ b/python/tk-core/python/tank/descriptor/__init__.py
@@ -10,22 +10,20 @@
from .descriptor import Descriptor, create_descriptor
-from .descriptor_core import CoreDescriptor
-from .descriptor_bundle import AppDescriptor, FrameworkDescriptor, EngineDescriptor
+from .descriptor_bundle import AppDescriptor, EngineDescriptor, FrameworkDescriptor
from .descriptor_config import ConfigDescriptor
-
+from .descriptor_core import CoreDescriptor
from .errors import (
+ CheckVersionConstraintsError,
+ InvalidAppStoreCredentialsError,
TankAppStoreConnectionError,
TankAppStoreError,
+ TankCheckVersionConstraintsError,
TankDescriptorError,
- InvalidAppStoreCredentialsError,
TankInvalidAppStoreCredentialsError,
- CheckVersionConstraintsError,
- TankCheckVersionConstraintsError,
TankInvalidInterpreterLocationError,
TankMissingManifestError,
)
-
from .io_descriptor import (
descriptor_dict_to_uri,
descriptor_uri_to_dict,
diff --git a/python/tk-core/python/tank/descriptor/descriptor.py b/python/tk-core/python/tank/descriptor/descriptor.py
index 0ef5f448..b767fa93 100644
--- a/python/tk-core/python/tank/descriptor/descriptor.py
+++ b/python/tk-core/python/tank/descriptor/descriptor.py
@@ -8,17 +8,15 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import os
import copy
+import os
+from .. import constants as constants2
from ..log import LogManager
-from ..util import filesystem
-from .io_descriptor import create_io_descriptor
-from .errors import TankDescriptorError
-from ..util import LocalFileStorageManager
+from ..util import LocalFileStorageManager, filesystem
from . import constants
-from .. import constants as constants2
-
+from .errors import TankDescriptorError
+from .io_descriptor import create_io_descriptor
logger = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/descriptor/descriptor_bundle.py b/python/tk-core/python/tank/descriptor/descriptor_bundle.py
index 92eb2a9c..0a591262 100644
--- a/python/tk-core/python/tank/descriptor/descriptor_bundle.py
+++ b/python/tk-core/python/tank/descriptor/descriptor_bundle.py
@@ -8,13 +8,11 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from .descriptor import Descriptor
-from .errors import TankDescriptorError, CheckVersionConstraintsError
-from . import constants
-from .. import LogManager
+from .. import LogManager, pipelineconfig_utils
from ..util.version import is_version_older
-from ..util import shotgun
-from .. import pipelineconfig_utils
+from . import constants
+from .descriptor import Descriptor
+from .errors import CheckVersionConstraintsError, TankDescriptorError
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/descriptor/descriptor_cached_config.py b/python/tk-core/python/tank/descriptor/descriptor_cached_config.py
index 15ddb494..5c826420 100644
--- a/python/tk-core/python/tank/descriptor/descriptor_cached_config.py
+++ b/python/tk-core/python/tank/descriptor/descriptor_cached_config.py
@@ -12,10 +12,10 @@
from tank_vendor import yaml
+from .. import LogManager
from . import constants
-from .errors import TankDescriptorError
from .descriptor_config import ConfigDescriptor
-from .. import LogManager
+from .errors import TankDescriptorError
log = LogManager.get_logger(__name__)
@@ -69,7 +69,7 @@ def associated_core_descriptor(self):
#
# location:
# name: tk-core
- # type: app_store
+ # type: app_store
# version: v0.16.34
log.debug("Detected core descriptor file '%s'" % core_descriptor_path)
diff --git a/python/tk-core/python/tank/descriptor/descriptor_config.py b/python/tk-core/python/tank/descriptor/descriptor_config.py
index ca8e00da..e86f12ba 100644
--- a/python/tk-core/python/tank/descriptor/descriptor_config.py
+++ b/python/tk-core/python/tank/descriptor/descriptor_config.py
@@ -10,16 +10,14 @@
import os
+from .. import LogManager
from ..errors import TankFileDoesNotExistError
+from ..util import ShotgunPath, StorageRoots
+from ..util.version import is_version_older
from . import constants
-from .errors import TankInvalidInterpreterLocationError
from .descriptor import Descriptor, create_descriptor
-from .io_descriptor import is_descriptor_version_missing
-from .. import LogManager
-from ..util import StorageRoots
-from ..util import ShotgunPath
-from ..util.version import is_version_older
-from .io_descriptor import descriptor_uri_to_dict
+from .errors import TankInvalidInterpreterLocationError
+from .io_descriptor import descriptor_uri_to_dict, is_descriptor_version_missing
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/descriptor/descriptor_core.py b/python/tk-core/python/tank/descriptor/descriptor_core.py
index 904899d9..3a639f6c 100644
--- a/python/tk-core/python/tank/descriptor/descriptor_core.py
+++ b/python/tk-core/python/tank/descriptor/descriptor_core.py
@@ -8,9 +8,9 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
+from . import constants
from .descriptor import Descriptor
from .errors import TankMissingManifestError
-from . import constants
class CoreDescriptor(Descriptor):
diff --git a/python/tk-core/python/tank/descriptor/descriptor_installed_config.py b/python/tk-core/python/tank/descriptor/descriptor_installed_config.py
index 1c2e3927..7c33a318 100644
--- a/python/tk-core/python/tank/descriptor/descriptor_installed_config.py
+++ b/python/tk-core/python/tank/descriptor/descriptor_installed_config.py
@@ -10,18 +10,16 @@
import os
-from .descriptor_config import ConfigDescriptor
-from .. import pipelineconfig_utils
-from .. import LogManager
-from ..util import ShotgunPath
-from . import constants
-from .errors import TankMissingManifestError
-
+from .. import LogManager, pipelineconfig_utils
from ..errors import (
- TankNotPipelineConfigurationError,
TankFileDoesNotExistError,
TankInvalidCoreLocationError,
+ TankNotPipelineConfigurationError,
)
+from ..util import ShotgunPath
+from . import constants
+from .descriptor_config import ConfigDescriptor
+from .errors import TankMissingManifestError
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/descriptor/errors.py b/python/tk-core/python/tank/descriptor/errors.py
index 3ea15ae0..5774355d 100644
--- a/python/tk-core/python/tank/descriptor/errors.py
+++ b/python/tk-core/python/tank/descriptor/errors.py
@@ -12,8 +12,8 @@
All custom exceptions that this module emits are defined here.
"""
-from ..errors import TankError
from .. import errors
+from ..errors import TankError
class TankDescriptorError(TankError):
diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/__init__.py b/python/tk-core/python/tank/descriptor/io_descriptor/__init__.py
index 50763697..e294b962 100644
--- a/python/tk-core/python/tank/descriptor/io_descriptor/__init__.py
+++ b/python/tk-core/python/tank/descriptor/io_descriptor/__init__.py
@@ -10,8 +10,8 @@
from .factory import (
create_io_descriptor,
- descriptor_uri_to_dict,
descriptor_dict_to_uri,
+ descriptor_uri_to_dict,
is_descriptor_version_missing,
)
@@ -22,15 +22,15 @@ def _initialize_descriptor_factory():
This complex process for handling the IODescriptor abstract factory
management is in order to avoid local imports in classes.
"""
- from .base import IODescriptorBase
from .appstore import IODescriptorAppStore
+ from .base import IODescriptorBase
from .dev import IODescriptorDev
- from .path import IODescriptorPath
- from .shotgun_entity import IODescriptorShotgunEntity
- from .git_tag import IODescriptorGitTag
from .git_branch import IODescriptorGitBranch
+ from .git_tag import IODescriptorGitTag
from .github_release import IODescriptorGithubRelease
from .manual import IODescriptorManual
+ from .path import IODescriptorPath
+ from .shotgun_entity import IODescriptorShotgunEntity
IODescriptorBase.register_descriptor_factory("app_store", IODescriptorAppStore)
IODescriptorBase.register_descriptor_factory("dev", IODescriptorDev)
diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/appstore.py b/python/tk-core/python/tank/descriptor/io_descriptor/appstore.py
index 746b6c54..d6bfdf5a 100644
--- a/python/tk-core/python/tank/descriptor/io_descriptor/appstore.py
+++ b/python/tk-core/python/tank/descriptor/io_descriptor/appstore.py
@@ -65,7 +65,7 @@ class IODescriptorAppStore(IODescriptorDownloadable):
_app_store_connections = {}
# internal app store mappings
- (APP, FRAMEWORK, ENGINE, CONFIG, CORE) = range(5)
+ APP, FRAMEWORK, ENGINE, CONFIG, CORE = range(5)
_APP_STORE_OBJECT = {
constants.DESCRIPTOR_APP: constants.TANK_APP_ENTITY_TYPE,
@@ -231,7 +231,7 @@ def __refresh_metadata(self, path, sg_bundle_data=None, sg_version_data=None):
link_field = self._APP_STORE_LINK[self._bundle_type]
# connect to the app store
- (sg, _) = self.__create_sg_app_store_connection()
+ sg, _ = self.__create_sg_app_store_connection()
if self._bundle_type == self.CORE:
# special handling of core since it doesn't have a high-level 'bundle' entity
@@ -413,7 +413,7 @@ def _download_local(self, destination_path):
is to be downloaded to.
"""
# connect to the app store
- (sg, script_user) = self.__create_sg_app_store_connection()
+ sg, script_user = self.__create_sg_app_store_connection()
# fetch metadata from sg...
metadata = self.__refresh_metadata(destination_path)
@@ -445,7 +445,7 @@ def _post_download(self, download_path):
# write a stats record to the tank app store
try:
# connect to the app store
- (sg, script_user) = self.__create_sg_app_store_connection()
+ sg, script_user = self.__create_sg_app_store_connection()
# fetch metadata from sg...
metadata = self.__refresh_metadata(download_path)
@@ -745,7 +745,7 @@ def get_latest_version(self, constraint_pattern=None):
)
# connect to the app store
- (sg, _) = self.__create_sg_app_store_connection()
+ sg, _ = self.__create_sg_app_store_connection()
# get latest get the filter logic for what to exclude
if constants.APP_STORE_QA_MODE_ENV_VAR in os.environ:
@@ -981,7 +981,7 @@ def __create_sg_app_store_connection(self):
# Connect to associated Shotgun site and retrieve the credentials to use to
# connect to the app store site
try:
- (script_name, script_key) = self.__get_app_store_key_from_shotgun()
+ script_name, script_key = self.__get_app_store_key_from_shotgun()
except urllib.error.HTTPError as e:
if e.code == 403:
# edge case alert!
@@ -994,7 +994,7 @@ def __create_sg_app_store_connection(self):
# trigger a refresh of our session token by issuing a shotgun API call
self._sg_connection.find_one("HumanUser", [])
# and retry
- (script_name, script_key) = self.__get_app_store_key_from_shotgun()
+ script_name, script_key = self.__get_app_store_key_from_shotgun()
else:
raise
@@ -1146,7 +1146,7 @@ def has_remote_access(self):
% self
)
# connect to the app store
- (sg, _) = self.__create_sg_app_store_connection()
+ sg, _ = self.__create_sg_app_store_connection()
log.debug("...connection established: %s" % sg)
except Exception as e:
log.debug("...could not establish connection: %s" % e)
diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/dev.py b/python/tk-core/python/tank/descriptor/io_descriptor/dev.py
index 73dfbd76..f5e621c6 100644
--- a/python/tk-core/python/tank/descriptor/io_descriptor/dev.py
+++ b/python/tk-core/python/tank/descriptor/io_descriptor/dev.py
@@ -8,9 +8,8 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from .path import IODescriptorPath
-
from ... import LogManager
+from .path import IODescriptorPath
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/downloadable.py b/python/tk-core/python/tank/descriptor/io_descriptor/downloadable.py
index 4d9e4f5f..a875f30d 100644
--- a/python/tk-core/python/tank/descriptor/io_descriptor/downloadable.py
+++ b/python/tk-core/python/tank/descriptor/io_descriptor/downloadable.py
@@ -12,11 +12,10 @@
import os
import uuid
-from .base import IODescriptorBase
-from ..errors import TankDescriptorIOError
-from ...util import filesystem
-
from ... import LogManager
+from ...util import filesystem
+from ..errors import TankDescriptorIOError
+from .base import IODescriptorBase
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/factory.py b/python/tk-core/python/tank/descriptor/io_descriptor/factory.py
index 4759e031..9cb740e5 100644
--- a/python/tk-core/python/tank/descriptor/io_descriptor/factory.py
+++ b/python/tk-core/python/tank/descriptor/io_descriptor/factory.py
@@ -10,11 +10,10 @@
import copy
+from ... import LogManager
from ..errors import TankDescriptorError
from .base import IODescriptorBase
-from ... import LogManager
-
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/git.py b/python/tk-core/python/tank/descriptor/io_descriptor/git.py
index dcaede1e..536aa61d 100644
--- a/python/tk-core/python/tank/descriptor/io_descriptor/git.py
+++ b/python/tk-core/python/tank/descriptor/io_descriptor/git.py
@@ -8,18 +8,16 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
import os
-import uuid
import shutil
-import tempfile
import subprocess
+import tempfile
+import uuid
-from .downloadable import IODescriptorDownloadable
from ... import LogManager
-from ...util.process import subprocess_check_output, SubprocessCalledProcessError
-
+from ...util import filesystem, is_windows
+from ...util.process import SubprocessCalledProcessError, subprocess_check_output
from ..errors import TankError
-from ...util import filesystem
-from ...util import is_windows
+from .downloadable import IODescriptorDownloadable
log = LogManager.get_logger(__name__)
@@ -115,7 +113,7 @@ def _clone_then_execute_git_commands(
log.debug("Checking that git exists and can be executed...")
try:
output = _check_output(["git", "--version"])
- except:
+ except Exception:
log.exception("Unexpected error:")
raise TankGitError(
"Cannot execute the 'git' command. Please make sure that git is "
@@ -236,7 +234,7 @@ def get_system_name(self):
and for folders on disk, e.g. 'tk-maya'
"""
bn = os.path.basename(self._path)
- (name, ext) = os.path.splitext(bn)
+ name, ext = os.path.splitext(bn)
return name
def has_remote_access(self):
diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/git_branch.py b/python/tk-core/python/tank/descriptor/io_descriptor/git_branch.py
index 62951553..bfb779ed 100644
--- a/python/tk-core/python/tank/descriptor/io_descriptor/git_branch.py
+++ b/python/tk-core/python/tank/descriptor/io_descriptor/git_branch.py
@@ -115,7 +115,7 @@ def _is_latest_commit(self, version, branch):
log.debug("Checking if the version is pointing to the latest commit...")
try:
output = _check_output(["git", "ls-remote", self._path, branch])
- except:
+ except Exception:
log.exception("Unexpected error:")
raise TankGitError(
"Cannot execute the 'git' command. Please make sure that git is "
diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/git_tag.py b/python/tk-core/python/tank/descriptor/io_descriptor/git_tag.py
index aa1026ed..33a7d71b 100644
--- a/python/tk-core/python/tank/descriptor/io_descriptor/git_tag.py
+++ b/python/tk-core/python/tank/descriptor/io_descriptor/git_tag.py
@@ -7,13 +7,13 @@
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import os
import copy
+import os
import re
-from .git import IODescriptorGit
-from ..errors import TankDescriptorError
from ... import LogManager
+from ..errors import TankDescriptorError
+from .git import IODescriptorGit
log = LogManager.get_logger(__name__)
@@ -51,9 +51,7 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type):
)
# call base class
- super().__init__(
- descriptor_dict, sg_connection, bundle_type
- )
+ super().__init__(descriptor_dict, sg_connection, bundle_type)
# path is handled by base class - all git descriptors
# have a path to a repo
diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/manual.py b/python/tk-core/python/tank/descriptor/io_descriptor/manual.py
index ff11158b..0b86fcff 100644
--- a/python/tk-core/python/tank/descriptor/io_descriptor/manual.py
+++ b/python/tk-core/python/tank/descriptor/io_descriptor/manual.py
@@ -8,9 +8,10 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
import os
-from .base import IODescriptorBase
+
from ... import LogManager
from ..errors import TankDescriptorError
+from .base import IODescriptorBase
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/path.py b/python/tk-core/python/tank/descriptor/io_descriptor/path.py
index 2dc603eb..502c44b3 100644
--- a/python/tk-core/python/tank/descriptor/io_descriptor/path.py
+++ b/python/tk-core/python/tank/descriptor/io_descriptor/path.py
@@ -10,10 +10,10 @@
import os
-from .base import IODescriptorBase
-from ..errors import TankDescriptorError
-from ...util import ShotgunPath
from ... import LogManager
+from ...util import ShotgunPath
+from ..errors import TankDescriptorError
+from .base import IODescriptorBase
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/shotgun_entity.py b/python/tk-core/python/tank/descriptor/io_descriptor/shotgun_entity.py
index d04bd57a..62fae486 100644
--- a/python/tk-core/python/tank/descriptor/io_descriptor/shotgun_entity.py
+++ b/python/tk-core/python/tank/descriptor/io_descriptor/shotgun_entity.py
@@ -64,7 +64,7 @@ class IODescriptorShotgunEntity(IODescriptorDownloadable):
The latest version is defined as the current record available in Shotgun.
"""
- (_MODE_ID_BASED, _MODE_NAME_BASED) = range(2)
+ _MODE_ID_BASED, _MODE_NAME_BASED = range(2)
def __init__(self, descriptor_dict, sg_connection, bundle_type):
"""
diff --git a/python/tk-core/python/tank/flowam/__init__.py b/python/tk-core/python/tank/flowam/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/python/tk-core/python/tank/flowam/config.json b/python/tk-core/python/tank/flowam/config.json
new file mode 100644
index 00000000..16d3ff4b
--- /dev/null
+++ b/python/tk-core/python/tank/flowam/config.json
@@ -0,0 +1,188 @@
+{
+ "name": "demo_pipeline",
+ "version": "1.0.2",
+ "description": "Flow Toolkit pipeline schema definitions",
+ "schemas": [
+ {
+ "name": "type.container",
+ "version": "1.0.0",
+ "display_name": "Container",
+ "kind": "type",
+ "description": "Basic container representing a work unit.",
+ "inherits": [],
+ "properties": []
+ },
+ {
+ "name": "type.container.asset",
+ "version": "1.0.0",
+ "display_name": "Asset Build",
+ "kind": "type",
+ "description": "Asset type container.",
+ "inherits": [
+ "$ref:type.container"
+ ],
+ "properties": []
+ },
+ {
+ "name": "type.container.shot",
+ "version": "1.0.0",
+ "display_name": "Shot",
+ "kind": "type",
+ "description": "Shot type container.",
+ "inherits": [
+ "$ref:type.container"
+ ],
+ "properties": []
+ },
+ {
+ "name": "type.pipelineStep",
+ "version": "1.0.0",
+ "display_name": "Pipeline Step",
+ "kind": "type",
+ "description": "Base type for a step in a production pipeline.",
+ "inherits": [],
+ "properties": []
+ },
+ {
+ "name": "type.workfile",
+ "version": "1.0.0",
+ "display_name": "Workfile",
+ "kind": "type",
+ "description": "File-based work unit.",
+ "inherits": [],
+ "properties": []
+ },
+ {
+ "name": "type.workfile.generic",
+ "version": "1.0.0",
+ "display_name": "Generic Workfile",
+ "kind": "type",
+ "description": "Generic workfile which can contain arbitrary file types.",
+ "inherits": [
+ "$ref:type.workfile"
+ ],
+ "properties": []
+ },
+ {
+ "name": "type.workfile.maya",
+ "version": "1.0.0",
+ "display_name": "Maya Workfile",
+ "kind": "type",
+ "description": "Maya native workfile.",
+ "inherits": [
+ "$ref:type.workfile"
+ ],
+ "properties": []
+ },
+ {
+ "name": "type.workfile.hou",
+ "version": "1.0.0",
+ "display_name": "Houdini Workfile",
+ "kind": "type",
+ "description": "Houdini native workfile.",
+ "inherits": [
+ "$ref:type.workfile"
+ ],
+ "properties": []
+ },
+ {
+ "name": "type.workfile.nuke",
+ "version": "1.0.0",
+ "display_name": "Nuke Workfile",
+ "kind": "type",
+ "description": "Nuke script workfile.",
+ "inherits": [
+ "$ref:type.workfile"
+ ],
+ "properties": []
+ },
+ {
+ "name": "type.workfile.usd",
+ "version": "1.0.0",
+ "display_name": "USD Workfile",
+ "kind": "type",
+ "description": "USD native workfile.",
+ "inherits": [
+ "$ref:type.workfile"
+ ],
+ "properties": []
+ },
+ {
+ "name": "type.derivative",
+ "version": "1.0.0",
+ "display_name": "Derivative",
+ "kind": "type",
+ "description": "Derivative output from a workfile, also referred to as a work product.",
+ "inherits": [],
+ "properties": []
+ },
+ {
+ "name": "type.derivative.maya",
+ "version": "1.0.0",
+ "display_name": "Maya Derivative",
+ "kind": "type",
+ "description": "Maya native type derivative work product.",
+ "inherits": [
+ "$ref:type.derivative"
+ ],
+ "properties": []
+ },
+ {
+ "name": "type.derivative.usd",
+ "version": "1.0.0",
+ "display_name": "USD Derivative",
+ "kind": "type",
+ "description": "Universal Scene Description (USD) derivative work product.",
+ "inherits": [
+ "$ref:type.derivative"
+ ],
+ "properties": []
+ },
+ {
+ "name": "type.template",
+ "version": "1.0.0",
+ "display_name": "Template",
+ "kind": "type",
+ "description": "Indicates an asset that is intended to be used as a template for building other assets.",
+ "inherits": [],
+ "properties": []
+ },
+ {
+ "name": "type.derivative.abc",
+ "version": "1.0.0",
+ "display_name": "Alembic Derivative",
+ "kind": "type",
+ "description": "Alembic derivative work product.",
+ "inherits": [
+ "$ref:type.derivative"
+ ],
+ "properties": []
+ },
+ {
+ "name": "type.fileSequence",
+ "version": "1.0.0",
+ "display_name": "File Sequence",
+ "kind": "type",
+ "description": "Asset contains a file sequence.",
+ "inherits": [],
+ "properties": [
+ {
+ "name": "frameStart",
+ "data_type": "Int32"
+ },
+ {
+ "name": "frameEnd",
+ "data_type": "Int32"
+ },
+ {
+ "name": "frameSet",
+ "data_type": "String"
+ },
+ {
+ "name": "fileFormat",
+ "data_type": "String"
+ }
+ ]
+ }
+ ]
+}
diff --git a/python/tk-core/python/tank/flowam/constants.py b/python/tk-core/python/tank/flowam/constants.py
new file mode 100644
index 00000000..08352768
--- /dev/null
+++ b/python/tk-core/python/tank/flowam/constants.py
@@ -0,0 +1,34 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+"""Constants for Flow Integration."""
+
+import pathlib
+
+# Flow Settings
+# -------------
+# These are settings names that can be configured within a flow.yml file.
+# These attributes are used to prepare a Flow Integration SDK session.
+
+FLOW_AUTH_APP_ID = "auth_application_id"
+FLOW_AUTH_BASE_URL = "auth_base_url"
+FLOW_AUTH_CALLBACK_URL = "auth_callback_url"
+FLOW_ENDPOINT = "endpoint"
+FLOW_WEB_URL = "web_url"
+FLOW_SANDBOX_ROOT = "sandbox_root"
+FLOW_STORAGE_ROOT = "storage_root"
+
+# Location of config for specifying custom schemas used
+# in the toolkit Flow integration
+FLOW_SCHEMA_CONFIG_PATH = str(pathlib.Path(__file__).resolve().parent) + "/config.json"
+
+# SG Project field that stores the current schema config version,
+# used to skip schema provisioning when the version already matches.
+FLOW_SCHEMA_VERSION_FIELD = "sg_flow_schema_config_version"
diff --git a/python/tk-core/python/tank/flowam/create.py b/python/tk-core/python/tank/flowam/create.py
new file mode 100644
index 00000000..125989e8
--- /dev/null
+++ b/python/tk-core/python/tank/flowam/create.py
@@ -0,0 +1,250 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+from __future__ import annotations # needed for python 3.9 support
+
+import re
+from enum import Enum
+
+from tank_vendor.flow_integration_sdk.exceptions import CreateAssetError, FlowError
+from tank_vendor.flow_integration_sdk.globals import FOLDER_TYPE_ID
+from tank_vendor.flow_integration_sdk.objects import FlowAsset, FlowProject
+from tank_vendor.flow_integration_sdk.publish import (
+ TypeComponentSpec,
+ publish_new_asset,
+)
+from tank_vendor.flow_integration_sdk.schema import get_schema_id
+from tank_vendor.flow_integration_sdk.utils import get_logger, trace
+
+from .utils import BaseInputs
+
+# Folder names
+ASSET_FOLDER = "Assets"
+SHOT_FOLDER = "Shots"
+GENERIC_FOLDER = "Generic"
+TEMPLATE_FOLDER = "Templates"
+
+# SG entity types
+SHOT_TYPE = "Shot"
+ASSET_TYPE = "Asset"
+
+# Custom schema types
+ASSET_CONTAINER_TYPE = "type.container.asset"
+CONTAINER_TYPE = "type.container"
+DERIVATIVE_TYPE = "type.derivative"
+GENERIC_WORKFILE_TYPE = "type.workfile.generic"
+PIPELINE_STEP_TYPE = "type.pipelineStep"
+SHOT_CONTAINER_TYPE = "type.container.shot"
+TEMPLATE_TYPE = "type.template"
+WORKFILE_TYPE = "type.workfile"
+
+
+class CreateMode(Enum):
+ """Enum of modes for creating a new asset."""
+
+ NEW = "new" #: Create a DCC asset from a new scene as the source.
+ CURRENT = "current" #: Create a DCC asset from the current scene as the source.
+ TEMPLATE = "template" #: Create a DCC asset from template scene as the source.
+ GENERIC = "generic" #: Create a generic asset from a specified source file.
+
+
+@trace
+def create_asset_hierarchy(inputs: BaseInputs) -> FlowAsset:
+ """Ensure the folder hierarchy above a new generic workfile exists.
+
+ Returns the immediate parent under which the workfile asset should be
+ created.
+
+ Args:
+ inputs: A ``BaseInputs`` instance (or any inputs object with
+ ``am_project_id``, ``sg_entity_name``, and a ``create_mode``
+ compatible with ``CreateMode.GENERIC``).
+
+ Returns:
+ The parent :class:`FlowAsset` for the new workfile.
+ """
+ root_folder = get_or_create_root_folder(inputs)
+
+ if inputs.sg_entity_name:
+ parent = get_or_create_workfile_parent(root_folder, inputs)
+ else:
+ parent = root_folder
+
+ return parent
+
+
+@trace
+def get_or_create_root_folder(inputs: BaseInputs) -> FlowAsset:
+ """Retrieve (or create) the top-level folder for the new asset.
+
+ Returns:
+ Folder :class:`FlowAsset`.
+
+ Raises:
+ CreateAssetError
+ """
+ logger = get_logger(__name__)
+
+ am_project_id = inputs.am_project_id
+ sg_entity_type = inputs.sg_entity_type
+
+ try:
+ project = FlowProject(am_project_id)
+ except FlowError as exc:
+ msg = f"Invalid Flow project id provided: {am_project_id}"
+ raise CreateAssetError(data=inputs.asdict(), details=msg) from exc
+
+ if sg_entity_type == SHOT_TYPE:
+ folder = project.find_child(SHOT_TYPE)
+ if not folder:
+ logger.info(f'Creating "{SHOT_TYPE}" folder...')
+ raw_asset = publish_new_asset(
+ name=ensure_unique_name(SHOT_TYPE, project),
+ parent_id=project.id,
+ description="Folder for Shot assets.",
+ components=[
+ TypeComponentSpec(type_id=FOLDER_TYPE_ID, name=f"Type {SHOT_TYPE}")
+ ],
+ )
+ folder = FlowAsset(raw_asset)
+ elif sg_entity_type == ASSET_TYPE:
+ folder = project.find_child(ASSET_FOLDER)
+ if not folder:
+ logger.info(f'Creating "{ASSET_FOLDER}" folder...')
+ raw_asset = publish_new_asset(
+ name=ensure_unique_name(ASSET_FOLDER, project),
+ parent_id=project.id,
+ description="Folder for Asset Build assets.",
+ components=[
+ TypeComponentSpec(type_id=FOLDER_TYPE_ID, name=f"Type {ASSET_TYPE}")
+ ],
+ )
+ folder = FlowAsset(raw_asset)
+ elif inputs.create_mode == CreateMode.GENERIC:
+ folder = project.find_child(GENERIC_FOLDER)
+ if not folder:
+ logger.info(f'Creating "{GENERIC_FOLDER}" folder...')
+ raw_asset = publish_new_asset(
+ name=ensure_unique_name(GENERIC_FOLDER, project),
+ parent_id=project.id,
+ description="Folder for Generic assets.",
+ components=[
+ TypeComponentSpec(
+ type_id=FOLDER_TYPE_ID, name=f"Type {GENERIC_FOLDER}"
+ )
+ ],
+ )
+ folder = FlowAsset(raw_asset)
+ else:
+ msg = f"Invalid entity type provided: {sg_entity_type}."
+ raise CreateAssetError(data=inputs.asdict(), details=msg)
+
+ return folder
+
+
+@trace
+def get_or_create_workfile_parent(
+ root_folder: FlowAsset, inputs: BaseInputs
+) -> FlowAsset:
+ """Determine (and create if necessary) the task-level folder that will be
+ the direct parent of the workfile asset.
+
+ Returns:
+ The task-folder :class:`FlowAsset`.
+ """
+ logger = get_logger(__name__)
+
+ sg_entity_type = inputs.sg_entity_type
+ sg_entity_name = inputs.sg_entity_name
+ sg_pipeline_step = inputs.sg_pipeline_step
+ sg_task_name = inputs.sg_task_name
+
+ container = root_folder.find_child(sg_entity_name)
+ if not container:
+ logger.info(
+ f'Creating container asset for "{sg_entity_name}" under '
+ f'folder "{root_folder.name}"...'
+ )
+ container_type = (
+ ASSET_CONTAINER_TYPE
+ if sg_entity_type == ASSET_TYPE
+ else SHOT_CONTAINER_TYPE
+ )
+ raw_asset = publish_new_asset(
+ name=ensure_unique_name(sg_entity_name, root_folder),
+ parent_id=root_folder.id,
+ components=[
+ TypeComponentSpec(
+ type_id=get_schema_id(container_type), name=f"Type {container_type}"
+ )
+ ],
+ )
+ container = FlowAsset(raw_asset)
+
+ pipeline_step = container.find_child(sg_pipeline_step)
+ if not pipeline_step:
+ logger.info(f'Creating pipeline step asset for "{sg_pipeline_step}"...')
+ raw_asset = publish_new_asset(
+ name=ensure_unique_name(sg_pipeline_step, container),
+ parent_id=container.id,
+ components=[
+ TypeComponentSpec(
+ type_id=get_schema_id(PIPELINE_STEP_TYPE),
+ name=f"Type {PIPELINE_STEP_TYPE}",
+ )
+ ],
+ )
+ pipeline_step = FlowAsset(raw_asset)
+
+ task_folder = pipeline_step.find_child(sg_task_name)
+ if not task_folder:
+ logger.info(f'Creating task folder asset for "{sg_task_name}"...')
+ raw_asset = publish_new_asset(
+ name=ensure_unique_name(sg_task_name, pipeline_step),
+ parent_id=pipeline_step.id,
+ description=f'Folder for task "{sg_task_name}".',
+ components=[
+ TypeComponentSpec(type_id=FOLDER_TYPE_ID, name=f"Type {FOLDER_TYPE_ID}")
+ ],
+ )
+ task_folder = FlowAsset(raw_asset)
+
+ return task_folder
+
+
+def ensure_unique_name(name: str, parent: FlowAsset | FlowProject) -> str:
+ """Return a unique sibling name under *parent*, adding a numeric suffix
+ if a child with the same name already exists.
+
+ Example: siblings ``["asset 1", "asset 2 (1)"]``, input ``"asset 2"``
+ → returns ``"asset 2 (2)"``.
+ """
+ logger = get_logger(__name__)
+
+ if parent.find_child(name):
+ copies = parent.find_children(f"{name} (*)")
+ highest_index = 0
+ for c in copies:
+ m = re.match(rf"{re.escape(name)} \((?P\d+)\)", c.name)
+ if m:
+ try:
+ index = int(m.group("index"))
+ except ValueError:
+ continue
+ if index > highest_index:
+ highest_index = index
+ new_name = f"{name} ({highest_index + 1})"
+ logger.warning(
+ f'A child named "{name}" already exists under "{parent.name}". '
+ f'Renaming to "{new_name}".'
+ )
+ name = new_name
+
+ return name
diff --git a/python/tk-core/python/tank/flowam/host.py b/python/tk-core/python/tank/flowam/host.py
new file mode 100644
index 00000000..ac09eced
--- /dev/null
+++ b/python/tk-core/python/tank/flowam/host.py
@@ -0,0 +1,199 @@
+# -
+# *****************************************************************************
+# Copyright 2026 Autodesk, Inc. All rights reserved.
+#
+# These coded instructions, statements, and computer programs contain
+# unpublished proprietary information written by Autodesk, Inc. and are
+# protected by Federal copyright law. They may not be disclosed to third
+# parties or copied or duplicated in any form, in whole or in part, without
+# the prior written consent of Autodesk, Inc.
+# *****************************************************************************
+#
+
+from __future__ import annotations # needed for python 3.9 support
+
+from abc import ABC, abstractmethod
+
+from tank_vendor.flow_integration_sdk.dependency import DependencyData
+
+
+class FlowHost(ABC):
+ """Abstract base class defining a required interface for DCC/engine
+ integration with Flow asset management in toolkit.
+ """
+
+ #: The schema name associated with workfiles created for this host
+ WORKFILE_TYPE = ""
+ #: File extensions associated with host DCC (e.g. "ma" for Maya)
+ #: Must have at least one value in a subclass implementation
+ FILE_TYPES: list[str] = []
+ #: Host specific mime type mappings
+ #: key = file extension from FILE_TYPES, value = mime type
+ MIME_TYPES: dict[str, str] = {}
+
+ def __init__(self, context):
+ """Base class initialization.
+
+ Args:
+ context: Sgtk context object to be passed in from engine.
+ """
+ # Store sgtk context
+ self.context = context
+
+ @abstractmethod
+ def current_file(self) -> str:
+ """Return current open file path in dcc."""
+ raise NotImplementedError()
+
+ @abstractmethod
+ def new_scene(self, force: bool = True) -> bool:
+ """Open new scene in the host application.
+
+ Args:
+ force: If true, force action even if there are unsaved changes.
+
+ Returns:
+ True if new scene is opened, False on error or if operation is cancelled.
+ """
+ raise NotImplementedError()
+
+ @abstractmethod
+ def open_file(self, file_path: str) -> bool:
+ """Open the specified file in the host application.
+
+ Args:
+ file_path: Absolute path to the local file to open.
+
+ Returns:
+ True if file is opened, False on error or if operation is cancelled.
+ """
+ raise NotImplementedError()
+
+ def save_file(self, file_path: str):
+ """Save the current scene to the specified file path.
+
+ This operation may not be applicable to all hosts.
+
+ Args:
+ file_path: Absolute local path to save file.
+ """
+ pass
+
+ def export(self, file_path: str):
+ """Export current scene to file path specified and file type
+ designated by file extension.
+
+ This operation may not be applicable to all hosts.
+
+ Args:
+ file_path: Absolute local path to export file.
+ """
+ pass
+
+ @abstractmethod
+ def dialog(
+ self,
+ title: str,
+ msg: str,
+ buttons: list[str] | None = None,
+ default: int = 0,
+ cancel: int | None = None,
+ no_ui_option: int | None = None,
+ ) -> int:
+ """Pop up a dialog in the host application.
+
+ Args:
+ title: Title of dialog window.
+ msg: Message to be displayed.
+ buttons: List of strings denoting buttons to be added to dialog.
+ default: Index of default button.
+ cancel: Index of cancel button.
+ no_ui_option: If host is running without a UI,
+ this option will automatically be returned.
+ If None, use default value.
+
+ Returns:
+ The index of the button selected by user.
+ Value of -1 indicates a dismissed dialog.
+ """
+ raise NotImplementedError()
+
+ @abstractmethod
+ def file_dialog(
+ self,
+ title: str,
+ starting_dir: str = "",
+ folder_mode: bool = False,
+ file_type: str = "",
+ multi_select: bool = False,
+ ) -> list[str]:
+ """Invoke a file dialog for selecting one or more file paths.
+
+ Args:
+ title: Title of dialog.
+ starting_dir: Starting location of dialog.
+ folder_mode: If True, dialog will browse folders instead of files.
+ file_type: File extension to filter for.
+ Applicable only when browsing files.
+ multi_select: If True, allow multiple selection of files.
+ Applicable only when browsing files.
+
+ Returns:
+ A list of file/directory paths.
+ If multi_select = False, the return value will be a list of size 1.
+ If user cancels, list will be empty.
+ """
+ raise NotImplementedError()
+
+ @abstractmethod
+ def copy_to_clipboard(self, text: str) -> bool:
+ """Copy given text to clipboard of relevant application.
+ Default implementation copies to system clipboard.
+
+ Args:
+ text: Text to be copied.
+
+ Returns:
+ True on success.
+ """
+ raise NotImplementedError()
+
+ def get_dependency_tree(self, must_exist: bool = True) -> DependencyData:
+ """Return a DependencyData object which is the root of the
+ dependency tree for the scene.
+
+ Args:
+ must_exist: Only return dependencies that can be found on disk.
+ """
+ # Return empty root node by default
+ return DependencyData()
+
+ def update_dependency(
+ self,
+ dep: DependencyData,
+ file_path: str,
+ ) -> DependencyData:
+ """Update an existing dependency to point to given file in current scene.
+
+ Args:
+ dep: DependencyData node which identifies the dependency to be updated.
+ file_path: New path to set dependency to.
+
+ Returns:
+ DependencyData object describing new state of dependency.
+ NOTE: This will be an isolated node, not including sub-dependency info.
+ """
+ # Do nothing by default
+ return dep
+
+ def env_var_marker(self, var_name: str) -> str:
+ """Return the environment variable marker format for this host.
+
+ Args:
+ var_name: The environment variable name.
+
+ Returns:
+ Environment variable marker in host-specific format.
+ Default implementation returns shell format: ${VAR_NAME}
+ """
+ return "${" + var_name + "}"
diff --git a/python/tk-core/python/tank/flowam/open.py b/python/tk-core/python/tank/flowam/open.py
new file mode 100644
index 00000000..490332d2
--- /dev/null
+++ b/python/tk-core/python/tank/flowam/open.py
@@ -0,0 +1,218 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+from __future__ import annotations # needed for python 3.9 support
+
+import os
+
+import sgtk
+from tank import LogManager
+from tank_vendor.flow_integration_sdk.exceptions import (
+ DraftExistsError,
+ FlowError,
+ InvalidDraftError,
+)
+from tank_vendor.flow_integration_sdk.globals import SOURCE_PURPOSE
+from tank_vendor.flow_integration_sdk.objects import FlowRevision, FlowVersion
+from tank_vendor.flow_integration_sdk.sandbox import (
+ CheckoutDraftInfo,
+)
+from tank_vendor.flow_integration_sdk.sandbox import (
+ checkout_revision as checkout_revision_in_sandbox,
+)
+from tank_vendor.flow_integration_sdk.sandbox import (
+ get_draft_id,
+ is_local_draft,
+ read_draft_info,
+)
+from tank_vendor.flow_integration_sdk.utils import trace
+
+logger = LogManager.get_logger(__name__)
+
+
+@trace
+def checkout_revision(
+ revision_id: str, force: bool = False
+) -> CheckoutDraftInfo | None:
+ """Check out the given asset revision/version into sandbox.
+ If the version is already checked out, offer an overwrite.
+ If a different version of the same asset is already checkout out, offer an overwrite.
+
+ Args:
+ revision_id: Id of a published AM asset revision. This can also be a version id.
+ force: If True, force a re-checkout even if an existing draft is found.
+
+ Returns:
+ CheckoutDraftInfo object or None if operation was cancelled.
+
+ Raises:
+ FlowError
+ """
+ if FlowVersion.is_version_id(revision_id):
+ revision = FlowVersion(revision_id).revision
+ else:
+ revision = FlowRevision.get_revision(revision_id)
+
+ try:
+ draft_info = checkout_revision_in_sandbox(
+ revision._revision,
+ component_purpose=SOURCE_PURPOSE,
+ force=force,
+ )
+ except DraftExistsError:
+ draft_info = _handle_existing_draft(revision)
+ if draft_info is None:
+ # Operation was cancelled so exit
+ return None
+
+ # Open checked out draft file
+ flow_host = sgtk.platform.current_engine().flow_host
+ if flow_host:
+ draft_path = draft_info.source_path
+ logger.info(f"Opening draft path: {draft_path}")
+ flow_host.open_file(draft_path)
+
+ return draft_info
+
+
+@trace
+def _handle_existing_draft(revision: FlowRevision) -> CheckoutDraftInfo | None:
+ """Called during a checkout operation when an existing draft is detected
+ to handle the situation as safely as possible. If host class is available,
+ pop-up a dialog to ask the user what to do, otherwise output an informative
+ message and cancel the operation.
+
+ NOTE: We are only concerned with versions here and not the granularity of
+ revisions. This is because we are assuming that binaries and dependencies
+ (i.e. components and uses relationships) cannot change between revisions
+ of the same version. Therefore, checking out any given revision of a version
+ should be sufficient for ensuring that we fetch and copy the correct binaries.
+
+ Returns CheckoutDraftInfo object if successful, or None if operation is cancelled.
+ """
+ draft_id = get_draft_id(revision.id)
+ try:
+ draft_info = read_draft_info(draft_id)
+ checkout_version = draft_info.version
+ checkout_revision = draft_info.revision
+ except InvalidDraftError:
+ # This indicates that the draft is corrupted
+ checkout_version = None
+ checkout_revision = None
+
+ # If not host is available, just cancel the operation
+ flow_host = sgtk.platform.current_engine().flow_host
+ if flow_host is None:
+ msg = "There is an existing draft detected with checkout version: "
+ msg += f"{checkout_version}."
+ logger.error(msg)
+ return None
+
+ title = "Existing draft detected"
+ if checkout_version is None:
+ # Warning to user with options to
+ # 0 - overwrite existing draft
+ # 1 - cancel
+ msg = "There is an existing draft which is corrupted. "
+ msg += "Would you like to overwrite this with a fresh checkout?"
+ options = ["New checkout", "Cancel"]
+
+ action_idx = flow_host.dialog(
+ title=title,
+ msg=msg,
+ buttons=options,
+ default=1, # cancel by default
+ no_ui_option=1, # cancel when no dialog can be launched
+ )
+ action = options[action_idx]
+
+ elif checkout_version == revision.version_number:
+ # Warning to user with options to
+ # 0 - overwrite existing draft
+ # 1 - proceed with existing draft
+ msg = "This version is already checked out in sandbox. "
+ msg += "Would you like to overwrite this with a fresh checkout?"
+ options = ["New checkout", "Use original checkout"]
+
+ action_idx = flow_host.dialog(
+ title=title,
+ msg=msg,
+ buttons=options,
+ default=1, # use original by default
+ no_ui_option=1, # use original when no dialog can be launched
+ )
+ action = options[action_idx]
+
+ else:
+ # Warning to user with options to
+ # 1 - overwrite existing draft
+ # 2 - cancel
+ msg = f"An existing checkout already exists of version {checkout_version} "
+ msg += f"(r{checkout_revision}) of this asset. Would you like to overwrite "
+ msg += f"this with a checkout of version {revision.version_number} "
+ msg += f"(r{revision.revision_number})?"
+ options = ["New checkout", "Cancel"]
+
+ action_idx = flow_host.dialog(
+ title=title,
+ msg=msg,
+ buttons=options,
+ default=1, # cancel by default
+ no_ui_option=1, # cancel when no dialog can be launched
+ )
+ action = options[action_idx]
+
+ if action == "New checkout":
+ # Force a new checkout
+ return checkout_revision_in_sandbox(
+ revision._revision,
+ component_purpose=SOURCE_PURPOSE,
+ force=True,
+ )
+ elif action == "Use original checkout":
+ msg = f"Using original draft of version {checkout_version} "
+ msg += f"(r{checkout_revision})."
+ logger.info(msg)
+ return draft_info
+ else:
+ logger.warning("Checkout operation cancelled.")
+ return None
+
+
+@trace
+def open_draft(draft_id: str):
+ """Open draft source file for editing if draft is local.
+
+ Args:
+ draft_id: Unique id that identifies a draft location in local sandbox.
+
+ Raises:
+ FlowError
+ InvalidDraftError
+ """
+ flow_host = sgtk.platform.current_engine().flow_host
+ if not flow_host:
+ raise FlowError(
+ "Opening a draft must be done in an engine which supports Flow integration."
+ )
+
+ if not is_local_draft(draft_id):
+ msg = f'The draft "{draft_id}" is not in local sandbox.'
+ raise InvalidDraftError(draft_id=draft_id, details=msg)
+
+ draft_info = read_draft_info(draft_id)
+ draft_path = draft_info.source_path
+ if not os.path.exists(draft_path):
+ msg = f'Corrupted draft folder. The file "{draft_path}" does not exist.'
+ raise InvalidDraftError(draft_id=draft_id, details=msg)
+
+ # Open file
+ logger.info(f"Opening file: {draft_path}")
+ flow_host.open_file(draft_path)
diff --git a/python/tk-core/python/tank/flowam/utils.py b/python/tk-core/python/tank/flowam/utils.py
new file mode 100644
index 00000000..38092202
--- /dev/null
+++ b/python/tk-core/python/tank/flowam/utils.py
@@ -0,0 +1,304 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+"""Shared utilities for Flow Integration."""
+
+from __future__ import annotations # needed for python 3.9 support
+
+import glob
+import os
+import re
+import webbrowser
+from dataclasses import asdict, dataclass
+from typing import TYPE_CHECKING
+
+import fileseq
+from tank import LogManager
+from tank.authentication import flow_auth
+from tank.pipelineconfig import PipelineConfiguration
+from tank.util import yaml_cache
+
+if TYPE_CHECKING:
+ from tank.context import Context
+
+from tank_vendor.flow_integration_sdk import globals, schema, storage
+from tank_vendor.flow_integration_sdk.exceptions import FlowError
+from tank_vendor.flow_integration_sdk.objects import FlowProject
+from tank_vendor.flow_integration_sdk.publish import (
+ CommentComponentSpec,
+ ComponentSpec,
+ FileSeqComponentSpec,
+ SourceComponentSpec,
+ ThumbnailComponentSpec,
+ TypeComponentSpec,
+)
+from tank_vendor.flow_integration_sdk.schema_builder import create_pipeline_schemas
+from tank_vendor.flow_integration_sdk.utils import trace
+
+from .constants import FLOW_SCHEMA_CONFIG_PATH, FLOW_SCHEMA_VERSION_FIELD
+
+logger = LogManager.get_logger(__name__)
+
+
+@dataclass
+class BaseInputs:
+ def format_str(self, tab: int = 0):
+ s = ""
+ for prop, value in self.__dict__.items():
+ s += "\t" * tab + f"{prop} = {value}\n"
+ return s.rstrip()
+
+ def validate(self):
+ """Do validation of input values."""
+ pass
+
+ def asdict(self):
+ return asdict(self)
+
+ def log_intro(self, intro_message: str):
+ """Log an intro message along with the values of
+ input object.
+ """
+
+ msg = f"""
+ ---------------------------------------
+ {intro_message} with inputs:
+{self.format_str(tab=3)}
+ --------------------------------------"""
+ logger.info(msg)
+
+
+def get_config_flow_settings(pipeline_config: PipelineConfiguration) -> dict:
+ """Retrieve Flow settings from config.
+
+ Args:
+ pipeline_config: PipelineConfiguration object.
+ This is used to determine the location from which to
+ search for the "flow.yml" config file that contains Flow settings.
+ Config file will be searched in two locations:
+ * {CONFIG ROOT}/config/core/flow.yml
+ * {CONFIG ROOT}/core/flow.yml
+
+ Returns:
+ Dictionary result of reading the "flow.yml" config file if found.
+ Otherwise, return empty dictionary.
+ """
+ config_root = pipeline_config.get_config_location()
+ if not config_root:
+ logger.error("Pipeline config has no config location.")
+ return {}
+
+ # Config path if reading from cached config
+ override_path_cache = os.path.join(
+ config_root,
+ "config",
+ "core",
+ "flow.yml",
+ )
+ # Config path if reading form dev descriptor
+ override_path_dev = os.path.join(
+ config_root,
+ "core",
+ "flow.yml",
+ )
+
+ logger.info(f"Checking for Flow config: {override_path_dev}")
+ if os.path.exists(override_path_dev):
+ return yaml_cache.g_yaml_cache.get(override_path_dev) or {}
+ else:
+ logger.info(f"Checking for Flow config: {override_path_cache}")
+ if os.path.exists(override_path_cache):
+ return yaml_cache.g_yaml_cache.get(override_path_cache) or {}
+ else:
+ logger.error("Flow config could not be found!")
+ return {}
+
+
+def init_flow(
+ pipeline_config: PipelineConfiguration,
+ sg_connection,
+ context: Context,
+):
+ """Do session set up + schema provisioning for the Flow Integration SDK.
+
+ Args:
+ pipeline_config: PipelineConfiguration object, used to read flow
+ settings from config.
+ sg_connection: Shotgun connection, used to write the schema config
+ version back to SG.
+ context: The current Toolkit context, used to read the flow project id,
+ schema version, and SG project id.
+
+ Raises:
+ RuntimeError
+ """
+ logger.info("Doing Flow Integration SDK initialization...")
+ flow_project_id = context.flow_project_id
+ sg_project_id = context.project["id"]
+ logger.info(f"Flow AM Project ID: {flow_project_id}")
+
+ # Read flow settings from config
+ settings = get_config_flow_settings(pipeline_config)
+ flow_endpoint = settings.get("endpoint")
+ flow_web_url = settings.get("web_url")
+ flow_sandbox_root = settings.get("sandbox_root")
+ flow_storage_root = settings.get("storage_root")
+
+ # Configure logger
+ globals.set_logger_callback(LogManager().get_logger)
+ # Initialize MEDM GQL client
+ globals.init_client(flow_endpoint, flow_auth.FlowAuthenticationHandler())
+ # Store web url
+ globals.set_webapp_url(flow_web_url)
+ # Set session collection
+ try:
+ project = FlowProject(flow_project_id)
+ except FlowError as exc:
+ msg = f"Could not complete Flow initialization: {exc}"
+ raise RuntimeError(msg) from exc
+ globals.init_session_collection(
+ project.collection_id, project.organization_id, project.group_id
+ )
+ # Cache custom schema config
+ try:
+ schema.cache_schema_config(FLOW_SCHEMA_CONFIG_PATH)
+ except (RuntimeError, ValueError) as exc:
+ msg = "Could not complete Flow initialization: {exc}"
+ raise RuntimeError(msg) from exc
+ # Configure storage roots
+ storage.set_sandbox_root(flow_sandbox_root, create_dir=True)
+ storage.set_storage_root(flow_storage_root, create_dir=True)
+
+ # Provision pipeline schemas for CPA collections only
+ session_collection = globals.get_session_collection()
+ if not session_collection.is_cpa_collection():
+ logger.info("Skipping pipeline schema provisioning - not a CPA collection.")
+ else:
+ current_version = schema.get_schema_config_version(FLOW_SCHEMA_CONFIG_PATH)
+ if context.flow_schema_version == current_version:
+ logger.info(
+ f"Schema config version {current_version} matches. "
+ "Skipping schema provisioning."
+ )
+ else:
+ try:
+ create_pipeline_schemas(
+ project_id=flow_project_id,
+ config_path=FLOW_SCHEMA_CONFIG_PATH,
+ )
+ sg_connection.update(
+ "Project",
+ sg_project_id,
+ {FLOW_SCHEMA_VERSION_FIELD: current_version},
+ )
+ except (
+ FlowError,
+ RuntimeError,
+ ValueError,
+ KeyError,
+ FileNotFoundError,
+ ) as exc:
+ msg = f"Could not complete Flow schema provisioning: {exc}"
+ raise RuntimeError(msg) from exc
+
+ logger.info("Initialization complete!")
+
+
+@trace
+def create_components_for_publish(
+ source_paths: list[str] | None = None,
+ thumbnail_path: str = "",
+ comment: str = "",
+ type_ids: list[str] | None = None,
+) -> list[ComponentSpec]:
+ """Generate the components relevant to publish a new revision.
+
+ Args:
+ source_paths: Optional list of local paths to the source files.
+ thumbnail_path: Optional path to thumbnail file.
+ comment: Generate a comment component with given comment string.
+ Should only be included if the publish method does not
+ explicitly accept a `comment` parameter.
+ type_ids: A list of type ids to be converted into type components.
+ This is only relevant if publishing a new asset direct to remote
+ (i.e. not going through sandbox).
+ """
+ # Source component contains the source file
+ components: list[ComponentSpec] = []
+ if source_paths:
+ source_comp = SourceComponentSpec(*source_paths)
+ components.append(source_comp)
+ # Thumbnail component contains the thumbnail file
+ if thumbnail_path:
+ thumbnail_comp = ThumbnailComponentSpec(thumbnail_path)
+ components.append(thumbnail_comp)
+ # Need to add a special file sequence type component if there are
+ # multiple source files
+ if source_paths and len(source_paths) > 1:
+ sequences = fileseq.findSequencesInList(source_paths)
+ if len(sequences) > 1:
+ msg = "Ambiguous file sequence provided for publish."
+ msg += " Multiple file sequences have been detected in the"
+ msg += " source path list. Please input files with common"
+ msg += " base name with frame padding (e.g. .####.)."
+ raise FlowError(msg)
+ # Determine file format
+ pad_len = sequences[0].getPaddingNum(sequences[0].padding())
+ frame_expr = f"%0{pad_len}d"
+ file_format = os.path.basename(sequences[0].frame(frame_expr))
+ components.append(
+ FileSeqComponentSpec(
+ type_id=schema.get_schema_id(globals.FILE_SEQ_TYPE),
+ frame_start=sequences[0].start(),
+ frame_end=sequences[0].end(),
+ frame_set=str(sequences[0].frameSet()),
+ file_format=file_format,
+ name=globals.FILE_SEQ_COMP,
+ )
+ )
+ # Add comment if provided
+ if comment:
+ comment_comp = CommentComponentSpec(comment=comment)
+ components.append(comment_comp)
+ # Add type components if specified
+ type_ids = [] if type_ids is None else type_ids
+ for i, type_id in enumerate(type_ids):
+ # NOTE: component names must be unique!
+ type_comp = TypeComponentSpec(type_id=type_id, name=f"Type {i}")
+ components.append(type_comp)
+ return components
+
+
+def open_explorer(dir_path: str):
+ """Open a file explorer to the directory path provided.
+
+ Args:
+ dir_path: Full path to local directory.
+ """
+ return webbrowser.open(f"file:///{dir_path}")
+
+
+def search_file_expression(file_path: str):
+ """Search file system for files matching expression that
+ uses frame padding of the format "%0Nd" where N is an integer
+ denoting number of digits in frame padding.
+
+ If path does not contain a frame padding, search for the path as is.
+ """
+ expr = r"(?P.+\.)%0(?P\d)d(?P\..+)"
+ m = re.match(expr, file_path)
+ if m:
+ frame_pad = int(m.group("frame_pad"))
+ file_expr = m.group("base_path") + frame_pad * "[0-9]" + m.group("ext")
+ file_list = glob.glob(file_expr)
+ return file_list
+ elif os.path.exists(file_path):
+ return [file_path]
+ return []
diff --git a/python/tk-core/python/tank/folder/__init__.py b/python/tk-core/python/tank/folder/__init__.py
index 26d2fe81..63352810 100644
--- a/python/tk-core/python/tank/folder/__init__.py
+++ b/python/tk-core/python/tank/folder/__init__.py
@@ -13,5 +13,5 @@
"""
-from .operations import process_filesystem_structure, synchronize_folders
from .configuration import read_ignore_files
+from .operations import process_filesystem_structure, synchronize_folders
diff --git a/python/tk-core/python/tank/folder/configuration.py b/python/tk-core/python/tank/folder/configuration.py
index dfc9d369..f79811c8 100644
--- a/python/tk-core/python/tank/folder/configuration.py
+++ b/python/tk-core/python/tank/folder/configuration.py
@@ -13,22 +13,21 @@
"""
-import os
import fnmatch
+import os
+from ..errors import TankError, TankUnreadableFileError
+from ..util import yaml_cache
from .folder_types import (
- Static,
- ListField,
Entity,
+ ListField,
Project,
- UserWorkspace,
ShotgunStep,
ShotgunTask,
+ Static,
+ UserWorkspace,
)
-from ..errors import TankError, TankUnreadableFileError
-from ..util import yaml_cache
-
def read_ignore_files(schema_config_path):
"""
@@ -336,7 +335,7 @@ def _process_config_r(self, parent_node, parent_path):
self._process_config_r(cur_node, full_path)
# process symlinks
- for (path, target, metadata) in self._get_symlinks_in_folder(parent_path):
+ for path, target, metadata in self._get_symlinks_in_folder(parent_path):
parent_node.add_symlink(path, target, metadata)
# now process all files and add them to the parent_node token
diff --git a/python/tk-core/python/tank/folder/folder_io.py b/python/tk-core/python/tank/folder/folder_io.py
index 96381d82..8b7c1bf1 100644
--- a/python/tk-core/python/tank/folder/folder_io.py
+++ b/python/tk-core/python/tank/folder/folder_io.py
@@ -16,10 +16,9 @@
"""
-from . import constants
from ..errors import TankError
-
from ..path_cache import PathCache
+from . import constants
class FolderIOReceiver(object):
diff --git a/python/tk-core/python/tank/folder/folder_types/__init__.py b/python/tk-core/python/tank/folder/folder_types/__init__.py
index 7b4124ec..27d05683 100644
--- a/python/tk-core/python/tank/folder/folder_types/__init__.py
+++ b/python/tk-core/python/tank/folder/folder_types/__init__.py
@@ -13,11 +13,11 @@
folder types that can be created.
"""
+from .entity import Entity
from .errors import EntityLinkTypeMismatch
-from .static import Static
from .listfield import ListField
-from .entity import Entity
from .project import Project
-from .user import UserWorkspace
+from .static import Static
from .step import ShotgunStep
from .task import ShotgunTask
+from .user import UserWorkspace
diff --git a/python/tk-core/python/tank/folder/folder_types/base.py b/python/tk-core/python/tank/folder/folder_types/base.py
index ac84ca82..ca87ba25 100644
--- a/python/tk-core/python/tank/folder/folder_types/base.py
+++ b/python/tk-core/python/tank/folder/folder_types/base.py
@@ -8,8 +8,8 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import os
import copy
+import os
from .expression_tokens import SymlinkToken
@@ -174,9 +174,9 @@ def create_folders(
# before recursing down our specific recursion path, make sure all static content
# has been created at this level in the folder structure
- static_children = [ch for ch in self._children if ch.is_dynamic() == False]
+ static_children = [ch for ch in self._children if ch.is_dynamic() is False]
- for (created_folder, sg_data_dict) in created_data:
+ for created_folder, sg_data_dict in created_data:
# first process the static folders
for cp in static_children:
@@ -208,7 +208,7 @@ def create_folders(
# no explicit list! instead process all children.
# run the folder creation for all new folders created and for all
# configuration children
- for (created_folder, sg_data_dict) in created_data:
+ for created_folder, sg_data_dict in created_data:
for cp in self._children:
cp.create_folders(
io_receiver,
@@ -255,7 +255,7 @@ def _should_item_be_processed(self, engine_str, is_primary):
dc_value = self._config_metadata.get("defer_creation")
# if defer_creation config param not specified or None means we
# can always go ahead with folder creation!!
- if dc_value is None or dc_value == False:
+ if dc_value is None or dc_value is False:
# deferred value not specified means proceed with creation!
return True
@@ -266,7 +266,7 @@ def _should_item_be_processed(self, engine_str, is_primary):
return False
# now handle all cases where we have an engine_str and some sort of deferred behaviour.
- if dc_value == True:
+ if dc_value is True:
# defer create for all engine_strs!
return True
diff --git a/python/tk-core/python/tank/folder/folder_types/entity.py b/python/tk-core/python/tank/folder/folder_types/entity.py
index cd0a5b4b..97fb7735 100644
--- a/python/tk-core/python/tank/folder/folder_types/entity.py
+++ b/python/tk-core/python/tank/folder/folder_types/entity.py
@@ -8,16 +8,15 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import os
import copy
+import os
from ...errors import TankError
from ...util import shotgun_entity
-
-from .errors import EntityLinkTypeMismatch
from .base import Folder
+from .errors import EntityLinkTypeMismatch
from .expression_tokens import FilterExpressionToken
-from .util import translate_filter_tokens, resolve_shotgun_filters
+from .util import resolve_shotgun_filters, translate_filter_tokens
class Entity(Folder):
@@ -117,7 +116,7 @@ def _should_item_be_processed(self, engine_str, is_primary):
"""
# check our special condition - is this node set to be auto-created with its parent node?
# note that primary nodes are always created with their parent nodes!
- if is_primary == False and self._create_with_parent == False:
+ if is_primary is False and self._create_with_parent is False:
return False
# base class implementation
diff --git a/python/tk-core/python/tank/folder/folder_types/expression_tokens.py b/python/tk-core/python/tank/folder/folder_types/expression_tokens.py
index 1a3d1a3e..a7d7ef2e 100644
--- a/python/tk-core/python/tank/folder/folder_types/expression_tokens.py
+++ b/python/tk-core/python/tank/folder/folder_types/expression_tokens.py
@@ -59,7 +59,7 @@ def resolve_token(self, folder_obj, sg_data):
# - listfield tokens contain the value
# - sg entity values contain the value in a compute_name key
name_value = None
- for (field_name, field_value) in sg_data.items():
+ for field_name, field_value in sg_data.items():
if token == field_name:
if isinstance(field_value, dict):
diff --git a/python/tk-core/python/tank/folder/folder_types/listfield.py b/python/tk-core/python/tank/folder/folder_types/listfield.py
index 57ccf0a0..62647253 100644
--- a/python/tk-core/python/tank/folder/folder_types/listfield.py
+++ b/python/tk-core/python/tank/folder/folder_types/listfield.py
@@ -8,12 +8,11 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import os
import copy
+import os
from ...errors import TankError
from ...util import shotgun_entity
-
from .base import Folder
from .expression_tokens import FilterExpressionToken
@@ -106,7 +105,7 @@ def _should_item_be_processed(self, engine_str, is_primary):
# list fields are only created when they are on the primary path,
# e.g. we don't recurse down to create asset types when shots are created,
# but only when assets are created.
- if is_primary == False and self._create_with_parent == False:
+ if is_primary is False and self._create_with_parent is False:
return False
# base class implementation
@@ -172,7 +171,7 @@ def _create_folders_impl(self, io_receiver, parent_path, sg_data):
chunks = self._field_name.split(".")
entity_type = chunks[1]
field_name = chunks[2]
- except:
+ except Exception:
msg = (
"Folder creation error: Cannot resolve the field expression %s."
% self._field_name
@@ -190,7 +189,9 @@ def _create_folders_impl(self, io_receiver, parent_path, sg_data):
# validate that the data type is of type list
field_type = resp[field_name]["data_type"]["value"]
except Exception as e:
- msg = "Folder creation error: Cannot retrieve values for PTR list field "
+ msg = (
+ "Folder creation error: Cannot retrieve values for PTR list field "
+ )
msg += "%s.%s. Error reported: %s" % (entity_type, field_name, e)
raise TankError(msg)
diff --git a/python/tk-core/python/tank/folder/folder_types/project.py b/python/tk-core/python/tank/folder/folder_types/project.py
index bb472a0a..9bbb6b51 100644
--- a/python/tk-core/python/tank/folder/folder_types/project.py
+++ b/python/tk-core/python/tank/folder/folder_types/project.py
@@ -8,10 +8,10 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from ...errors import TankError
-from .entity import Entity
from ... import constants
+from ...errors import TankError
from ...log import LogManager
+from .entity import Entity
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/folder/folder_types/static.py b/python/tk-core/python/tank/folder/folder_types/static.py
index 41d85157..79c64dd2 100644
--- a/python/tk-core/python/tank/folder/folder_types/static.py
+++ b/python/tk-core/python/tank/folder/folder_types/static.py
@@ -11,10 +11,9 @@
import os
from ...errors import TankError
-
from .base import Folder
from .entity import Entity
-from .util import translate_filter_tokens, resolve_shotgun_filters
+from .util import resolve_shotgun_filters, translate_filter_tokens
class Static(Folder):
@@ -136,7 +135,7 @@ def _should_item_be_processed(self, engine_str, is_primary):
"""
# check our special condition - is this node set to be auto-created with its parent node?
# note that primary nodes are always created with their parent nodes!
- if is_primary == False and self._create_with_parent == False:
+ if is_primary is False and self._create_with_parent is False:
return False
# base class implementation
diff --git a/python/tk-core/python/tank/folder/folder_types/step.py b/python/tk-core/python/tank/folder/folder_types/step.py
index f22f6dbf..733dc206 100644
--- a/python/tk-core/python/tank/folder/folder_types/step.py
+++ b/python/tk-core/python/tank/folder/folder_types/step.py
@@ -11,10 +11,9 @@
import os
from ...errors import TankError
-
from .entity import Entity
+from .expression_tokens import CurrentStepExpressionToken, FilterExpressionToken
from .util import translate_filter_tokens
-from .expression_tokens import FilterExpressionToken, CurrentStepExpressionToken
class ShotgunStep(Entity):
@@ -148,7 +147,7 @@ def __init__(
# shot, we want all the steps to be created at the same time.
# however, if we have create_with_parent set to False, we only want to create
# this node if we are creating folders for a task.
- if create_with_parent != True:
+ if create_with_parent is False:
# do not auto-create with parent - only create when a task has been specified.
# create an expression object to represent the current step.
# we pass in the field which is the connection between the task and the step field
diff --git a/python/tk-core/python/tank/folder/folder_types/task.py b/python/tk-core/python/tank/folder/folder_types/task.py
index c4f6daaf..1840c8ec 100644
--- a/python/tk-core/python/tank/folder/folder_types/task.py
+++ b/python/tk-core/python/tank/folder/folder_types/task.py
@@ -11,11 +11,10 @@
import os
from ...errors import TankError
-
from .entity import Entity
-from .util import translate_filter_tokens
-from .expression_tokens import FilterExpressionToken, CurrentTaskExpressionToken
+from .expression_tokens import CurrentTaskExpressionToken, FilterExpressionToken
from .step import ShotgunStep
+from .util import translate_filter_tokens
class ShotgunTask(Entity):
@@ -160,7 +159,7 @@ def __init__(
# shot, we want all the tasks to be created at the same time.
# however, if we have create_with_client set to False, we only want to create
# this node if we are creating folders for a task.
- if create_with_parent != True:
+ if create_with_parent is False:
# do not auto-create with parent - only create when a task has been specified.
# create an expression object to represent the current step
current_task_id_token = CurrentTaskExpressionToken()
diff --git a/python/tk-core/python/tank/folder/folder_types/user.py b/python/tk-core/python/tank/folder/folder_types/user.py
index cdd27589..e4c8f192 100644
--- a/python/tk-core/python/tank/folder/folder_types/user.py
+++ b/python/tk-core/python/tank/folder/folder_types/user.py
@@ -8,9 +8,8 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from ...util import login
from ...errors import TankError
-
+from ...util import login
from .entity import Entity
from .util import translate_filter_tokens
diff --git a/python/tk-core/python/tank/folder/folder_types/util.py b/python/tk-core/python/tank/folder/folder_types/util.py
index 8e435665..f7873d70 100644
--- a/python/tk-core/python/tank/folder/folder_types/util.py
+++ b/python/tk-core/python/tank/folder/folder_types/util.py
@@ -15,11 +15,10 @@
import copy
from ...errors import TankError
-
from .expression_tokens import (
- FilterExpressionToken,
CurrentStepExpressionToken,
CurrentTaskExpressionToken,
+ FilterExpressionToken,
)
diff --git a/python/tk-core/python/tank/folder/operations.py b/python/tk-core/python/tank/folder/operations.py
index 506e2a2c..dfa29cae 100644
--- a/python/tk-core/python/tank/folder/operations.py
+++ b/python/tk-core/python/tank/folder/operations.py
@@ -13,10 +13,10 @@
"""
+from ..errors import TankError
from .configuration import FolderConfiguration
from .folder_io import FolderIOReceiver
from .folder_types import EntityLinkTypeMismatch
-from ..errors import TankError
def create_single_folder_item(
diff --git a/python/tk-core/python/tank/hook.py b/python/tk-core/python/tank/hook.py
index 12b2502e..76eb3807 100644
--- a/python/tk-core/python/tank/hook.py
+++ b/python/tk-core/python/tank/hook.py
@@ -11,18 +11,20 @@
"""
Defines the base class for all Tank Hooks.
"""
+
+import inspect
+import logging
import os
import sys
-import logging
-import inspect
import threading
-from .util.loader import load_plugin
+
from . import LogManager
from .errors import (
TankError,
TankFileDoesNotExistError,
TankHookMethodDoesNotExistError,
)
+from .util.loader import load_plugin
log = LogManager.get_logger(__name__)
@@ -447,7 +449,7 @@ def some_method(self):
try:
engine = self.__parent.engine
- except:
+ except Exception:
raise TankError(
"Cannot load framework %s for %r - it does not have a "
"valid engine property!" % (framework_instance_name, self.__parent)
diff --git a/python/tk-core/python/tank/log.py b/python/tk-core/python/tank/log.py
index 012a5962..87f5363a 100644
--- a/python/tk-core/python/tank/log.py
+++ b/python/tk-core/python/tank/log.py
@@ -220,15 +220,14 @@ def my_method():
For more information, see https://docs.python.org/2/library/logging.handlers.html#module-logging.handlers
"""
-
import logging
-from logging.handlers import RotatingFileHandler
import os
-import sys
import time
-import weakref
import uuid
+import weakref
from functools import wraps
+from logging.handlers import RotatingFileHandler
+
from . import constants
@@ -303,7 +302,7 @@ def doRollover(self):
try:
os.rename(self.baseFilename, temp_backup_name)
- except:
+ except Exception:
# It failed, so we'll simply append from now on.
log.debug(
"Cannot rotate log file '%s'. Logging will continue to this file, "
@@ -318,7 +317,7 @@ def doRollover(self):
# so doRollover can do its work.
try:
os.rename(temp_backup_name, self.baseFilename)
- except:
+ except Exception:
# For some reason we couldn't move the backup in its place.
log.debug(
"Unexpected issue while rotating log file '%s'. Logging will continue to this file, "
@@ -338,7 +337,7 @@ def doRollover(self):
# disable rollover and append to the current log.
try:
RotatingFileHandler.doRollover(self)
- except:
+ except Exception:
# Something probably failed trying to rollover the backups,
# since the code above proved that in theory the main log file
# should be renamable. In any case, we didn't succeed in renaming,
@@ -595,7 +594,7 @@ def _get_global_debug(self):
@property
def log_file(self):
- """ Full path to the current log file or None if logging is not active. """
+ """Full path to the current log file or None if logging is not active."""
return self._std_file_handler_log_file
@property
@@ -832,6 +831,8 @@ def initialize_base_file_handler_from_path(self, log_file):
# this should not be changed, but any filtering
# should happen via log handlers
sgtk_root_logger.setLevel(logging.DEBUG)
+
+
#
# create a 'nop' log handler to be attached.
# this is to avoid warnings being reported that
diff --git a/python/tk-core/python/tank/path_cache.py b/python/tk-core/python/tank/path_cache.py
index 600893c1..70264edf 100644
--- a/python/tk-core/python/tank/path_cache.py
+++ b/python/tk-core/python/tank/path_cache.py
@@ -15,16 +15,15 @@
"""
import collections
-import sqlite3
-import sys
-import os
import itertools
import json
+import os
+import sqlite3
+import sys
-from .platform.engine import show_global_busy, clear_global_busy
-from . import constants
+from . import LogManager, constants
from .errors import TankError
-from . import LogManager
+from .platform.engine import clear_global_busy, show_global_busy
from .util.login import get_current_user
# Shotgun field definitions to store the path cache data
@@ -115,8 +114,7 @@ def _init_db(self):
# up the default page size somewhat (from 4k -> 8k) to improve
# performance. See https://sqlite.org/pragma.html#pragma_page_size
- c.executescript(
- """
+ c.executescript("""
PRAGMA page_size=8192;
CREATE TABLE path_cache (entity_type text, entity_id integer, entity_name text, root text, path text, primary_entity integer);
@@ -134,8 +132,7 @@ def _init_db(self):
CREATE UNIQUE INDEX shotgun_status_id ON shotgun_status(path_cache_id);
CREATE INDEX shotgun_status_shotgun_id ON shotgun_status(shotgun_id);
- """
- )
+ """)
self._connection.commit()
else:
@@ -160,8 +157,7 @@ def _init_db(self):
# check for primary entity field - this was added back in 0.12.x
if "primary_entity" not in field_names:
- c.executescript(
- """
+ c.executescript("""
ALTER TABLE path_cache ADD COLUMN primary_entity integer;
UPDATE path_cache SET primary_entity=1;
@@ -170,8 +166,7 @@ def _init_db(self):
DROP INDEX IF EXISTS path_cache_all;
CREATE UNIQUE INDEX IF NOT EXISTS path_cache_all ON path_cache(entity_type, entity_id, root, path, primary_entity);
- """
- )
+ """)
self._connection.commit()
@@ -501,7 +496,10 @@ def _upload_cache_data_to_shotgun(self, data, event_log_desc):
sg_batch_data.append(req)
# push to shotgun in a single xact
- log.debug("Uploading %s path entries to Flow Production Tracking..." % len(sg_batch_data))
+ log.debug(
+ "Uploading %s path entries to Flow Production Tracking..."
+ % len(sg_batch_data)
+ )
try:
response = self._tk.shotgun.batch(sg_batch_data)
@@ -513,14 +511,23 @@ def _upload_cache_data_to_shotgun(self, data, event_log_desc):
# now create a dictionary where input path cache rowid (path_cache_row_id)
# is mapped to the shotgun ids that were just created
+ def _normalize_path(p):
+ # Local cache paths use os.sep (backslashes on Windows, per
+ # _sanitize_path) while paths round-tripped through Shotgun's
+ # local_path field come back with forward slashes. normpath
+ # unifies separators and collapses redundant components;
+ # normcase lowercases on case-insensitive filesystems (Windows).
+ return os.path.normcase(os.path.normpath(p)) if p else p
+
def _rowid_from_filesystem_entity(fsl_entity):
path = fsl_entity[SG_PATH_FIELD]["local_path"]
+ normalized_path = _normalize_path(path)
for d in data:
# We need to match not only the path but also the entity type when associating the FilesystemLocation
# entities with the local cache's row ids, because Task folders generate two entries with the same
# path, one for the Task and one for the Step.
if (
- d["path"] == path
+ _normalize_path(d["path"]) == normalized_path
and d["entity"]["type"] == fsl_entity["linked_entity_type"]
):
return d["path_cache_row_id"]
@@ -896,7 +903,9 @@ def _replay_folder_entities(self, cursor, max_event_log_id):
- path
"""
- log.debug("Fetching already registered folders from Flow Production Tracking...")
+ log.debug(
+ "Fetching already registered folders from Flow Production Tracking..."
+ )
sg_data = self._get_filesystem_location_entities(folder_ids=None)
@@ -1320,19 +1329,19 @@ def add_mappings(self, data, entity_type, entity_ids):
)
# now push to shotgun
- (event_log_id, sg_id_lookup) = self._upload_cache_data_to_shotgun(
+ event_log_id, sg_id_lookup = self._upload_cache_data_to_shotgun(
data_for_sg, desc
)
self._update_last_event_log_synced(c, event_log_id)
# and indicate in the path cache that all these records have been pushed
- for (pc_row_id, sg_id) in sg_id_lookup.items():
+ for pc_row_id, sg_id in sg_id_lookup.items():
c.execute(
"INSERT INTO shotgun_status(path_cache_id, shotgun_id) "
"VALUES(?, ?)",
(pc_row_id, sg_id),
)
- except:
+ except Exception:
# error processing shotgun. Make sure we roll back the sqlite path cache
# transaction
self._connection.rollback()
@@ -1736,7 +1745,9 @@ def ensure_all_entries_are_in_shotgun(self):
SG_BATCH_SIZE = 50
log.info("")
- log.info("Step 1 - Downloading current path data from Flow Production Tracking...")
+ log.info(
+ "Step 1 - Downloading current path data from Flow Production Tracking..."
+ )
sg_data = self._tk.shotgun.find(
SHOTGUN_ENTITY,
@@ -1764,18 +1775,14 @@ def ensure_all_entries_are_in_shotgun(self):
try:
# get all records and check each one against shotgun.
- pc_data = list(
- cursor.execute(
- """select pc.rowid,
+ pc_data = list(cursor.execute("""select pc.rowid,
pc.entity_type,
pc.entity_id,
pc.entity_name,
pc.root,
pc.path,
pc.primary_entity
- from path_cache pc"""
- )
- )
+ from path_cache pc"""))
finally:
cursor.close()
@@ -1849,11 +1856,13 @@ def ensure_all_entries_are_in_shotgun(self):
ids_to_look_for[sg_record["entity"]["type"]].append(sg_record)
# now query shotgun for each of the types
- ids_in_shotgun = {}
sg_valid_records = []
- for (et, sg_records_for_et) in ids_to_look_for.items():
+ for et, sg_records_for_et in ids_to_look_for.items():
- log.info(" - Checking %s %ss in Flow Production Tracking..." % (len(sg_records_for_et), et))
+ log.info(
+ " - Checking %s %ss in Flow Production Tracking..."
+ % (len(sg_records_for_et), et)
+ )
# get the ids from shotgun for the current et.
sg_ids = [x["entity"]["id"] for x in sg_records_for_et]
@@ -1889,5 +1898,6 @@ def ensure_all_entries_are_in_shotgun(self):
log.info("")
log.info(
- "Migration complete. %s records created in Flow Production Tracking" % len(sg_valid_records)
+ "Migration complete. %s records created in Flow Production Tracking"
+ % len(sg_valid_records)
)
diff --git a/python/tk-core/python/tank/pipelineconfig.py b/python/tk-core/python/tank/pipelineconfig.py
index d90531a1..9656faa3 100644
--- a/python/tk-core/python/tank/pipelineconfig.py
+++ b/python/tk-core/python/tank/pipelineconfig.py
@@ -12,26 +12,20 @@
Encapsulates the pipeline configuration and helps navigate and resolve paths
across storages, configurations etc.
"""
-import os
+
import glob
+import os
import pickle
from tank_vendor import yaml
+from . import LogManager, constants, hook, pipelineconfig_utils, template_includes
+from .descriptor import Descriptor, create_descriptor, descriptor_uri_to_dict
from .errors import TankError, TankUnreadableFileError
-from .util.version import is_version_older
-from . import constants
from .platform.environment import InstalledEnvironment, WritableEnvironment
-from .util import shotgun, yaml_cache
-from .util import ShotgunPath
-from .util import StorageRoots
+from .util import ShotgunPath, StorageRoots, shotgun, yaml_cache
from .util.pickle import retrieve_env_var_pickled
-from . import hook
-from . import pipelineconfig_utils
-from . import template_includes
-from . import LogManager
-
-from .descriptor import Descriptor, create_descriptor, descriptor_uri_to_dict
+from .util.version import is_version_older
log = LogManager.get_logger(__name__)
@@ -664,7 +658,7 @@ def get_local_storage_for_root(self, root_name):
return None
# get the storage data for required roots
- (mapped_roots, unmapped_roots) = self.get_local_storage_mapping()
+ mapped_roots, unmapped_roots = self.get_local_storage_mapping()
if root_name in mapped_roots:
return mapped_roots[root_name]
@@ -1127,7 +1121,7 @@ def get_environments(self):
env_names = []
for f in glob.glob(self.get_environment_path("*")):
file_name = os.path.basename(f)
- (name, _) = os.path.splitext(file_name)
+ name, _ = os.path.splitext(file_name)
env_names.append(name)
return env_names
@@ -1215,7 +1209,7 @@ def execute_core_hook_internal(self, hook_name, parent, **kwargs):
try:
return_value = hook.execute_hook(hook_path, parent, **kwargs)
- except:
+ except Exception:
# log the full callstack to make sure that whatever the
# calling code is doing, this error is logged to help
# with troubleshooting and support
@@ -1249,9 +1243,6 @@ def execute_core_hook_method_internal(
)
hook_paths = [os.path.join(hooks_path, file_name)]
- # the hook.method display name used when logging the metric
- hook_method_display = "%s.%s" % (hook_name, method_name)
-
# now add a custom hook if that exists.
hook_folder = self.get_core_hooks_location()
hook_path = os.path.join(hook_folder, file_name)
@@ -1262,7 +1253,7 @@ def execute_core_hook_method_internal(
return_value = hook.execute_hook_method(
hook_paths, parent, method_name, **kwargs
)
- except:
+ except Exception:
# log the full callstack to make sure that whatever the
# calling code is doing, this error is logged to help
# with troubleshooting and support
diff --git a/python/tk-core/python/tank/pipelineconfig_factory.py b/python/tk-core/python/tank/pipelineconfig_factory.py
index 7f61f4e7..02ed1d54 100644
--- a/python/tk-core/python/tank/pipelineconfig_factory.py
+++ b/python/tk-core/python/tank/pipelineconfig_factory.py
@@ -8,21 +8,14 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import os
import collections
+import os
import pprint
-
+from . import LogManager, constants, pipelineconfig_utils
from .errors import TankError, TankInitError
-from . import LogManager
-from .util import shotgun
-from .util import pickle
-from .util import filesystem
-from .util import ShotgunPath
-from . import constants
-from . import pipelineconfig_utils
from .pipelineconfig import PipelineConfiguration
-from .util import LocalFileStorageManager
+from .util import LocalFileStorageManager, ShotgunPath, filesystem, pickle, shotgun
log = LogManager.get_logger(__name__)
@@ -184,7 +177,8 @@ def _from_path(path, force_reread_shotgun_cache):
pc_registered_path = pipelineconfig_utils.get_config_install_location(path)
log.debug(
- "Resolved the official path registered in PTR to be %s." % pc_registered_path
+ "Resolved the official path registered in PTR to be %s."
+ % pc_registered_path
)
if pc_registered_path is None:
@@ -259,7 +253,7 @@ def _validate_and_create_pipeline_configuration(associated_pipeline_configs, sou
"""
# extract path data from the pipeline configuration shotgun data
# this will return lists of dicts with keys ``id``, (local os) ``path`` and ``project_id``
- (all_pc_data, primary_pc_data) = _get_pipeline_configuration_data(
+ all_pc_data, primary_pc_data = _get_pipeline_configuration_data(
associated_pipeline_configs
)
diff --git a/python/tk-core/python/tank/pipelineconfig_utils.py b/python/tk-core/python/tank/pipelineconfig_utils.py
index 7a5543dc..38819681 100644
--- a/python/tk-core/python/tank/pipelineconfig_utils.py
+++ b/python/tk-core/python/tank/pipelineconfig_utils.py
@@ -13,19 +13,15 @@
across storages, configurations etc.
"""
+import importlib.metadata
import os
from tank_vendor import yaml
-from . import constants
-from . import LogManager
-
-from .util import yaml_cache
-from .util import StorageRoots
-from .util import ShotgunPath
-from .util.shotgun import get_deferred_sg_connection
-
+from . import LogManager, constants
from .errors import TankError
+from .util import ShotgunPath, StorageRoots, yaml_cache
+from .util.shotgun import get_deferred_sg_connection
logger = LogManager.get_logger(__name__)
@@ -439,7 +435,16 @@ def get_currently_running_api_version():
info_yml_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "..", "info.yml")
)
- return _get_version_from_manifest(info_yml_path)
+ version = _get_version_from_manifest(info_yml_path)
+ if version is not None:
+ return version
+ # In a pip install the flat site-packages layout has no info.yml.
+ # Fall back to the installed distribution metadata; PEP 440 strips the
+ # leading 'v', so re-add it to match the info.yml convention.
+ try:
+ return "v" + importlib.metadata.version("sgtk")
+ except importlib.metadata.PackageNotFoundError:
+ return "unknown"
def get_core_api_version(core_install_root):
@@ -455,7 +460,19 @@ def get_core_api_version(core_install_root):
"""
# now try to get to the info.yml file to get the version number
info_yml_path = os.path.join(core_install_root, "install", "core", "info.yml")
- return _get_version_from_manifest(info_yml_path)
+ version = _get_version_from_manifest(info_yml_path)
+ if version is not None:
+ return version
+ # In a pip install the flat site-packages layout has no install/core/info.yml.
+ # If the requested core is the currently-running one, defer to
+ # get_currently_running_api_version which falls back to distribution metadata.
+ try:
+ current_core_root = get_path_to_current_core()
+ except TankError:
+ return "unknown"
+ if os.path.realpath(core_install_root) == os.path.realpath(current_core_root):
+ return get_currently_running_api_version()
+ return "unknown"
def _get_version_from_manifest(info_yml_path):
@@ -464,15 +481,14 @@ def _get_version_from_manifest(info_yml_path):
Returns the version given a manifest.
:param info_yml_path: path to manifest file.
- :returns: Always a string, 'unknown' if data cannot be found
+ :returns: Version string, or None if data cannot be found.
"""
try:
data = yaml_cache.g_yaml_cache.get(info_yml_path, deepcopy_data=False) or {}
- data = str(data.get("version", "unknown"))
+ version = data.get("version")
+ return str(version) if version is not None else None
except Exception:
- data = "unknown"
-
- return data
+ return None
def _get_core_descriptor_file(pipeline_config_path):
diff --git a/python/tk-core/python/tank/platform/__init__.py b/python/tk-core/python/tank/platform/__init__.py
index 801f4ba7..f772af42 100644
--- a/python/tk-core/python/tank/platform/__init__.py
+++ b/python/tk-core/python/tank/platform/__init__.py
@@ -9,28 +9,38 @@
# not expressly granted therein are reserved by Shotgun Software Inc.
+from . import events
+
+# base classes to derive from
+from .application import Application
+
# Engine management
-from .engine import start_engine, current_engine, get_engine_path, find_app_settings
+from .engine import (
+ Engine,
+ current_engine,
+ find_app_settings,
+ get_engine_path,
+ start_engine,
+)
from .errors import (
- TankEngineInitError,
- TankUnresolvedEnvironmentError,
TankContextChangeNotSupportedError,
+ TankEngineInitError,
TankMissingEngineError,
TankMissingEnvironmentFile,
+ TankUnresolvedEnvironmentError,
)
-from .software_launcher import create_engine_launcher
-
-# base classes to derive from
-from .application import Application
-from .engine import Engine
-from .software_launcher import SoftwareLauncher, SoftwareVersion, LaunchInformation
from .framework import Framework
+from .software_launcher import (
+ LaunchInformation,
+ SoftwareLauncher,
+ SoftwareVersion,
+ create_engine_launcher,
+)
from .util import (
change_context,
+ current_bundle,
get_framework,
+ get_logger,
import_framework,
- current_bundle,
restart,
- get_logger,
)
-from . import events
diff --git a/python/tk-core/python/tank/platform/application.py b/python/tk-core/python/tank/platform/application.py
index f97b5293..e37ba7a9 100644
--- a/python/tk-core/python/tank/platform/application.py
+++ b/python/tk-core/python/tank/platform/application.py
@@ -17,10 +17,9 @@
import sys
from ..util.loader import load_plugin
+from ..util.metrics import EventMetric
from . import constants
-
from .bundle import TankBundle
-from ..util.metrics import EventMetric
class Application(TankBundle):
diff --git a/python/tk-core/python/tank/platform/bundle.py b/python/tk-core/python/tank/platform/bundle.py
index 3347cff5..3f84e690 100644
--- a/python/tk-core/python/tank/platform/bundle.py
+++ b/python/tk-core/python/tank/platform/bundle.py
@@ -827,7 +827,7 @@ def __resolve_hook_path(self, settings_name, hook_expression):
# Entries are on the following form
#
# hook_publish_file:
- # type: hook
+ # type: hook
# description: Called when a file is published, e.g. copied from a work area to a publish area.
# default_value: maya_publish_file
#
@@ -1074,7 +1074,7 @@ def _get_engine_name(self):
# therefore get an error.
try:
engine_name = self.engine.name
- except:
+ except Exception:
engine_name = None
return engine_name
@@ -1133,7 +1133,7 @@ def _post_process_settings_r(tk, key, value, schema, bundle=None):
items = schema.get("items", {})
# note - we assign the original values here because we
processed_val = value
- for (key, value_schema) in items.items():
+ for key, value_schema in items.items():
processed_val[key] = _post_process_settings_r(
tk=tk, key=key, value=value[key], schema=value_schema, bundle=bundle
)
diff --git a/python/tk-core/python/tank/platform/engine.py b/python/tk-core/python/tank/platform/engine.py
index 636fc33b..c68d3f54 100644
--- a/python/tk-core/python/tank/platform/engine.py
+++ b/python/tk-core/python/tank/platform/engine.py
@@ -12,44 +12,39 @@
Defines the base class for all Tank Engines.
"""
-import os
-import sys
+from __future__ import annotations # required to support python 3.9
+
+import inspect
import logging
+import os
import pprint
+import sys
+import threading
import traceback
-import inspect
import weakref
-import threading
-from ..util.qt_importer import QtImporter
-from ..util.loader import load_plugin
-from .. import hook
+from tank.flowam import host as flow_host # noqa: F401 (used in return annotation)
+from tank.flowam import utils as flow_utils
+from .. import hook
from ..errors import TankError
+from ..log import LogManager
+from ..util import metrics_cache
+from ..util import sgre as re
+from ..util.loader import load_plugin
+from ..util.metrics import EventMetric, MetricsDispatcher
+from ..util.qt_importer import QtImporter
+from . import application, constants, events, qt, qt5, qt6, validation
+from .bundle import TankBundle
+from .engine_logging import ToolkitEngineHandler, ToolkitEngineLegacyHandler
from .errors import (
- TankEngineInitError,
- TankUnresolvedEnvironmentError,
TankContextChangeNotSupportedError,
TankEngineEventError,
+ TankEngineInitError,
TankMissingEngineError,
+ TankUnresolvedEnvironmentError,
)
-
-from ..util import sgre as re
-from ..util.metrics import EventMetric
-from ..util.metrics import MetricsDispatcher
-from ..util import metrics_cache
-from ..log import LogManager
-
-from . import application
-from . import constants
-from . import validation
-from . import events
-from . import qt
-from . import qt5
-from . import qt6
-from .bundle import TankBundle
from .framework import setup_frameworks
-from .engine_logging import ToolkitEngineHandler, ToolkitEngineLegacyHandler
# std core level logger
core_logger = LogManager.get_logger(__name__)
@@ -68,6 +63,11 @@ def __init__(self, tk, context, engine_instance_name, env):
Engine instances are constructed by the toolkit launch process
and various factory methods such as :meth:`start_engine`.
+ For Flow-enabled contexts (``context.flow_project_id`` set), this
+ also runs ``flow_utils.init_flow()`` to set up the Flow Integration
+ SDK session and provision pipeline schemas when the SG schema
+ config version does not match the bundled config.
+
:param tk: :class:`~sgtk.Sgtk` instance
:param context: A context object to define the context on disk where the engine is operating
:type context: :class:`~sgtk.Context`
@@ -105,6 +105,10 @@ def __init__(self, tk, context, engine_instance_name, env):
self._invoker = None
self._async_invoker = None
+ # Flow host object used in Flow asset management integration
+ # Engines that support Flow integration will initialize this value to an instance of FlowHost
+ self._flow_host = None
+
# get the engine settings
settings = self.__env.get_engine_settings(self.__engine_instance_name)
@@ -162,6 +166,18 @@ def __init__(self, tk, context, engine_instance_name, env):
self.log_debug("Appending to PYTHONPATH: %s" % python_path)
sys.path.append(python_path)
+ # Do Flow sdk initialization if context is configured with Flow
+ if context.flow_project_id:
+ try:
+ flow_utils.init_flow(
+ tk.pipeline_configuration,
+ tk.shotgun,
+ context,
+ )
+ except RuntimeError as exc:
+ self.log_error("Error occurred during Flow initialization!")
+ self.log_exception(exc)
+
# Note, 'init_engine()' is now deprecated and all derived initialisation should be
# done in either 'pre_app_init()' or 'post_app_init()'. 'init_engine()' is left
# in here to provide backwards compatibility with any legacy code.
@@ -322,7 +338,7 @@ def __open_log_folder(self):
if self.has_ui:
# only import QT if we have a UI
- from .qt import QtGui, QtCore
+ from .qt import QtCore, QtGui
url = QtCore.QUrl.fromLocalFile(LogManager().log_folder)
status = QtGui.QDesktopServices.openUrl(url)
@@ -410,10 +426,10 @@ def __show_busy(self, title, details):
if self.has_ui:
# we cannot import QT until here as non-ui engines don't have QT defined.
try:
+ from .qt import QtCore
from .qt.busy_dialog import BusyDialog
- from .qt import QtGui, QtCore
- except:
+ except Exception:
# QT import failed. This may be because someone has upgraded the core
# to the latest but are still running a earlier version of the
# Shotgun or Shell engine where the self.has_ui method is not
@@ -684,6 +700,18 @@ def register_toggle_debug_command(self):
"""
return True
+ @property
+ def flow_host(self) -> flow_host.FlowHost | None: # noqa: F811
+ """If the current context is Flow enabled, and the current
+ engine supports Flow integration, this value will be an instance of FlowHost.
+ The FlowHost class implements the required interface for the Flow asset management
+ integration to work within a dcc/engine.
+
+ If the current context is not Flow enabled, or the current engine has not had
+ Flow support added, the value will be None.
+ """
+ return self._flow_host
+
##########################################################################################
# init and destroy
@@ -774,7 +802,7 @@ def change_context(self, new_context):
# context change, it's that the target context isn't configured properly.
# As such, we'll let any exceptions (mostly TankEngineInitError) bubble
# up since it's a critical error case.
- (new_env, engine_descriptor) = get_env_and_descriptor_for_engine(
+ new_env, engine_descriptor = get_env_and_descriptor_for_engine(
engine_name=self.instance_name, tk=self.tank, context=new_context
)
@@ -808,7 +836,6 @@ def change_context(self, new_context):
# a context change. If one of them is not, then we remove it
# from the persistent app pool, which will force it to be
# rebuilt when apps are loaded later on.
- non_compliant_app_paths = []
for install_path, app_instances in self.__application_pool.items():
for instance_name, app in app_instances.items():
self.log_debug(
@@ -1198,7 +1225,7 @@ def _execute_in_main_thread(self, invoker_id, func, *args, **kwargs):
self._invoker if invoker_id == self._SYNC_INVOKER else self._async_invoker
)
if invoker:
- from .qt import QtGui, QtCore
+ from .qt import QtCore, QtGui
if (
QtGui.QApplication.instance()
@@ -1247,7 +1274,7 @@ def get_matching_commands(self, command_selectors):
"""
# return a dictionary grouping all the commands by instance name
commands_by_instance = {}
- for (name, value) in self.commands.items():
+ for name, value in self.commands.items():
app_instance = value["properties"].get("app")
if app_instance is None:
continue
@@ -1684,7 +1711,8 @@ def _create_widget(self, widget_class, *args, **kwargs):
self.logger.exception(exc)
import traceback
- from sgtk.platform.qt import QtGui, QtCore
+
+ from sgtk.platform.qt import QtCore, QtGui
# A very simple widget that ensures that the exception is visible and
# selectable should the user need to copy/paste it into a support
@@ -1984,7 +2012,7 @@ def _resolve_sg_stylesheet_tokens(self, style_sheet):
:returns: Stylesheet string with replacements applied
"""
processed_style_sheet = style_sheet
- for (token, value) in constants.SG_STYLESHEET_CONSTANTS.items():
+ for token, value in constants.SG_STYLESHEET_CONSTANTS.items():
processed_style_sheet = processed_style_sheet.replace(
"{{%s}}" % token, value
)
@@ -2128,7 +2156,12 @@ def _define_qt_base(self):
:returns: dict
"""
- base = {"qt_core": None, "qt_gui": None, "qt_web_engine_widgets": None, "dialog_base": None}
+ base = {
+ "qt_core": None,
+ "qt_gui": None,
+ "qt_web_engine_widgets": None,
+ "dialog_base": None,
+ }
try:
importer = QtImporter()
base["qt_core"] = importer.QtCore
@@ -2140,7 +2173,7 @@ def _define_qt_base(self):
base["dialog_base"] = None
base["wrapper"] = importer.binding
base["shiboken"] = importer.shiboken
- except:
+ except Exception:
self.log_exception(
"Default engine QT definition failed to find QT. "
@@ -2441,7 +2474,7 @@ def __create_invokers(self):
invoker = None
async_invoker = None
if self.has_ui:
- from .qt import QtGui, QtCore
+ from .qt import QtCore, QtGui
# Classes are defined locally since Qt might not be available.
if QtGui and QtCore:
@@ -2863,7 +2896,6 @@ def current_engine():
:returns: :class:`Engine` instance or None if no engine is running.
"""
- global g_current_engine
return g_current_engine
@@ -2892,7 +2924,7 @@ def get_engine_path(engine_name, tk, context):
"""
# get environment and engine location
try:
- (env, engine_descriptor) = get_env_and_descriptor_for_engine(
+ env, engine_descriptor = get_env_and_descriptor_for_engine(
engine_name, tk, context
)
except TankEngineInitError:
@@ -3068,7 +3100,7 @@ def _start_engine(engine_name, tk, old_context, new_context):
LogManager().initialize_base_file_handler(engine_name)
# get environment and engine location
- (env, engine_descriptor) = get_env_and_descriptor_for_engine(
+ env, engine_descriptor = get_env_and_descriptor_for_engine(
engine_name, tk, new_context
)
diff --git a/python/tk-core/python/tank/platform/environment.py b/python/tk-core/python/tank/platform/environment.py
index 1a2f176c..faa46240 100644
--- a/python/tk-core/python/tank/platform/environment.py
+++ b/python/tk-core/python/tank/platform/environment.py
@@ -13,19 +13,18 @@
"""
+import copy
import os
import sys
-import copy
from tank_vendor import yaml
-from .bundle import resolve_default_value
-from . import constants
-from . import environment_includes
-from ..errors import TankError, TankUnreadableFileError
-from .errors import TankMissingEnvironmentFile
-from ..util.yaml_cache import g_yaml_cache
from .. import LogManager
+from ..errors import TankError, TankUnreadableFileError
+from ..util.yaml_cache import g_yaml_cache
+from . import constants, environment_includes
+from .bundle import resolve_default_value
+from .errors import TankMissingEnvironmentFile
logger = LogManager.get_logger(__name__)
@@ -67,8 +66,7 @@ def __str__(self):
return "Environment %s" % os.path.basename(self._env_path)
def _refresh(self):
- """Refreshes the environment data from disk
- """
+ """Refreshes the environment data from disk"""
data = self.__load_environment_data()
self._env_data = environment_includes.process_includes(
@@ -233,7 +231,7 @@ def __extract_locations(self):
constants.ENVIRONMENT_LOCATION_KEY
)
- for (eng, app) in self.__app_settings:
+ for eng, app in self.__app_settings:
descriptor_dict = self.__app_settings[(eng, app)].get(
constants.ENVIRONMENT_LOCATION_KEY
)
@@ -280,7 +278,7 @@ def name(self):
without its extension
"""
file_name_with_ext = os.path.basename(self._env_path)
- (file_name, ext) = os.path.splitext(file_name_with_ext)
+ file_name, ext = os.path.splitext(file_name_with_ext)
return file_name
@property
@@ -323,7 +321,7 @@ def get_apps(self, engine):
apps = []
engine_app_tuples = list(self.__app_settings.keys())
- for (engine_name, app_name) in engine_app_tuples:
+ for engine_name, app_name in engine_app_tuples:
if engine_name == engine:
apps.append(app_name)
return apps
@@ -597,7 +595,7 @@ def _find_location_for_app(self, engine_name, app_name, absolute_location=False)
absolute_location,
)
# first, find the location of the engine:
- (engine_tokens, engine_yml_file) = self.find_location_for_engine(engine_name)
+ engine_tokens, engine_yml_file = self.find_location_for_engine(engine_name)
# load the engine data:
engine_yml_data = self.__load_data(engine_yml_file)
@@ -814,7 +812,7 @@ class WritableEnvironment(InstalledEnvironment):
content back to disk.
"""
- (NONE, INCLUDE_DEFAULTS, STRIP_DEFAULTS) = range(3)
+ NONE, INCLUDE_DEFAULTS, STRIP_DEFAULTS = range(3)
"""Format enumeration to use when dumping an environment.
NONE: Don't modify the settings.
@@ -834,7 +832,7 @@ def __init__(self, env_path, pipeline_config, context=None):
super().__init__(env_path, pipeline_config, context)
def _get_ruamel_yaml(self):
- vendor_path = os.path.join(os.path.dirname(__file__), "../..", "tank_vendor")
+ vendor_path = os.path.join(os.path.dirname(__file__), "../..", "tank_vendor")
if vendor_path not in sys.path:
sys.path.append(vendor_path)
@@ -1014,7 +1012,7 @@ def _update_location_data(self, data, new_location_data):
# It is the difference between the following:
#
# common.engines.tk-maya.location:
- # type: app_store
+ # type: app_store
# name: tk-maya
# version: v0.8.1
#
@@ -1022,7 +1020,7 @@ def _update_location_data(self, data, new_location_data):
#
# common.apps.tk-multi-shotgunpanel:
# location:
- # type: app_store
+ # type: app_store
# name: tk-multi-shotgunpanel
# version: v1.4.3
#
@@ -1052,7 +1050,7 @@ def update_engine_settings(self, engine_name, new_data, new_location):
# in a concrete manner (ie: the actual dict and not an include
# to another yml file). The absolute_location argument will allow
# us to do that.
- (tokens, yml_file) = self._find_location_for_engine(
+ tokens, yml_file = self._find_location_for_engine(
engine_name, absolute_location=True
)
@@ -1095,7 +1093,7 @@ def update_app_settings(self, engine_name, app_name, new_data, new_location):
# in a concrete manner (ie: the actual dict and not an include
# to another yml file). The absolute_location argument will allow
# us to do that.
- (tokens, yml_file) = self._find_location_for_app(
+ tokens, yml_file = self._find_location_for_app(
engine_name, app_name, absolute_location=True
)
@@ -1133,7 +1131,7 @@ def update_framework_settings(self, framework_name, new_data, new_location):
# in a concrete manner (ie: the actual dict and not an include
# to another yml file). The absolute_location argument will allow
# us to do that.
- (tokens, yml_file) = self._find_location_for_framework(
+ tokens, yml_file = self._find_location_for_framework(
framework_name, absolute_location=True
)
@@ -1405,7 +1403,7 @@ def dump(self, file, transform, include_debug_comments=True):
for engine_name in self.get_engines():
# only process settings in this file
- (tokens, engine_file) = self.find_location_for_engine(engine_name)
+ tokens, engine_file = self.find_location_for_engine(engine_name)
if not engine_file == self._env_path:
continue
@@ -1438,7 +1436,7 @@ def dump(self, file, transform, include_debug_comments=True):
for app_name in self.get_apps(engine_name):
# only process settings in this file
- (tokens, app_file) = self.find_location_for_app(engine_name, app_name)
+ tokens, app_file = self.find_location_for_app(engine_name, app_name)
if not app_file == self._env_path:
continue
@@ -1471,7 +1469,7 @@ def dump(self, file, transform, include_debug_comments=True):
for fw_name in self.get_frameworks():
# only process settings in this file
- (tokens, fw_file) = self.find_location_for_framework(fw_name)
+ tokens, fw_file = self.find_location_for_framework(fw_name)
if not fw_file == self._env_path:
continue
diff --git a/python/tk-core/python/tank/platform/environment_includes.py b/python/tk-core/python/tank/platform/environment_includes.py
index 3f4ec8ff..bd47068b 100644
--- a/python/tk-core/python/tank/platform/environment_includes.py
+++ b/python/tk-core/python/tank/platform/environment_includes.py
@@ -27,21 +27,17 @@
"""
-
-import os
-import sys
import copy
+import os
from ..errors import TankError
+from ..log import LogManager
from ..template import TemplatePath
from ..templatekey import StringKey
-from ..log import LogManager
-
-from . import constants
-
from ..util import sgre as re
-from ..util.yaml_cache import g_yaml_cache
from ..util.includes import resolve_include
+from ..util.yaml_cache import g_yaml_cache
+from . import constants
log = LogManager.get_logger(__name__)
@@ -106,7 +102,7 @@ def _resolve_includes(file_name, data, context):
try:
f = context.as_template_fields(template)
full_path = template.apply_fields(f)
- except TankError as e:
+ except TankError:
# if this path could not be resolved, that's ok! These paths are always optional.
continue
@@ -136,7 +132,7 @@ def _resolve_refs_r(lookup_dict, data):
elif isinstance(data, dict):
processed_val = {}
- for (k, v) in data.items():
+ for k, v in data.items():
processed_val[k] = _resolve_refs_r(lookup_dict, v)
elif isinstance(data, str) and data.startswith("@"):
@@ -350,9 +346,7 @@ def find_reference(file_name, context, token, absolute_location=False):
# If the value of the token is an include, then we can
# recurse up, directly referencing the include name as
# the new token.
- if isinstance(token_data, str) and token_data.startswith(
- "@"
- ):
+ if isinstance(token_data, str) and token_data.startswith("@"):
include_token = token_data
else:
# In the case where the data isn't itself an include,
diff --git a/python/tk-core/python/tank/platform/events/__init__.py b/python/tk-core/python/tank/platform/events/__init__.py
index 4d80ad84..67615b8d 100644
--- a/python/tk-core/python/tank/platform/events/__init__.py
+++ b/python/tk-core/python/tank/platform/events/__init__.py
@@ -9,5 +9,5 @@
# not expressly granted therein are reserved by Shotgun Software Inc.
from .event_engine import EngineEvent
-from .event_file_open import FileOpenEvent
from .event_file_close import FileCloseEvent
+from .event_file_open import FileOpenEvent
diff --git a/python/tk-core/python/tank/platform/framework.py b/python/tk-core/python/tank/platform/framework.py
index bd81825c..cd17f76a 100644
--- a/python/tk-core/python/tank/platform/framework.py
+++ b/python/tk-core/python/tank/platform/framework.py
@@ -15,12 +15,10 @@
import os
-from ..util.loader import load_plugin
-from . import constants
-
from ..errors import TankError
+from ..util.loader import load_plugin
+from . import constants, validation
from .bundle import TankBundle
-from . import validation
class Framework(TankBundle):
diff --git a/python/tk-core/python/tank/platform/qt/busy_dialog.py b/python/tk-core/python/tank/platform/qt/busy_dialog.py
index c9b0a612..a51e48a6 100644
--- a/python/tk-core/python/tank/platform/qt/busy_dialog.py
+++ b/python/tk-core/python/tank/platform/qt/busy_dialog.py
@@ -8,7 +8,7 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from . import QtCore, QtGui
+from . import QtGui
from .ui_busy_dialog import Ui_BusyDialog
diff --git a/python/tk-core/python/tank/platform/qt/config_item.py b/python/tk-core/python/tank/platform/qt/config_item.py
index 44db298c..6068e519 100644
--- a/python/tk-core/python/tank/platform/qt/config_item.py
+++ b/python/tk-core/python/tank/platform/qt/config_item.py
@@ -8,14 +8,10 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import os
-import shutil
-import sys
-
-from . import QtCore, QtGui
-from .ui_item import Ui_Item
from ..bundle import resolve_default_value
from ..engine import current_engine
+from . import QtGui
+from .ui_item import Ui_Item
class ConfigItem(QtGui.QWidget):
@@ -47,7 +43,7 @@ def __init__(self, setting, params, value, bundle, parent=None):
# special cases for some things:
value_str = ""
- if type(value) == str and value.startswith("hook:"):
+ if isinstance(value, str) and value.startswith("hook:"):
# this is the generic hook override that any type can have
value_str = "Value: %s" % value
value_str += "
"
diff --git a/python/tk-core/python/tank/platform/qt/resources_rc.py b/python/tk-core/python/tank/platform/qt/resources_rc.py
index 51f7b1fa..85a139af 100644
--- a/python/tk-core/python/tank/platform/qt/resources_rc.py
+++ b/python/tk-core/python/tank/platform/qt/resources_rc.py
@@ -1959,10 +1959,17 @@
\x00\x00\x01\x8f05\xd2\x9e\
"
+
def qInitResources():
- QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+ QtCore.qRegisterResourceData(
+ 0x03, qt_resource_struct, qt_resource_name, qt_resource_data
+ )
+
def qCleanupResources():
- QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+ QtCore.qUnregisterResourceData(
+ 0x03, qt_resource_struct, qt_resource_name, qt_resource_data
+ )
+
qInitResources()
diff --git a/python/tk-core/python/tank/platform/qt/tankqdialog.py b/python/tk-core/python/tank/platform/qt/tankqdialog.py
index da378e35..52d06905 100644
--- a/python/tk-core/python/tank/platform/qt/tankqdialog.py
+++ b/python/tk-core/python/tank/platform/qt/tankqdialog.py
@@ -13,18 +13,13 @@
"""
-from . import QtCore, QtGui
-from . import ui_tank_dialog
-from . import TankDialogBase
-from .config_item import ConfigItem
-from .. import engine
-from .. import application
-from .. import constants
-from ...errors import TankError
-
-import sys
-import os
import inspect
+import os
+
+from ...errors import TankError
+from .. import application, constants, engine
+from . import QtCore, QtGui, TankDialogBase, ui_tank_dialog
+from .config_item import ConfigItem
class TankQDialog(TankDialogBase):
@@ -70,7 +65,7 @@ def _stop_buggy_background_worker_qthreads(widget):
# stop if we've previously checked this class:
cls_type = checked_classes.get(cls, None)
- if cls_type != None:
+ if cls_type is not None:
break
checked_classes[cls] = ""
@@ -104,7 +99,7 @@ def _stop_buggy_background_worker_qthreads(widget):
# assume that this is derived from an actual tk-multi-workfiles.SaveAsForm!
cls_type = "SaveAsForm"
- if cls_type != None:
+ if cls_type is not None:
checked_classes[cls] = cls_type
break
@@ -331,7 +326,8 @@ def _format_context_property(p, show_type=False):
tooltip += "System Information"
tooltip += "
"
tooltip += (
- "Flow Production Tracking Toolkit Version: %s
" % self._bundle.tank.version
+ "Flow Production Tracking Toolkit Version: %s
"
+ % self._bundle.tank.version
)
tooltip += "Pipeline Config: %s
" % pc.get_name()
tooltip += "Config Path: %s
" % pc.get_path()
@@ -360,7 +356,7 @@ def _format_context_property(p, show_type=False):
context_info += "You are currently running in the %s environment." % (
self._bundle.engine.environment["name"]
)
- except:
+ except Exception:
pass
self.ui.app_work_area_info.setText(context_info)
diff --git a/python/tk-core/python/tank/platform/qt/ui_busy_dialog.py b/python/tk-core/python/tank/platform/qt/ui_busy_dialog.py
index d81bbfb3..a62690fb 100644
--- a/python/tk-core/python/tank/platform/qt/ui_busy_dialog.py
+++ b/python/tk-core/python/tank/platform/qt/ui_busy_dialog.py
@@ -9,69 +9,76 @@
################################################################################
from . import QtCore
+
for name, cls in QtCore.__dict__.items():
- if isinstance(cls, type): globals()[name] = cls
+ if isinstance(cls, type):
+ globals()[name] = cls
from . import QtGui
+
for name, cls in QtGui.__dict__.items():
- if isinstance(cls, type): globals()[name] = cls
+ if isinstance(cls, type):
+ globals()[name] = cls
+
+from . import resources_rc
-from . import resources_rc
class Ui_BusyDialog(object):
def setupUi(self, BusyDialog):
if not BusyDialog.objectName():
- BusyDialog.setObjectName(u"BusyDialog")
+ BusyDialog.setObjectName("BusyDialog")
BusyDialog.resize(500, 110)
- BusyDialog.setStyleSheet(u"/* Style for the window itself */\n"
-"#frame {\n"
-"border-color: #30A7E3;\n"
-"border-style: solid;\n"
-"border-width: 2px;\n"
-"}\n"
-"\n"
-"/* Style for the header text */\n"
-"#title {\n"
-"color: #30A7E3;\n"
-"margin-top: 15px;\n"
-"margin-bottom: 0px;\n"
-"margin-left: 1px;\n"
-"font-size: 16px;\n"
-"font-weight: bold;\n"
-"}\n"
-"\n"
-"/* Style for the details text */\n"
-"#details {\n"
-"margin-top: 1px;\n"
-"margin-left: 3px;\n"
-"margin-bottom: 0px;\n"
-"font-size: 11px;\n"
-"}\n"
-"")
+ BusyDialog.setStyleSheet(
+ "/* Style for the window itself */\n"
+ "#frame {\n"
+ "border-color: #30A7E3;\n"
+ "border-style: solid;\n"
+ "border-width: 2px;\n"
+ "}\n"
+ "\n"
+ "/* Style for the header text */\n"
+ "#title {\n"
+ "color: #30A7E3;\n"
+ "margin-top: 15px;\n"
+ "margin-bottom: 0px;\n"
+ "margin-left: 1px;\n"
+ "font-size: 16px;\n"
+ "font-weight: bold;\n"
+ "}\n"
+ "\n"
+ "/* Style for the details text */\n"
+ "#details {\n"
+ "margin-top: 1px;\n"
+ "margin-left: 3px;\n"
+ "margin-bottom: 0px;\n"
+ "font-size: 11px;\n"
+ "}\n"
+ ""
+ )
self.horizontalLayout_2 = QHBoxLayout(BusyDialog)
self.horizontalLayout_2.setSpacing(2)
self.horizontalLayout_2.setContentsMargins(2, 2, 2, 2)
- self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.frame = QFrame(BusyDialog)
- self.frame.setObjectName(u"frame")
+ self.frame.setObjectName("frame")
self.frame.setFrameShape(QFrame.StyledPanel)
self.frame.setFrameShadow(QFrame.Raised)
self.horizontalLayout = QHBoxLayout(self.frame)
self.horizontalLayout.setSpacing(5)
self.horizontalLayout.setContentsMargins(5, 5, 5, 5)
- self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QLabel(self.frame)
- self.label.setObjectName(u"label")
- self.label.setPixmap(QPixmap(u":/Tank.Platform.Qt/sg_logo_80px.png"))
+ self.label.setObjectName("label")
+ self.label.setPixmap(QPixmap(":/Tank.Platform.Qt/sg_logo_80px.png"))
self.horizontalLayout.addWidget(self.label)
self.verticalLayout = QVBoxLayout()
self.verticalLayout.setSpacing(0)
- self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setObjectName("verticalLayout")
self.title = QLabel(self.frame)
- self.title.setObjectName(u"title")
+ self.title.setObjectName("title")
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -81,13 +88,13 @@ def setupUi(self, BusyDialog):
self.verticalLayout.addWidget(self.title)
self.details = QLabel(self.frame)
- self.details.setObjectName(u"details")
+ self.details.setObjectName("details")
sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.details.sizePolicy().hasHeightForWidth())
self.details.setSizePolicy(sizePolicy1)
- self.details.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignTop)
+ self.details.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignTop)
self.details.setWordWrap(True)
self.verticalLayout.addWidget(self.details)
@@ -99,11 +106,21 @@ def setupUi(self, BusyDialog):
self.retranslateUi(BusyDialog)
QMetaObject.connectSlotsByName(BusyDialog)
+
# setupUi
def retranslateUi(self, BusyDialog):
- BusyDialog.setWindowTitle(QCoreApplication.translate("BusyDialog", u"Dialog", None))
+ BusyDialog.setWindowTitle(
+ QCoreApplication.translate("BusyDialog", "Dialog", None)
+ )
self.label.setText("")
- self.title.setText(QCoreApplication.translate("BusyDialog", u"Doing something, hang on!", None))
- self.details.setText(QCoreApplication.translate("BusyDialog", u"Lots of interesting details about what is going on", None))
+ self.title.setText(
+ QCoreApplication.translate("BusyDialog", "Doing something, hang on!", None)
+ )
+ self.details.setText(
+ QCoreApplication.translate(
+ "BusyDialog", "Lots of interesting details about what is going on", None
+ )
+ )
+
# retranslateUi
diff --git a/python/tk-core/python/tank/platform/qt/ui_item.py b/python/tk-core/python/tank/platform/qt/ui_item.py
index 0f1bc565..1b6f8aa9 100644
--- a/python/tk-core/python/tank/platform/qt/ui_item.py
+++ b/python/tk-core/python/tank/platform/qt/ui_item.py
@@ -9,56 +9,61 @@
################################################################################
from . import QtCore
+
for name, cls in QtCore.__dict__.items():
- if isinstance(cls, type): globals()[name] = cls
+ if isinstance(cls, type):
+ globals()[name] = cls
from . import QtGui
+
for name, cls in QtGui.__dict__.items():
- if isinstance(cls, type): globals()[name] = cls
+ if isinstance(cls, type):
+ globals()[name] = cls
+
+from . import resources_rc
-from . import resources_rc
class Ui_Item(object):
def setupUi(self, Item):
if not Item.objectName():
- Item.setObjectName(u"Item")
+ Item.setObjectName("Item")
Item.resize(335, 110)
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(Item.sizePolicy().hasHeightForWidth())
Item.setSizePolicy(sizePolicy)
- Item.setStyleSheet(u"QLabel{\n"
-" font-size: 11px;\n"
-" margin-bottom: 3px\n"
-"}\n"
-"")
+ Item.setStyleSheet(
+ "QLabel{\n" " font-size: 11px;\n" " margin-bottom: 3px\n" "}\n" ""
+ )
self.verticalLayout = QVBoxLayout(Item)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
- self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setObjectName("verticalLayout")
self.verticalLayout_2 = QVBoxLayout()
self.verticalLayout_2.setSpacing(0)
- self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
self.name = QLabel(Item)
- self.name.setObjectName(u"name")
+ self.name.setObjectName("name")
sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.name.sizePolicy().hasHeightForWidth())
self.name.setSizePolicy(sizePolicy1)
- self.name.setStyleSheet(u"font-size: 13px;")
- self.name.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.name.setStyleSheet("font-size: 13px;")
+ self.name.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter)
self.name.setWordWrap(True)
self.verticalLayout_2.addWidget(self.name)
self.line = QFrame(Item)
- self.line.setObjectName(u"line")
- self.line.setStyleSheet(u"border: none;\n"
-"border-bottom-color: rgba(150,150,150,100);\n"
-"border-bottom-width: 1px;\n"
-"border-bottom-style: solid;")
+ self.line.setObjectName("line")
+ self.line.setStyleSheet(
+ "border: none;\n"
+ "border-bottom-color: rgba(150,150,150,100);\n"
+ "border-bottom-width: 1px;\n"
+ "border-bottom-style: solid;"
+ )
self.line.setFrameShape(QFrame.HLine)
self.line.setFrameShadow(QFrame.Sunken)
@@ -67,36 +72,44 @@ def setupUi(self, Item):
self.verticalLayout.addLayout(self.verticalLayout_2)
self.value = QLabel(Item)
- self.value.setObjectName(u"value")
+ self.value.setObjectName("value")
sizePolicy1.setHeightForWidth(self.value.sizePolicy().hasHeightForWidth())
self.value.setSizePolicy(sizePolicy1)
- self.value.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.value.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter)
self.value.setWordWrap(True)
- self.value.setTextInteractionFlags(Qt.LinksAccessibleByMouse|Qt.TextSelectableByMouse)
+ self.value.setTextInteractionFlags(
+ Qt.LinksAccessibleByMouse | Qt.TextSelectableByMouse
+ )
self.verticalLayout.addWidget(self.value)
self.type = QLabel(Item)
- self.type.setObjectName(u"type")
+ self.type.setObjectName("type")
sizePolicy1.setHeightForWidth(self.type.sizePolicy().hasHeightForWidth())
self.type.setSizePolicy(sizePolicy1)
- self.type.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.type.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter)
self.type.setWordWrap(True)
- self.type.setTextInteractionFlags(Qt.LinksAccessibleByMouse|Qt.TextSelectableByMouse)
+ self.type.setTextInteractionFlags(
+ Qt.LinksAccessibleByMouse | Qt.TextSelectableByMouse
+ )
self.verticalLayout.addWidget(self.type)
self.description = QLabel(Item)
- self.description.setObjectName(u"description")
+ self.description.setObjectName("description")
self.description.setMaximumSize(QSize(350, 16777215))
self.description.setTextFormat(Qt.RichText)
- self.description.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.description.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter)
self.description.setWordWrap(True)
- self.description.setTextInteractionFlags(Qt.LinksAccessibleByMouse|Qt.TextSelectableByMouse)
+ self.description.setTextInteractionFlags(
+ Qt.LinksAccessibleByMouse | Qt.TextSelectableByMouse
+ )
self.verticalLayout.addWidget(self.description)
- self.verticalSpacer = QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer = QSpacerItem(
+ 20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding
+ )
self.verticalLayout.addItem(self.verticalSpacer)
@@ -105,16 +118,24 @@ def setupUi(self, Item):
self.retranslateUi(Item)
QMetaObject.connectSlotsByName(Item)
+
# setupUi
def retranslateUi(self, Item):
- Item.setWindowTitle(QCoreApplication.translate("Item", u"Form", None))
- self.name.setText(QCoreApplication.translate("Item", u"Settings Name", None))
- self.value.setText(QCoreApplication.translate("Item", u"Value: foo bar", None))
- self.type.setText(QCoreApplication.translate("Item", u"Type: bool", None))
- self.description.setText(QCoreApplication.translate("Item", u"\n"
-"\n"
-"description
", None))
+ Item.setWindowTitle(QCoreApplication.translate("Item", "Form", None))
+ self.name.setText(QCoreApplication.translate("Item", "Settings Name", None))
+ self.value.setText(QCoreApplication.translate("Item", "Value: foo bar", None))
+ self.type.setText(QCoreApplication.translate("Item", "Type: bool", None))
+ self.description.setText(
+ QCoreApplication.translate(
+ "Item",
+ '\n'
+ '\n"
+ 'description
',
+ None,
+ )
+ )
+
# retranslateUi
diff --git a/python/tk-core/python/tank/platform/qt/ui_tank_dialog.py b/python/tk-core/python/tank/platform/qt/ui_tank_dialog.py
index 344b2eb7..1f81444b 100644
--- a/python/tk-core/python/tank/platform/qt/ui_tank_dialog.py
+++ b/python/tk-core/python/tank/platform/qt/ui_tank_dialog.py
@@ -9,138 +9,153 @@
################################################################################
from . import QtCore
+
for name, cls in QtCore.__dict__.items():
- if isinstance(cls, type): globals()[name] = cls
+ if isinstance(cls, type):
+ globals()[name] = cls
from . import QtGui
+
for name, cls in QtGui.__dict__.items():
- if isinstance(cls, type): globals()[name] = cls
+ if isinstance(cls, type):
+ globals()[name] = cls
+
+from . import resources_rc
-from . import resources_rc
class Ui_TankDialog(object):
def setupUi(self, TankDialog):
if not TankDialog.objectName():
- TankDialog.setObjectName(u"TankDialog")
+ TankDialog.setObjectName("TankDialog")
TankDialog.resize(879, 551)
- TankDialog.setStyleSheet(u"")
+ TankDialog.setStyleSheet("")
self.verticalLayout_3 = QVBoxLayout(TankDialog)
self.verticalLayout_3.setSpacing(0)
- self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.verticalLayout_3.setObjectName("verticalLayout_3")
self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
self.stackedWidget = QStackedWidget(TankDialog)
- self.stackedWidget.setObjectName(u"stackedWidget")
+ self.stackedWidget.setObjectName("stackedWidget")
self.page_1 = QWidget()
- self.page_1.setObjectName(u"page_1")
- self.page_1.setStyleSheet(u"QWidget#page_1 {\n"
-"margin: 0px;\n"
-"}")
+ self.page_1.setObjectName("page_1")
+ self.page_1.setStyleSheet("QWidget#page_1 {\n" "margin: 0px;\n" "}")
self.verticalLayout = QVBoxLayout(self.page_1)
self.verticalLayout.setSpacing(0)
- self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setObjectName("verticalLayout")
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.top_group = QGroupBox(self.page_1)
- self.top_group.setObjectName(u"top_group")
+ self.top_group.setObjectName("top_group")
self.top_group.setMinimumSize(QSize(0, 45))
self.top_group.setMaximumSize(QSize(16777215, 45))
- self.top_group.setStyleSheet(u"#top_group {\n"
-"background-color: #2D2D2D;\n"
-"border: none;\n"
-"border-bottom:1px solid #202020;\n"
-"}\n"
-"")
+ self.top_group.setStyleSheet(
+ "#top_group {\n"
+ "background-color: #2D2D2D;\n"
+ "border: none;\n"
+ "border-bottom:1px solid #202020;\n"
+ "}\n"
+ ""
+ )
self.top_group.setFlat(False)
self.horizontalLayout = QHBoxLayout(self.top_group)
self.horizontalLayout.setSpacing(0)
- self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalLayout.setObjectName("horizontalLayout")
self.horizontalLayout.setContentsMargins(4, 0, 1, 1)
self.tank_logo = QLabel(self.top_group)
- self.tank_logo.setObjectName(u"tank_logo")
+ self.tank_logo.setObjectName("tank_logo")
sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.tank_logo.sizePolicy().hasHeightForWidth())
self.tank_logo.setSizePolicy(sizePolicy)
- self.tank_logo.setPixmap(QPixmap(u":/Tank.Platform.Qt/tank_logo.png"))
+ self.tank_logo.setPixmap(QPixmap(":/Tank.Platform.Qt/tank_logo.png"))
self.horizontalLayout.addWidget(self.tank_logo)
self.label = QLabel(self.top_group)
- self.label.setObjectName(u"label")
- self.label.setStyleSheet(u"/* want this stylesheet to apply to the label but not the tooltip */\n"
-"QLabel{\n"
-" color: white;\n"
-" font-size: 20px;\n"
-" margin-left: 5px;\n"
-" font-family: \"Open Sans\";\n"
-" font-style: \"Regular\";\n"
-"}")
+ self.label.setObjectName("label")
+ self.label.setStyleSheet(
+ "/* want this stylesheet to apply to the label but not the tooltip */\n"
+ "QLabel{\n"
+ " color: white;\n"
+ " font-size: 20px;\n"
+ " margin-left: 5px;\n"
+ ' font-family: "Open Sans";\n'
+ ' font-style: "Regular";\n'
+ "}"
+ )
self.horizontalLayout.addWidget(self.label)
self.lbl_context = QLabel(self.top_group)
- self.lbl_context.setObjectName(u"lbl_context")
- self.lbl_context.setStyleSheet(u"/* want this stylesheet to apply to the label but not the tooltip */\n"
-"QLabel {\n"
-" color: rgba(250,250,250,180);\n"
-" font-size: 11px;\n"
-" margin-right: 8px;\n"
-" font-family: \"Open Sans\";\n"
-" font-style: \"Regular\";\n"
-"}\n"
-"\n"
-"\n"
-"")
- self.lbl_context.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
+ self.lbl_context.setObjectName("lbl_context")
+ self.lbl_context.setStyleSheet(
+ "/* want this stylesheet to apply to the label but not the tooltip */\n"
+ "QLabel {\n"
+ " color: rgba(250,250,250,180);\n"
+ " font-size: 11px;\n"
+ " margin-right: 8px;\n"
+ ' font-family: "Open Sans";\n'
+ ' font-style: "Regular";\n'
+ "}\n"
+ "\n"
+ "\n"
+ ""
+ )
+ self.lbl_context.setAlignment(
+ Qt.AlignRight | Qt.AlignTrailing | Qt.AlignVCenter
+ )
self.horizontalLayout.addWidget(self.lbl_context)
self.details_show = QToolButton(self.top_group)
- self.details_show.setObjectName(u"details_show")
+ self.details_show.setObjectName("details_show")
self.details_show.setMinimumSize(QSize(34, 34))
self.details_show.setFocusPolicy(Qt.ClickFocus)
- self.details_show.setStyleSheet(u"QToolButton{\n"
-"width: 12px;\n"
-"height: 20px;\n"
-"background-image: url(:/Tank.Platform.Qt/arrow.png);\n"
-"border: none;\n"
-"background-color: none;\n"
-"}\n"
-"\n"
-"QToolButton:hover{\n"
-"background-image: url(:/Tank.Platform.Qt/arrow_hover.png);\n"
-"}\n"
-"\n"
-"QToolButton:pressed{\n"
-"background-image: url(:/Tank.Platform.Qt/arrow_pressed.png);\n"
-"}\n"
-"")
+ self.details_show.setStyleSheet(
+ "QToolButton{\n"
+ "width: 12px;\n"
+ "height: 20px;\n"
+ "background-image: url(:/Tank.Platform.Qt/arrow.png);\n"
+ "border: none;\n"
+ "background-color: none;\n"
+ "}\n"
+ "\n"
+ "QToolButton:hover{\n"
+ "background-image: url(:/Tank.Platform.Qt/arrow_hover.png);\n"
+ "}\n"
+ "\n"
+ "QToolButton:pressed{\n"
+ "background-image: url(:/Tank.Platform.Qt/arrow_pressed.png);\n"
+ "}\n"
+ ""
+ )
self.details_show.setAutoRaise(True)
self.horizontalLayout.addWidget(self.details_show)
self.details_hide = QToolButton(self.top_group)
- self.details_hide.setObjectName(u"details_hide")
+ self.details_hide.setObjectName("details_hide")
self.details_hide.setMinimumSize(QSize(34, 34))
self.details_hide.setFocusPolicy(Qt.ClickFocus)
self.details_hide.setVisible(False)
- self.details_hide.setStyleSheet(u"QToolButton{\n"
-" width: 12px;\n"
-" height: 20px;\n"
-" background-image: url(:/Tank.Platform.Qt/arrow_flipped.png);\n"
-" border: none;\n"
-" background-color: none;\n"
-" }\n"
-"\n"
-" QToolButton:hover{\n"
-" background-image: url(:/Tank.Platform.Qt/arrow_flipped_hover.png);\n"
-" }\n"
-"\n"
-" QToolButton:pressed{\n"
-" background-image: url(:/Tank.Platform.Qt/arrow_flipped_pressed.png);\n"
-" }\n"
-" ")
+ self.details_hide.setStyleSheet(
+ "QToolButton{\n"
+ " width: 12px;\n"
+ " height: 20px;\n"
+ " background-image: url(:/Tank.Platform.Qt/arrow_flipped.png);\n"
+ " border: none;\n"
+ " background-color: none;\n"
+ " }\n"
+ "\n"
+ " QToolButton:hover{\n"
+ " background-image: url(:/Tank.Platform.Qt/arrow_flipped_hover.png);\n"
+ " }\n"
+ "\n"
+ " QToolButton:pressed{\n"
+ " background-image: url(:/Tank.Platform.Qt/arrow_flipped_pressed.png);\n"
+ " }\n"
+ " "
+ )
self.details_hide.setAutoRaise(True)
self.horizontalLayout.addWidget(self.details_hide)
@@ -149,79 +164,85 @@ def setupUi(self, TankDialog):
self.target = QVBoxLayout()
self.target.setSpacing(4)
- self.target.setObjectName(u"target")
+ self.target.setObjectName("target")
self.verticalLayout.addLayout(self.target)
self.stackedWidget.addWidget(self.page_1)
self.page_2 = QWidget()
- self.page_2.setObjectName(u"page_2")
- self.page_2.setStyleSheet(u"QWidget {\n"
-" font-family: \"Open Sans\";\n"
-" font-style: \"Regular\";\n"
-"}")
+ self.page_2.setObjectName("page_2")
+ self.page_2.setStyleSheet(
+ "QWidget {\n"
+ ' font-family: "Open Sans";\n'
+ ' font-style: "Regular";\n'
+ "}"
+ )
self.verticalLayout_2 = QVBoxLayout(self.page_2)
- self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
self.verticalLayout_2.setContentsMargins(1, 1, 1, 1)
self.page_2_group = QGroupBox(self.page_2)
- self.page_2_group.setObjectName(u"page_2_group")
+ self.page_2_group.setObjectName("page_2_group")
self.page_2_group.setMinimumSize(QSize(0, 100))
- self.page_2_group.setStyleSheet(u"QGroupBox {\n"
-"margin: 0px;\n"
-"}")
+ self.page_2_group.setStyleSheet("QGroupBox {\n" "margin: 0px;\n" "}")
self.horizontalLayout_2 = QHBoxLayout(self.page_2_group)
self.horizontalLayout_2.setSpacing(0)
- self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
- self.horizontalSpacer = QSpacerItem(145, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontalSpacer = QSpacerItem(
+ 145, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
+ )
self.horizontalLayout_2.addItem(self.horizontalSpacer)
self.label_3 = QLabel(self.page_2_group)
- self.label_3.setObjectName(u"label_3")
+ self.label_3.setObjectName("label_3")
self.label_3.setMinimumSize(QSize(40, 0))
self.label_3.setMaximumSize(QSize(40, 16777215))
self.horizontalLayout_2.addWidget(self.label_3)
self.gradient = QGroupBox(self.page_2_group)
- self.gradient.setObjectName(u"gradient")
+ self.gradient.setObjectName("gradient")
self.gradient.setMinimumSize(QSize(11, 0))
self.gradient.setMaximumSize(QSize(11, 16777215))
- self.gradient.setStyleSheet(u"#gradient {\n"
-"background-image: url(:/Tank.Platform.Qt/gradient.png);\n"
-"border: none;\n"
-"}")
+ self.gradient.setStyleSheet(
+ "#gradient {\n"
+ "background-image: url(:/Tank.Platform.Qt/gradient.png);\n"
+ "border: none;\n"
+ "}"
+ )
self.horizontalLayout_2.addWidget(self.gradient)
self.scrollArea = QScrollArea(self.page_2_group)
- self.scrollArea.setObjectName(u"scrollArea")
+ self.scrollArea.setObjectName("scrollArea")
self.scrollArea.setMinimumSize(QSize(400, 0))
self.scrollArea.setMaximumSize(QSize(400, 16777215))
- self.scrollArea.setStyleSheet(u"/*\n"
-"All labels inside this scroll area should be 12px font.\n"
-"This is to avoid the UI looking different in different app like\n"
-"maya and nuke which all use slightly different style sheets.\n"
-" */\n"
-"QLabel{\n"
-" font-size: 11px;\n"
-" margin-bottom: 8px\n"
-"}\n"
-"")
+ self.scrollArea.setStyleSheet(
+ "/*\n"
+ "All labels inside this scroll area should be 12px font.\n"
+ "This is to avoid the UI looking different in different app like\n"
+ "maya and nuke which all use slightly different style sheets.\n"
+ " */\n"
+ "QLabel{\n"
+ " font-size: 11px;\n"
+ " margin-bottom: 8px\n"
+ "}\n"
+ ""
+ )
self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
- self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents")
+ self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 398, 550))
self.verticalLayout_4 = QVBoxLayout(self.scrollAreaWidgetContents)
- self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+ self.verticalLayout_4.setObjectName("verticalLayout_4")
self.horizontalLayout_4 = QHBoxLayout()
- self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
+ self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.app_icon = QLabel(self.scrollAreaWidgetContents)
- self.app_icon.setObjectName(u"app_icon")
+ self.app_icon.setObjectName("app_icon")
self.app_icon.setMinimumSize(QSize(64, 64))
self.app_icon.setMaximumSize(QSize(64, 64))
- self.app_icon.setPixmap(QPixmap(u":/Tank.Platform.Qt/default_app_icon_256.png"))
+ self.app_icon.setPixmap(QPixmap(":/Tank.Platform.Qt/default_app_icon_256.png"))
self.app_icon.setScaledContents(True)
self.app_icon.setAlignment(Qt.AlignCenter)
@@ -229,12 +250,11 @@ def setupUi(self, TankDialog):
self.verticalLayout_8 = QVBoxLayout()
self.verticalLayout_8.setSpacing(1)
- self.verticalLayout_8.setObjectName(u"verticalLayout_8")
+ self.verticalLayout_8.setObjectName("verticalLayout_8")
self.app_name = QLabel(self.scrollAreaWidgetContents)
- self.app_name.setObjectName(u"app_name")
- self.app_name.setStyleSheet(u"font-size: 16px;\n"
-"")
- self.app_name.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.app_name.setObjectName("app_name")
+ self.app_name.setStyleSheet("font-size: 16px;\n" "")
+ self.app_name.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter)
self.verticalLayout_8.addWidget(self.app_name)
@@ -243,61 +263,66 @@ def setupUi(self, TankDialog):
self.verticalLayout_4.addLayout(self.horizontalLayout_4)
self.app_description = QLabel(self.scrollAreaWidgetContents)
- self.app_description.setObjectName(u"app_description")
+ self.app_description.setObjectName("app_description")
self.app_description.setMaximumSize(QSize(350, 16777215))
self.app_description.setWordWrap(True)
self.verticalLayout_4.addWidget(self.app_description)
self.app_tech_details = QLabel(self.scrollAreaWidgetContents)
- self.app_tech_details.setObjectName(u"app_tech_details")
+ self.app_tech_details.setObjectName("app_tech_details")
sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
- sizePolicy1.setHeightForWidth(self.app_tech_details.sizePolicy().hasHeightForWidth())
+ sizePolicy1.setHeightForWidth(
+ self.app_tech_details.sizePolicy().hasHeightForWidth()
+ )
self.app_tech_details.setSizePolicy(sizePolicy1)
self.app_tech_details.setMinimumSize(QSize(0, 22))
self.app_tech_details.setMaximumSize(QSize(16777215, 22))
- self.app_tech_details.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.app_tech_details.setAlignment(
+ Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter
+ )
self.app_tech_details.setWordWrap(True)
self.verticalLayout_4.addWidget(self.app_tech_details)
self.horizontalLayout_9 = QHBoxLayout()
self.horizontalLayout_9.setSpacing(2)
- self.horizontalLayout_9.setObjectName(u"horizontalLayout_9")
+ self.horizontalLayout_9.setObjectName("horizontalLayout_9")
self.btn_documentation = QToolButton(self.scrollAreaWidgetContents)
- self.btn_documentation.setObjectName(u"btn_documentation")
+ self.btn_documentation.setObjectName("btn_documentation")
self.horizontalLayout_9.addWidget(self.btn_documentation)
self.btn_support = QToolButton(self.scrollAreaWidgetContents)
- self.btn_support.setObjectName(u"btn_support")
+ self.btn_support.setObjectName("btn_support")
self.horizontalLayout_9.addWidget(self.btn_support)
- self.horizontalSpacer_5 = QSpacerItem(0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontalSpacer_5 = QSpacerItem(
+ 0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
+ )
self.horizontalLayout_9.addItem(self.horizontalSpacer_5)
self.verticalLayout_4.addLayout(self.horizontalLayout_9)
self.label_5 = QLabel(self.scrollAreaWidgetContents)
- self.label_5.setObjectName(u"label_5")
- self.label_5.setStyleSheet(u"font-size: 16px;\n"
-"margin-top: 30px;")
+ self.label_5.setObjectName("label_5")
+ self.label_5.setStyleSheet("font-size: 16px;\n" "margin-top: 30px;")
self.verticalLayout_4.addWidget(self.label_5)
self.line = QFrame(self.scrollAreaWidgetContents)
- self.line.setObjectName(u"line")
+ self.line.setObjectName("line")
self.line.setFrameShape(QFrame.HLine)
self.line.setFrameShadow(QFrame.Sunken)
self.verticalLayout_4.addWidget(self.line)
self.app_work_area_info = QLabel(self.scrollAreaWidgetContents)
- self.app_work_area_info.setObjectName(u"app_work_area_info")
+ self.app_work_area_info.setObjectName("app_work_area_info")
self.app_work_area_info.setMaximumSize(QSize(350, 16777215))
self.app_work_area_info.setWordWrap(True)
@@ -305,51 +330,52 @@ def setupUi(self, TankDialog):
self.horizontalLayout_10 = QHBoxLayout()
self.horizontalLayout_10.setSpacing(2)
- self.horizontalLayout_10.setObjectName(u"horizontalLayout_10")
+ self.horizontalLayout_10.setObjectName("horizontalLayout_10")
self.btn_file_system = QToolButton(self.scrollAreaWidgetContents)
- self.btn_file_system.setObjectName(u"btn_file_system")
+ self.btn_file_system.setObjectName("btn_file_system")
self.horizontalLayout_10.addWidget(self.btn_file_system)
self.btn_shotgun = QToolButton(self.scrollAreaWidgetContents)
- self.btn_shotgun.setObjectName(u"btn_shotgun")
+ self.btn_shotgun.setObjectName("btn_shotgun")
self.horizontalLayout_10.addWidget(self.btn_shotgun)
- self.horizontalSpacer_6 = QSpacerItem(0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontalSpacer_6 = QSpacerItem(
+ 0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
+ )
self.horizontalLayout_10.addItem(self.horizontalSpacer_6)
self.verticalLayout_4.addLayout(self.horizontalLayout_10)
self.app_work_area_info_2 = QLabel(self.scrollAreaWidgetContents)
- self.app_work_area_info_2.setObjectName(u"app_work_area_info_2")
+ self.app_work_area_info_2.setObjectName("app_work_area_info_2")
self.app_work_area_info_2.setMaximumSize(QSize(350, 16777215))
self.app_work_area_info_2.setWordWrap(True)
self.verticalLayout_4.addWidget(self.app_work_area_info_2)
self.btn_reload = QToolButton(self.scrollAreaWidgetContents)
- self.btn_reload.setObjectName(u"btn_reload")
+ self.btn_reload.setObjectName("btn_reload")
self.verticalLayout_4.addWidget(self.btn_reload)
self.config_header = QLabel(self.scrollAreaWidgetContents)
- self.config_header.setObjectName(u"config_header")
- self.config_header.setStyleSheet(u"font-size: 16px;\n"
-"margin-top: 30px;")
+ self.config_header.setObjectName("config_header")
+ self.config_header.setStyleSheet("font-size: 16px;\n" "margin-top: 30px;")
self.verticalLayout_4.addWidget(self.config_header)
self.config_line = QFrame(self.scrollAreaWidgetContents)
- self.config_line.setObjectName(u"config_line")
+ self.config_line.setObjectName("config_line")
self.config_line.setFrameShape(QFrame.HLine)
self.config_line.setFrameShadow(QFrame.Sunken)
self.verticalLayout_4.addWidget(self.config_line)
self.config_label = QLabel(self.scrollAreaWidgetContents)
- self.config_label.setObjectName(u"config_label")
+ self.config_label.setObjectName("config_label")
self.config_label.setMaximumSize(QSize(350, 16777215))
self.config_label.setWordWrap(True)
@@ -357,11 +383,13 @@ def setupUi(self, TankDialog):
self.config_layout = QVBoxLayout()
self.config_layout.setSpacing(20)
- self.config_layout.setObjectName(u"config_layout")
+ self.config_layout.setObjectName("config_layout")
self.verticalLayout_4.addLayout(self.config_layout)
- self.verticalSpacer_2 = QSpacerItem(328, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer_2 = QSpacerItem(
+ 328, 0, QSizePolicy.Minimum, QSizePolicy.Expanding
+ )
self.verticalLayout_4.addItem(self.verticalSpacer_2)
@@ -380,41 +408,94 @@ def setupUi(self, TankDialog):
self.stackedWidget.setCurrentIndex(0)
QMetaObject.connectSlotsByName(TankDialog)
+
# setupUi
def retranslateUi(self, TankDialog):
- TankDialog.setWindowTitle(QCoreApplication.translate("TankDialog", u"Dialog", None))
+ TankDialog.setWindowTitle(
+ QCoreApplication.translate("TankDialog", "Dialog", None)
+ )
self.top_group.setTitle("")
self.tank_logo.setText("")
- self.label.setText(QCoreApplication.translate("TankDialog", u"TextLabel", None))
-#if QT_CONFIG(tooltip)
- self.lbl_context.setToolTip(QCoreApplication.translate("TankDialog", u"foo bar", None))
-#endif // QT_CONFIG(tooltip)
- self.lbl_context.setText(QCoreApplication.translate("TankDialog", u"Current Work Area:\n"
-"TextLabel", None))
-#if QT_CONFIG(tooltip)
- self.details_show.setToolTip(QCoreApplication.translate("TankDialog", u"Click for App Details", None))
-#endif // QT_CONFIG(tooltip)
+ self.label.setText(QCoreApplication.translate("TankDialog", "TextLabel", None))
+ # if QT_CONFIG(tooltip)
+ self.lbl_context.setToolTip(
+ QCoreApplication.translate("TankDialog", "foo bar", None)
+ )
+ # endif // QT_CONFIG(tooltip)
+ self.lbl_context.setText(
+ QCoreApplication.translate(
+ "TankDialog", "Current Work Area:\n" "TextLabel", None
+ )
+ )
+ # if QT_CONFIG(tooltip)
+ self.details_show.setToolTip(
+ QCoreApplication.translate("TankDialog", "Click for App Details", None)
+ )
+ # endif // QT_CONFIG(tooltip)
self.details_show.setText("")
-#if QT_CONFIG(tooltip)
- self.details_hide.setToolTip(QCoreApplication.translate("TankDialog", u"Hide App Details", None))
-#endif // QT_CONFIG(tooltip)
+ # if QT_CONFIG(tooltip)
+ self.details_hide.setToolTip(
+ QCoreApplication.translate("TankDialog", "Hide App Details", None)
+ )
+ # endif // QT_CONFIG(tooltip)
self.details_hide.setText("")
self.page_2_group.setTitle("")
self.label_3.setText("")
self.gradient.setTitle("")
self.app_icon.setText("")
- self.app_name.setText(QCoreApplication.translate("TankDialog", u"Publish And Snapshot", None))
- self.app_description.setText(QCoreApplication.translate("TankDialog", u"Tools to see what is out of date in your scene etc etc.", None))
- self.app_tech_details.setText(QCoreApplication.translate("TankDialog", u"tk-multi-snapshot, v1.2.3", None))
- self.btn_documentation.setText(QCoreApplication.translate("TankDialog", u"Documentation", None))
- self.btn_support.setText(QCoreApplication.translate("TankDialog", u"Help && Support", None))
- self.label_5.setText(QCoreApplication.translate("TankDialog", u"Your Current Work Area", None))
- self.app_work_area_info.setText(QCoreApplication.translate("TankDialog", u"TextLabel", None))
- self.btn_file_system.setText(QCoreApplication.translate("TankDialog", u"Jump to File System", None))
- self.btn_shotgun.setText(QCoreApplication.translate("TankDialog", u"Jump to Flow Production Tracking", None))
- self.app_work_area_info_2.setText(QCoreApplication.translate("TankDialog", u"If you are making changes to configuration or code, use the reload button to quickly load your changes in without having to restart:", None))
- self.btn_reload.setText(QCoreApplication.translate("TankDialog", u"Reload Engine and Apps", None))
- self.config_header.setText(QCoreApplication.translate("TankDialog", u"Configuration", None))
- self.config_label.setText(QCoreApplication.translate("TankDialog", u"Below is a list of all the configuration settings for this app, as defined in your environment file:", None))
+ self.app_name.setText(
+ QCoreApplication.translate("TankDialog", "Publish And Snapshot", None)
+ )
+ self.app_description.setText(
+ QCoreApplication.translate(
+ "TankDialog",
+ "Tools to see what is out of date in your scene etc etc.",
+ None,
+ )
+ )
+ self.app_tech_details.setText(
+ QCoreApplication.translate("TankDialog", "tk-multi-snapshot, v1.2.3", None)
+ )
+ self.btn_documentation.setText(
+ QCoreApplication.translate("TankDialog", "Documentation", None)
+ )
+ self.btn_support.setText(
+ QCoreApplication.translate("TankDialog", "Help && Support", None)
+ )
+ self.label_5.setText(
+ QCoreApplication.translate("TankDialog", "Your Current Work Area", None)
+ )
+ self.app_work_area_info.setText(
+ QCoreApplication.translate("TankDialog", "TextLabel", None)
+ )
+ self.btn_file_system.setText(
+ QCoreApplication.translate("TankDialog", "Jump to File System", None)
+ )
+ self.btn_shotgun.setText(
+ QCoreApplication.translate(
+ "TankDialog", "Jump to Flow Production Tracking", None
+ )
+ )
+ self.app_work_area_info_2.setText(
+ QCoreApplication.translate(
+ "TankDialog",
+ "If you are making changes to configuration or code, use the reload button to quickly load your changes in without having to restart:",
+ None,
+ )
+ )
+ self.btn_reload.setText(
+ QCoreApplication.translate("TankDialog", "Reload Engine and Apps", None)
+ )
+ self.config_header.setText(
+ QCoreApplication.translate("TankDialog", "Configuration", None)
+ )
+ self.config_label.setText(
+ QCoreApplication.translate(
+ "TankDialog",
+ "Below is a list of all the configuration settings for this app, as defined in your environment file:",
+ None,
+ )
+ )
+
# retranslateUi
diff --git a/python/tk-core/python/tank/platform/software_launcher.py b/python/tk-core/python/tank/platform/software_launcher.py
index 39764c4e..96340d05 100644
--- a/python/tk-core/python/tank/platform/software_launcher.py
+++ b/python/tk-core/python/tank/platform/software_launcher.py
@@ -13,20 +13,18 @@
should implement.
"""
-import os
-import sys
import glob
+import os
import pprint
+import sys
from ..errors import TankError
from ..log import LogManager
+from ..util import ShotgunPath, is_windows
+from ..util import sgre as re
from ..util.loader import load_plugin
from ..util.version import is_version_older
-from ..util import ShotgunPath, is_windows, sgre as re
-
-from . import constants
-from . import validation
-
+from . import constants, validation
from .bundle import resolve_setting_value
from .engine import get_env_and_descriptor_for_engine
@@ -79,9 +77,7 @@ def create_engine_launcher(tk, context, engine_name, versions=None, products=Non
on disk.
"""
# Get the engine environment and descriptor using engine.py code
- (env, engine_descriptor) = get_env_and_descriptor_for_engine(
- engine_name, tk, context
- )
+ env, engine_descriptor = get_env_and_descriptor_for_engine(engine_name, tk, context)
# Make sure it exists locally
if not engine_descriptor.exists_local():
diff --git a/python/tk-core/python/tank/platform/util.py b/python/tk-core/python/tank/platform/util.py
index 903d918b..be642720 100644
--- a/python/tk-core/python/tank/platform/util.py
+++ b/python/tk-core/python/tank/platform/util.py
@@ -10,11 +10,11 @@
import logging
-from .import_stack import ImportStack
from ..errors import TankError
-from .errors import TankContextChangeNotSupportedError, TankCurrentModuleNotFoundError
-from .engine import current_engine, _restart_engine
from ..log import LogManager
+from .engine import _restart_engine, current_engine
+from .errors import TankContextChangeNotSupportedError, TankCurrentModuleNotFoundError
+from .import_stack import ImportStack
def _get_current_bundle():
diff --git a/python/tk-core/python/tank/platform/validation.py b/python/tk-core/python/tank/platform/validation.py
index e4a219fb..fb73ceed 100644
--- a/python/tk-core/python/tank/platform/validation.py
+++ b/python/tk-core/python/tank/platform/validation.py
@@ -12,15 +12,16 @@
App configuration and schema validation.
"""
+
import os
import sys
-from . import constants
from ..errors import TankError, TankNoDefaultValueError
+from ..log import LogManager
from ..template import TemplateString
+from ..util.version import is_version_number, is_version_older
+from . import constants
from .bundle import resolve_default_value
-from ..util.version import is_version_older, is_version_number
-from ..log import LogManager
# We're potentially running here in an environment with
# no engine available via current_engine(), so we'll have
@@ -354,7 +355,7 @@ def __validate_schema_type(self, settings_key, data_type):
% (settings_key, self._display_name)
)
- if not data_type in constants.TANK_SCHEMA_VALID_TYPES:
+ if data_type not in constants.TANK_SCHEMA_VALID_TYPES:
params = (data_type, settings_key, self._display_name)
raise TankError("Invalid type '%s' in schema '%s' for '%s'!" % params)
@@ -406,14 +407,14 @@ def __validate_schema_value(self, settings_key, schema):
def __validate_schema_list(self, settings_key, schema):
# Check that the schema contains "values"
- if not "values" in schema or type(schema["values"]) != dict:
+ if "values" not in schema or not isinstance(schema["values"], dict):
params = (settings_key, self._display_name)
raise TankError(
"Missing or invalid 'values' dict in schema '%s' for '%s'!" % params
)
# If there's an "allows_empty" key, it should be a bool
- if "allows_empty" in schema and type(schema["allows_empty"]) != bool:
+ if "allows_empty" in schema and not isinstance(schema["allows_empty"], bool):
params = (settings_key, self._display_name)
raise TankError(
"Invalid 'allows_empty' bool in schema '%s' for '%s'!" % params
@@ -423,13 +424,13 @@ def __validate_schema_list(self, settings_key, schema):
def __validate_schema_dict(self, settings_key, schema):
# Check that if the schema contains "items" then it must be a dict
- if "items" in schema and type(schema["items"]) != dict:
+ if "items" in schema and not isinstance(schema["items"], dict):
params = (settings_key, self._display_name)
raise TankError("Invalid 'items' dict in schema '%s' for '%s'!" % params)
for key, value_schema in schema.get("items", {}).items():
# Check that the value is a dict, and validate it...
- if type(value_schema) != dict:
+ if not isinstance(value_schema, dict):
params = (key, settings_key, self._display_name)
raise TankError("Invalid '%s' dict in schema '%s' for '%s'" % params)
@@ -438,19 +439,21 @@ def __validate_schema_dict(self, settings_key, schema):
def __validate_schema_template(self, settings_key, schema):
# new style template def: if there is a fields key, it should be a str
- if "fields" in schema and type(schema["fields"]) != str:
+ if "fields" in schema and not isinstance(schema["fields"], str):
params = (settings_key, self._display_name)
raise TankError("Invalid 'fields' string in schema '%s' for '%s'!" % params)
# old-style - if there's a required_fields key, it should contain a list of strs.
- if "required_fields" in schema and type(schema["required_fields"]) != list:
+ if "required_fields" in schema and not isinstance(
+ schema["required_fields"], list
+ ):
params = (settings_key, self._display_name)
raise TankError(
"Invalid 'required_fields' list in schema '%s' for '%s'!" % params
)
for field in schema.get("required_fields", []):
- if type(field) != str:
+ if not isinstance(field, str):
params = (field, settings_key, self._display_name)
raise TankError(
"Invalid 'required_fields' value '%s' in schema '%s' for '%s'!"
@@ -458,9 +461,9 @@ def __validate_schema_template(self, settings_key, schema):
)
# old-style - if there's an optional_fields key, it should contain a list of strs or be "*"
- if "optional_fields" in schema and type(schema["optional_fields"]) == list:
+ if "optional_fields" in schema and isinstance(schema["optional_fields"], list):
for field in schema.get("optional_fields", []):
- if type(field) != str:
+ if not isinstance(field, str):
params = (field, settings_key, self._display_name)
raise TankError(
"Invalid 'optional_fields' value '%s' in schema '%s' for '%s'!"
@@ -473,7 +476,7 @@ def __validate_schema_template(self, settings_key, schema):
)
# If there's an "allows_empty" key, it should be a bool
- if "allows_empty" in schema and type(schema["allows_empty"]) != bool:
+ if "allows_empty" in schema and not isinstance(schema["allows_empty"], bool):
params = (settings_key, self._display_name)
raise TankError(
"Invalid 'allows_empty' bool in schema '%s' for '%s'!" % params
@@ -542,7 +545,7 @@ def __validate_settings_value(self, settings_key, schema, value):
data_type = schema.get("type")
# functor values which refer to a hook are never validated
- if type(value) == str and value.startswith("hook:"):
+ if isinstance(value, str) and value.startswith("hook:"):
return
# shotgun filters can be a variety of formats so assume it is
@@ -604,7 +607,7 @@ def __validate_settings_dict(self, settings_key, schema, value):
items = schema.get("items", {})
for key, value_schema in items.items():
# Check for required keys
- if not key in value:
+ if key not in value:
params = (key, settings_key, self._display_name)
raise TankError(
"Missing required key '%s' in setting '%s' for '%s'" % params
@@ -890,7 +893,7 @@ def __validate_new_style_template(self, cur_template, fields_str):
# one or more mandatory issues. No point checking further
return problems
- if star == True:
+ if star:
# means an open ended number of fields can be used.
# no need to do more validation
return problems
diff --git a/python/tk-core/python/tank/template.py b/python/tk-core/python/tank/template.py
index d5f41f04..5a76a62e 100644
--- a/python/tk-core/python/tank/template.py
+++ b/python/tk-core/python/tank/template.py
@@ -16,11 +16,12 @@
import os
import sys
-from . import templatekey
+from tank.util import is_linux, is_macos, is_windows
+from tank.util import sgre as re
+
+from . import constants, templatekey
from .errors import TankError
-from . import constants
from .template_path_parser import TemplatePathParser
-from tank.util import is_linux, is_macos, is_windows, sgre as re
class Template(object):
@@ -474,7 +475,7 @@ def validate(self, path, fields=None, skip_keys=None):
:returns: True if the path is valid for this template
:rtype: Bool
"""
- return self.validate_and_get_fields(path, fields, skip_keys) != None
+ return self.validate_and_get_fields(path, fields, skip_keys) is not None
def get_fields(self, input_path, skip_keys=None):
"""
@@ -503,7 +504,7 @@ def get_fields(self, input_path, skip_keys=None):
for ordered_keys, static_tokens in zip(self._ordered_keys, self._static_tokens):
path_parser = TemplatePathParser(ordered_keys, static_tokens)
fields = path_parser.parse_path(input_path, skip_keys)
- if fields != None:
+ if fields is not None:
break
if fields is None:
diff --git a/python/tk-core/python/tank/template_includes.py b/python/tk-core/python/tank/template_includes.py
index 18e3e082..dc4d7a1c 100644
--- a/python/tk-core/python/tank/template_includes.py
+++ b/python/tk-core/python/tank/template_includes.py
@@ -28,10 +28,8 @@
"""
-import os
-
-from .errors import TankError
from . import constants
+from .errors import TankError
from .util import yaml_cache
from .util.includes import resolve_include
diff --git a/python/tk-core/python/tank/template_path_parser.py b/python/tk-core/python/tank/template_path_parser.py
index f703617a..bab1e1ca 100644
--- a/python/tk-core/python/tank/template_path_parser.py
+++ b/python/tk-core/python/tank/template_path_parser.py
@@ -13,6 +13,7 @@
"""
import os
+
from .errors import TankError
diff --git a/python/tk-core/python/tank/templatekey.py b/python/tk-core/python/tank/templatekey.py
index b165eaf3..c3276b9f 100644
--- a/python/tk-core/python/tank/templatekey.py
+++ b/python/tk-core/python/tank/templatekey.py
@@ -12,9 +12,9 @@
Classes for fields on TemplatePaths and TemplateStrings
"""
-import sys
import collections.abc
import datetime
+
from . import constants
from .errors import TankError
from .util import sgre as re
@@ -514,7 +514,7 @@ def _as_string(self, value):
if match is None:
# no match. return empty string
# validate should prevent this from happening
- resolved_value = u""
+ resolved_value = ""
elif self._subset_format:
# we have an explicit format string we want to apply to the match.
@@ -1099,9 +1099,7 @@ def validate(self, value):
error_msg += "Valid frame specs: %s\n" % str(self._frame_specs)
error_msg += "Valid format strings: %s\n" % full_format_strings
- if isinstance(value, str) and value.startswith(
- self.FRAMESPEC_FORMAT_INDICATOR
- ):
+ if isinstance(value, str) and value.startswith(self.FRAMESPEC_FORMAT_INDICATOR):
# FORMAT: YXZ string - check that XYZ is in VALID_FORMAT_STRINGS
pattern = self._extract_format_string(value)
if pattern in self.VALID_FORMAT_STRINGS:
@@ -1110,9 +1108,7 @@ def validate(self, value):
self._last_error = error_msg
return False
- elif isinstance(value, str) and re.match(
- self.FLAME_PATTERN_REGEX, value
- ):
+ elif isinstance(value, str) and re.match(self.FLAME_PATTERN_REGEX, value):
# value is matching the flame-style sequence pattern
# [1234-5678]
return True
@@ -1131,16 +1127,12 @@ def validate(self, value):
def _as_string(self, value):
- if isinstance(value, str) and value.startswith(
- self.FRAMESPEC_FORMAT_INDICATOR
- ):
+ if isinstance(value, str) and value.startswith(self.FRAMESPEC_FORMAT_INDICATOR):
# this is a FORMAT: XYZ - convert it to the proper resolved frame spec
pattern = self._extract_format_string(value)
return self._resolve_frame_spec(pattern, self.format_spec)
- if isinstance(value, str) and re.match(
- self.FLAME_PATTERN_REGEX, value
- ):
+ if isinstance(value, str) and re.match(self.FLAME_PATTERN_REGEX, value):
# this is a flame style sequence token [1234-56773]
return value
@@ -1167,9 +1159,7 @@ def _extract_format_string(self, value):
"""
Returns XYZ given the string "FORMAT: XYZ"
"""
- if isinstance(value, str) and value.startswith(
- self.FRAMESPEC_FORMAT_INDICATOR
- ):
+ if isinstance(value, str) and value.startswith(self.FRAMESPEC_FORMAT_INDICATOR):
pattern = value.replace(self.FRAMESPEC_FORMAT_INDICATOR, "").strip()
else:
# passthrough
diff --git a/python/tk-core/python/tank/util/__init__.py b/python/tk-core/python/tank/util/__init__.py
index 80a7e284..6b304350 100644
--- a/python/tk-core/python/tank/util/__init__.py
+++ b/python/tk-core/python/tank/util/__init__.py
@@ -8,51 +8,42 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from .platforms import is_windows, is_linux, is_macos
-from .shotgun import register_publish
-from .shotgun import resolve_publish_path
-from .shotgun import find_publish
-from .shotgun import download_url
-from .shotgun import create_event_log_entry
-from .shotgun import get_entity_type_display_name
-from .shotgun import get_published_file_entity_type
+from . import filesystem, json, pickle
+from .environment import append_path_to_env_var, prepend_path_to_env_var
+from .errors import (
+ EnvironmentVariableFileLookupError,
+ PublishPathNotDefinedError,
+ PublishPathNotSupported,
+ PublishResolveError,
+ ShotgunAttachmentDownloadError,
+ ShotgunPublishError,
+ UnresolvableCoreConfigurationError,
+)
+from .local_file_storage import LocalFileStorageManager
+from .login import get_current_user, get_shotgun_user
+# DO keep the following two log_user_*_metric to preserve retro
+# compatibility and prevent exception in legacy engine code.
+from .metrics import EventMetric, log_user_activity_metric, log_user_attribute_metric
+from .platforms import is_linux, is_macos, is_windows
+from .shotgun import (
+ create_event_log_entry,
+ download_url,
+ find_publish,
+ get_entity_type_display_name,
+ get_published_file_entity_type,
+ register_publish,
+ resolve_publish_path,
+)
+from .shotgun_entity import get_sg_entity_name_field
+from .shotgun_path import ShotgunPath
+from .storage_roots import StorageRoots
+from .user_settings import UserSettings
from .version import (
- is_version_older,
is_version_newer,
- is_version_older_or_equal,
is_version_newer_or_equal,
+ is_version_older,
+ is_version_older_or_equal,
suppress_known_deprecation,
version_parse,
)
-
-from .shotgun_entity import get_sg_entity_name_field
-
-from .environment import append_path_to_env_var
-from .environment import prepend_path_to_env_var
-
-from .login import get_shotgun_user
-from .login import get_current_user
-
-# DO keep the following two log_user_*_metric to preserve retro
-# compatibility and prevent exception in legacy engine code.
-from .metrics import log_user_activity_metric
-from .metrics import log_user_attribute_metric
-from .metrics import EventMetric
-from .shotgun_path import ShotgunPath
-
-from . import filesystem
-from . import pickle
-from . import json
-
-from .local_file_storage import LocalFileStorageManager
-
-from .errors import PublishResolveError
-from .errors import UnresolvableCoreConfigurationError, ShotgunAttachmentDownloadError
-from .errors import EnvironmentVariableFileLookupError, ShotgunPublishError
-from .errors import PublishResolveError
-from .errors import PublishPathNotDefinedError, PublishPathNotSupported
-
-from .user_settings import UserSettings
-
-from .storage_roots import StorageRoots
diff --git a/python/tk-core/python/tank/util/environment.py b/python/tk-core/python/tank/util/environment.py
index b572555d..a0cef9b6 100644
--- a/python/tk-core/python/tank/util/environment.py
+++ b/python/tk-core/python/tank/util/environment.py
@@ -13,6 +13,7 @@
"""
import os
+
from .platforms import is_windows
diff --git a/python/tk-core/python/tank/util/filesystem.py b/python/tk-core/python/tank/util/filesystem.py
index 6a36d6e0..ab2c1742 100644
--- a/python/tk-core/python/tank/util/filesystem.py
+++ b/python/tk-core/python/tank/util/filesystem.py
@@ -11,15 +11,15 @@
Utility methods for manipulating files and folders
"""
+import datetime
+import errno
+import functools
import os
import re
-import sys
-import errno
-import stat
import shutil
-import datetime
-import functools
+import stat
import subprocess
+import sys
from contextlib import contextmanager
from .. import LogManager
diff --git a/python/tk-core/python/tank/util/includes.py b/python/tk-core/python/tank/util/includes.py
index 1b3708f7..d9c39ede 100644
--- a/python/tk-core/python/tank/util/includes.py
+++ b/python/tk-core/python/tank/util/includes.py
@@ -8,14 +8,13 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
+import ntpath
import os
-import sys
import posixpath
-import ntpath
-from .shotgun_path import ShotgunPath
-from .platforms import is_windows
from ..errors import TankError
+from .platforms import is_windows
+from .shotgun_path import ShotgunPath
def _is_abs(path):
diff --git a/python/tk-core/python/tank/util/json.py b/python/tk-core/python/tank/util/json.py
index 896cd4e7..2d303766 100644
--- a/python/tk-core/python/tank/util/json.py
+++ b/python/tk-core/python/tank/util/json.py
@@ -14,8 +14,9 @@
# We need to add this to the file or the import json will reimport this
# module instead of importing the global json module.
from __future__ import absolute_import
-import sys
+
import json
+import sys
from .unicode import ensure_contains_str
diff --git a/python/tk-core/python/tank/util/loader.py b/python/tk-core/python/tank/util/loader.py
index 3f81a0be..4b30b84b 100644
--- a/python/tk-core/python/tank/util/loader.py
+++ b/python/tk-core/python/tank/util/loader.py
@@ -13,13 +13,13 @@
"""
-import sys
import importlib.util
-import traceback
import inspect
+import sys
+import traceback
-from ..errors import TankError
from .. import LogManager
+from ..errors import TankError
log = LogManager.get_logger(__name__)
@@ -57,8 +57,8 @@ def load_plugin(plugin_file, valid_base_class, alternate_base_classes=None):
try:
plugin_spec = importlib.util.spec_from_file_location(module_uid, plugin_file)
module = importlib.util.module_from_spec(plugin_spec)
- plugin_spec.loader.exec_module(module)
sys.modules[module.__name__] = module
+ plugin_spec.loader.exec_module(module)
except Exception:
# log the full callstack to make sure that whatever the
# calling code is doing, this error is logged to help
@@ -66,7 +66,7 @@ def load_plugin(plugin_file, valid_base_class, alternate_base_classes=None):
log.exception("Cannot load plugin file '%s'" % plugin_file)
# dump out the callstack for this one -- to help people get good messages when there is a plugin error
- (exc_type, exc_value, exc_traceback) = sys.exc_info()
+ exc_type, exc_value, exc_traceback = sys.exc_info()
message = ""
message += (
"Failed to load plugin %s. The following error was reported:\n"
diff --git a/python/tk-core/python/tank/util/local_file_storage.py b/python/tk-core/python/tank/util/local_file_storage.py
index 6215586e..ff805a6c 100644
--- a/python/tk-core/python/tank/util/local_file_storage.py
+++ b/python/tk-core/python/tank/util/local_file_storage.py
@@ -57,10 +57,10 @@ class LocalFileStorageManager(object):
"""
# generation of path structures
- (CORE_V17, CORE_V18) = range(2)
+ CORE_V17, CORE_V18 = range(2)
# supported types of paths
- (LOGGING, CACHE, PERSISTENT, PREFERENCES) = range(4)
+ LOGGING, CACHE, PERSISTENT, PREFERENCES = range(4)
@classmethod
def get_global_root(cls, path_type, generation=CORE_V18):
diff --git a/python/tk-core/python/tank/util/login.py b/python/tk-core/python/tank/util/login.py
index 442cb377..31e99232 100644
--- a/python/tk-core/python/tank/util/login.py
+++ b/python/tk-core/python/tank/util/login.py
@@ -14,7 +14,6 @@
"""
import os
-import sys
from . import constants
from .platforms import is_windows
@@ -34,7 +33,7 @@ def get_login_name():
pwd_entry = pwd.getpwuid(os.geteuid())
return pwd_entry[0]
- except:
+ except Exception:
return None
diff --git a/python/tk-core/python/tank/util/metrics.py b/python/tk-core/python/tank/util/metrics.py
index 022de787..521f183c 100644
--- a/python/tk-core/python/tank/util/metrics.py
+++ b/python/tk-core/python/tank/util/metrics.py
@@ -72,7 +72,7 @@ def get_darwin_version(cls):
# For macOS / OSX we keep only the Major.minor
os_version = re.findall(r"\d*\.\d*", raw_version_str)[0]
- except:
+ except Exception:
pass
return os_version
@@ -96,7 +96,7 @@ def get_linux_version(cls):
major_version_str = re.findall(r"\d*", raw_version_str)[0]
os_version = "%s %s" % (distribution, major_version_str)
- except:
+ except Exception:
pass
return os_version
@@ -116,7 +116,7 @@ def get_windows_version(cls):
# as it returns a friendly name e.g: XP, 7, 10 etc.
os_version = platform.release()
- except:
+ except Exception:
pass
return os_version
@@ -161,7 +161,7 @@ def get_platform_info(cls):
else:
os_info["OS"] = "Unsupported system: (%s)" % (system)
- except:
+ except Exception:
# On any exception we fallback to default value
pass
@@ -243,7 +243,7 @@ def log(self, metric, log_once=False):
# remember that we've logged this one already
self.__logged_metrics.add(metric_identifier)
- except:
+ except Exception:
pass
finally:
self._lock.release()
@@ -275,7 +275,7 @@ def get_metrics(self, count=None):
# would be nice to be able to pop N from deque. oh well.
metrics = [self._queue.popleft() for i in range(0, count)]
- except:
+ except Exception:
pass
finally:
self._lock.release()
@@ -434,7 +434,7 @@ def run(self):
else:
break
- except Exception as e:
+ except Exception:
pass
finally:
# wait, checking for halt event before more processing
@@ -790,7 +790,7 @@ def log(cls, group, name, properties=None, log_once=False, bundle=None):
from sgtk.platform.util import current_bundle
bundle = current_bundle()
- except:
+ except Exception:
pass
if not bundle:
@@ -800,7 +800,7 @@ def log(cls, group, name, properties=None, log_once=False, bundle=None):
from ..platform.engine import current_engine
bundle = current_engine()
- except:
+ except Exception:
# Bailing out trying to guess bundle
pass
diff --git a/python/tk-core/python/tank/util/metrics_cache.py b/python/tk-core/python/tank/util/metrics_cache.py
index 4e5ec77d..9693addd 100644
--- a/python/tk-core/python/tank/util/metrics_cache.py
+++ b/python/tk-core/python/tank/util/metrics_cache.py
@@ -29,9 +29,8 @@
import json
import os
-from . import metrics
-
from .. import LogManager
+from . import metrics
logger = LogManager.get_logger(__name__)
@@ -97,7 +96,7 @@ def consume():
logger.debug("Invalid cached metric format")
continue
- (args, kwargs) = data
+ args, kwargs = data
if not isinstance(args, list) or not isinstance(kwargs, dict):
logger.debug("Invalid cached metric format")
continue
diff --git a/python/tk-core/python/tank/util/process.py b/python/tk-core/python/tank/util/process.py
index 34fd5e86..6fd585e0 100644
--- a/python/tk-core/python/tank/util/process.py
+++ b/python/tk-core/python/tank/util/process.py
@@ -8,11 +8,11 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import subprocess
import pprint
+import subprocess
-from .platforms import is_windows
from ..log import LogManager
+from .platforms import is_windows
logger = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/util/pyside2_patcher.py b/python/tk-core/python/tank/util/pyside2_patcher.py
index 7ef8ae83..974269fc 100644
--- a/python/tk-core/python/tank/util/pyside2_patcher.py
+++ b/python/tk-core/python/tank/util/pyside2_patcher.py
@@ -12,10 +12,10 @@
PySide 2 backwards compatibility layer for use with PySide 1 code.
"""
-import os
-import sys
import functools
+import os
import subprocess
+import sys
import types
import warnings
import webbrowser
@@ -328,7 +328,7 @@ def openUrl(cls, url):
# returns False or raises some error.
try:
return webbrowser.open_new_tab(url.toString().encode("utf-8"))
- except:
+ except Exception:
return False
@classmethod
diff --git a/python/tk-core/python/tank/util/pyside6_patcher.py b/python/tk-core/python/tank/util/pyside6_patcher.py
index 5aed21d8..0cef38f0 100644
--- a/python/tk-core/python/tank/util/pyside6_patcher.py
+++ b/python/tk-core/python/tank/util/pyside6_patcher.py
@@ -91,7 +91,7 @@ def _patch_QTextCodec(cls, QtCore):
QTextCodec has been removed in Qt6. Using this class will do nothing.
"""
- class QTextCodec():
+ class QTextCodec:
@staticmethod
def codecForName(name):
return None
@@ -135,7 +135,6 @@ def grabWindow(window=0, x=0, y=0, width=-1, height=-1):
screen = QtGui.QApplication.primaryScreen()
return screen.grabWindow(window, x, y, width, height)
-
QtGui.QPixmap = QPixmap
@classmethod
@@ -153,7 +152,6 @@ def pixmap(self, *args, **kwargs):
QtGui.QIcon.pixmap = pixmap
-
@classmethod
def _patch_QLabel(cls, QtGui):
"""
@@ -229,11 +227,11 @@ def emit(screen_index):
screens = QtGui.QGuiApplication.screens()
screen = screens[screen_index]
screen.geometryChanged.emit()
- except:
+ except Exception:
pass
-
original_QScreen_availableGeometry = QtGui.QScreen.availableGeometry
+
def availableGeometry(self, arg__1=None):
"""
Patch QScreen to also act as QDesktopWidget.
@@ -298,7 +296,9 @@ def _patch_QOpenGLContext(cls, QtGui):
def versionFunctions(self, version_profile=None):
if version_profile:
- return QtGui.QOpenGLVersionFunctionsFactory.get(versionProfile=version_profile, context=self)
+ return QtGui.QOpenGLVersionFunctionsFactory.get(
+ versionProfile=version_profile, context=self
+ )
return QtGui.QOpenGLVersionFunctionsFactory.get(context=self)
QtGui.QOpenGLContext.versionFunctions = versionFunctions
@@ -352,18 +352,39 @@ def __init__(self, *args, **kwargs):
if case_sensitivity is None:
original_QRegularExpression.__init__(self, args[0])
else:
- if case_sensitivity == original_QRegularExpression.CaseInsensitiveOption:
+ if (
+ case_sensitivity
+ == original_QRegularExpression.CaseInsensitiveOption
+ ):
opts = original_QRegularExpression.CaseInsensitiveOption
else:
opts = original_QRegularExpression.NoPatternOption
- original_QRegularExpression.__init__(self, args[0], options=opts)
+ original_QRegularExpression.__init__(
+ self, args[0], options=opts
+ )
- self.isEmpty = lambda *args, **kwargs: QRegularExpression.isEmpty(self, *args, **kwargs)
- self.indexIn = lambda *args, **kwargs: QRegularExpression.indexIn(self, *args, **kwargs)
- self.matchedLength = lambda *args, **kwargs: QRegularExpression.matchedLength(self, *args, **kwargs)
- self.setCaseSensitivity = lambda *args, **kwargs: QRegularExpression.setCaseSensitivity(self, *args, **kwargs)
- self.pos = lambda *args, **kwargs: QRegularExpression.pos(self, *args, **kwargs)
- self.cap = lambda *args, **kwargs: QRegularExpression.cap(self, *args, **kwargs)
+ self.isEmpty = lambda *args, **kwargs: QRegularExpression.isEmpty(
+ self, *args, **kwargs
+ )
+ self.indexIn = lambda *args, **kwargs: QRegularExpression.indexIn(
+ self, *args, **kwargs
+ )
+ self.matchedLength = (
+ lambda *args, **kwargs: QRegularExpression.matchedLength(
+ self, *args, **kwargs
+ )
+ )
+ self.setCaseSensitivity = (
+ lambda *args, **kwargs: QRegularExpression.setCaseSensitivity(
+ self, *args, **kwargs
+ )
+ )
+ self.pos = lambda *args, **kwargs: QRegularExpression.pos(
+ self, *args, **kwargs
+ )
+ self.cap = lambda *args, **kwargs: QRegularExpression.cap(
+ self, *args, **kwargs
+ )
@staticmethod
def isEmpty(re):
@@ -426,7 +447,9 @@ def cap(re, n):
QtCore.QRegularExpression.isEmpty = QRegularExpression.isEmpty
QtCore.QRegularExpression.indexIn = QRegularExpression.indexIn
QtCore.QRegularExpression.matchedLength = QRegularExpression.matchedLength
- QtCore.QRegularExpression.setCaseSensitivity = QRegularExpression.setCaseSensitivity
+ QtCore.QRegularExpression.setCaseSensitivity = (
+ QRegularExpression.setCaseSensitivity
+ )
QtCore.QRegularExpression.pos = QRegularExpression.pos
# This pattern matching flag is obsolete now.
@@ -452,9 +475,9 @@ def flush():
@classmethod
def patch(
- cls,
- QtWebEngineWidgets,
- QtWebEngineCore,
+ cls,
+ QtWebEngineWidgets,
+ QtWebEngineCore,
):
"""
Patch the PySide6 modules, classes and function to conform to the PySide interface.
@@ -475,8 +498,8 @@ def patch(
from PySide6 import (
QtCore,
QtGui,
- QtWidgets,
QtOpenGL,
+ QtWidgets,
)
# First create new modules to act as the PySide modules
@@ -546,8 +569,12 @@ def patch(
cls._patch_QRegularExpression(qt_core_shim)
qt_core_shim.QRegExp = qt_core_shim.QRegularExpression
# Rename RegExp functions to RegularExpression
- qt_gui_shim.QSortFilterProxyModel.filterRegExp = qt_gui_shim.QSortFilterProxyModel.filterRegularExpression
- qt_gui_shim.QSortFilterProxyModel.setFilterRegExp = qt_gui_shim.QSortFilterProxyModel.setFilterRegularExpression
+ qt_gui_shim.QSortFilterProxyModel.filterRegExp = (
+ qt_gui_shim.QSortFilterProxyModel.filterRegularExpression
+ )
+ qt_gui_shim.QSortFilterProxyModel.setFilterRegExp = (
+ qt_gui_shim.QSortFilterProxyModel.setFilterRegularExpression
+ )
# Patch the QCoreApplication.flush() method to ensure compatibility with code
# that expects this method, which is marked as obsolete.
@@ -588,7 +615,9 @@ def patch(
# The default timeout parameter removed. This param, if given, will be ignored. It will
# always timeout after 100 ms
# https://doc.qt.io/qt-6/widgets-changes-qt6.html#the-qabstractbutton-class
- qt_gui_shim.QAbstractButton.animateClick = lambda self, msec=0: self.animateClick()
+ qt_gui_shim.QAbstractButton.animateClick = (
+ lambda self, msec=0: self.animateClick()
+ )
# Changes to QFont
# https://doc.qt.io/qt-6/gui-changes-qt6.html#the-qfont-class
@@ -596,7 +625,9 @@ def patch(
qt_gui_shim.QFont.setWeight = qt_gui_shim.QFont.setLegacyWeight
# QHeaderView method rename
- qt_gui_shim.QHeaderView.setResizeMode = qt_gui_shim.QHeaderView.setSectionResizeMode
+ qt_gui_shim.QHeaderView.setResizeMode = (
+ qt_gui_shim.QHeaderView.setSectionResizeMode
+ )
# QPainter HighQualityAntialiasing is obsolete. Use Antiasliasing instead.
# https://doc.qt.io/qt-5/qpainter.html#RenderHint-enum
@@ -609,6 +640,8 @@ def patch(
if qt_web_engine_widgets_shim:
# QtWwebEngineWidgets
qt_web_engine_widgets_shim.QWebEnginePage = QtWebEngineCore.QWebEnginePage
- qt_web_engine_widgets_shim.QWebEngineProfile = QtWebEngineCore.QWebEngineProfile
+ qt_web_engine_widgets_shim.QWebEngineProfile = (
+ QtWebEngineCore.QWebEngineProfile
+ )
return qt_core_shim, qt_gui_shim, qt_web_engine_widgets_shim
diff --git a/python/tk-core/python/tank/util/qt_importer.py b/python/tk-core/python/tank/util/qt_importer.py
index b0fc282e..297059a3 100644
--- a/python/tk-core/python/tank/util/qt_importer.py
+++ b/python/tk-core/python/tank/util/qt_importer.py
@@ -148,7 +148,6 @@ def _import_module_by_name(self, parent_module_name, module_name):
except Exception as e:
logger.debug("Unable to import module '%s': %s", module_name, e)
-
def _import_pyside2(self):
"""
This will be called at initialization to discover every PySide 2 modules.
@@ -159,8 +158,8 @@ def _import_pyside2(self):
# throw an import error which will be handled by the calling code. Note that PySide2 can be
# imported even if the Qt binaries are missing, so it's better to try importing QtCore for
# testing.
- from PySide2 import QtCore
import shiboken2
+ from PySide2 import QtCore
# List of all Qt 5 modules.
sub_modules = [
@@ -234,8 +233,9 @@ def _import_pyside2_as_pyside(self):
:returns: The (binding name, binding version, modules) tuple.
"""
import PySide2
- from PySide2 import QtCore, QtGui, QtWidgets
import shiboken2
+ from PySide2 import QtCore, QtGui, QtWidgets
+
from .pyside2_patcher import PySide2Patcher
QtCore, QtGui = PySide2Patcher.patch(QtCore, QtGui, QtWidgets, PySide2)
@@ -269,6 +269,7 @@ def _import_pyside6_as_pyside(self): # pragma: no cover
import PySide6
import shiboken6
+
from .pyside6_patcher import PySide6Patcher
QtWebEngineWidgets, QtWebEngineCore = None, None
diff --git a/python/tk-core/python/tank/util/resources/no_preview.jpg b/python/tk-core/python/tank/util/resources/no_preview.jpg
deleted file mode 100644
index c5239376..00000000
Binary files a/python/tk-core/python/tank/util/resources/no_preview.jpg and /dev/null differ
diff --git a/python/tk-core/python/tank/util/sgre.py b/python/tk-core/python/tank/util/sgre.py
index 4283058a..424ee385 100644
--- a/python/tk-core/python/tank/util/sgre.py
+++ b/python/tk-core/python/tank/util/sgre.py
@@ -13,28 +13,28 @@
some functionality.
"""
+# For Python 3, we'll wrap the re functions to inject the ASCII flag when necessary
+# to maintain the previous behavior.
+import re as _re
+import typing as _typing
+
# Import constants and functions that won't be wrapped
-from re import (
+from re import LOCALE # noqa import into namespace
+from re import escape # noqa import into namespace
+from re import ( # noqa: F401 import into namespace
DEBUG,
- I,
+ DOTALL,
IGNORECASE,
+ MULTILINE,
+ UNICODE,
+ VERBOSE,
+ I,
L,
- LOCALE, # noqa import into namespace
M,
- MULTILINE,
S,
- DOTALL,
U,
- UNICODE,
X,
- VERBOSE,
)
-from re import escape # noqa import into namespace
-
-# For Python 3, we'll wrap the re functions to inject the ASCII flag when necessary
-# to maintain the previous behavior.
-import re as _re
-import typing as _typing
def _re_wrap(fn, flags_arg_position):
diff --git a/python/tk-core/python/tank/util/shotgun/__init__.py b/python/tk-core/python/tank/util/shotgun/__init__.py
index 1d72bbd6..43490691 100644
--- a/python/tk-core/python/tank/util/shotgun/__init__.py
+++ b/python/tk-core/python/tank/util/shotgun/__init__.py
@@ -10,25 +10,23 @@
from .connection import (
- get_project_name_studio_hook_location,
+ create_sg_connection,
get_associated_sg_base_url,
get_associated_sg_config_data,
get_deferred_sg_connection,
+ get_project_name_studio_hook_location,
get_sg_connection,
- create_sg_connection,
-)
-
-from .publish_util import (
- get_entity_type_display_name,
- find_publish,
- create_event_log_entry,
- get_published_file_entity_type,
)
-
-from .publish_creation import register_publish
-from .publish_resolve import resolve_publish_path
from .download import (
- download_url,
download_and_unpack_attachment,
download_and_unpack_url,
+ download_url,
+)
+from .publish_creation import register_publish
+from .publish_resolve import resolve_publish_path
+from .publish_util import (
+ create_event_log_entry,
+ find_publish,
+ get_entity_type_display_name,
+ get_published_file_entity_type,
)
diff --git a/python/tk-core/python/tank/util/shotgun/connection.py b/python/tk-core/python/tank/util/shotgun/connection.py
index dda58b7c..97acd236 100644
--- a/python/tk-core/python/tank/util/shotgun/connection.py
+++ b/python/tk-core/python/tank/util/shotgun/connection.py
@@ -372,7 +372,6 @@ def get_sg_connection():
:return: PTR API handle
"""
- global _g_sg_cached_connections
sg = getattr(_g_sg_cached_connections, "sg", None)
if sg is None:
diff --git a/python/tk-core/python/tank/util/shotgun/download.py b/python/tk-core/python/tank/util/shotgun/download.py
index 9887f71d..7e73ed10 100644
--- a/python/tk-core/python/tank/util/shotgun/download.py
+++ b/python/tk-core/python/tank/util/shotgun/download.py
@@ -13,7 +13,6 @@
"""
import os
-import sys
import tempfile
import time
import urllib.parse
@@ -181,7 +180,9 @@ def download_and_unpack_attachment(
)
-def download_and_unpack_url(sg, url, target, retries=5, auto_detect_bundle=False, headers=None):
+def download_and_unpack_url(
+ sg, url, target, retries=5, auto_detect_bundle=False, headers=None
+):
"""
Downloads the content from the provided url, assumes it is a zip file
and attempts to unpack it into the given location.
@@ -197,7 +198,9 @@ def download_and_unpack_url(sg, url, target, retries=5, auto_detect_bundle=False
the bundle in a subfolder, this should be correctly unfolded.
:raises: ShotgunAttachmentDownloadError on failure
"""
- return _download_and_unpack(sg, target, retries, auto_detect_bundle, url=url, headers=headers or {})
+ return _download_and_unpack(
+ sg, target, retries, auto_detect_bundle, url=url, headers=headers or {}
+ )
@LogManager.log_timing
diff --git a/python/tk-core/python/tank/util/shotgun/publish_creation.py b/python/tk-core/python/tank/util/shotgun/publish_creation.py
index c8670cc6..7eaa6483 100644
--- a/python/tk-core/python/tank/util/shotgun/publish_creation.py
+++ b/python/tk-core/python/tank/util/shotgun/publish_creation.py
@@ -311,24 +311,17 @@ def register_publish(tk, context, path, name, version_number, **kwargs):
)
# entity
- if update_entity_thumbnail == True and context.entity is not None:
+ if update_entity_thumbnail is True and context.entity is not None:
tk.shotgun.upload_thumbnail(
context.entity["type"], context.entity["id"], thumbnail_path
)
# task
- if update_task_thumbnail == True and task is not None:
+ if update_task_thumbnail and task is not None:
tk.shotgun.upload_thumbnail("Task", task["id"], thumbnail_path)
else:
- # no thumbnail found - instead use the default one
- this_folder = os.path.abspath(os.path.dirname(__file__))
- no_thumb = os.path.join(
- this_folder, os.path.pardir, "resources", "no_preview.jpg"
- )
- tk.shotgun.upload_thumbnail(
- published_file_entity_type, entity.get("id"), no_thumb
- )
+ log.debug("Publish: No thumbnail provided, skipping thumbnail upload.")
# register dependencies
log.debug("Publish: Register dependencies")
diff --git a/python/tk-core/python/tank/util/shotgun/publish_util.py b/python/tk-core/python/tank/util/shotgun/publish_util.py
index baa7c67d..a135d350 100644
--- a/python/tk-core/python/tank/util/shotgun/publish_util.py
+++ b/python/tk-core/python/tank/util/shotgun/publish_util.py
@@ -13,9 +13,8 @@
"""
from ...log import LogManager
+from .. import constants, login
from ..shotgun_path import ShotgunPath
-from .. import constants
-from .. import login
log = LogManager.get_logger(__name__)
@@ -50,7 +49,7 @@ def get_entity_type_display_name(tk, entity_type_code):
try:
if entity_type_code in schema_data:
display_name = schema_data[entity_type_code]["name"]["value"]
- except:
+ except Exception:
pass
return display_name
diff --git a/python/tk-core/python/tank/util/shotgun_entity.py b/python/tk-core/python/tank/util/shotgun_entity.py
index b0d40ae2..901bf389 100644
--- a/python/tk-core/python/tank/util/shotgun_entity.py
+++ b/python/tk-core/python/tank/util/shotgun_entity.py
@@ -12,8 +12,9 @@
Utilities relating to Shotgun entities
"""
-from . import constants, sgre as re
from ..errors import TankError
+from . import constants
+from . import sgre as re
# A dictionary for Shotgun entities which do not store their name
# in the standard "code" field.
@@ -165,7 +166,7 @@ def __init__(self, tk, entity_type, field_name_expr):
regex_obj = None
if ":" in field_token_expression:
- (full_field_name, regex) = field_token_expression.split(":", 1)
+ full_field_name, regex = field_token_expression.split(":", 1)
try:
regex_obj = re.compile(regex, re.UNICODE)
@@ -428,7 +429,7 @@ def _process_regex(self, value, regex_obj):
match = regex_obj.match(value_to_convert)
if match is None:
# no match. return empty string
- resolved_value = u""
+ resolved_value = ""
else:
# we have a match object. concatenate the groups
resolved_value = "".join(match.groups())
diff --git a/python/tk-core/python/tank/util/shotgun_path.py b/python/tk-core/python/tank/util/shotgun_path.py
index 1b7ee3b6..bbb783d5 100644
--- a/python/tk-core/python/tank/util/shotgun_path.py
+++ b/python/tk-core/python/tank/util/shotgun_path.py
@@ -9,6 +9,7 @@
# not expressly granted therein are reserved by Shotgun Software Inc.
import sys
+
from .platforms import is_linux, is_macos, is_windows
diff --git a/python/tk-core/python/tank/util/storage_roots.py b/python/tk-core/python/tank/util/storage_roots.py
index 64cfda36..0d1c3fc1 100644
--- a/python/tk-core/python/tank/util/storage_roots.py
+++ b/python/tk-core/python/tank/util/storage_roots.py
@@ -14,10 +14,7 @@
from .. import LogManager
from ..errors import TankError
-
-from . import filesystem
-from . import ShotgunPath
-from . import yaml_cache
+from . import ShotgunPath, filesystem, yaml_cache
log = LogManager.get_logger(__name__)
@@ -144,7 +141,7 @@ def write(cls, sg_connection, config_folder, storage_roots):
the required roots.
"""
- (local_storage_lookup, unmapped_roots) = storage_roots.get_local_storages(
+ local_storage_lookup, unmapped_roots = storage_roots.get_local_storages(
sg_connection
)
diff --git a/python/tk-core/python/tank/util/user_settings.py b/python/tk-core/python/tank/util/user_settings.py
index 6e9edbd5..148b3fb6 100644
--- a/python/tk-core/python/tank/util/user_settings.py
+++ b/python/tk-core/python/tank/util/user_settings.py
@@ -15,13 +15,12 @@
import configparser
import os
-from .local_file_storage import LocalFileStorageManager
-from .errors import EnvironmentVariableFileLookupError, TankError
from .. import LogManager
+from .errors import EnvironmentVariableFileLookupError, TankError
+from .local_file_storage import LocalFileStorageManager
from .singleton import Singleton
from .system_settings import SystemSettings
-
logger = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank/util/yaml_cache.py b/python/tk-core/python/tank/util/yaml_cache.py
index 61275f4e..9bfc6042 100644
--- a/python/tk-core/python/tank/util/yaml_cache.py
+++ b/python/tk-core/python/tank/util/yaml_cache.py
@@ -13,12 +13,13 @@
unless it's changed on disk.
"""
-import os
import copy
+import os
import threading
from tank_vendor import yaml
-from ..errors import TankError, TankUnreadableFileError, TankFileDoesNotExistError
+
+from ..errors import TankError, TankFileDoesNotExistError, TankUnreadableFileError
class CacheItem(object):
diff --git a/python/tk-core/python/tank/util/zip.py b/python/tk-core/python/tank/util/zip.py
index a92964f7..32f6d3f9 100644
--- a/python/tk-core/python/tank/util/zip.py
+++ b/python/tk-core/python/tank/util/zip.py
@@ -10,8 +10,9 @@
import os
import zipfile
-from . import filesystem
+
from .. import LogManager
+from . import filesystem
log = LogManager.get_logger(__name__)
diff --git a/python/tk-core/python/tank_vendor/__init__.py b/python/tk-core/python/tank_vendor/__init__.py
index 6fe433ca..6ab8f1f1 100644
--- a/python/tk-core/python/tank_vendor/__init__.py
+++ b/python/tk-core/python/tank_vendor/__init__.py
@@ -12,9 +12,14 @@
tank_vendor module - Third-party dependency management for Shotgun Toolkit.
This module handles loading and importing third-party Python packages from
-version-specific ZIP archives (pkgs.zip). It provides:
-
-1. Auto-discovery of packages in pkgs.zip
+ZIP archives. It provides:
+
+1. Auto-discovery of packages in two locations:
+ - requirements/./pkgs.zip (per-Python-version; present in
+ source checkouts, absent when
+ tk-core is pip-installed and
+ dependencies come from the env)
+ - requirements/any/*.zip (Python-version-independent, optional)
2. Lazy import hook for transparent tank_vendor.* namespace aliasing
3. Package-specific patches (e.g., SSL certificate handling for shotgun_api3)
@@ -22,16 +27,24 @@
# Direct imports work automatically:
from tank_vendor import yaml
from tank_vendor.shotgun_api3 import Shotgun
+ from tank_vendor import flow_data_sdk
# Submodule imports work via lazy loading:
from tank_vendor.shotgun_api3.lib import httplib2
+ from tank_vendor.flow_data_sdk.base import client
# Mock.patch works seamlessly:
mock.patch("tank_vendor.shotgun_api3.Shotgun.find")
-Supported Python versions: 3.7+
+Shared zips in requirements/any/ are loaded after pkgs.zip, so per-version
+pinned packages take precedence over anything in the shared directory.
+Packages whose top-level name is already registered are skipped with a
+warning.
+
+Supported Python versions: 3.9+
"""
+import os
import pathlib
import sys
import warnings
@@ -187,6 +200,12 @@ def _patched_get_certs_file(ca_certs=None):
# If ca_certs explicitly provided, use original behavior
if ca_certs is not None:
return _original_get_certs_file(ca_certs)
+ # Preserve shotgun_api3's documented SHOTGUN_API_CACERTS override.
+ # Without this branch, a network-hosted extracted cert_file can be
+ # dramatically slower to open than a local override (see SG-44256).
+ environment_certs = os.environ.get("SHOTGUN_API_CACERTS")
+ if environment_certs:
+ return environment_certs
# Otherwise return extracted certificate path instead of ZIP path
return str(cert_file)
@@ -199,7 +218,7 @@ def _install_import_hook():
Install a lazy import hook that redirects tank_vendor.* imports to real packages.
This enables transparent namespace aliasing, allowing code to use tank_vendor.package
- while the actual package is loaded from pkgs.zip without the tank_vendor prefix.
+ while the actual package is loaded from a ZIP without the tank_vendor prefix.
Examples:
from tank_vendor.shotgun_api3.lib import httplib2
@@ -227,121 +246,238 @@ def _install_import_hook():
sys.meta_path.insert(0, sys._tank_vendor_meta_finder)
-# ============================================================================
-# MAIN INITIALIZATION: Load third-party packages from pkgs.zip
-# ============================================================================
+def _discover_top_level_packages(zip_path):
+ """
+ Return the set of top-level importable package names inside a zip.
+
+ Filters out:
+ - .dist-info: Package metadata directories (still in zip for importlib.metadata,
+ but not importable as packages)
+ - __pycache__: Python bytecode cache
+ - .pyd/.so/.dylib/.dll: Platform-specific binary extensions
+ - _*: Private/internal modules (e.g., _ruamel_yaml.cp311-win_amd64.pyd)
+ """
+ with zipfile.ZipFile(zip_path, "r") as zf:
+ top_level = set()
+ for name in zf.namelist():
+ parts = name.split("/")
+ if parts[0] and not parts[0].endswith(".py"):
+ top_level.add(parts[0])
+ elif parts[0].endswith(".py") and parts[0] != "__pycache__":
+ top_level.add(parts[0][:-3])
+
+ return {
+ pkg
+ for pkg in top_level
+ if not pkg.endswith(".dist-info")
+ and pkg != "__pycache__"
+ and not pkg.endswith(".py")
+ and not pkg.endswith(".pyd")
+ and not pkg.endswith(".so")
+ and not pkg.endswith(".dylib")
+ and not pkg.endswith(".dll")
+ and not pkg.startswith("_")
+ }
+
+
+def _load_packages_from_zip(zip_path):
+ """
+ Validate a vendor zip, insert it at the front of sys.path, and register
+ its top-level packages under the tank_vendor namespace.
-# Construct path to Python version-specific pkgs.zip containing third-party dependencies.
-# Path structure: /requirements/./pkgs.zip
-# Example: requirements/3.11/pkgs.zip for Python 3.11
-pkgs_zip_path = (
- pathlib.Path(__file__).resolve().parent.parent.parent
- / "requirements"
- / f"{sys.version_info.major}.{sys.version_info.minor}"
- / "pkgs.zip"
-)
+ Missing or unreadable zips are tolerated (return False, with a warning
+ for unreadable). A wholesale failure during package discovery/import
+ raises RuntimeError after cleaning the zip path off sys.path. Individual
+ package import failures inside the zip warn and are skipped.
+
+ Each zip is always inserted at sys.path[0], so the LAST zip loaded ends
+ up at the front of sys.path. Collisions are resolved by sys.modules
+ (first-registered wins), independent of sys.path order — see callers
+ for the intentional load order.
+
+ Args:
+ zip_path: pathlib.Path to the zip file.
+
+ Returns:
+ True if the zip was successfully loaded, False if it was missing
+ or unreadable.
+ """
+ # Step 1: Validate the zip exists, is a file (not a directory of extracted
+ # contents, as some CI environments produce), and can be opened as a ZIP.
+ # Missing zips are silent (pip-installed setups have no pkgs.zip). Unreadable
+ # zips warn so the failure mode is visible, but don't fail the import.
+ if not zip_path.exists() or not zip_path.is_file():
+ return False
+
+ try:
+ with zipfile.ZipFile(zip_path, "r") as zf:
+ zf.namelist()
+ except (zipfile.BadZipFile, OSError, IOError) as e:
+ warnings.warn(
+ f"Failed to load packages from {zip_path}: {e}. "
+ "Any dependencies it would have provided will need to be resolved "
+ "from the Python environment, or will fail at import time.",
+ RuntimeWarning,
+ stacklevel=2,
+ )
+ return False
+
+ # Step 2: Put the zip on sys.path so Python can import directly from it.
+ # Insertion ordering is load-bearing: importlib.metadata.version() resolves
+ # dist-info inside a zip only after the zip is on sys.path.
+ sys.path.insert(0, str(zip_path))
-# Validate pkgs.zip before attempting to load from it.
-# This provides backward compatibility for:
-# - Installations using old vendored copies
-# - Temporary locations without the requirements directory
-# - CI/CD environments where pkgs.zip might be extracted to a directory
-_pkgs_zip_valid = False
-if pkgs_zip_path.exists():
- # Check if it's a file (not a directory) - in some CI environments,
- # pkgs.zip might be extracted to a directory instead of kept as a ZIP.
- if pkgs_zip_path.is_file():
- # Validate that it's actually a valid ZIP file before adding to sys.path
- try:
- with zipfile.ZipFile(pkgs_zip_path, "r") as zf:
- # Quick validation - just check that we can read the ZIP directory
- zf.namelist()
- _pkgs_zip_valid = True
- except (zipfile.BadZipFile, OSError, IOError) as e:
- # Not a valid ZIP file or can't be read - skip loading from pkgs.zip
- warnings.warn(
- f"Failed to load packages from {pkgs_zip_path}: {e}. "
- "Third-party dependencies will be loaded from the Python environment instead.",
- RuntimeWarning,
- stacklevel=2,
- )
-
-# If pkgs.zip is not found, assume pip-style installation where dependencies
-# are installed directly in the Python environment. In this case, we still
-# install the import hook to enable tank_vendor.* aliasing for compatibility.
-if not _pkgs_zip_valid:
- # Install import hook even without pkgs.zip for pip installations
- _install_import_hook()
-else:
- # Add pkgs.zip to sys.path so Python can import packages directly from the ZIP.
- # Insert at position 0 to prioritize over other installed packages.
- sys.path.insert(0, str(pkgs_zip_path))
try:
- # Step 1: Auto-discover all top-level packages in pkgs.zip
import importlib
- with zipfile.ZipFile(pkgs_zip_path, "r") as zf:
- # Get all top-level package names from the ZIP
- top_level_packages = set()
- for name in zf.namelist():
- # Extract first component of path (top-level package/module)
- parts = name.split("/")
- if parts[0] and not parts[0].endswith(".py"):
- # It's a package directory
- top_level_packages.add(parts[0])
- elif parts[0].endswith(".py") and parts[0] != "__pycache__":
- # It's a top-level module file
- top_level_packages.add(parts[0][:-3]) # Remove .py
-
- # Filter out non-importable items:
- # - .dist-info: Package metadata directories
- # - __pycache__: Python bytecode cache
- # - .py: Single file modules (already captured as packages)
- # - .pyd/.so/.dylib: Platform-specific binary extensions
- # - _*: Private/internal modules (e.g., _ruamel_yaml.cp311-win_amd64.pyd)
- top_level_packages = {
- pkg
- for pkg in top_level_packages
- if not pkg.endswith(".dist-info")
- and pkg != "__pycache__"
- and not pkg.endswith(".py")
- and not pkg.endswith(".pyd") # Windows binary modules
- and not pkg.endswith(".so") # Unix/Linux binary modules
- and not pkg.endswith(".dylib") # macOS binary modules
- and not pkg.startswith("_") # Private/internal modules
- }
-
- # Step 2: Import and register each top-level package under tank_vendor namespace
+ # Step 3: Auto-discover all top-level packages in the zip.
+ top_level_packages = _discover_top_level_packages(zip_path)
+
+ # Step 4: Import and register each top-level package under the
+ # tank_vendor namespace.
for package_name in sorted(top_level_packages):
+ # Collision check: an earlier zip already claimed this name.
+ # Earlier zips win (pkgs.zip is loaded before requirements/any/).
+ if f"tank_vendor.{package_name}" in sys.modules:
+ warnings.warn(
+ f"Skipping {package_name} from {zip_path}: "
+ f"already registered under tank_vendor.{package_name} "
+ f"from an earlier zip.",
+ RuntimeWarning,
+ )
+ continue
+
try:
+ # Import the real module and alias it under tank_vendor.* in
+ # sys.modules; also expose it as an attribute on this package
+ # so `from tank_vendor import ` works without going
+ # through the meta path finder.
+
# Import the package
mod = importlib.import_module(package_name)
# Register in sys.modules under tank_vendor namespace
sys.modules[f"tank_vendor.{package_name}"] = mod
-
- # Also set as attribute on tank_vendor module for direct access
globals()[package_name] = mod
-
- except ImportError as e:
- # Some packages might not import cleanly on all platforms
- # Log but don't fail - they might not be needed
- warnings.warn(f"Could not import {package_name} from pkgs.zip: {e}")
-
- # Step 3: Install import hook for lazy submodule loading
- # This enables imports like: from tank_vendor.shotgun_api3.lib import httplib2
- # without pre-importing all submodules (which can fail on version incompatibilities)
- _install_import_hook()
-
- # Step 4: Apply package-specific patches
- # These patches work around limitations or fix issues with specific packages
- if "shotgun_api3" in sys.modules:
- _patch_shotgun_api3_certs(pkgs_zip_path)
+ except Exception as e:
+ # Per-package import failures are tolerated. The catch is
+ # intentionally broad: a future shared vendor using syntax
+ # newer than the current Python (e.g. PEP 604 union syntax
+ # `int | None`) would raise SyntaxError at parse time, not
+ # ImportError. flow_data_sdk on Python 3.9 raises ImportError
+ # for its references to types.UnionType / typing.TypeAlias,
+ # which this catch also handles. Wholesale loader failures
+ # are still handled by the outer try/except.
+ warnings.warn(
+ f"Could not import {package_name} from {zip_path}: {e}",
+ RuntimeWarning,
+ stacklevel=2,
+ )
except Exception as e:
- # Clean up sys.path on failure to avoid leaving it in an inconsistent state
- # with a non-functional ZIP path that could interfere with subsequent imports
- sys.path.remove(str(pkgs_zip_path))
+ # Clean up sys.path on a wholesale failure so we don't leave a
+ # non-functional zip on the path interfering with other imports.
+ try:
+ sys.path.remove(str(zip_path))
+ except ValueError:
+ pass
raise RuntimeError(
- f"Failed to import required modules from {pkgs_zip_path}: {e}"
+ f"Failed to import required modules from {zip_path}: {e}"
) from e
+
+ return True
+
+
+def _release_importlib_metadata_handles():
+ """
+ Release file handles that importlib.metadata holds on vendor zips.
+
+ Windows-only workaround.
+
+ importlib.metadata.FastPath.__new__ is @lru_cache'd, so the FastPath
+ instance for any zip it probes is kept alive forever. Inside
+ FastPath.zip_children(), the line `self.joinpath = zip_path.joinpath`
+ binds the zipfile.Path (and its underlying open ZipFile) as an instance
+ attribute on the cached FastPath — so the file handle stays open for
+ the lifetime of the cache.
+
+ This bites us on Windows / Python 3.13 when flow_data_sdk's _version.py
+ runs importlib.metadata.version("flow-data-sdk") during import. The
+ cached FastPath keeps our shared zip open, which then prevents the
+ tank share_core command from moving install/core (WinError 32 sharing
+ violation).
+
+ Linux and macOS don't have Windows' sharing-violation semantics — moving
+ or deleting files with open handles is allowed — so this cleanup is a
+ no-op on those platforms (and was observed to break a Linux/3.13
+ integration test, so we gate strictly on win32).
+
+ invalidate_caches() calls FastPath.__new__.cache_clear() which drops
+ the FastPath references. gc.collect() forces __del__ on the underlying
+ ZipFile objects so the handles close immediately rather than at the
+ next garbage collection cycle.
+ """
+ if sys.platform != "win32":
+ return
+ from importlib.metadata import MetadataPathFinder
+ # invalidate_caches() is declared as `def invalidate_caches(cls)` without
+ # @classmethod in some Python versions, so call it on an instance for
+ # cross-version compatibility.
+ MetadataPathFinder().invalidate_caches()
+ import gc
+
+ gc.collect()
+
+
+# ============================================================================
+# MAIN INITIALIZATION
+# ============================================================================
+
+_requirements_dir = pathlib.Path(__file__).resolve().parent.parent.parent / "requirements"
+
+# Load order matters for two distinct reasons:
+#
+# 1. sys.modules registration: the FIRST zip to register a top-level package
+# wins (later zips' duplicates are skipped). So pkgs.zip is loaded first
+# to keep its version-pinned dependencies authoritative.
+#
+# 2. sys.path order: we insert each zip at sys.path[0], so the LAST zip
+# loaded ends up at the front. We want shared zips ahead of pkgs.zip on
+# sys.path so that importlib.metadata.version() lookups (e.g. flow_data_sdk's
+# _version.py querying its own dist-info) short-circuit on the shared zip
+# and never scan pkgs.zip. Scanning pkgs.zip via importlib.metadata caches
+# a FastPath instance that holds an open zipfile, which on Windows
+# prevents the tank share_core command from moving install/core.
+#
+# So: load pkgs.zip first (sys.modules), then shared zips (sys.path front).
+_pkgs_zip_path = (
+ _requirements_dir
+ / f"{sys.version_info.major}.{sys.version_info.minor}"
+ / "pkgs.zip"
+)
+_pkgs_loaded = _load_packages_from_zip(_pkgs_zip_path)
+if _pkgs_loaded and "shotgun_api3" in sys.modules:
+ _patch_shotgun_api3_certs(_pkgs_zip_path)
+
+# Shared zips (optional, Python-version-independent). Drop a *.zip into
+# requirements/any/ and it will be loaded automatically. Shared vendors are
+# expected to use the system trust store and not ship data files that would
+# need extraction from inside the zip.
+_shared_dir = _requirements_dir / "any"
+if not _shared_dir.is_dir():
+ # Fallback for pip-installed packages: setup.py's build_py copies
+ # requirements/any/*.zip into tank_vendor/vendor_any/ in the wheel.
+ _shared_dir = pathlib.Path(__file__).resolve().parent / "vendor_any"
+if _shared_dir.is_dir():
+ for _shared_zip in sorted(_shared_dir.glob("*.zip")):
+ _load_packages_from_zip(_shared_zip)
+
+# Install the lazy import hook for nested submodule access.
+# Idempotent via the _tank_vendor_meta_finder guard, so calling it once
+# after both load steps is safe and sufficient.
+_install_import_hook()
+
+# Windows-only cleanup: drop importlib.metadata's cached file handles on our
+# vendor zips so the tank share_core command can move install/core without
+# hitting WinError 32 sharing violations. No-op on Linux/macOS.
+_release_importlib_metadata_handles()
diff --git a/python/tk-core/python/tank_vendor/adsk_auth/__init__.py b/python/tk-core/python/tank_vendor/adsk_auth/__init__.py
new file mode 100644
index 00000000..5f8dcd21
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/adsk_auth/__init__.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2025 Shotgun Software Inc.
+# CONFIDENTIAL AND PROPRIETARY
+
+"""
+Adsk auth – minimal Autodesk Platform Services (APS) authentication using PKCE.
+
+Single flow: file store -> refresh token -> browser PKCE. No Identity Client.
+"""
+
+from .config import AuthConfig
+from .token import clear_stored_tokens, get_access_token
+
+__all__ = [
+ "AuthConfig",
+ "get_access_token",
+ "clear_stored_tokens",
+]
diff --git a/python/tk-core/python/tank_vendor/adsk_auth/config.py b/python/tk-core/python/tank_vendor/adsk_auth/config.py
new file mode 100644
index 00000000..31668bf1
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/adsk_auth/config.py
@@ -0,0 +1,52 @@
+# Copyright (c) 2025 Shotgun Software Inc.
+# CONFIDENTIAL AND PROPRIETARY
+
+"""APS PKCE configuration."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import List
+
+
+@dataclass
+class AuthConfig:
+ """Configuration for APS PKCE authentication."""
+
+ application_id: str
+ base_url: str
+ callback_url: str
+ required_application_scopes: List[str]
+ storage_dir: str
+ description: str = ""
+
+ def __post_init__(self) -> None:
+ if not self.application_id or not self.application_id.strip():
+ raise ValueError("application_id is required")
+ if not self.base_url or not self.base_url.strip():
+ raise ValueError("base_url is required")
+ if not self.callback_url or not self.callback_url.strip():
+ raise ValueError("callback_url is required")
+ if not self.required_application_scopes:
+ raise ValueError("required_application_scopes must not be empty")
+ if not self.storage_dir or not self.storage_dir.strip():
+ raise ValueError("storage_dir is required")
+ self.base_url = _normalize_base_url(self.base_url.strip())
+ self.application_id = self.application_id.strip()
+ self.callback_url = self.callback_url.strip()
+
+
+def _normalize_base_url(base_url: str) -> str:
+ """Normalize base URL for APS (scheme + netloc)."""
+ from urllib.parse import urlsplit
+
+ scheme, netloc, path, _, _ = urlsplit(base_url)
+ if scheme == "" and netloc == "" and path:
+ if path.startswith("localhost") or path.startswith("localhost/"):
+ port = path.split("/")[0].split(":")[-1] if ":" in path else ""
+ return f"http://localhost:{port}" if port else "http://localhost"
+ first = path.split("/")[0]
+ return f"https://{first}"
+ if scheme in ("http", "https"):
+ return f"{scheme}://{netloc}"
+ raise ValueError(f"base_url must use http or https, got scheme={scheme!r}")
diff --git a/python/tk-core/python/tank_vendor/adsk_auth/file_store.py b/python/tk-core/python/tank_vendor/adsk_auth/file_store.py
new file mode 100644
index 00000000..09185df8
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/adsk_auth/file_store.py
@@ -0,0 +1,114 @@
+# Copyright (c) 2025 Shotgun Software Inc.
+# CONFIDENTIAL AND PROPRIETARY
+
+"""Token storage via a local JSON file (replaces keyring dependency)."""
+
+from __future__ import annotations
+
+import getpass
+import json
+import logging
+import os
+import sys
+from typing import Any, Dict
+
+_logger = logging.getLogger(__name__)
+
+SERVICE_PREFIX = "adsk.flow"
+TOKEN_TYPES = ("access_token", "refresh_token")
+
+_TOKEN_FILE_NAME = "adsk_flow_tokens.json"
+
+
+def _service_name(application_id: str, token_type: str) -> str:
+ return f"{SERVICE_PREFIX}.{application_id}.{token_type}"
+
+
+def _token_file_path(storage_dir: str) -> str:
+ return os.path.join(storage_dir, _TOKEN_FILE_NAME)
+
+
+def _load(storage_dir: str) -> Dict[str, Any]:
+ path = _token_file_path(storage_dir)
+ if not os.path.exists(path):
+ return {}
+ try:
+ with open(path, "r") as fh:
+ data = json.load(fh)
+ return data if isinstance(data, dict) else {}
+ except Exception:
+ _logger.debug("Could not read token file %s", path, exc_info=True)
+ return {}
+
+
+def _save(storage_dir: str, data: Dict[str, Any]) -> None:
+ if not os.path.exists(storage_dir):
+ old_umask = os.umask(0o077)
+ try:
+ os.makedirs(storage_dir, 0o700)
+ finally:
+ os.umask(old_umask)
+
+ path = _token_file_path(storage_dir)
+ old_umask = os.umask(0o177)
+ try:
+ with open(path, "w") as fh:
+ json.dump(data, fh)
+ finally:
+ os.umask(old_umask)
+
+ # Belt-and-suspenders: explicitly set permissions on POSIX.
+ if sys.platform != "win32":
+ try:
+ os.chmod(path, 0o600)
+ except OSError:
+ pass
+
+
+def get_access_token(
+ storage_dir: str, application_id: str, profile: str
+) -> str | None:
+ """Read access token from the file store."""
+ data = _load(storage_dir)
+ return data.get(_service_name(application_id, "access_token"), {}).get(profile)
+
+
+def get_refresh_token(
+ storage_dir: str, application_id: str, profile: str
+) -> str | None:
+ """Read refresh token from the file store."""
+ data = _load(storage_dir)
+ return data.get(_service_name(application_id, "refresh_token"), {}).get(profile)
+
+
+def persist_tokens(
+ storage_dir: str,
+ application_id: str,
+ profile: str,
+ tokens: Dict[str, Any],
+) -> None:
+ """Store token dict (access_token, refresh_token) in the file store."""
+ data = _load(storage_dir)
+ for token_type in TOKEN_TYPES:
+ if tokens.get(token_type):
+ service = _service_name(application_id, token_type)
+ data.setdefault(service, {})[profile] = tokens[token_type]
+ _save(storage_dir, data)
+
+
+def delete_tokens(storage_dir: str, application_id: str, profile: str) -> None:
+ """Remove all stored tokens for this app and profile."""
+ data = _load(storage_dir)
+ changed = False
+ for token_type in TOKEN_TYPES:
+ service = _service_name(application_id, token_type)
+ if service in data and profile in data[service]:
+ del data[service][profile]
+ changed = True
+ if changed:
+ _save(storage_dir, data)
+
+
+def get_user_profile(profile: str | None) -> str:
+ """Return profile (username) for the store; default current OS user."""
+ return profile or getpass.getuser()
diff --git a/python/tk-core/python/tank_vendor/adsk_auth/pkce.py b/python/tk-core/python/tank_vendor/adsk_auth/pkce.py
new file mode 100644
index 00000000..a1fa57fa
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/adsk_auth/pkce.py
@@ -0,0 +1,292 @@
+# Copyright (c) 2025 Shotgun Software Inc.
+# CONFIDENTIAL AND PROPRIETARY
+
+"""PKCE flow: code pair, auth URL, callback server, code/refresh exchange."""
+
+from __future__ import annotations
+
+import base64
+import errno
+import hashlib
+import json
+import logging
+import secrets
+import socket
+import threading
+import urllib.request
+import webbrowser
+from http.server import BaseHTTPRequestHandler, HTTPServer
+from socketserver import ThreadingMixIn
+from typing import Any, Dict
+from urllib.parse import parse_qs, urlencode, urlparse
+
+from .config import AuthConfig
+
+_logger = logging.getLogger(__name__)
+
+REST_TIMEOUT = 30
+
+
+def create_code_pair() -> tuple[str, str]:
+ """Create PKCE code_verifier and code_challenge (S256)."""
+ code_verifier = secrets.token_urlsafe(40)
+ digest = hashlib.sha256(code_verifier.encode("utf-8")).digest()
+ code_challenge = base64.urlsafe_b64encode(digest).decode("utf-8").rstrip("=")
+ return (code_challenge, code_verifier)
+
+
+def build_authorize_url(config: AuthConfig, code_challenge: str) -> tuple[str, str]:
+ """Build authorize URL and state; returns (url, state)."""
+ state = secrets.token_urlsafe()
+ params = {
+ "client_id": config.application_id,
+ "redirect_uri": config.callback_url,
+ "response_type": "code",
+ "scope": " ".join(config.required_application_scopes),
+ "state": state,
+ "code_challenge_method": "S256",
+ "code_challenge": code_challenge,
+ "nonce": secrets.token_urlsafe(),
+ }
+ url = f"{config.base_url}/authentication/v2/authorize?{urlencode(params)}"
+ return (url, state)
+
+
+def exchange_code(
+ config: AuthConfig,
+ code: str,
+ code_verifier: str,
+) -> Dict[str, Any]:
+ """Exchange authorization code for tokens."""
+ data = {
+ "code": code,
+ "grant_type": "authorization_code",
+ "redirect_uri": config.callback_url,
+ "client_id": config.application_id,
+ "code_verifier": code_verifier,
+ }
+ encoded_data = urlencode(data).encode("utf-8")
+ req = urllib.request.Request(
+ f"{config.base_url}/authentication/v2/token",
+ data=encoded_data,
+ headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"},
+ method="POST",
+ )
+ with urllib.request.urlopen(req, timeout=REST_TIMEOUT) as response:
+ return json.loads(response.read())
+
+
+def exchange_refresh_token(config: AuthConfig, refresh_token: str) -> Dict[str, Any]:
+ """Exchange refresh token for new access (and optionally refresh) token."""
+ data = {
+ "grant_type": "refresh_token",
+ "refresh_token": refresh_token,
+ "client_id": config.application_id,
+ }
+ encoded_data = urlencode(data).encode("utf-8")
+ req = urllib.request.Request(
+ f"{config.base_url}/authentication/v2/token",
+ data=encoded_data,
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
+ method="POST",
+ )
+ with urllib.request.urlopen(req, timeout=20) as response:
+ return json.loads(response.read())
+
+
+def _callback_server_port(callback_url: str) -> int:
+ parsed = urlparse(callback_url)
+ if parsed.port is not None:
+ return parsed.port
+ return 80 if parsed.scheme == "http" else 443
+
+
+# Errno for "address family not supported" (IPv6 disabled or unavailable).
+# Unix: EAFNOSUPPORT (97); Windows: WSAEAFNOSUPPORT (10047).
+_ERRNO_AF_NOT_SUPPORTED = (getattr(errno, "EAFNOSUPPORT", 97), 10047)
+
+
+def _is_port_in_use(port: int) -> bool:
+ """Return True if the loopback port is already bound (IPv4 or IPv6)."""
+ port = int(port)
+ try:
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ s.bind(("127.0.0.1", port))
+ except OSError as e:
+ if getattr(e, "errno", None) in (errno.EADDRINUSE, errno.EACCES):
+ return True
+ raise
+ # If IPv6 is not available (EAFNOSUPPORT etc.), skip probe and assume port is free for our use.
+ try:
+ with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
+ s.bind(("::1", port))
+ except OSError as e:
+ err = getattr(e, "errno", None)
+ if err in (errno.EADDRINUSE, errno.EACCES):
+ return True
+ if err in _ERRNO_AF_NOT_SUPPORTED:
+ _logger.debug("IPv6 not available, skipping IPv6 port probe")
+ return False
+ raise
+ return False
+
+
+# Simple page with button (no immediate window.close) so callback is fully
+# processed before the window closes.
+_CALLBACK_HTML = b"""
+ Authentication successful. You can close this window.
+
+"""
+
+
+class _CallbackHandler(BaseHTTPRequestHandler):
+ """Capture OAuth callback ?code=...&state=... from the local HTTP server."""
+
+ session_store: Dict[str, Any] = {}
+
+ def do_GET(self) -> None:
+ q = parse_qs(urlparse(self.path).query)
+ if "error" in q:
+ msg = q.get("error_description", q.get("error", [b"Unknown error"]))
+ _logger.error("OAuth callback error: %s", msg[0] if msg else "unknown")
+ elif "code" in q and "state" in q:
+ self.session_store[q["state"][0]] = q["code"][0]
+ _logger.info("Received OAuth callback with code and state")
+ self.send_response(200)
+ self.end_headers()
+ self.wfile.write(_CALLBACK_HTML)
+ # Do NOT call server.shutdown() here: with ThreadingMixIn the handler runs
+ # in a worker thread; the main server thread is blocked in accept(). Rely
+ # on the main thread polling session_store and timing out instead.
+
+ def log_message(self, format: str, *args: Any) -> None:
+ _logger.debug("%s", args)
+
+
+class _ThreadingCallbackServerDualStack(ThreadingMixIn, HTTPServer):
+ """Threaded HTTP server binding to ::1 (IPv6 loopback only)."""
+
+ address_family = socket.AF_INET6
+
+ def server_bind(self) -> None:
+ self.socket = socket.socket(self.address_family, socket.SOCK_STREAM)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.socket.bind(("::1", self.server_address[1]))
+ self.server_address = self.socket.getsockname()
+
+
+class _ThreadingCallbackServerIPv4(ThreadingMixIn, HTTPServer):
+ """Threaded HTTP server binding to 127.0.0.1 (IPv4 loopback only)."""
+
+ def server_bind(self) -> None:
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.socket.bind(("127.0.0.1", self.server_address[1]))
+ self.server_address = self.socket.getsockname()
+
+
+def run_callback_server(
+ session_store: Dict[str, Any],
+ port: int,
+ ready_event: threading.Event | None = None,
+ init_error: list[BaseException] | None = None,
+ server_container: list | None = None,
+) -> None:
+ """Run HTTP server in current thread to capture ?code=...&state=... ."""
+ _CallbackHandler.session_store = session_store
+ port = int(port)
+ try:
+ server = _ThreadingCallbackServerDualStack(("::1", port), _CallbackHandler)
+ except OSError as e:
+ err = getattr(e, "errno", None)
+ if err in (errno.EADDRINUSE, errno.EACCES):
+ if init_error is not None:
+ init_error.append(e)
+ if ready_event is not None:
+ ready_event.set()
+ return
+ if err in _ERRNO_AF_NOT_SUPPORTED:
+ _logger.debug("IPv6 not available, using IPv4 loopback callback server")
+ server = _ThreadingCallbackServerIPv4(("127.0.0.1", port), _CallbackHandler)
+ else:
+ raise
+ # Request handler threads must be daemon so the process exits after we have the code.
+ server.daemon_threads = True
+ if server_container is not None:
+ server_container.append(server)
+ if ready_event is not None:
+ ready_event.set()
+ server.serve_forever()
+
+
+def web_authenticate(
+ config: AuthConfig,
+ *,
+ time_out: float = 30.0,
+ browser: Any = None,
+) -> dict[str, Any]:
+ """Run PKCE in browser; return token dict (access_token, refresh_token, ...)."""
+ import time
+
+ code_challenge, code_verifier = create_code_pair()
+ auth_url, state = build_authorize_url(config, code_challenge)
+ session_store: Dict[str, Any] = {state: None}
+ port = _callback_server_port(config.callback_url)
+ if _is_port_in_use(port):
+ raise RuntimeError(
+ f"Port {port} is already in use (callback URL {config.callback_url}). "
+ "Another auth process may be running. Exit it or use a different callback URL."
+ )
+ ready_event = threading.Event()
+ init_error: list[BaseException] = []
+ server_container: list = []
+
+ server_thread = threading.Thread(
+ target=run_callback_server,
+ args=(session_store, port),
+ kwargs={
+ "ready_event": ready_event,
+ "init_error": init_error,
+ "server_container": server_container,
+ },
+ )
+ server_thread.daemon = True
+ server_thread.start()
+ # Ensure server is bound and listening before opening browser (avoids race on Windows)
+ if not ready_event.wait(timeout=5):
+ _logger.warning("Callback server may not be ready yet")
+ if init_error:
+ e = init_error[0]
+ raise RuntimeError(
+ f"Port {port} is already in use (callback URL {config.callback_url}). "
+ "Another auth process may be running. Exit it or use a different callback URL."
+ ) from e
+ time.sleep(0.5)
+ try:
+ try:
+ b = webbrowser.get(using=browser)
+ b.open_new(auth_url)
+ # With ThreadingMixIn the server thread never exits; poll with short joins
+ # so we return as soon as the callback handler has set session_store[state].
+ deadline = time.monotonic() + time_out
+ while time.monotonic() < deadline:
+ if session_store.get(state) is not None:
+ break
+ server_thread.join(timeout=0.25)
+ except webbrowser.Error as e:
+ _logger.error("Browser error: %s", e)
+ server_thread.join(timeout=0.5)
+ finally:
+ if server_container:
+ server_container[0].shutdown()
+ server_container[0].server_close()
+
+ if session_store.get(state) is None:
+ raise RuntimeError("Failed to obtain authorization code from browser")
+ code = session_store[state]
+ token = exchange_code(config, code, code_verifier)
+ if not token or "access_token" not in token:
+ raise RuntimeError("Token exchange did not return an access_token")
+ return token
diff --git a/python/tk-core/python/tank_vendor/adsk_auth/token.py b/python/tk-core/python/tank_vendor/adsk_auth/token.py
new file mode 100644
index 00000000..2442e2a9
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/adsk_auth/token.py
@@ -0,0 +1,112 @@
+# Copyright (c) 2025 Shotgun Software Inc.
+# CONFIDENTIAL AND PROPRIETARY
+
+"""Get access token: file store -> refresh -> browser PKCE."""
+
+from __future__ import annotations
+
+import logging
+from typing import Any, Optional
+from urllib.error import HTTPError
+
+try:
+ import jwt
+except ImportError:
+ # TODO: Remove when Python 3.7 is discontinued
+ jwt = None
+
+from .config import AuthConfig
+from .file_store import delete_tokens
+from .file_store import get_access_token as get_access_token_from_store
+from .file_store import get_refresh_token, get_user_profile, persist_tokens
+from .pkce import exchange_refresh_token, web_authenticate
+
+_logger = logging.getLogger(__name__)
+
+# In-memory cache: profile -> access_token (avoids file read every call)
+_access_token_cache: dict[str, str] = {}
+
+
+def get_access_token(
+ config: AuthConfig,
+ *,
+ profile: Optional[str] = None,
+ force_refresh: bool = False,
+ force_reauthentication: bool = False,
+ time_out: float = 30.0,
+ browser: Any = None,
+) -> str:
+ """
+ Return a valid access token: use cache, then file store, then refresh, then browser PKCE.
+
+ Raises:
+ RuntimeError: If a token could not be obtained.
+ """
+ global _access_token_cache
+ user_profile = get_user_profile(profile)
+
+ if force_reauthentication or force_refresh:
+ _access_token_cache.pop(user_profile, None)
+
+ # 1. Valid token in cache?
+ cached = _access_token_cache.get(user_profile)
+ if cached and not (force_refresh or force_reauthentication):
+ if jwt is None: # TODO: Remove when Python 3.7 is discontinued
+ return cached
+ try:
+ jwt.decode(cached, options={"verify_signature": False, "verify_exp": True})
+ return cached
+ except (jwt.ExpiredSignatureError, jwt.DecodeError):
+ pass
+
+ if force_reauthentication:
+ delete_tokens(config.storage_dir, config.application_id, user_profile)
+
+ # 2. Valid token in file store?
+ if not (force_reauthentication or force_refresh):
+ try:
+ access_token = get_access_token_from_store(
+ config.storage_dir, config.application_id, user_profile
+ )
+ if access_token:
+ if jwt is None: # TODO: Remove when Python 3.7 is discontinued
+ _access_token_cache[user_profile] = access_token
+ return access_token
+ jwt.decode(
+ access_token,
+ options={"verify_signature": False, "verify_exp": True},
+ )
+ _access_token_cache[user_profile] = access_token
+ return access_token
+ except (jwt.ExpiredSignatureError, jwt.DecodeError):
+ pass
+
+ # 3. Refresh token?
+ try:
+ refresh_token = get_refresh_token(
+ config.storage_dir, config.application_id, user_profile
+ )
+ if refresh_token:
+ _logger.debug("Using refresh token")
+ token_dict = exchange_refresh_token(config, refresh_token)
+ persist_tokens(config.storage_dir, config.application_id, user_profile, token_dict)
+ access_token = token_dict["access_token"]
+ _access_token_cache[user_profile] = access_token
+ return access_token
+ except (RuntimeError, HTTPError) as e:
+ _logger.debug("Refresh failed: %s", e)
+
+ # 4. Browser PKCE
+ _logger.warning("Opening browser to authenticate (timeout %.1fs)", time_out)
+ token_dict = web_authenticate(config, time_out=time_out, browser=browser)
+ persist_tokens(config.storage_dir, config.application_id, user_profile, token_dict)
+ access_token = token_dict["access_token"]
+ _access_token_cache[user_profile] = access_token
+ return access_token
+
+
+def clear_stored_tokens(config: AuthConfig, profile: Optional[str] = None) -> None:
+ """Remove tokens from file store and in-memory cache for this app/profile."""
+ user_profile = get_user_profile(profile)
+ _access_token_cache.pop(user_profile, None)
+ delete_tokens(config.storage_dir, config.application_id, user_profile)
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/__init__.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/dependency.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/dependency.py
new file mode 100644
index 00000000..3ecca656
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/dependency.py
@@ -0,0 +1,467 @@
+# -
+# *****************************************************************************
+# Copyright 2026 Autodesk, Inc. All rights reserved.
+#
+# These coded instructions, statements, and computer programs contain
+# unpublished proprietary information written by Autodesk, Inc. and are
+# protected by Federal copyright law. They may not be disclosed to third
+# parties or copied or duplicated in any form, in whole or in part, without
+# the prior written consent of Autodesk, Inc.
+# *****************************************************************************
+# +
+
+"""
+This module contains convenience classes/utilities for dealing with
+file dependencies within an asset.
+"""
+from __future__ import annotations # needed for python 3.9 support
+
+import json
+import re
+from dataclasses import dataclass, field, fields
+from enum import Enum
+
+from tank_vendor.flow_integration_sdk.exceptions import FlowError
+from tank_vendor.flow_integration_sdk.storage import storage_key_to_asset_id
+from tank_vendor.flow_integration_sdk.objects import (
+ FlowRevision,
+ FlowVersion,
+)
+from tank_vendor.flow_integration_sdk.utils import (
+ cleanpath,
+ to_regex_safe_wildcard_string,
+ trace,
+)
+
+
+class DepType(Enum):
+ """Enum of dependency data types."""
+
+ #: Link to another asset in Flow.
+ ASSET = "asset"
+ #: Link to a file external to Flow, or packaged within current asset.
+ LOCAL = "local"
+ #: No specific type.
+ NONE = ""
+ #: Link to the main scene file of the current asset.
+ ROOT = "root"
+
+
+@dataclass
+class DependencyData:
+ """Store data relevant to an dependency within a scene.
+
+ General Properties:
+
+ - dep_type: General type of dependency. See :class:`.DepType` enum for valid values.
+ - node_handle: Unique identifier to the node with the dependency.
+ - node_type: Type name of node (specific type of dependency).
+ - attribute: Name of attribute which stores the file path (not always applicable).
+ - file_path: Absolute file path of dependency.
+ - raw_path: Raw file path stored in scene.
+ - dependencies: List of nested dependencies within the file designated by file_path.
+
+ Asset Properties: (pertinent to asset type dependencies only)
+
+ - asset_id: Asset entity id.
+ - version: Version of revision.
+ - revision_id: Revision id.
+ - component_name: Unique component identifier within revision.
+ - blob_index: Index into blob array of component.
+ """
+
+ # General properties
+
+ dep_type: DepType = DepType.NONE
+ node_handle: str = ""
+ node_type: str = ""
+ attribute: str = ""
+ file_path: str = ""
+ raw_path: str = ""
+ parent: DependencyData | None = None
+ dependencies: list[DependencyData] = field(default_factory=lambda: [])
+
+ # Asset properties
+ asset_id: str = ""
+ revision_id: str = ""
+ version_id: str = ""
+ component_name: str = ""
+ blob_index: int = 0
+
+ def identify_component(self):
+ """Determine asset component identification based on file path.
+ If provided, copy info from cached dependency tree to avoid querying it.
+ """
+ # Parse file path for asset info
+ comp_info = identify_component(self.file_path)
+ if comp_info:
+ self.asset_id = comp_info.get("asset_id")
+ self.revision_id = comp_info.get("revision_id")
+ self.version_id = comp_info.get("version_id")
+ self.component_name = comp_info.get("component_name")
+ self.blob_index = comp_info.get("blob_index")
+
+ @property
+ def revision_num(self):
+ """Return number of revision based on revision id."""
+ return FlowRevision.get_revision_number(self.revision_id)
+
+ @property
+ def version_num(self):
+ """Return number of version based on version id."""
+ return FlowVersion.get_version_number(self.version_id)
+
+ def set_type(self):
+ """Set dependency type based on current properties."""
+ # Can assume any dependency with asset information is an asset dependency
+ self.dep_type = DepType.ASSET if self.asset_id else DepType.LOCAL
+
+ def flatten_dependencies(self):
+ """Create flat list of all dependencies in tree."""
+ deps = [self]
+ for dep in self.dependencies:
+ deps.extend(dep.flatten_dependencies())
+ return deps
+
+ def get_external_dependencies(self, top_level: bool = True) -> list[DependencyData]:
+ """Return list of external dependency objects.
+
+ .. note:: external dependencies of internal dependencies will be ignored
+ because they are not considered external to the current asset context.
+
+ Args:
+ top_level: If True, only return external dependencies in immediate
+ dependency list. Otherwise, include sub dependencies too.
+
+ Returns
+ List of DependencyData objects of type DepType.LOCAL.
+ """
+ ext_deps = []
+ for dep in self.dependencies:
+ if dep.dep_type == DepType.LOCAL:
+ ext_deps.append(dep)
+ if not top_level and dep.dep_type != DepType.ASSET:
+ ext_deps.extend(dep.get_external_dependencies(top_level=False))
+ return ext_deps
+
+ def get_internal_dependencies(self, top_level: bool = True) -> list[DependencyData]:
+ """Return list of internal dependency objects.
+
+ Args:
+ top_level: If True, only return internal dependencies in immediate
+ dependency list. Otherwise, include all sub dependencies too.
+
+ Returns:
+ List of DependencyData objects of type DepType.ASSET.
+ """
+ int_deps = []
+ for dep in self.dependencies:
+ # In searching for internal dependencies, we must also include those
+ # that are referenced by external dependencies because they also
+ # need to be "registered" as internal references of current asset.
+ #
+ # If only getting top level internal references, do not search sub
+ # dependencies once an internal dependency is found, but must continue
+ # to search sub dependencies of external dependencies in case an internal
+ # reference exists within.
+ if dep.dep_type == DepType.ASSET:
+ int_deps.append(dep)
+ if top_level:
+ continue
+ int_deps.extend(dep.get_internal_dependencies(top_level=top_level))
+
+ return int_deps
+
+ def find(
+ self,
+ node_handle: str | None = None,
+ file_path: str | None = None,
+ node_type: str | None = None,
+ ) -> list[DependencyData]:
+ """Find node within dependency tree.
+
+ Note: multiple criteria acts as a union, not an intersection.
+
+ Args:
+ node_handle: If provided, match dependency node with this handle.
+ Wildcard character '*' is supported.
+ This criteria will be given precedence.
+ file_path: If provided, match by file path.
+ Wildcard character '*' is supported.
+ This criteria has second priority.
+ node_type: If provided, match by node type.
+ Wildcard character '*' is supported.
+ This criteria has third priority.
+
+ Returns:
+ List of DepedencyData nodes that match to ANY of given criteria.
+ """
+ matches = []
+ matched_self = False
+ if node_handle is not None:
+ expression = f"^{to_regex_safe_wildcard_string(node_handle)}$"
+ if re.match(expression, self.node_handle):
+ matches.append(self)
+ matched_self = True
+ if not matched_self and file_path is not None:
+ expression = f"{to_regex_safe_wildcard_string(file_path.lower())}$"
+ if re.match(expression, self.file_path.lower()):
+ matches.append(self)
+ matched_self = True
+ if not matched_self and node_type is not None:
+ expression = f"{to_regex_safe_wildcard_string(node_type)}$"
+ if re.match(expression, self.node_type):
+ matches.append(self)
+ for dep in self.dependencies:
+ result = dep.find(node_handle, file_path, node_type)
+ matches.extend(result)
+
+ return matches
+
+ def contains(self, dep: DependencyData) -> DependencyData | None:
+ """Check tree for equivalent dependency node and return."""
+ deps = self.flatten_dependencies()
+ for d in deps:
+ if d == dep:
+ return d
+ return None
+
+ def match(self, dep: DependencyData) -> DependencyData | None:
+ """Find matching dependency node in tree.
+
+ A match entails that the following properties are the same:
+ * node_handle
+ * node_type
+ * attribute
+ * asset_id
+ * component_name
+
+ Args:
+ dep: The dependency data to be matched.
+
+ Returns:
+ First node in the tree that fits matching criteria, or None if not found.
+ """
+ deps = self.flatten_dependencies()
+ for d in deps:
+ if d.node_handle != dep.node_handle:
+ continue
+ elif d.node_type != dep.node_type:
+ continue
+ elif d.attribute != dep.attribute:
+ continue
+ elif d.asset_id != dep.asset_id:
+ continue
+ elif d.component_name != dep.component_name:
+ continue
+ return d
+ return None
+
+ def __lt__(self, other: DependencyData) -> bool:
+ """Implement less-than comparison operator to make objects sortable."""
+ return self.node_handle < other.node_handle
+
+ def __eq__(self, other: DependencyData) -> bool:
+ """Implement equals operator."""
+ if (
+ self.node_handle == other.node_handle
+ and self.node_type == other.node_type
+ and self.attribute == other.attribute
+ and self.file_path == other.file_path
+ ):
+ return True
+ return False
+
+ def asdict(self):
+ """Convert object to dictionary."""
+ # NOTE: using default asdict() function causes infinite recursion
+
+ # Convert basic properties
+ result = {}
+ for f in fields(self):
+ if f.name in ["dependencies", "parent"]:
+ continue
+ result[f.name] = getattr(self, f.name)
+
+ # Convert dependency list recursively
+ result["dependencies"] = [dep.asdict() for dep in self.dependencies]
+
+ # Convert dep_type to string
+ result["dep_type"] = self.dep_type.value
+
+ return result
+
+ def pprint(self, index: int = 0, tabs: int = 0, recursive=True):
+ """Print dependency tree in a readable way."""
+ print("\t" * tabs + "-----------------------------------------")
+ print("\t" * tabs + f"{index} - {self.dep_type.name}")
+ print("\t" * tabs + "-----------------------------------------")
+
+ asset_props = [
+ "asset_id",
+ "version",
+ "revision_id",
+ "component_name",
+ "blob_index",
+ ]
+
+ for prop, value in self.__dict__.items():
+ if prop in ["dependencies", "dep_type"]:
+ continue
+ elif prop == "parent":
+ if value is not None:
+ print("\t" * tabs + f"{prop}: {value.node_handle}")
+ continue
+ elif prop in asset_props and self.dep_type is not DepType.ASSET:
+ continue
+ print("\t" * tabs + f"{prop}: {str(value)}")
+
+ if recursive and self.dependencies:
+ print(" ")
+ print("\t" * tabs + "Sub-dependencies:")
+ for i, dep in enumerate(self.dependencies):
+ dep.pprint(i, tabs + 1)
+
+ @classmethod
+ def convert(cls, dep_json: dict | str) -> DependencyData:
+ """Convert a json representation of a dependency tree into object representation.
+
+ Args:
+ dep_json: Json in dictionary or string format.
+
+ Returns:
+ Root of dependency tree.
+
+ Raises:
+ ValueError: If dep_json is a string that cannot be parsed as JSON.
+ """
+ # Convert json string to dictionary
+ if isinstance(dep_json, str):
+ try:
+ dep_json = json.loads(dep_json)
+ except Exception as exc: # pylint: disable=broad-except
+ raise ValueError("Invalid json provided.") from exc
+
+ dep = DependencyData(**dep_json) # type: ignore[arg-type]
+
+ try:
+ # dep type is assumed to be a string, convert back to enum value
+ dep.dep_type = DepType(dep.dep_type)
+ except Exception:
+ dep.dep_type = DepType.NONE
+
+ # Properly convert sub-dependencies to objects
+ subdeps = []
+ for d in dep.dependencies:
+ subdeps.append(DependencyData.convert(d)) # type: ignore[arg-type]
+ dep.dependencies = subdeps
+ for d in subdeps:
+ d.parent = dep
+
+ return dep
+
+
+@trace
+def identify_component(file_path: str) -> dict | None:
+ """Given a file path, determine if it belongs to an asset, and
+ return information identifying the exact component blob that the path
+ is associated with.
+
+ NOTE: Only paths within primary storage (NFS cache) can be identified.
+ All other paths, including sandbox paths, will return None.
+
+ Args:
+ file_path: Absolute path to a file.
+ ignore_root: If True, root directory does not need to map to
+ current configured roots. It is recommended to keep
+ this to True if not trying validate full path for optimal
+ performance.
+
+ Returns:
+ Dictionary with keys:
+ * asset_id -> Id of asset
+ * revision_id -> Id of revision
+ * version_id -> Id of version
+ * component_name -> Name of component
+ * blob_index -> Index into binary array of component
+
+ or None if the file path cannot be identified.
+ """
+ file_path = cleanpath(file_path)
+
+ # Absolute path pattern (expecting a root)
+ expr = r".*(?P/[^/]+/((r\d+)|(draft))/.+)"
+ m = re.match(expr, file_path)
+ if not m:
+ # Relative path pattern (expecting to begin with storage id)
+ expr = r"(?P[^/]+/((r\d+)|(draft))/.+)"
+ m = re.match(expr, file_path)
+ if not m:
+ # Not an asset path - failed test for overall asset path pattern
+ return None
+ comp_path = m.group("comp_path")
+
+ # add prepended / to match expected asset path pattern if not present
+ if not comp_path.startswith("/"):
+ comp_path = "/" + comp_path
+
+ # Parse expected pieces of asset path
+ try:
+ _, storage_id, rev_num, comp_path = comp_path.split("/", maxsplit=3)
+ except ValueError:
+ # Not an asset path - unable to parse into key path components
+ return None
+
+ # Look up asset based on storage key
+ try:
+ asset_id = storage_key_to_asset_id(storage_id)
+ except FlowError:
+ # Not an asset path - storage key component does not map to an asset
+ return None
+
+ # Convert revision number to integer
+ if rev_num == "draft":
+ # Not an asset path - draft paths don't count
+ return None
+ else:
+ try:
+ rev_num = int(rev_num.strip("r"))
+ except ValueError:
+ # Not an asset path - non-int value for revision number component
+ return None
+
+ revision_id = FlowRevision.get_revision_id(asset_id, rev_num)
+ try:
+ # NOTE: Using accessor method rather than constructing new instance
+ # because this will return a cached object if it exists
+ # This is ok since revisions are immutable.
+ revision = FlowRevision.get_revision(revision_id)
+ except FlowError:
+ # Not an asset - revision number is out of range
+ return None
+
+ # Determine component and blob index based on component path
+ # Try and match against existing binary components on revision
+ component = blob_index = None
+ bin_comps = revision.get_binary_components()
+ if "%" in comp_path:
+ # File sequence paths will be stored as a zip file
+ comp_path, _, _ = comp_path.rsplit(".", maxsplit=2)
+ comp_path += ".zip"
+ for comp in bin_comps:
+ for i, blob in enumerate(comp.blobs):
+ if blob.path == comp_path:
+ component = comp
+ blob_index = i
+ break
+ if component is None:
+ # Not an asset path - no component blob matches file
+ return None
+
+ return {
+ "asset_id": asset_id,
+ "revision_id": revision_id,
+ "version_id": revision.version_id,
+ "component_name": component.name,
+ "blob_index": blob_index,
+ }
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/exceptions.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/exceptions.py
new file mode 100644
index 00000000..76b017d7
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/exceptions.py
@@ -0,0 +1,163 @@
+# *****************************************************************************
+# Copyright 2026 Autodesk, Inc. All rights reserved.
+#
+# These coded instructions, statements, and computer programs contain
+# unpublished proprietary information written by Autodesk, Inc. and are
+# protected by Federal copyright law. They may not be disclosed to third
+# parties or copied or duplicated in any form, in whole or in part, without
+# the prior written consent of Autodesk, Inc.
+# *****************************************************************************
+# +
+
+"""
+This module contains custom exception classes.
+"""
+
+from __future__ import annotations # needed for python 3.9 support
+
+
+class FlowError(Exception):
+ """Base error class for Flow asset management errors."""
+
+ def __init__(
+ self, message, *args, details: str = "", data: dict | None = None, **kwargs
+ ):
+ """
+ Args:
+ message: Base message for exception.
+ details: Additional information.
+ data: Any additional data relevant to the error.
+ Stored as a dictionary of key-value pairs.
+ """
+ if details:
+ message = f"{message} {details}"
+ super().__init__(message, *args, **kwargs)
+ self.data = data
+
+
+class CreateAssetError(FlowError):
+ def __init__(self, *args, **kwargs):
+ message = "Could not create asset."
+ super().__init__(message, *args, **kwargs)
+
+
+class ComponentSpecError(FlowError):
+ def __init__(self, *args, **kwargs):
+ message = "Invalid component spec provided."
+ super().__init__(message, *args, **kwargs)
+
+
+class ConfigurationError(FlowError):
+ def __init__(self, *args, **kwargs):
+ message = "Flow settings have not been configured correctly."
+ super().__init__(message, *args, **kwargs)
+
+
+class DirectoryNotCreatedError(FlowError):
+ def __init__(self, *args, dir_path: str, **kwargs):
+ message = f'Couldn\'t create directory "{dir_path}".'
+ super().__init__(message, *args, **kwargs)
+
+
+class DraftExistsError(FlowError):
+ def __init__(self, *args, draft_id: str, draft_folder: str, **kwargs):
+ """
+ Args:
+ draft_id: Id that uniquely identifies a draft in local sandbox.
+ draft_folder: Path to draft folder in local sandbox.
+ """
+ message = f'The draft "{draft_id}" already exists at {draft_folder}.'
+ super().__init__(message, *args, **kwargs)
+ self.draft_id = draft_id
+ self.draft_folder = draft_folder
+
+
+class EntityNotFoundError(FlowError):
+ """Thrown when a given MEDM entity id is invalid, and cannot be used to
+ successfully query the entity.
+ """
+
+ def __init__(self, *args, entity_id: str, **kwargs):
+ """
+ Args:
+ entity_id: Id for an Asset or Project.
+ """
+ message = f'Entity id "{entity_id}" is invalid.'
+ super().__init__(message, *args, **kwargs)
+ self.entity_id = entity_id
+
+
+class FileUploadError(FlowError):
+ def __init__(self, *args, file_path: str, **kwargs):
+ message = f"Error uploading file {file_path}."
+ super().__init__(message, *args, **kwargs)
+
+
+class InvalidDraftError(FlowError):
+ def __init__(self, *args, draft_id: str, **kwargs):
+ """
+ Args:
+ draft_id: Id that uniquely identifies a draft in local sandbox.
+ """
+ message = f'Draft id "{draft_id}" is invalid.'
+ super().__init__(message, *args, **kwargs)
+ self.draft_id = draft_id
+
+
+class FlowSchemaBuilderError(FlowError):
+ """General exception for errors related to SchemaBuilder operations."""
+
+ def __init__(self, *args, **kwargs):
+ message = "SchemaBuilder operation failed."
+ super().__init__(message, *args, **kwargs)
+
+
+class FlowSchemaDisplayDataError(FlowError):
+ def __init__(self, *args, **kwargs):
+ message = "Schema display data operation failed."
+ super().__init__(message, *args, **kwargs)
+
+
+class FlowSchemaError(FlowError):
+ def __init__(self, *args, **kwargs):
+ message = "Schema operation failed."
+ super().__init__(message, *args, **kwargs)
+
+
+class FlowSchemaLibraryError(FlowError):
+ def __init__(self, *args, **kwargs):
+ message = "Schema library operation failed."
+ super().__init__(message, *args, **kwargs)
+
+
+class PublishAssetError(FlowError):
+ def __init__(self, *args, **kwargs):
+ message = "Could not publish asset."
+ super().__init__(message, *args, **kwargs)
+
+
+class PublishConflictError(FlowError):
+ def __init__(
+ self, *args, asset, checkout_version: int, checkout_revision: int, **kwargs
+ ):
+ message = "A publish conflict has been detected. New revisions of the asset "
+ message += f"have been published since version {checkout_version} "
+ message += f"(r{checkout_revision}) was checked out. "
+ message += f"The latest revision is now {asset.version_number} "
+ message += f"(r{asset.revision_number})."
+
+ super().__init__(message, *args, **kwargs)
+ self.asset = asset
+ self.checkout_version = checkout_version
+ self.checkout_revision = checkout_revision
+
+
+class ThumbnailError(FlowError):
+ def __init__(self, *args, revision_id: str, **kwargs):
+ """
+ Args:
+ revision_id: Id of revision.
+ """
+ message = f"Could not retrieve thumbnail for revision: {revision_id}."
+ super().__init__(message, *args, **kwargs)
+ self.revision_id = revision_id
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/fetch.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/fetch.py
new file mode 100644
index 00000000..a48621cc
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/fetch.py
@@ -0,0 +1,405 @@
+# -
+# *****************************************************************************
+# Copyright 2026 Autodesk, Inc. All rights reserved.
+#
+# These coded instructions, statements, and computer programs contain
+# unpublished proprietary information written by Autodesk, Inc. and are
+# protected by Federal copyright law. They may not be disclosed to third
+# parties or copied or duplicated in any form, in whole or in part, without
+# the prior written consent of Autodesk, Inc.
+# *****************************************************************************
+# +
+
+"""
+This module provides asset download/fetching utilities.
+"""
+
+from __future__ import annotations # needed for python 3.9 support
+
+import fileseq
+import os
+import zipfile
+from collections.abc import Iterator
+from functools import cache
+
+from tank_vendor.flow_data_sdk.base import model as medm_model
+from tank_vendor.flow_data_sdk.base.exceptions import GQLAPIError
+
+from .exceptions import FlowError, ThumbnailError
+from .globals import (
+ get_client,
+ FILE_SEQ_TYPE,
+ THUMBNAIL_PURPOSE,
+)
+from .schema import get_schema_id
+from .storage import (
+ _cache_asset_info,
+ _find_component,
+ get_storage_component_path,
+ get_storage_revision_dir,
+)
+from .utils import cleanpath, download_file, get_logger, trace
+
+
+# urn to url cache - optimization to avoid re-querying urls that are fixed
+# Format: key = urn, value = url
+_urn_to_url = {}
+
+
+@trace
+def fetch_blob_urls(project_id: str, urns: list[str]) -> list[str]:
+ """Query list of urls for given blob urns that can be used for
+ downloading. Order should be preserved.
+
+ Args:
+ project_id: Id of project to which binary components belong.
+ urns: List of blob urns to be converted to urls.
+
+ Returns:
+ List of urls from which blobs can be downloaded.
+
+ Raises:
+ FlowError
+ """
+ query_urns = [] # urns to be queried
+ result_urls = [] # final url list to be returned
+
+ # Add any cached urls to the return result first
+ # Leave spaces in the list for values that must be queried
+ for urn in urns:
+ if urn in _urn_to_url:
+ result_urls.append(_urn_to_url[urn])
+ else:
+ result_urls.append(None)
+ query_urns.append(urn)
+
+ # Query the download URLs from the API
+ if query_urns:
+ client = get_client()
+ q_input = medm_model.BinaryComponentUrlsByUrnsInput(
+ project_id=project_id,
+ urns=query_urns,
+ )
+ q_urls = client.service_binary.binary_component_urls_by_urns(q_input)
+ try:
+ q_urls.call()
+ except GQLAPIError as exc:
+ msg = f"Error fetching binary component urls: {exc}"
+ raise FlowError(msg) from exc
+
+ queried_urls = [bin_comp_url.url for bin_comp_url in q_urls.urls]
+
+ # Cache the urls we just queried
+ for i, urn in enumerate(query_urns):
+ _urn_to_url[urn] = queried_urls[i]
+
+ # Merge quered urls with result by filling in the None spaces
+ # (this should preserve the input order)
+ for i, url in enumerate(result_urls):
+ if url is None:
+ result_urls[i] = queried_urls.pop(0)
+
+ return result_urls
+
+
+@trace
+def download(
+ component: medm_model.Component,
+ project_id: str,
+ directory: str,
+ file_sequence: bool = False,
+ skip_download: bool = False,
+) -> dict[int, str]:
+ """Download all binary blobs in component to given directory.
+ Directory must exist, and component must be a binary component.
+
+ Args:
+ component: Component to be downloaded.
+ project_id: Project that component belongs to.
+ directory: Existing folder location to be downloaded to.
+ file_sequence: If True, expect the component to contain a
+ zipped file sequence, and automatically expand it.
+ skip_download: Only relevant for file sequences. Used when
+ the source zip file has already been downloaded, but
+ the files haven't been extracted.
+
+ Returns:
+ Dictionary of blob index to full path of downloaded file.
+
+ Raises:
+ FlowError
+ """
+ # Get list of urls for each component blob
+ urns = [blob["uri"] for blob in component.data.get("data", [])]
+ urls = fetch_blob_urls(project_id, urns)
+
+ result = {}
+ for i, url in enumerate(urls):
+ # Determine destination path
+ # NOTE: this blob index is guaranteed to exist because we retrieved its url
+ blob_path = component.data["data"][i]["path"]
+ file_path = cleanpath(directory, blob_path)
+
+ # Finally download the file and save to disk
+ try:
+ if not skip_download:
+ download_file(url, file_path)
+ except Exception as exc: # pylint: disable=broad-except
+ raise FlowError(
+ f'Failed to download blob {i} with url "{url}". {exc}'
+ ) from exc
+ result[i] = file_path
+
+ if file_sequence:
+ # NOTE: This is a temporary solution. When we cease to zip
+ # up file sequences, this code block and parameter can be removed!
+ zip_file_path = result[0]
+ with zipfile.ZipFile(zip_file_path, "r") as zip_file:
+ # Update result dictionary to reflect frames extracted
+ # (Do this to mimic the future behaviour where each frame
+ # will be stored as its own blob)
+ result = {}
+ blob_index = 0
+ for file_name in zip_file.namelist():
+ result[blob_index] = cleanpath(directory, file_name)
+ blob_index += 1
+ # Check for unsafe file paths within zip
+ # We don't want to allow extracting outside of download directory
+ if file_name.startswith("/") or ".." in file_name:
+ msg = f"Unsafe file path detected in zip file: {file_name} - aborting extraction."
+ raise FlowError(msg)
+ # Extract the files
+ zip_file.extractall(directory)
+
+ return result
+
+
+@trace
+def fetch(
+ revision: medm_model.AssetRevision,
+ component_name: str = "",
+ component_purpose: str = "",
+ fetch_dependencies: bool = False,
+):
+ """Fetch the given component of a revision if not already on disk.
+ If the specified component does not exist, nothing will happen.
+
+ Args:
+ revision: Revision whose component should be fetched.
+ component_name: If provided, search for component with this name to be fetched.
+ This should be unique within the revision.
+ component_purpose: If provided, search for a component with this purpose to be fetched.
+ There may be multiple components with the same purpose,
+ so the first match will be returned.
+ fetch_dependencies: If True, also fetch the full dependency tree of the revision
+ using the same component criteria.
+
+ ..note:: If both component name and purpose are provided, the first intersection
+ of both criteria will be returned.
+ """
+ logger = get_logger(__name__)
+
+ # Get project id
+ project_id = _get_project_id(revision.id)
+
+ # List of revisions to be fetched
+ rev_list = [revision]
+ if fetch_dependencies:
+ # Add any dependencies (this will include entire dependency tree)
+ # NOTE: the uses query will return the input revision itself so this
+ # item will be duplicated in the list, however that should not cause
+ # a problem.
+ rev_list.extend(list(_iterate_uses(revision.numbered_version_id)))
+
+ def missing_seq_files(file_seq_comp):
+ # Return True if any files from the file seq are missing
+ file_list = _get_file_list(file_seq_comp)
+ # Convert to absolute paths
+ storage_dir = get_storage_revision_dir(rev.asset_id, rev.revision_number)
+ file_list = [cleanpath(storage_dir, f) for f in file_list]
+ missing_files = False
+ for f in file_list:
+ if not os.path.exists(f):
+ missing_files = True
+ break
+ return missing_files
+
+ for rev in rev_list:
+ # Check that revision has the specified component
+ # NOTE: making the assumption here that we only have a single blob
+ comp = _find_component(rev, name=component_name, purpose=component_purpose)
+ if comp is None:
+ # If the component doesn't exist, we will log a warning
+ # but not consider it a failure. The purpose of this function
+ # is to fetch stored binaries, so if there are no stored
+ # binaries, the point is moot.
+ msg = "No component of "
+ msg += f'name "{component_name}" ' if component_name else ""
+ msg += f'purpose "{component_purpose}" found in revision "{rev.name}".'
+ msg += "Skipping fetch for this revision..."
+ logger.warning(msg)
+ continue
+ # NOTE: the component is guaranteed to exist because we already found
+ # it - can assume this path will not be None
+ cache_source_path = get_storage_component_path(rev, component_name=comp.name)
+ file_seq_comp = _find_component(rev, type_id=get_schema_id(FILE_SEQ_TYPE))
+ # Check primary storage for source path before fetching
+ if file_seq_comp and not missing_seq_files(file_seq_comp):
+ # For file sequences, it's not good enough to check for just
+ # the source zip file, but we should also make sure its been unzipped
+ continue
+ elif not file_seq_comp and os.path.exists(cache_source_path):
+ continue
+
+ msg = f"Fetching binaries for revision {rev.name} - r{rev.revision_number}"
+ msg += f', component "{comp.name}".'
+ logger.info(msg)
+
+ # Fetch the component (indicate if it's a file sequence)
+ download(
+ comp,
+ project_id,
+ get_storage_revision_dir(rev.asset_id, rev.revision_number),
+ file_sequence=file_seq_comp is not None,
+ # NOTE: we may be in a situation where the source file already exists
+ # but its a file seq that needs to be unzipped. In this case,
+ # indicate that the download step can be skipped.
+ skip_download=os.path.exists(cache_source_path),
+ )
+
+ # Once fetched, cache asset info in storage dir if necessary
+ _cache_asset_info(rev.asset_id)
+
+
+@trace
+def get_thumbnail_file(revision: medm_model.AssetRevision) -> str:
+ """Return the path to the thumbnail file on disk. Fetch the file if necessary.
+
+ Args:
+ revision: Revision whose thumbnail should be fetched.
+
+ Returns:
+ File path to thumbnail.
+
+ Raises:
+ ThumbnailError
+ """
+ # Check that thumbnail component exists and
+ # get path to thumbnail path of revision in local storage
+ thumbnail_comp = _find_component(revision, purpose=THUMBNAIL_PURPOSE)
+ if thumbnail_comp is None:
+ msg = "Revision does not have a thumbnail component."
+ raise ThumbnailError(revision_id=revision.id, details=msg)
+
+ # Fetch thumbnail component of revision
+ fetch(revision, component_purpose=THUMBNAIL_PURPOSE)
+
+ # Verify that fetch was successful
+ file_path = get_storage_component_path(revision, component_name=thumbnail_comp.name)
+ if not os.path.exists(file_path):
+ msg = f"Thumbnail file does not exist in storage: {file_path}"
+ raise ThumbnailError(revision_id=revision.id, details=msg)
+
+ return file_path
+
+
+@trace
+def get_thumbnail_url(revision: medm_model.AssetRevision) -> str:
+ """Return a signed url of the thumbnail for given revision.
+
+ Args:
+ revision: Revision whose thumbnail should be fetched.
+
+ Returns:
+ Url of thumbnail.
+
+ Raises:
+ ThumbnailError
+ """
+ # Get project id
+ project_id = _get_project_id(revision.id)
+
+ # Check that thumbnail component exists
+ thumbnail_comp = _find_component(revision, purpose=THUMBNAIL_PURPOSE)
+ if thumbnail_comp is None:
+ msg = "Revision has no thumbnail component."
+ raise ThumbnailError(revision_id=revision.id, details=msg)
+
+ # Fetch the thumbnail's url (assume single blob)
+ try:
+ urn = thumbnail_comp.data.get("data", [])[0]["uri"]
+ return fetch_blob_urls(project_id, [urn])[0]
+ except (FlowError, IndexError) as exc:
+ msg = "Could not fetch thumbnail url."
+ raise ThumbnailError(revision_id=revision.id, details=msg) from exc
+
+
+@cache
+@trace
+def _iterate_uses(version_id: str) -> Iterator[medm_model.AssetRevision]:
+ """Query uses relationships in this asset/revision.
+ Pagination is handled internally within this call via the V2 sdk.
+ If this query has already been performed, the cached result will be returned.
+ If refresh=True, do a fresh query.
+
+ NOTE: This implementation grabs the entire uses tree because in practice
+ this is usually what we need (to fetch dependencies).
+
+ Args:
+ version_id: Query "uses" dependency tree starting from this version id.
+
+ Returns:
+ Iterator which handles the paginated response of the query containing
+ medm_model.AssetRevision objects.
+
+ Raises:
+ FlowError
+ """
+ client = get_client()
+ q_input = medm_model.AssetVersionsByTraversalInput(
+ start_at_id=version_id, # type: ignore[attr-defined]
+ depth=0, # retrieve entire tree
+ direction=medm_model.TraverseDirectionEnum.OUTGOING.value,
+ )
+ q_uses = client.service_asset.asset_versions_by_traversal(q_input)
+
+ # Wrap existing iterator in V2 sdk
+ # NOTE: The iterator function makes the actual query calls so no need to
+ # explicitly invoke call() here.
+ try:
+ # NOTE: We are getting revisions for now because this is most useful to us
+ # as the primary use case for getting uses information is to fetch
+ # dependencies.
+ #
+ # This is fetched through the versions iterator and not the revisions
+ # iterator. Since we are querying outgoing edges, the response gives us
+ # a list of edges like
+ # Rx -> Vy
+ # Rx -> Vz
+ # Rw -> Vu
+ # ...
+ # where R = revision, V = version
+ # The revisions iterator gives us the revision objects associated with
+ # the source of those edges. The versions iterator gives us the version
+ # objects associated with the destination of those edges.
+ for ver in q_uses.versions_iterator:
+ revision = ver.revision
+ yield revision
+ except GQLAPIError as exc:
+ msg = f"Error querying uses relationships. {exc}"
+ raise FlowError(msg) from exc
+
+
+def _get_file_list(fileseq_comp: medm_model.Component) -> list[str]:
+ """Return list of file names described by a file sequence type component."""
+ frame_set = fileseq.FrameSet(fileseq_comp.data["frameSet"])
+ file_format = fileseq_comp.data["fileFormat"]
+ file_list = [file_format % i for i in list(frame_set)]
+ return file_list
+
+
+def _get_project_id(input_id: str) -> str:
+ """Convert a medm asset/revision/version id into a project id."""
+ parts = input_id.split(":")
+ project_id = f"urn:medm:project:{parts[3]}:{parts[4]}"
+ return project_id
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/globals.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/globals.py
new file mode 100644
index 00000000..a86599eb
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/globals.py
@@ -0,0 +1,215 @@
+# -
+# *****************************************************************************
+# Copyright 2026 Autodesk, Inc. All rights reserved.
+#
+# These coded instructions, statements, and computer programs contain
+# unpublished proprietary information written by Autodesk, Inc. and are
+# protected by Federal copyright law. They may not be disclosed to third
+# parties or copied or duplicated in any form, in whole or in part, without
+# the prior written consent of Autodesk, Inc.
+# *****************************************************************************
+# +
+
+"""
+This module contains convenience constants and globally accessible
+session variables relevant to MEDM access and asset management.
+"""
+
+from __future__ import annotations # needed for python 3.9 support
+
+from dataclasses import dataclass
+from typing import Callable
+
+from tank_vendor.flow_data_sdk import GQLClient
+from tank_vendor.flow_data_sdk.base.client import AuthenticationHandlerBase
+
+from . import utils
+from .exceptions import FlowError
+from .utils import get_logger
+
+# Component type ids
+# ------------------
+# Type ids correspond to specific MEDM schemas (and versions).
+# Schemas can be created in a hierarchical fashion using inheritance.
+# The schemas below are official Autodesk supported types that are
+# commonly relevant to asset management.
+
+BASE_TYPE_ID = "autodesk.me:type-1.1.0"
+BINARY_TYPE_ID = "autodesk.me:component.binary-1.0.0"
+COMMENT_TYPE_ID = "autodesk.me:component.publishComment-1.0.0"
+# NOTE: This is a temporary schema being annexed for representing derivative source
+# which should be switched for a dedicated schema later.
+DER_SOURCE_TYPE_ID = "autodesk.me:component.dynamicPlaylistSource-1.0.0"
+FOLDER_TYPE_ID = "autodesk.me:type.folder-1.0.0"
+IMAGE_TYPE_ID = "autodesk.me:component.binary.image-1.0.0"
+
+# Component types
+# ---------------
+# Component base type names without full ids.
+# This should be a temporary measure, only necessary while some types
+# are not yet added to the autodesk domain, and must be created per collection.
+FILE_SEQ_TYPE = "type.fileSequence"
+
+
+# Component purposes
+# ------------------
+# Purposes are special designations that can be added to a binary component to
+# differentiate it based on function/characteristic. Values are completely arbitrary.
+# The purposes below are some basic defaults that are generally useful,
+# but integrations can choose to use other values.
+
+SOURCE_PURPOSE = "source"
+THUMBNAIL_PURPOSE = "thumbnail"
+
+
+# Component names
+# ---------------
+# Conventional component names for formally supported components.
+# NOTE: Component names must be unique within an asset revision.
+
+COMMENT_COMP = "Comment"
+DER_SOURCE_COMP = "Derivative Source"
+FILE_SEQ_COMP = "File Sequence"
+SOURCE_COMP = "Source"
+THUMBNAIL_COMP = "Thumbnail"
+TYPE_COMP = "Type"
+
+
+# MEDM GQL Client Access
+# ----------------------
+# Global gql client instance that is initialized per session.
+# This variable is for internal use only and should be initialized
+# explicitly using the init_client() function, and accessed via get_client().
+
+_gql_client: GQLClient | None = None
+
+
+def init_client(endpoint_url: str, auth_handler: AuthenticationHandlerBase):
+ """Initialize a global client instance that can be reused for the session.
+ Client object can be accessed using `get_client()` function.
+
+ Args:
+ endpoint_url: Endpoint to connect to.
+ auth_handler: An instance of a subclass of AuthenticationHandlerBase
+ with required authentication interface implemented.
+ """
+ global _gql_client
+
+ logger = get_logger(__name__)
+ logger.info(f"Creating V2 GQL client with endpoint: {endpoint_url}")
+ _gql_client = GQLClient(endpoint=endpoint_url, auth_handler=auth_handler)
+
+
+def get_client() -> GQLClient:
+ """Return global client if initialized, otherwise raises an error.
+
+ Raises:
+ FlowError
+ """
+ if _gql_client is None:
+ raise FlowError("GQL client has not been initialized.")
+ return _gql_client
+
+
+# MEDM Session Collection
+# ----------------------
+# The session collection stores the MEDM collection that we are operating
+# under for the current session. Tracking this helps us to query schema information
+# from the right collection.
+
+_session_collection: SessionCollection | None = None
+
+
+@dataclass
+class SessionCollection:
+ """Data class containing relevant information for session collection.
+ It tracks the collection id, and provides easy access to pertinent attributes.
+ """
+
+ #: Id of collection
+ id: str
+ #: Organization ID of collection
+ organization_id: str
+ #: Group Id of collection
+ group_id: str
+
+ def is_cpa_collection(self) -> bool:
+ """Return true if session project is from a CPA provisioned collection."""
+ if self.organization_id == "fstech":
+ return False
+ return True
+
+ def __str__(self):
+ """Stringify object info in readable way."""
+ s = "SESSION COLLECTION:\n"
+ s += f"\tid: {self.id}\n"
+ s += f"\torganization_id: {self.organization_id}\n"
+ s += f"\tgroup_id: {self.group_id}\n"
+ return s
+
+
+def init_session_collection(collection_id: str, organization_id: str, group_id: str):
+ """Store collection info for session.
+
+ Global SessionCollection data object can be accessed using `get_session_collection()`
+ function.
+
+ Args:
+ collection_id: MEDM collection id.
+ organization_id: Organization id of collection.
+ group_id: Group id of collection.
+ """
+ global _session_collection
+
+ logger = get_logger(__name__)
+ logger.info("Setting session collection info...")
+ _session_collection = SessionCollection(
+ id=collection_id,
+ organization_id=organization_id,
+ group_id=group_id,
+ )
+ logger.info(_session_collection)
+
+
+def get_session_collection() -> SessionCollection:
+ """Return session collection if initialized, otherwise raises an error.
+
+ Raises:
+ FlowError
+ """
+ if _session_collection is None:
+ raise FlowError("Session collection has not been initialized.")
+ return _session_collection
+
+
+# Web App url
+# -----------
+# Store configured web app url as a global for the session.
+# This variable is for internal use only.
+# Configure this value via set_webapp_url() and access is via get_webapp_url().
+
+_webapp_url = None
+
+
+def set_webapp_url(url: str):
+ """Set global variable for webapp url."""
+ global _webapp_url
+ logger = get_logger(__name__)
+ logger.info(f"Setting Flow web app url to: {url}")
+ _webapp_url = url
+
+
+def get_webapp_url() -> str | None:
+ """Return configured webapp url or None if it is not set."""
+ return _webapp_url
+
+
+# Configure logger
+# ----------------
+# Set a logger callback for use in the integration sdk.
+# If not configured, a default python logger will be used.
+
+
+def set_logger_callback(callback: Callable):
+ """Set the function that returns the logger for the session."""
+ utils._logger_callback = callback
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/objects.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/objects.py
new file mode 100644
index 00000000..ad27fcb2
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/objects.py
@@ -0,0 +1,1470 @@
+# -
+# *****************************************************************************
+# Copyright 2026 Autodesk, Inc. All rights reserved.
+#
+# These coded instructions, statements, and computer programs contain
+# unpublished proprietary information written by Autodesk, Inc. and are
+# protected by Federal copyright law. They may not be disclosed to third
+# parties or copied or duplicated in any form, in whole or in part, without
+# the prior written consent of Autodesk, Inc.
+# *****************************************************************************
+# +
+
+"""
+This module contains custom object wrappers representing MEDM data.
+The object inferface provides a convenient way to access, query and do paginated
+iteration of MEDM data. It also provides an interface to the Flow file storage
+solution for seamless access to both the data model and associated binaries.
+"""
+
+from __future__ import annotations # needed for python 3.9 support
+
+import datetime
+import re
+import urllib
+from collections.abc import Iterator
+from functools import cache
+from typing import Any
+
+from tank_vendor.flow_data_sdk.base import model as medm_model
+from tank_vendor.flow_data_sdk.base.exceptions import GQLAPIError
+
+from .exceptions import (
+ EntityNotFoundError,
+ FlowError,
+)
+from .fetch import (
+ download,
+ fetch,
+ fetch_blob_urls,
+ get_thumbnail_file,
+ get_thumbnail_url,
+)
+from .globals import (
+ BASE_TYPE_ID,
+ BINARY_TYPE_ID,
+ COMMENT_TYPE_ID,
+ DER_SOURCE_TYPE_ID,
+ get_client,
+ get_webapp_url,
+)
+from .sandbox import CheckoutDraftInfo, get_asset_drafts
+from .storage import (
+ _cache_asset_info,
+ get_storage_asset_dir,
+ get_storage_component_path,
+ get_storage_key,
+ get_storage_revision_dir,
+)
+from .utils import (
+ get_logger,
+ to_regex_safe_wildcard_string,
+ trace,
+)
+
+
+class FlowEntity:
+ """Base class with functionality pertinent to MEDM entities.
+ This includes Assets and Projects, and basically entails having containership
+ capabilities (i.e. having children).
+ """
+
+ def __init__(self, entity: medm_model.Asset | medm_model.Project, **kwargs):
+ """
+ Args:
+ entity: The medm entity that this object represents.
+ """
+ # The medm entity that this class represents
+ # This should be a medm_model.Asset or medm_model.Project
+ # Storing this object so we can access its properties
+ self._entity = entity
+
+ # Query objects - initialized on demand
+ # NOTE: In V2 api, query objects should be instantiated every call
+ # and persist the results of that query for the object's lifetime.
+ self._q_children: Any = None
+
+ super().__init__(**kwargs)
+
+ @property
+ def id(self) -> str:
+ """Return MEDM id of entity."""
+ return self._entity.id
+
+ @property
+ def name(self) -> str:
+ """Return MEDM name of entity."""
+ return self._entity.name
+
+ @trace
+ def iterate_children(self, refresh: bool = False) -> Iterator[FlowAsset]:
+ """Query assets contained in this entity.
+ Pagination is handled internally within this call via the V2 sdk.
+ If this query has already been performed, the cached result will be returned.
+ If refresh=True, do a fresh query.
+
+ Raises:
+ FlowError
+ """
+ # Construct new "contains" query if necessary
+ if self._q_children is None or refresh:
+ client = get_client()
+ q_input = medm_model.AssetsByTraversalInput(
+ start_at_id=self.id,
+ depth=1,
+ direction=medm_model.TraverseDirectionEnum.OUTGOING.value,
+ )
+ self._q_children = client.service_asset.assets_by_traversal(q_input)
+
+ # Wrap existing iterator in V2 sdk
+ try:
+ for child in self._q_children.assets_iterator:
+ # Skip self in the results (traversal API includes start node)
+ if child.id == self.id:
+ continue
+ yield FlowAsset(child) # convert to custom Asset object
+ except GQLAPIError as exc:
+ msg = f'Error querying children of entity "{self.name}". {exc}'
+ raise FlowError(msg) from exc
+
+ @trace
+ def find_child(self, name: str, force_query: bool = False) -> FlowAsset | None:
+ """Find the child that has the given name.
+
+ .. note:: The first match found will be returned.
+
+ Args:
+ name: Name to be matched.
+ force_query: Force a new query of the asset's children.
+ If False, previous query results will be used if available.
+
+ Returns:
+ The FlowAsset entity if found, or None.
+
+ Raises:
+ FlowError
+ """
+ for child in self.iterate_children(refresh=force_query):
+ if child.name == name:
+ return child
+ return None
+
+ @trace
+ def find_children(
+ self,
+ name: str = "",
+ type_id: str = "",
+ force_query: bool = False,
+ ) -> list[FlowAsset]:
+ """Return any children that matches the given criteria.
+
+ Args:
+ name: Name to be matched. Can support wildcard character '*'.
+ If blank ignore this filter.
+ type_id: Match children marked as this type (sub-types will be included).
+ If blank ignore this filter.
+ force_query: Force a new query of the asset's children.
+ If False, previous query results will be used if available.
+ Returns:
+ List of FlowAsset objects.
+
+ Raises:
+ FlowError
+ """
+ matches = []
+ regex = "^{}$".format(to_regex_safe_wildcard_string(name))
+ for child in self.iterate_children(refresh=force_query):
+ if name and not re.match(regex, child.name):
+ continue
+ if type_id and not child.find_component(type_id=type_id):
+ continue
+ matches.append(child)
+ return matches
+
+
+class UsesMixin:
+ """Mixin class with convenience functionality pertinent to MEDM objects
+ that contain "uses" relationships (i.e. dependencies on other MEDM assets).
+ This includes FlowAssets and FlowRevisions.
+ """
+
+ @trace
+ def init_uses(self, version_id: str):
+ """Explicit initialization function to store pertinent info.
+ This should be called in the __init__() function of any inheriting classes.
+
+ Args:
+ version_id: Version id of asset or revision.
+ """
+ self._uses_version_id = version_id
+
+ # Query objects - initialized on demand
+ # NOTE: In V2 api, query objects should be instantiated every call
+ # and persist the results of that query for the object's lifetime.
+ self._q_uses: Any = None
+
+ @trace
+ def iterate_uses(self, refresh: bool = False) -> Iterator[FlowRevision]:
+ """Query uses relationships in this asset/revision.
+ Pagination is handled internally within this call via the V2 sdk.
+ If this query has already been performed, the cached result will be returned.
+ If refresh=True, do a fresh query.
+
+ NOTE: This implementation grabs the entire uses tree because in practice
+ this is usually what we need (to fetch dependencies).
+
+ Raises:
+ FlowError
+ """
+ # Construct new "uses" query if necessary
+ if self._q_uses is None or refresh:
+ client = get_client()
+ q_input = medm_model.AssetVersionsByTraversalInput(
+ start_at_id=self._uses_version_id, # type: ignore[attr-defined]
+ depth=0, # retrieve entire tree
+ direction=medm_model.TraverseDirectionEnum.OUTGOING.value,
+ )
+ self._q_uses = client.service_asset.asset_versions_by_traversal(q_input)
+
+ # Wrap existing iterator in V2 sdk
+ # NOTE: The iterator function makes the actual query calls so no need to
+ # explicitly invoke call() here.
+ try:
+ # NOTE: We are getting revisions for now because this is most useful to us
+ # as the primary use case for getting uses information is to fetch
+ # dependencies.
+ #
+ # This is fetched through the versions iterator and not the revisions
+ # iterator. Since we are querying outgoing edges, the response gives us
+ # a list of edges like
+ # Rx -> Vy
+ # Rx -> Vz
+ # Rw -> Vu
+ # ...
+ # where R = revision, V = version
+ # The revisions iterator gives us the revision objects associated with
+ # the source of those edges. The versions iterator gives us the version
+ # objects associated with the destination of those edges.
+ for ver in self._q_uses.versions_iterator:
+ revision = FlowVersion(ver).revision
+ yield revision
+ except GQLAPIError as exc:
+ msg = f"Error querying uses relationships. {exc}"
+ raise FlowError(msg) from exc
+
+
+class ComponentMixin:
+ """Mixin class with convenience functionality pertinent to MEDM objects
+ that contain components. This includes FlowAssets and FlowRevisions.
+ """
+
+ @trace
+ def init_components(self, components: list[medm_model.Component]):
+ """Explicit initialization function to ingest component data.
+ This should be called in the __init__() function of any inheriting classes.
+
+ Args:
+ components: List of medm_model.Component objects to be translated and stored
+ as internal Component objects. Must not be None.
+
+ Raises:
+ FlowError: If components is None or any component data is invalid.
+ """
+ if components is None:
+ raise FlowError(
+ "Components cannot be None. This indicates the query did not "
+ "include the components field, or the SDK returned NOT_SET."
+ )
+
+ self.components = [] # List of components encoded as custom objects
+ for medm_comp in components:
+ self.components.append(FlowComponent(self, medm_comp))
+
+ @trace
+ def get_binary_components(self) -> list[FlowComponent]:
+ """Search for all binary components on asset/revision and return.
+
+ Returns:
+ Return a list of Components whose type id inherits from BINARY_TYPE_ID.
+ Empty list implies none found.
+ """
+ type_comps = []
+ for comp in self.components:
+ if BINARY_TYPE_ID in comp.parent_type_ids:
+ type_comps.append(comp)
+ return type_comps
+
+ @trace
+ def get_type_components(self) -> list[FlowComponent]:
+ """Search for all type components on asset/revision and return.
+
+ Returns:
+ Return a list of Components whose type id inherits from BASE_TYPE_ID.
+ Empty list implies none found.
+ """
+ type_comps = []
+ for comp in self.components:
+ if BASE_TYPE_ID in comp.parent_type_ids:
+ type_comps.append(comp)
+ return type_comps
+
+ @trace
+ def get_type_ids(self) -> list[str]:
+ """Return the type ids of the given asset/revision.
+
+ Returns:
+ Return a list of type ids associated with type components found on asset/revision.
+ Empty list implies no types are assigned to asset.
+ """
+ type_comps = self.get_type_components()
+ return [comp.type_id for comp in type_comps]
+
+ @trace
+ def find_components(
+ self,
+ name: str = "",
+ purpose: str = "",
+ type_id: str = "",
+ ) -> list[FlowComponent]:
+ """Search for component with matching name in given revision.
+
+ ..note:: Filters, if defined, are treated as an intersection, meaning
+ results will conform to all filters.
+
+ Args:
+ name: Component name to match. '*' wild card supported.
+ If blank ignore this filter.
+ purpose: Match this purpose on component. If blank ignore this filter.
+ type_id: Match this type id on component. If blank ignore this filter.
+
+ Returns:
+ Component object or None if not found.
+ """
+ regex = "^{}$".format(to_regex_safe_wildcard_string(name))
+ matches = []
+ for comp in self.components:
+ if name and not re.match(regex, comp.name):
+ continue
+ if purpose and purpose != comp.purpose:
+ continue
+ if type_id and type_id not in comp.parent_type_ids:
+ continue
+ matches.append(comp)
+ return matches
+
+ @trace
+ def find_component(
+ self,
+ name: str = "",
+ purpose: str = "",
+ type_id: str = "",
+ ) -> FlowComponent | None:
+ """Search for component matching criteria in the same way as described in
+ `find_components()`, but return only the first match.
+
+ Args:
+ See `find_components()` documentation.
+
+ Returns:
+ Component object or None if not found.
+ """
+ matches = self.find_components(name, purpose, type_id)
+ if matches:
+ return matches[0]
+ return None
+
+
+class FlowProject(FlowEntity):
+ """Container class for data relevant to a particular medm_model.Project.
+ Stores various immutable properties to avoid needing to re-query them.
+ Provides functions for obtaining extra data about the medm_model.Project.
+ """
+
+ # MEDM entity name
+ MEDM_ENTITY = "project"
+
+ @classmethod
+ def is_project_id(cls, project_id: str):
+ """Return True if given id conforms to expected structure for
+ MEDM project ids.
+ """
+ if not project_id.startswith(f"urn:medm:{cls.MEDM_ENTITY}:"):
+ return False # does not have correct header
+ parts = project_id.split(":")
+ if len(parts) != 5:
+ return False # does not have correct segments
+ return True
+
+ @classmethod
+ def get_project_id(cls, input_id: str) -> str:
+ """Convert an asset, revision or version id to a project id.
+
+ Args:
+ input_id: An MEDM asset, revision or version id.
+
+ Returns:
+ An MEDM project id.
+
+ Raises:
+ ValueError
+ """
+ if not FlowAsset.is_asset_id(input_id):
+ if not FlowRevision.is_revision_id(input_id):
+ if not FlowVersion.is_version_id(input_id):
+ msg = f"Invalid input id provided: {input_id}. "
+ msg += "Input must be an asset, version or revision id."
+ raise FlowError(msg)
+
+ parts = input_id.split(":")
+ project_id = f"urn:medm:project:{parts[3]}:{parts[4]}"
+ return project_id
+
+ @classmethod
+ def get_collection_id(cls, input_id: str) -> str:
+ """Return the collection id of the collection session project belongs to.
+
+ Args:
+ input_id: An MEDM project, asset, revision or version id.
+
+ Raises:
+ FlowError
+ """
+ try:
+ prefix, col_id, sub_id = input_id.rsplit(":", maxsplit=2)
+ except ValueError as exc:
+ msg = f'Input id "{input_id}" is invalid. {exc}'
+ raise FlowError(msg) from exc
+ return col_id
+
+ @trace
+ def __init__(self, project: str | medm_model.Project):
+ """
+ Args:
+ project: Either an MEDM project id, or an medm_model.Project object.
+
+ Raises:
+ EntityNotFoundError
+ FlowError
+ """
+ logger = get_logger(__name__)
+
+ if isinstance(project, str):
+ if not self.is_project_id(project):
+ msg = f"Invalid project id provided: {project}"
+ raise FlowError(msg)
+ # Query medm project by id
+ client = get_client()
+ q_input = medm_model.ProjectsByIdsInput(ids=[project])
+ q_project = client.service_project.projects_by_ids(q_input)
+ try:
+ q_project.call()
+ except GQLAPIError as exc:
+ msg = f"Error querying project: {project}. {exc}"
+ raise FlowError(msg) from exc
+ if len(q_project.projects) == 0:
+ msg = "Error retrieving MEDM project."
+ raise EntityNotFoundError(entity_id=project, details=msg)
+ project = q_project.projects[0]
+ logger.info(f'Queried project "{project.name}".')
+ elif not isinstance(project, medm_model.Project):
+ msg = "Project error: input provided is not an medm_model.Project or project id."
+ raise FlowError(msg)
+
+ # Trigger Entity class initialization
+ super().__init__(project)
+
+ @property
+ def collection_id(self) -> str:
+ """The id of the collection to which this project belongs."""
+ return self.get_collection_id(self.id)
+
+ @property
+ def organization_id(self) -> str:
+ """Organization id of collection to which project belongs.
+ NOTE: This is useful for schema id determination.
+ """
+ return self._entity.schema_registry_info.organization_id
+
+ @property
+ def group_id(self) -> str:
+ """Group id of collection to which project belongs.
+ NOTE: This is useful for schema id determination.
+ """
+ return self._entity.schema_registry_info.group_id
+
+ def __str__(self):
+ """Readable string representation of project object."""
+ s = "------------------------------\n"
+ s += f"PROJECT: {self.name}\n"
+ s += "------------------------------\n"
+ s += f" id: {self.id}\n"
+ s += f" organization_id: {self.organization_id}\n"
+ s += f" group_id: {self.group_id}\n"
+ return s
+
+
+class FlowAsset(ComponentMixin, UsesMixin, FlowEntity):
+ """Container class for data relevant to a particular medm_model.Asset.
+ Stores various immutable properties to avoid needing to re-query them.
+ Provides functions for obtaining extra data about the medm_model.Asset as well
+ as publishing capabilities.
+ """
+
+ # MEDM entity nmae
+ MEDM_ENTITY = "asset"
+
+ @classmethod
+ def is_asset_id(cls, asset_id: str) -> bool:
+ """Return True if given id conforms to expected structure for
+ MEDM asset ids.
+ """
+ if not asset_id.startswith(f"urn:medm:{cls.MEDM_ENTITY}:"):
+ return False # does not have correct header
+ parts = asset_id.split(":")
+ if len(parts) != 6: # does not have correct segments
+ return False
+ return True
+
+ @classmethod
+ def get_asset_id(cls, input_id: str) -> str:
+ """Convert a revision or version id to an asset id.
+
+ Args:
+ input_id: An MEDM revision or version id.
+
+ Returns:
+ An MEDM asset id.
+
+ Raises:
+ ValueError
+ """
+ if not FlowRevision.is_revision_id(input_id):
+ if not FlowVersion.is_version_id(input_id):
+ msg = f"Invalid input id provided: {input_id}. "
+ msg += "Input must be a version or revision id."
+ raise FlowError(msg)
+
+ parts = input_id.split(":")
+ asset_id = f"urn:medm:asset:{parts[3]}:{parts[4]}:{parts[5]}"
+ return asset_id
+
+ @classmethod
+ def get_web_url(cls, asset_id: str):
+ """Return url which will open Flow Web App to given asset id.
+
+ ..note:: Please ensure web app base url is configured via
+ `globals.set_webapp_url()`.
+ """
+ webapp_url = get_webapp_url()
+ if webapp_url is None:
+ raise FlowError("Web app url has not been configured.")
+ webapp_url = webapp_url.rstrip("/")
+ web_id = urllib.parse.quote(asset_id)
+ return f"{webapp_url}/assets?id={web_id}"
+
+ @classmethod
+ def get_drafts(cls, asset_id: str) -> list[CheckoutDraftInfo]:
+ """Return all local drafts that exist for the given asset.
+
+ .. note:: Currently, we only support a single draft per asset,
+ however this could change in the future with the introduction
+ of multiple checkouts/sandboxes. Returning a list keeps this
+ utility flexible.
+
+ The returned values will be CheckoutDraftInfo objects which will contain
+ detailed information about each draft.
+
+ Args:
+ asset_id: Id of MEDM asset. This can also be a revision or version id
+ from the same asset.
+
+ Returns:
+ List of CheckoutDraftInfo objects.
+
+ Raises:
+ FlowError
+ """
+ # Convert to asset id if necessary
+ if FlowRevision.is_revision_id(asset_id) or FlowVersion.is_version_id(asset_id):
+ asset_id = FlowAsset.get_asset_id(asset_id)
+ elif not FlowAsset.is_asset_id(asset_id):
+ msg = f"Invalid input id provided: {asset_id}. "
+ msg += "Input must be an asset, revision or version id."
+ raise FlowError(msg)
+ return get_asset_drafts(asset_id)
+
+ @trace
+ def __init__(self, asset: str | medm_model.Asset):
+ """
+ Args:
+ asset: Either an MEDM asset id, or an medm_model.Asset object.
+
+ Raises:
+ EntityNotFoundError
+ FlowError
+ """
+ logger = get_logger(__name__)
+
+ if isinstance(asset, str):
+ if not self.is_asset_id(asset):
+ msg = f"Invalid asset id provided: {asset}"
+ raise FlowError(msg)
+ client = get_client()
+ q_input = medm_model.AssetsByIdsInput(ids=[asset])
+ q_asset = client.service_asset.assets_by_ids(q_input)
+ try:
+ q_asset.call()
+ except GQLAPIError as exc:
+ msg = f"Error querying asset: {asset}. {exc}"
+ raise FlowError(msg) from exc
+ if len(q_asset.assets) == 0:
+ msg = "Error retrieving MEDM asset."
+ raise EntityNotFoundError(entity_id=asset, details=msg)
+ asset = q_asset.assets[0]
+ logger.info(f'Queried asset "{asset.name}".')
+ elif not isinstance(asset, medm_model.Asset):
+ msg = "Asset error: input provided is not an medm_model.Asset or asset id."
+ raise FlowError(msg)
+
+ # Store general entity information
+ super().__init__(asset)
+ # Initialize ComponentMixin class
+ self.init_components(asset.components)
+ # Initialize UsesMixin class
+ self.init_uses(asset.numbered_version_id)
+
+ # Cache relevant data about this asset for future use
+ _cache_asset_info(self.id)
+
+ # Query objects - initialized on demand
+ # NOTE: In V2 api, query objects should be instantiated every call
+ # and persist the results of that query for the object's lifetime.
+ self._q_revisions: Any = None
+ self._q_versions: Any = None
+
+ @property
+ def storage_key(self) -> str:
+ """Storage key of asset."""
+ return get_storage_key(self.id)
+
+ @property
+ def parent_id(self) -> str:
+ """MEDM id of parent entity (could be an asset or project)."""
+ return self._entity.parent_id
+
+ @property
+ def project_id(self) -> str:
+ """MEDM id of containing project."""
+ return self._entity.project_id
+
+ @property
+ def revision_id(self) -> str:
+ """Revision id of latest revision of asset at the time of
+ object creation or publish.
+ """
+ return self._entity.revision_id
+
+ @property
+ def revision_number(self) -> int:
+ """Revision number of latest revision of asset at the time of
+ object creation or publish.
+ """
+ return self._entity.revision_number
+
+ @property
+ def version_id(self) -> str:
+ """Id of latest (numbered) version of asset at time of
+ object creation or publish.
+ """
+ return self._entity.numbered_version_id
+
+ @property
+ def version_number(self) -> int:
+ """Number of latest (numbered) version of asset at time of
+ object creation or publish.
+ """
+ return self._entity.version_number
+
+ @property
+ def description(self) -> str | None:
+ """Description of asset."""
+ return self._entity.description
+
+ @property
+ def created_at(self) -> datetime.datetime:
+ """Creation timestamp of this asset."""
+ return datetime.datetime.fromisoformat(
+ self._entity.created.date.replace("Z", "+00:00")
+ )
+
+ @property
+ def created_by(self) -> str | None:
+ """Username of creator of this asset."""
+ if self._entity.created.user:
+ return self._entity.created.user.user_name
+ return None
+
+ @property
+ @cache
+ def type_ids(self) -> list[str]:
+ """Return type ids explicitly assigned to asset.
+ (This will not include base types.)
+ """
+ return self.get_type_ids()
+
+ def get_storage_dir(self) -> str:
+ """Return the full path of asset directory in primary storage
+ (whether or not the directory exists).
+
+ Returns:
+ Full path to expected location of primary storage directory on local disk.
+ """
+ return get_storage_asset_dir(self.id)
+
+ def get_storage_revision_dir(self) -> str:
+ """Return the full path of asset directory in primary storage
+ for the latest revision of asset (whether or not the directory exists).
+
+ Returns:
+ Full path to expected location of primary storage directory on local disk.
+ """
+ return get_storage_revision_dir(self.id, self.revision_number)
+
+ @trace
+ def get_parent(self) -> FlowAsset | FlowProject:
+ """Return parent of this asset.
+
+ Raises:
+ FlowError
+ """
+ try:
+ if self.parent_id == self.project_id:
+ return FlowProject(self.parent_id)
+ else:
+ return FlowAsset(self.parent_id)
+ except FlowError as exc:
+ msg = f'Could not retrieve parent for asset "{self.name}".'
+ raise FlowError(msg, details=str(exc)) from exc
+
+ @trace
+ def get_latest_revision(self) -> FlowRevision:
+ """Return an object representing the latest revision of this asset."""
+ return FlowRevision.get_revision(self.revision_id)
+
+ @trace
+ def iterate_revisions(self, refresh: bool = False) -> Iterator[FlowRevision]:
+ """Query revisions of this asset.
+ Pagination is handled internally within this call via the V2 sdk.
+ If this query has already been performed, the cached result will be returned.
+ If refresh=True, do a fresh query.
+
+ Raises:
+ FlowError
+ """
+ # Construct new "revisions" query if necessary
+ if self._q_revisions is None or refresh:
+ client = get_client()
+ q_input = medm_model.AssetRevisionsByAssetIdInput(asset_id=self.id)
+ self._q_revisions = (
+ client.service_asset_revision.asset_revisions_by_asset_id(q_input)
+ )
+
+ # Wrap existing iterator in V2 sdk
+ try:
+ for rev in self._q_revisions.asset_revisions_iterator:
+ revision = FlowRevision(rev) # convert to custom FlowRevision object
+ yield revision
+ except GQLAPIError as exc:
+ msg = f'Error querying revisions of asset "{self.name}". {exc}'
+ raise FlowError(msg) from exc
+
+ @trace
+ def iterate_versions(self, refresh: bool = False) -> Iterator[FlowVersion]:
+ """Query numbered versions for this asset using V2 GraphQL API.
+ Pagination is handled internally within this call via the V2 sdk.
+ If this query has already been performed, the cached result will be returned.
+ If refresh=True, do a fresh query.
+
+ Only returns NumberedAssetVersion (v1, v2, v3...), not NamedAssetVersion ("latest"),
+ since NamedAssetVersion doesn't have direct revision access.
+
+ Args:
+ refresh: If True, force a fresh query. If False, use cached results if available.
+
+ Yields:
+ AssetVersion objects wrapping NumberedAssetVersion, ordered by version number
+ in descending order (newest first: v3, v2, v1).
+
+ Raises:
+ FlowError
+ """
+ # Construct new versions query if necessary
+ if self._q_versions is None or refresh:
+ client = get_client()
+ sort_input = medm_model.SortInput(
+ field="created.date", order=medm_model.SortOrderEnum.DESC
+ )
+ q_input = medm_model.AssetVersionsByAssetIdInput(
+ asset_id=self.id, sort=sort_input
+ )
+ self._q_versions = client.service_asset.asset_versions_by_asset_id(q_input)
+
+ # Wrap existing iterator in V2 sdk
+ try:
+ for asset_version in self._q_versions.asset_versions_iterator:
+ # Skip NamedAssetVersion (like "latest" - they're just pointers)
+ if isinstance(asset_version, medm_model.NumberedAssetVersion):
+ yield FlowVersion(asset_version)
+ except GQLAPIError as exc:
+ msg = f'Error querying versions for asset "{self.name}". {exc}'
+ raise FlowError(msg) from exc
+
+ @trace
+ def find_derivative(
+ self,
+ target_type_id: str,
+ target_component_name: str,
+ ) -> FlowAsset | None:
+ """Search asset to find an outbound derivative where the target
+ matches the criteria provided.
+
+ This function searches across ALL revisions of the source asset to find
+ any existing derivative relationship.
+
+ Args:
+ target_type_id: Type of target revision.
+ target_component_name: Name of component on target revision to be matched.
+ (Derivative relationships are component to component.)
+
+ Returns:
+ The first derivative asset found, or None.
+ """
+ # This target id should match the beginning of any revision id belonging to this asset
+ target_id = self.id.replace(self.MEDM_ENTITY, FlowRevision.MEDM_ENTITY)
+
+ # Generate a query to find assets which contain a source-derivative
+ # component with a matching target id
+ # Since we know derivative assets will be siblings of the current asset
+ # we can safely scope this query to the parent asset with depth of 1.
+ client = get_client()
+ q_filter = f"has.component.type=={DER_SOURCE_TYPE_ID};"
+ q_filter += f"components[typeId:{DER_SOURCE_TYPE_ID}].data.folder.objectId=like={target_id}*;"
+ q_filter += (
+ f"components[typeId:{DER_SOURCE_TYPE_ID}].name=='{target_component_name}'"
+ )
+ q_input = medm_model.AssetsByTraversalInput(
+ start_at_id=self.parent_id, # search under parent
+ depth=1, # search immediate children only
+ direction=medm_model.TraverseDirectionEnum.OUTGOING.value,
+ filters=q_filter,
+ )
+ q_derivatives = client.service_asset.assets_by_traversal(q_input)
+
+ try:
+ q_derivatives.call()
+ except GQLAPIError as exc:
+ msg = f'Derivative query failed for asset "{self.name}". {exc}'
+ raise FlowError(msg) from exc
+
+ # NOTE: the starting asset (i.e. parent) will always be returned in
+ # the asset list, so we must skip that one
+ der_assets = [
+ FlowAsset(a) for a in q_derivatives.assets if a.id != self.parent_id
+ ]
+ # Now filter out derivatives of the wrong type
+ der_assets = [a for a in der_assets if target_type_id in a.type_ids]
+
+ if len(der_assets) > 0:
+ return der_assets[0]
+
+ return None
+
+ def __str__(self):
+ """Readable string representation of asset object."""
+ s = "------------------------------\n"
+ s += f"ASSET: {self.name}\n"
+ s += "------------------------------\n"
+ s += f" id: {self.id}\n"
+ s += f" storage_key: {self.storage_key}\n"
+ s += f" version: {self.version_number}\n"
+ s += f" revision: {self.revision_number}\n"
+ s += f" type_ids: {self.type_ids}\n"
+ return s
+
+
+class FlowRevision(ComponentMixin, UsesMixin):
+ """Container class for data relevant to a particular medm_model.AssetRevision.
+ Stores various immutable properties to avoid needing to re-query them.
+ Since revisions are by definition immutable, this class stores a cache
+ of all previously accessed revisions so that they are never queried more than once.
+ """
+
+ # MEDM entity name
+ MEDM_ENTITY = "assetRevision"
+
+ # Dictionaries that track already accessed revisions for reuse
+ # key = revision ids, value = FlowRevision object
+ _revision_cache: dict[str, FlowRevision] = {}
+
+ @classmethod
+ def is_revision_id(cls, revision_id: str) -> bool:
+ """Return True if given id conforms to expected structure for
+ MEDM revision ids.
+ """
+ if not revision_id.startswith(f"urn:medm:{cls.MEDM_ENTITY}:"):
+ return False # does not have correct header
+ parts = revision_id.split(":")
+ if len(parts) != 8:
+ return False # does not have correct segments
+ if parts[-2] != "rev":
+ return False # does not fit revision format
+ try:
+ int(parts[-1])
+ except ValueError:
+ return False # does not have a valid revision number
+ return True
+
+ @classmethod
+ def get_revision_id(cls, asset_id: str, revision_number: int) -> str:
+ """Generate a revision id based on inputs.
+ NOTE: resultant revision id will be structurally valid, but
+ may not actually exist.
+
+ Args:
+ asset_id: MEDM asset id.
+ revision_number: Number of revision within asset.
+
+ Returns:
+ MEDM revision id.
+ """
+ if not FlowAsset.is_asset_id(asset_id):
+ raise FlowError(f"Invalid asset id provided: {asset_id}")
+
+ # Remove asset header in id
+ asset_id = asset_id.split(":", maxsplit=3)[-1]
+
+ return f"urn:medm:{cls.MEDM_ENTITY}:{asset_id}:rev:{revision_number}"
+
+ @classmethod
+ def get_revision_number(cls, revision_id: str) -> int:
+ """Return revision number based on id."""
+ if not cls.is_revision_id(revision_id):
+ raise FlowError(f"Invalid revision id provided: {revision_id}")
+ # NOTE: since id passed validation, there should be no errors here
+ return int(revision_id.split(":")[-1])
+
+ @classmethod
+ def get_revision(cls, revision: str | medm_model.AssetRevision) -> FlowRevision:
+ """Retrieve a cached FlowRevision object based on provided revision id
+ or create a new FlowRevision object to represent the revision.
+
+ Args:
+ revision: Either a string medm revision id, or a medm_model.AssetRevision object.
+
+ Returns:
+ FlowRevision object.
+
+ Raises:
+ EntityNotFoundError
+ FlowError
+ """
+ if isinstance(revision, medm_model.AssetRevision):
+ revision_id = revision.id
+ else:
+ revision_id = revision
+
+ # Return cached value if available
+ if revision_id in cls._revision_cache:
+ return cls._revision_cache[revision_id]
+
+ # Convert to custom object
+ return FlowRevision(revision)
+
+ @trace
+ def __init__(self, revision: str | medm_model.AssetRevision):
+ """
+ Args:
+ revision: Either an MEDM revision id, or an medm_model.AssetRevision object.
+
+ Raises:
+ EntityNotFoundError
+ FlowError
+ """
+ logger = get_logger(__name__)
+
+ if isinstance(revision, str):
+ if not self.is_revision_id(revision):
+ msg = f"Invalid revision id provided: {revision}"
+ raise FlowError(msg)
+ client = get_client()
+ q_input = medm_model.AssetRevisionsByIdsInput(ids=[revision])
+ q_revision = client.service_asset_revision.asset_revisions_by_ids(q_input)
+ try:
+ q_revision.call()
+ except GQLAPIError as exc:
+ msg = f"Error querying revision: {revision}. {exc}"
+ raise FlowError(msg) from exc
+ if len(q_revision.revisions) == 0:
+ msg = "Error retrieving MEDM revision."
+ raise EntityNotFoundError(entity_id=revision, details=msg)
+ revision = q_revision.revisions[0]
+ msg = f'Queried revision "{revision.name}" revision number {revision.revision_number}.'
+ logger.info(msg)
+ elif not isinstance(revision, medm_model.AssetRevision):
+ msg = "Revision error: input provided is not an medm_model.AssetRevision or revision id."
+ raise FlowError(msg)
+
+ # The medm revision that this class represents
+ # This should be a medm_model.AssetRevision
+ # Storing this object so we can access its properties as needed
+ self._revision = revision
+
+ # Initialize ComponentMixin class
+ self.init_components(revision.components)
+ # Initialize UsesMixin class
+ self.init_uses(revision.numbered_version_id)
+
+ # Cache object
+ # Revisions are immutable so we should only ever query each revision once!
+ FlowRevision._revision_cache[revision.id] = self
+
+ # --------------------------------------------------------------------
+ # Enforce read-only properties on revision objects since these objects
+ # are cached and should be considered immutable.
+ # --------------------------------------------------------------------
+
+ @property
+ def id(self) -> str:
+ return self._revision.id
+
+ @property
+ def asset_id(self) -> str:
+ """Return asset id of revision."""
+ return self._revision.asset_id
+
+ @property
+ def name(self) -> str:
+ return self._revision.name
+
+ @property
+ def revision_number(self) -> int:
+ """Number of this revision.
+ This value is strictly acsending per revision and begins at 1.
+ """
+ return self._revision.revision_number
+
+ @property
+ def version_id(self) -> str:
+ """Return id of "numbered version" to which this revision belongs.
+ NOTE: Multiple revisions may map to the same numbered version.
+ The current revision may or may not be the latest of that set.
+ """
+ return self._revision.numbered_version_id
+
+ @property
+ def version_number(self) -> int:
+ """Return version number to which this revision belongs.
+ Version numbers with respect to revisions are non-decreasing, however,
+ multiple successive revisions may have the same version number.
+ This value increments beginning at 1.
+ """
+ return self._revision.version_number
+
+ @property
+ def storage_key(self) -> str:
+ """Storage key is the asset identifier portion of the full asset id."""
+ return get_storage_key(self.asset_id)
+
+ @property
+ def comment(self) -> str | None:
+ """Return the publish comment stored in a comment component if it exists."""
+ comment_comp = self.find_component(type_id=COMMENT_TYPE_ID)
+ if comment_comp:
+ # Will ignore malformed comment components for now
+ return comment_comp.properties.get("subjectLine")
+ return None
+
+ def get_storage_dir(self) -> str:
+ """Return the full path of asset revision directory in primary storage
+ (whether or not the directory exists).
+
+ Returns:
+ Full path to expected location of cached storage directory on local disk.
+ """
+ return get_storage_revision_dir(self.asset_id, self.revision_number)
+
+ def get_storage_component_path(
+ self,
+ component_name: str = "",
+ component_purpose: str = "",
+ blob_index: int = 0,
+ ) -> str | None:
+ """Return the full path of the component blob file of the given asset revision
+ in primary storage (whether or not the file exists).
+
+ Args:
+ component_name: If provided, search for component with this name and return its storage path.
+ This should be unique within the revision.
+ component_purpose: If provided, search for a component with this purpose and return
+ its storage path. There may be multiple components with the same purpose,
+ so the first match will be returned.
+ blob_index: Specific blob from source component to get.
+
+ ..note:: If both component name and purpose are provided, the first intersection
+ of both criteria will be returned.
+
+ Returns:
+ Full path to expected location of cached source file on local disk, or
+ None if the component does not exist on the revision.
+ """
+ return get_storage_component_path(
+ self._revision,
+ component_name=component_name,
+ component_purpose=component_purpose,
+ blob_index=blob_index,
+ )
+
+ @trace
+ def fetch(self, component_purpose: str, fetch_dependencies: bool = False):
+ """Fetch the given component of this revision if not already on disk.
+ If the specified component does not exist, nothing will happen.
+
+ Args:
+ component_purpose: Fetch component of this purpose.
+ fetch_dependencies: If True, also fetch components of the same purpose
+ in this revisions "uses" tree.
+ (This may not be appropriate for all component purposes.)
+ """
+ fetch(
+ self._revision,
+ component_purpose=component_purpose,
+ fetch_dependencies=fetch_dependencies,
+ )
+
+ @trace
+ def get_thumbnail_file(self) -> str:
+ """Return the file path to the revision's thumbnail if it exists,
+ fetching it if necessary.
+
+ Returns:
+ Full path to thumbnail in storage.
+
+ Raises:
+ ThumbnailError
+ """
+ return get_thumbnail_file(self._revision)
+
+ @trace
+ def get_thumbnail_url(self) -> str:
+ """Return the signed url to the revision's thumbnail if it exists.
+
+ Returns:
+ Url that can be used to download thumbnail.
+
+ Raises:
+ ThumbnailError
+ """
+ return get_thumbnail_url(self._revision)
+
+ def __str__(self):
+ """Readable string representation of revision object."""
+ s = "------------------------------\n"
+ s += f"REVISION: {self.name} - r{self.revision_number}\n"
+ s += "------------------------------\n"
+ s += f" id: {self.id}\n"
+ s += f" storage_key: {self.storage_key}\n"
+ s += f" version: {self.version_number}\n"
+ return s
+
+
+class FlowVersion:
+ """Container class for data relevant to a medm_model.NumberedAssetVersion."""
+
+ # MEDM entity name
+ MEDM_ENTITY = "assetVersion"
+
+ @classmethod
+ def is_version_id(cls, version_id: str) -> bool:
+ """Return True if given id conforms to expected structure for
+ MEDM version ids.
+ """
+ if not version_id.startswith(f"urn:medm:{cls.MEDM_ENTITY}:"):
+ return False # does not have correct header
+ parts = version_id.split(":")
+ if len(parts) != 8:
+ return False # does not have correct segments
+ if parts[-2] != "ver":
+ return False # does not follow version format
+ # NOTE: ignoring "named" versions for now
+ try:
+ int(parts[-1])
+ except ValueError:
+ return False # does not have a valid version number
+ return True
+
+ @classmethod
+ def get_version_number(cls, version_id: str) -> int:
+ """Return version number based on id."""
+ if not cls.is_version_id(version_id):
+ raise FlowError(f"Invalid version id provided: {version_id}")
+ # NOTE: since id passed validation, there should be no errors here
+ return int(version_id.split(":")[-1])
+
+ @classmethod
+ def get_version_id(cls, asset_id: str, version_number: int) -> str:
+ """Generate a version id based on inputs.
+ NOTE: resultant version id will be structurally valid, but
+ may not actually exist.
+
+ Args:
+ asset_id: MEDM asset id.
+ version_number: Number of version within asset.
+
+ Returns:
+ MEDM version id.
+ """
+ if not FlowAsset.is_asset_id(asset_id):
+ raise FlowError(f"Invalid asset id provided: {asset_id}")
+
+ # Remove asset header in id
+ asset_id = asset_id.split(":", maxsplit=3)[-1]
+
+ return f"urn:medm:{cls.MEDM_ENTITY}:{asset_id}:ver:{version_number}"
+
+ @trace
+ def __init__(self, version: str | medm_model.NumberedAssetVersion):
+ """
+ Args:
+ version: Either an MEDM version id, or an medm_model.NumberedAssetVersion object.
+
+ Raises:
+ EntityNotFoundError
+ FlowError
+ """
+ logger = get_logger(__name__)
+
+ if isinstance(version, str):
+ if not self.is_version_id(version):
+ msg = f"Invalid version id provided: {version}"
+ raise FlowError(msg)
+ client = get_client()
+ q_input = medm_model.AssetVersionsByIdsInput(ids=[version])
+ q_version = client.service_asset.asset_versions_by_ids(q_input)
+ try:
+ q_version.call()
+ except GQLAPIError as exc:
+ msg = f"Error querying version: {version}. {exc}"
+ raise FlowError(msg) from exc
+ if len(q_version.versions) == 0:
+ msg = "Error retrieving MEDM version."
+ raise EntityNotFoundError(entity_id=version, details=msg)
+ version = q_version.versions[0]
+ msg = f"Queried version number {version.version_number}."
+ logger.info(msg)
+ elif not isinstance(version, medm_model.NumberedAssetVersion):
+ msg = "Version error: input provided is not an medm_model.NumberedAssetVersion or version id."
+ raise FlowError(msg)
+
+ # The medm version that this class represents
+ # This should be a medm_model.NumberedAssetVersion
+ # Storing this object so we can access its properties as needed
+ self._version = version
+
+ # Numbered versions have attached revision objects
+ # Take the opportunity to cache this since revisions are immutable
+ # and should only be queried once!
+ FlowRevision._revision_cache[self.revision.id] = self.revision
+
+ @property
+ def id(self) -> str:
+ return self._version.id
+
+ @property
+ def asset_id(self) -> str:
+ """Return asset id of version."""
+ return self._version.asset_id
+
+ @property
+ def version_number(self) -> int:
+ """Return number of version."""
+ return self._version.version_number
+
+ @property
+ def revision(self) -> FlowRevision:
+ """Return current revision associated with version.
+ The revision associated with a version can change over time.
+ The version object would need to be re-queried to see the changes.
+ """
+ return FlowRevision(self._version.revision)
+
+ @property
+ def created_at(self) -> datetime.datetime:
+ """Creation timestamp of this version."""
+ return datetime.datetime.fromisoformat(
+ self._version.created.date.replace("Z", "+00:00")
+ )
+
+ @property
+ def created_by(self) -> str | None:
+ """Username of creator of this version."""
+ if self._version.created.user:
+ return self._version.created.user.user_name
+ return None
+
+ def __str__(self):
+ """Readable string representation of version object."""
+ s = "------------------------------\n"
+ s += f"VERSION: v{self.version_number}\n"
+ s += "------------------------------\n"
+ s += f" id: {self.id}\n"
+ s += f" revision: {self.revision.name} (r{self.revision.revision_number})\n"
+ return s
+
+
+class FlowComponent:
+ """Container class for data relevant to a particular medm_model.Component.
+ Presents component data in a more readable and accessible way.
+ """
+
+ @trace
+ def __init__(self, revision: FlowRevision, component: medm_model.Component):
+ """
+ Args:
+ revision: Parent FlowRevision object.
+ component: Medm component object to convert to custom object.
+
+ Raises:
+ FlowError
+ """
+ # FlowRevision object that this component belongs to
+ self.revision = revision
+ # Original MEDM component object
+ self._component = component
+ # Uniquely identifying component name
+ self.name = component.name
+ # Type id specifies the type of the component (e.g. binary, type, etc)
+ self.type_id = component.type_id
+ # Ancestors of type id
+ self.parent_type_ids = component.parent_type_ids
+ # The purpose further specifies what kind of component this is
+ # For instance two binary components can have different purposes
+ self.purpose = component.data.get("purpose", "")
+ # For binary components, the blob array specifies the files associated
+ # with the component
+ self.blobs = []
+ # Arbitrary additional properties stored on the component
+ self.properties = {} # key = property name, value = property value
+
+ blob_data = component.data.get("data", [])
+ for blob in blob_data:
+ self.blobs.append(Blob(blob))
+
+ for prop, val in component.data.items():
+ if prop in ["data", "purpose"]:
+ continue # already processed these
+ self.properties[prop] = val
+
+ @trace
+ def get_blob_path(self, blob_index: int = 0) -> str:
+ """Return the relative path stored on the blob at the given index
+ of the given component.
+
+ Args:
+ blob_index: The index into the blob array to be inspected.
+
+ Returns:
+ The path of the blob relative to the asset revision folder.
+
+ Raises:
+ FlowError
+ """
+ if len(self.blobs) <= blob_index:
+ msg = f'Component "{self.name}" does not have a blob at index {blob_index}.'
+ raise FlowError(msg)
+ return self.blobs[blob_index].path
+
+ @trace
+ def fetch_blob_urls(self) -> list[str]:
+ """Query list of urls for component blobs that can be used for
+ downloading. Order should be preserved.
+
+ Raises:
+ FlowError
+ """
+ # Get the URNs that we need to fetch
+ urns = [blob.uri for blob in self.blobs]
+
+ # Get info about current project
+ project_id = FlowProject.get_project_id(self.revision.id)
+
+ return fetch_blob_urls(project_id, urns)
+
+ @trace
+ def download(
+ self,
+ directory: str,
+ file_sequence: bool = False,
+ skip_download: bool = False,
+ ) -> dict[int, str]:
+ """Download all binary blobs in component to given directory.
+ Directory must exist, and component must be a binary component.
+
+ Args:
+ directory: Existing folder location to be downloaded to.
+ file_sequence: If True, expect the component to contain a
+ zipped file sequence, and automatically expand it.
+ skip_download: Only relevant for file sequences. Used when
+ the source zip file has already been downloaded, but
+ the files haven't been extracted.
+
+ Returns:
+ Dictionary of blob index to full path of downloaded file.
+
+ Raises:
+ FlowError
+ """
+ # Get info about current project
+ project_id = FlowProject.get_project_id(self.revision.id)
+
+ # Do download
+ return download(
+ self._component,
+ project_id,
+ directory,
+ file_sequence,
+ skip_download,
+ )
+
+ def __str__(self):
+ """Readable string representation of component object."""
+ s = "------------------------------\n"
+ s += f"COMPONENT: {self.name}\n"
+ s += "------------------------------\n"
+ s += f" type_id: {self.type_id}\n"
+ if self.purpose:
+ s += f" purpose: {self.purpose}\n"
+ return s
+
+
+class Blob:
+ """Container class for data relevant to a blob within a component."""
+
+ @trace
+ def __init__(self, blob_data: dict):
+ """
+ Args:
+ blob_data: A dictionary of blob information from an medm component.
+
+ Raises:
+ FlowError
+ """
+ try:
+ self.path = blob_data["path"]
+ self.uri = blob_data["uri"]
+ except KeyError as exc:
+ msg = "Invalid blob data dictionary provided."
+ raise FlowError(msg, details=str(exc)) from exc
+
+ def __str__(self):
+ """Readable string representation of blob object."""
+ return f"[BLOB: {self.path}]"
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/publish.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/publish.py
new file mode 100644
index 00000000..4e822463
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/publish.py
@@ -0,0 +1,684 @@
+# -
+# *****************************************************************************
+# Copyright 2026 Autodesk, Inc. All rights reserved.
+#
+# These coded instructions, statements, and computer programs contain
+# unpublished proprietary information written by Autodesk, Inc. and are
+# protected by Federal copyright law. They may not be disclosed to third
+# parties or copied or duplicated in any form, in whole or in part, without
+# the prior written consent of Autodesk, Inc.
+# *****************************************************************************
+# +
+
+"""
+This module provides asset publishing utilities.
+"""
+
+from __future__ import annotations # needed for python 3.9 support
+
+import os
+import uuid
+import shutil
+import tempfile
+import zipfile
+from abc import ABC, abstractmethod
+from dataclasses import dataclass
+
+from tank_vendor.flow_data_sdk.base import model as medm_model
+from tank_vendor.flow_data_sdk.base.exceptions import GQLAPIError
+
+from . import transferapi
+from .exceptions import (
+ CreateAssetError,
+ ComponentSpecError,
+ FlowError,
+ PublishAssetError,
+)
+from .globals import (
+ BASE_TYPE_ID,
+ BINARY_TYPE_ID,
+ COMMENT_COMP,
+ COMMENT_TYPE_ID,
+ DER_SOURCE_COMP,
+ DER_SOURCE_TYPE_ID,
+ get_client,
+ IMAGE_TYPE_ID,
+ SOURCE_COMP,
+ SOURCE_PURPOSE,
+ THUMBNAIL_COMP,
+ THUMBNAIL_PURPOSE,
+ TYPE_COMP,
+)
+from .schema import is_sub_type
+from .storage import (
+ _cache_asset_info,
+ _find_component,
+ get_storage_revision_dir,
+)
+from .utils import cleanpath, get_logger, mimetype, trace
+
+
+@dataclass
+class UploadBlob:
+ """Data class containing relevant information for blob transfer."""
+
+ #: Local uri assigned to blob.
+ uri: str
+ #: Full path to blob file in sandbox.
+ full_path: str
+ #: Path of blob file relative to draft directory.
+ blob_path: str
+
+
+class ComponentSpec(ABC):
+ """Abstract base class for component specification objects.
+ This should be subclassed.
+ """
+
+ @property
+ def name(self) -> str:
+ """Return name of component.
+ (Should be uniquely identifying within a revision.)
+ """
+ raise NotImplementedError()
+
+ @abstractmethod
+ def create(self) -> medm_model.Component:
+ """Create an MEDM component based on specifications."""
+ raise NotImplementedError()
+
+ @staticmethod
+ @trace
+ def create_component(
+ name: str, type_id: str, **data
+ ) -> medm_model.ComponentDataInput:
+ """Generate a medm_model.Component object.
+
+ Args:
+ name: Name of component. This must be unique within a revision.
+ **data: The remaining keyword args will be treated as additional properties
+ to be set on the component, for example, purpose.
+
+ Returns:
+ Component object that can be added to an AssetRevision.
+ """
+ return medm_model.ComponentDataInput(name=name, data=data, type_id=type_id)
+
+
+class BinaryComponentSpec(ComponentSpec):
+ """Base class for all binary component specs."""
+
+ def __init__(self):
+ # List of blob upload information stored as UploadBlob objects
+ # At creation time, the medm component will be given a uuid per blob.
+ # The ids along with the associated file paths on disk are tracked in
+ # UploadBlob data structures to be used later on for blob uploading.
+ self._upload_blobs = []
+
+ def create(
+ self,
+ name: str,
+ files: list[str],
+ type_id: str = "",
+ purpose: str = "",
+ **properties,
+ ) -> medm_model.Component:
+ """Create a medm binary component that is ready for upload.
+
+ Args:
+ name: Name of component. If the component name already exists, an exception
+ will be thrown unless "overwrite" is True in which case, the existing
+ component will be replaced.
+ files: Full paths to existing files to be added to component.
+ (Order will be preserved.)
+ type_id: Type of binary component to be created. If not specified, the base
+ binary type will be used.
+ purpose: A string constant, describing the general purpose of the component.
+ **properties: The remaining keyword args will be treated as additional properties
+ to be set on the binary component, for example, resolution
+ information for a thumbnail.
+
+ Returns:
+ Binary component created.
+
+ Raises:
+ ComponentSpecError
+ """
+ # Check that the files are valid
+ for file in files:
+ if not os.path.exists(file):
+ msg = f"Invalid source path provided for binary component: {file}"
+ raise ComponentSpecError(details=msg)
+
+ # Check that the type is valid
+ if type_id and not is_sub_type(BINARY_TYPE_ID, type_id):
+ msg = f"Type id {type_id} is not a binary component type."
+ raise ComponentSpecError(details=msg)
+ type_id = type_id or BINARY_TYPE_ID
+
+ # NOTE: TEMPORARY SOLUTION!
+ # Due to a limitation in MEDM where only a max of 10 blobs
+ # can be stored per binary component, we must zip multiple files
+ # and upload as a single blob.
+ if len(files) > 1:
+ # Generate the zip in the same directory as the first file
+ base_dir = os.path.dirname(files[0])
+ # At this point we can assume the file sequence has been vetted
+ # for the correct naming convention - parse the base name out of
+ # the first file to use as the zip file name.
+ base_name = os.path.basename(files[0]).split(".")[0]
+ zip_path = cleanpath(base_dir, f"{base_name}.zip")
+ with zipfile.ZipFile(zip_path, "w") as zip_file:
+ # Add each file to zip with only its base name
+ # and with lossless compression
+ for f in files:
+ zip_file.write(
+ f,
+ arcname=os.path.basename(f),
+ compress_type=zipfile.ZIP_DEFLATED,
+ )
+ files = [zip_path]
+
+ blobs = [] # blob list
+ self._upload_blobs = []
+ for i, file in enumerate(files):
+ # Associate each blob with a unique id which will be
+ # used for uploading later
+ uri = f"upload://{uuid.uuid4()}"
+ blob_path = os.path.basename(file)
+ # Add blob info for medm component
+ blobs.append(
+ {
+ "uri": uri,
+ "path": blob_path,
+ "mimeType": mimetype(file),
+ "size": os.path.getsize(file),
+ }
+ )
+ # Store upload information for each blob for later use when publishing
+ self._upload_blobs.append(
+ UploadBlob(
+ uri=uri,
+ full_path=file,
+ blob_path=blob_path,
+ )
+ )
+
+ data = {} # data block
+ for prop, val in properties.items():
+ data[prop] = val
+ data["data"] = blobs
+ data["purpose"] = purpose
+
+ # Create new component
+ return self.create_component(name=name, type_id=type_id, **data)
+
+
+class CommentComponentSpec(ComponentSpec):
+ """Specifications for creating a comment component.
+ This is a component used to store the publish comment of a revision.
+ There is only expected to be one of these per revision.
+ """
+
+ def __init__(self, comment: str):
+ """
+ Args:
+ comment: Description of revision provided at publish time.
+ """
+ self.comment = comment
+
+ @property
+ def name(self) -> str:
+ return COMMENT_COMP
+
+ def create(self) -> medm_model.Component:
+ """Create an MEDM component based on specifications."""
+
+ # NOTE: comment components have an optional "body" property that we are not
+ # leveraging for now...
+ return self.create_component(
+ name=self.name,
+ type_id=COMMENT_TYPE_ID,
+ subjectLine=self.comment,
+ )
+
+
+class DerivativeSourceComponentSpec(ComponentSpec):
+ """Specifications for creating a derivative source component.
+ This is a component used in a derivative asset to point back to its source asset.
+ There is only expected to be one of these per revision.
+ """
+
+ def __init__(self, revision_id: str):
+ """
+ Args:
+ revision_id: Id of source revision.
+ """
+ self.revision_id = revision_id
+
+ @property
+ def name(self) -> str:
+ return DER_SOURCE_COMP
+
+ def create(self) -> medm_model.Component:
+ """Create an MEDM component based on specifications."""
+ return self.create_component(
+ name=self.name,
+ type_id=DER_SOURCE_TYPE_ID,
+ folder={"objectId": self.revision_id},
+ )
+
+
+class SourceComponentSpec(BinaryComponentSpec):
+ """Specifications for creating a source component.
+ This is a component used to store the main source file(s) of the revision.
+ There is only expected to be one of these per revision.
+ """
+
+ def __init__(self, *files):
+ """
+ Args:
+ files: List of files to be stored in the component.
+ """
+ self.files = files
+
+ @property
+ def name(self) -> str:
+ return SOURCE_COMP
+
+ @trace
+ def create(self) -> medm_model.Component:
+ """Create an MEDM component based on specifications."""
+ return super().create(name=self.name, files=self.files, purpose=SOURCE_PURPOSE)
+
+
+class ThumbnailComponentSpec(BinaryComponentSpec):
+ """Specifications for creating a thumbnail component.
+ This is a component used to store the thumbnail for the revision.
+ There is only expected to be one of these per revision.
+ """
+
+ def __init__(self, thumbnail_file: str):
+ """
+ Args:
+ thumbnail_file: Path to thumbnail file.
+ """
+ self.file = thumbnail_file
+
+ @property
+ def name(self) -> str:
+ return THUMBNAIL_COMP
+
+ @trace
+ def create(self) -> medm_model.Component:
+ """Create an MEDM component based on specifications."""
+ return super().create(
+ name=self.name,
+ files=[self.file],
+ purpose=THUMBNAIL_PURPOSE,
+ type_id=IMAGE_TYPE_ID,
+ )
+
+
+class TypeComponentSpec(ComponentSpec):
+ """Specifications for creating a type component.
+ This is a component used to provide a type designation for the revision.
+ More than one type component may be added to a revision.
+
+ .. note:: Designating a "type" for an asset in MEDM involves adding a "type" component
+ to a revision. There is no restriction on the number of type components
+ on a revision, and no requirement that types must remain the same from one revision
+ to the next. Therefore, maintaining a consistent type is largely up to the pipeline.
+ """
+
+ def __init__(self, type_id: str = "", name: str = ""):
+ """
+ Args:
+ type_id: The MEDM type identifier for the type component.
+ If not provided, the base type id will be used.
+ name: A unique name for the type component
+ (since there may be more than one).
+ If not provided, a default name will be used.
+
+ Raises:
+ ComponentSpecError
+ """
+ # If provided, type id must be subtype of base type
+ if type_id and not is_sub_type(BASE_TYPE_ID, type_id):
+ msg = f"Type id {type_id} is not a subclass of base type."
+ raise ComponentSpecError(details=msg)
+
+ self.type_id = type_id or BASE_TYPE_ID
+ self._name = name or TYPE_COMP
+
+ @property
+ def name(self) -> str:
+ return self._name
+
+ def create(self) -> medm_model.Component:
+ """Create an MEDM component based on specifications."""
+ return self.create_component(
+ name=self.name,
+ type_id=self.type_id,
+ )
+
+
+class FileSeqComponentSpec(TypeComponentSpec):
+ """Specifications for creating a file sequence type component.
+ This is a component used to designate an asset as containing a file sequence.
+ This may be combined with other type designations to describe the nature of the asset.
+ """
+
+ def __init__(
+ self,
+ type_id: str,
+ frame_start: int,
+ frame_end: int,
+ frame_set: str,
+ file_format: str,
+ name: str = "",
+ ):
+ """
+ Args:
+ type_id: The MEDM type identifier for the type component.
+ frame_start: First frame of file sequence.
+ frame_end: End frame of file sequence.
+ frame_set: A string expression denoting the set of frames
+ within the sequence. (e.g. "1-5,10,13-20")
+ file_format: A string expression denoting the file naming and
+ frame padding convention of the sequence.
+ (e.g. "render.%04d.exr")
+ name: A unique name for the type component
+ (since there may be more than one type component).
+ If not provided, a default name will be used.
+
+ Raises:
+ ComponentSpecError
+ """
+ # NOTE: for now, this type id will be passed in because it will
+ # be a custom schema. Later when the file sequence type id
+ # is formalized under autodesk namespace, we will create a
+ # global constant and use that instead.
+
+ # If provided, type id must be subtype of base type
+ if not is_sub_type(BASE_TYPE_ID, type_id):
+ msg = f"Type id {type_id} is not a subclass of base type."
+ raise ComponentSpecError(details=msg)
+
+ self.type_id = type_id
+ self.frame_start = frame_start
+ self.frame_end = frame_end
+ self.frame_set = frame_set
+ self.file_format = file_format
+ self._name = name or TYPE_COMP
+
+ @property
+ def name(self) -> str:
+ return self._name
+
+ @trace
+ def create(self) -> medm_model.Component:
+ """Create an MEDM component based on specifications."""
+ return self.create_component(
+ name=self.name,
+ type_id=self.type_id,
+ frameStart=self.frame_start,
+ frameEnd=self.frame_end,
+ frameSet=self.frame_set,
+ fileFormat=self.file_format,
+ )
+
+
+@trace
+def publish_new_asset(
+ name: str,
+ parent_id: str,
+ description: str = "",
+ components: list[ComponentSpec] | None = None,
+ used_versions: list[str] | None = None,
+) -> medm_model.Asset:
+ """Create a new asset in AM.
+
+ Args:
+ name: Name of asset.
+ parent: Entity under which to create the asset.
+ This should be an Asset or a Project object.
+ This can also be the id of an asset or project.
+ description: Optional description of asset.
+ components: List of component specifications that will be converted into components
+ to be attached to asset revision.
+ (Components are used to store binaries and metadata on revisions.)
+ used_versions: List of version ids of other assets used by this asset.
+ (Stored as "uses" relationships with other asset versions.)
+
+ Raises:
+ CreateAssetError
+ """
+ # Prepare medm component objects for publish
+ medm_components = _generate_medm_components(components)
+
+ # Prepare medm uses inputs for publish
+ medm_uses = _generate_medm_uses(used_versions)
+
+ # Call create asset mutation
+ client = get_client()
+ m_input = medm_model.CreateAssetInput(
+ name=name,
+ parent_id=parent_id,
+ components=medm_components,
+ description=description,
+ uses=medm_uses,
+ )
+ m_create = client.service_asset.create_asset(m_input)
+ try:
+ m_create.call()
+ except GQLAPIError as exc:
+ msg = f'Error creating asset "{name}" on remote. {exc}. Request id: {exc.request_id}'
+ raise CreateAssetError(details=msg) from exc
+
+ # Get new published asset
+ asset = m_create.asset
+
+ # Upload associated binaries
+ if components is not None:
+ bin_specs = [
+ spec for spec in components if isinstance(spec, BinaryComponentSpec)
+ ]
+ if not bin_specs:
+ return asset # nothing to transfer
+ _upload_binaries(asset, bin_specs)
+
+ return asset
+
+
+@trace
+def publish_new_revision(
+ asset_id: str,
+ components: list[ComponentSpec] | None = None,
+ used_versions: list[str] | None = None,
+) -> medm_model.Asset:
+ """Publish a new version of an existing asset.
+
+ Args:
+ asset_id: Medm id of asset to be published.
+ components: List of component specifications that will be converted into components
+ to be attached to asset revision.
+ (Components are used to store binaries and metadata on revisions.)
+ used_versions: List of version ids of other assets used by this asset.
+ (Stored as "uses" relationships with other asset versions.)
+
+ Returns:
+ Updated asset object.
+ NOTE: input asset object should be deprecated after publish.
+
+ Raises:
+ PublishAssetError
+ """
+ # Prepare medm component objects for publish
+ medm_components = _generate_medm_components(components)
+
+ # Prepare medm uses inputs for publish
+ medm_uses = _generate_medm_uses(used_versions)
+
+ client = get_client()
+ m_input = medm_model.UpdateAssetInput(
+ id=asset_id,
+ components=medm_components,
+ components_action=medm_model.ListAction.REPLACE.value,
+ named_version_change=medm_model.NamedVersionChangeEnum.CREATE_NEW.value,
+ uses=medm_uses,
+ )
+
+ m_update = client.service_asset.update_asset(m_input)
+ try:
+ m_update.call()
+ except GQLAPIError as exc:
+ msg = f"Failed to update asset. {exc}"
+ raise PublishAssetError(details=msg) from exc
+
+ # Get updated asset
+ asset = m_update.asset
+
+ # Upload associated binaries
+ if components is not None:
+ bin_specs = [
+ spec for spec in components if isinstance(spec, BinaryComponentSpec)
+ ]
+ if not bin_specs:
+ return asset # nothing to transfer
+ _upload_binaries(asset, bin_specs)
+
+ return asset
+
+
+def _generate_medm_components(
+ comp_specs: list[ComponentSpec],
+) -> medm_model.ComponentDataInput:
+ """Given component specs create medm component objects to be used in a publish."""
+ comp_specs = [] if comp_specs is None else comp_specs
+ medm_components = [comp.create() for comp in comp_specs]
+ return medm_components
+
+
+def _generate_medm_uses(used_versions: list[str]) -> medm_model.UsesTargetInput:
+ """Convert a list of version ids into a list of UsesTargetInput objects."""
+ used_versions = [] if used_versions is None else used_versions
+ uses_inputs = []
+ for version_id in used_versions:
+ uses_inputs.append(medm_model.UsesTargetInput(to_version_id=version_id))
+ return uses_inputs
+
+
+@trace
+def _upload_binaries(asset: medm_model.Asset, bin_specs: list[BinaryComponentSpec]):
+ """Upload binaries of an asset post publish.
+
+ Args:
+ bin_specs: List of BinaryComponentSpecs which indicate where
+ to find files to be uploaded. These should correspond
+ to existing binary components on asset.
+ """
+ logger = get_logger(__name__)
+
+ # Now upload the blobs to remote storage
+ # Before doing so, copy all binary data into a temporary location
+ # to insulate the transfer from external changes.
+
+ logger.info("Copying binary data to temporary location for transfer...")
+ with tempfile.TemporaryDirectory() as tmpdir:
+ for bin_spec in bin_specs:
+ for upload_blob in bin_spec._upload_blobs:
+ temp_path = cleanpath(tmpdir, upload_blob.blob_path)
+ try:
+ shutil.copyfile(upload_blob.full_path, temp_path)
+ except Exception as exc: # pylint: disable=broad-except
+ msg = f'Failed to copy "{upload_blob.full_path}" to temp location "{temp_path}". {exc}'
+ raise PublishAssetError(details=msg) from exc
+ upload_blob.full_path = temp_path
+
+ # Do file transfer to both remote and primary storage
+ _transfer_files(asset, bin_specs)
+
+
+@trace
+def _transfer_files(asset: medm_model.Asset, bin_specs: list[BinaryComponentSpec]):
+ """Transfer files associated with binary components to both remote
+ storage and primary storage. The medm asset provided is expected to
+ have been published to MEDM already. The binary components provided are expected
+ to have been relocated to a temporary location for transfer.
+
+ Raises:
+ PublishAssetError
+ """
+ logger = get_logger(__name__)
+ logger.info("Transfering binary data to cloud...")
+
+ # The API will have mutated the component objects on the medm asset now,
+ # and their uri attributes will have been replaced with a URN which can be used
+ # to upload the blobs to.
+
+ # NOTE: this whole process sucks! It's possible that the asset will be published,
+ # but the binary data transfer will fail, in which case you have a half published
+ # asset. This is really a flaw in the way the API works - you should upload the
+ # data first and then publish if the data transfer succeeds!
+
+ # The transfer API uses the raw graphql connection
+ gql_client = get_client()
+
+ for bin_spec in bin_specs:
+ # Find matching component represented by the component spec object
+ comp = _find_component(asset, name=bin_spec.name)
+ if not comp:
+ msg = f'Component "{bin_spec.name}" missing on medm asset object.'
+ raise PublishAssetError(details=msg)
+
+ for i, upload_blob in enumerate(bin_spec._upload_blobs):
+ comp_blobs = comp.data.get("data", [])
+ if i >= len(comp_blobs):
+ msg = f'Component "{comp.name}" missing blob {i}.'
+ raise PublishAssetError(details=msg)
+ blob_urn = comp_blobs[i]["uri"]
+
+ logger.info(f"Uploading {upload_blob.full_path}...")
+
+ transferapi.upload_blob(
+ client=gql_client,
+ file_path=upload_blob.full_path,
+ urn_id=blob_urn,
+ upload_uri=upload_blob.uri,
+ )
+
+ logger.info(f"File {upload_blob.full_path} uploaded successfully!")
+
+ # Copy files to blob storage as well (create folder if necessary)
+ logger.info("Transfering binary data to local storage...")
+ # Storage directory for latest version of asset
+ storage_dir = get_storage_revision_dir(asset.id, asset.revision_number)
+ if not os.path.exists(storage_dir):
+ os.makedirs(storage_dir)
+ has_failures = False
+ for bin_spec in bin_specs:
+ for upload_blob in bin_spec._upload_blobs:
+ source_path = cleanpath(upload_blob.full_path)
+ storage_path = cleanpath(storage_dir, upload_blob.blob_path)
+ logger.info(f"Copying {source_path} -> {storage_path}")
+ try:
+ shutil.copyfile(source_path, storage_path)
+ except Exception as exc: # pylint: disable=broad-except
+ # Since this copy is not publish critical, we will not stop execution
+ # if it fails
+ logger.warning(f"Copy to primary storage failed: {exc}")
+ has_failures = True
+
+ # Add sidecar file to cache important asset data if necessary
+ try:
+ _cache_asset_info(asset.id)
+ except FlowError as exc:
+ logger.warning(f"Caching asset info in storage dir failed: {exc}")
+ has_failures = True
+
+ if has_failures:
+ logger.info("All critical transfers complete!")
+ logger.warning("Some non-critical transfers/processes failed.")
+ else:
+ logger.info("All transfers complete!")
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/sandbox.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/sandbox.py
new file mode 100644
index 00000000..673fce99
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/sandbox.py
@@ -0,0 +1,794 @@
+# -
+# *****************************************************************************
+# Copyright 2026 Autodesk, Inc. All rights reserved.
+#
+# These coded instructions, statements, and computer programs contain
+# unpublished proprietary information written by Autodesk, Inc. and are
+# protected by Federal copyright law. They may not be disclosed to third
+# parties or copied or duplicated in any form, in whole or in part, without
+# the prior written consent of Autodesk, Inc.
+# *****************************************************************************
+# +
+
+"""
+This module contains classes and utilities that facilitate the "sandbox".
+This is a space on disk reserved for work in progress. Assets can be created,
+or checked out in the sandbox, modified, and then published back to MEDM.
+
+The collection of files for an asset that is in sandbox is called a "draft".
+These are contained within a "draft folder" whose location is managed by this module
+(though the root location is configured in `storage.set_sandbox_root()`).
+Within, the draft folder, a draft info sidecar file is stored to record important
+information about a new draft or checkout draft.
+ * new draft = working on brand new asset
+ * checkout draft = working on published asset
+
+Draft files are by definition unpublished. Drafts are identified by either
+an asset's storage key or temporary uuid for brand new assets.
+
+Within this module, we have utilities for querying draft information from the file system.
+As well, there are key utilities for manipulating drafts including
+ * creating new assets as drafts
+ * checking out assets as drafts
+ * discarding drafts
+ * publishing new assets or revisions from drafts
+"""
+
+from __future__ import annotations # needed for python 3.9 support
+
+import json
+import os
+import shutil
+import uuid
+from dataclasses import dataclass, asdict
+
+from tank_vendor.flow_data_sdk.base import model as medm_model
+from tank_vendor.flow_data_sdk.base.exceptions import GQLAPIError
+
+from .fetch import fetch
+from .globals import BASE_TYPE_ID, get_client
+from .exceptions import (
+ EntityNotFoundError,
+ FlowError,
+ DraftExistsError,
+ InvalidDraftError,
+ PublishAssetError,
+ PublishConflictError,
+)
+from .publish import (
+ CommentComponentSpec,
+ ComponentSpec,
+ publish_new_asset,
+ publish_new_revision,
+ TypeComponentSpec,
+)
+from .storage import (
+ get_sandbox_root,
+ get_storage_component_path,
+ get_storage_key,
+)
+from .utils import (
+ cleanpath,
+ get_logger,
+ is_sub_directory,
+ relpath,
+ trace,
+)
+
+
+@dataclass
+class DraftInfo:
+ """Base class for draft info dataclasses with providing common functionality."""
+
+ #: Id that uniquely identifies a draft in local sandbox.
+ draft_id: str
+
+ @trace
+ def write_file(self, file_path: str):
+ """Write contents of dataclass to draft info file.
+
+ Args:
+ file_path: Full path to destination file.
+
+ Raises:
+ FlowError
+ """
+ try:
+ with open(file_path, "w") as f:
+ f.write(json.dumps(asdict(self), indent=4))
+ except Exception as exc: # pylint: disable=broad-except
+ msg = f"Draft info file could not be written: {file_path}"
+ raise FlowError(msg) from exc
+
+ @classmethod
+ @trace
+ def read_file(cls, file_path: str) -> DraftInfo:
+ """Read a draft info file and convert into draft dataclass.
+
+ Args:
+ file_path: Full path to draft info file.
+
+ Returns:
+ Sub-class of BaseDraft.
+
+ Raises:
+ FlowError
+ """
+ try:
+ with open(file_path, "r") as f:
+ json_str = f.read()
+ except (IOError, OSError) as exc:
+ msg = f"Draft info file could not be opened: {file_path}"
+ raise FlowError(msg) from exc
+
+ try:
+ json_dict = json.loads(json_str)
+ except json.JSONDecodeError as exc:
+ msg = f"Draft info file format is invalid: {file_path}"
+ raise FlowError(msg) from exc
+
+ draft_type = json_dict.get("draft_type")
+ if draft_type == "new":
+ try:
+ return NewDraftInfo(**json_dict)
+ except TypeError as exc:
+ msg = f"Draft info file is missing required attributes for NewDraftInfo: {file_path}"
+ raise FlowError(msg) from exc
+ elif draft_type == "checkout":
+ try:
+ return CheckoutDraftInfo(**json_dict)
+ except TypeError as exc:
+ msg = f"Draft info file is missing required attributes for CheckoutDraftInfo: {file_path}"
+ raise FlowError(msg) from exc
+ else:
+ msg = f"Draft info file has invalid draft type designated: {file_path}"
+ raise FlowError(msg)
+
+ def pprint(self):
+ """Print dataclass contents in readable way."""
+
+ logger = get_logger(__name__)
+ printstr = "\n-----------------------------------------\n"
+ printstr += f"{self.__class__.__name__}\n"
+ printstr += "-----------------------------------------\n"
+ for prop, value in self.__dict__.items():
+ printstr += f" {prop}: {value}\n"
+ logger.info(printstr)
+
+
+@dataclass
+class CheckoutDraftInfo(DraftInfo):
+ """Container for info associated with a checkout draft.
+ NOTE: A checkout draft is a draft that results from checking out
+ a published revision.
+ """
+
+ #: Name of asset.
+ name: str
+ #: Id of asset.
+ asset_id: str
+ #: Version number of asset checked out.
+ version: int
+ #: Revision number of asset checked out.
+ revision: int
+ #: Latest published version of asset at the time of checkout.
+ latest_version: int
+ #: Latest published revision of asset at the time of checkout.
+ latest_revision: int
+ #: Source path of draft.
+ source_path: str
+ #: Record draft type as a constant property which makes recording to file easier.
+ draft_type: str = "checkout"
+
+
+@dataclass
+class NewDraftInfo(DraftInfo):
+ """Container for info associated with a new draft.
+ NOTE: A new draft is a draft for an asset that has been created and not yet published.
+ """
+
+ #: Name of asset.
+ name: str
+ #: Description of new asset.
+ description: str
+ #: Id of parent of new asset.
+ parent_id: str
+ #: Type ids to be associated with new asset.
+ type_ids: list[str]
+ #: Source path of draft.
+ source_path: str
+ #: Record draft type as a constant property which makes recording to file easier.
+ draft_type: str = "new"
+
+
+# ------------------------------------------
+# DRAFT DATA ACCESSORS
+# ------------------------------------------
+
+
+@trace
+def get_draft_id(asset_id: str | None = None) -> str:
+ """Return a local draft id for given asset.
+
+ Args:
+ asset_id: Medm asset id. If None, this is treated as a new asset -
+ return a temporary uuid.
+
+ Returns:
+ Draft id for asset.
+ """
+ # NOTE: For this sandbox implementation, we will use the storage key as the
+ # draft id for any published assets, and generate a uuid for new asset.
+ if asset_id is None:
+ return str(uuid.uuid4())
+ else:
+ return get_storage_key(asset_id)
+
+
+def get_draft_folder(draft_id: str) -> str:
+ """Return the location of the draft folder for the given draft id.
+
+ Args:
+ draft_id: Id that uniquely identifies a draft within local sandbox.
+
+ Returns:
+ Full path to draft folder on local disk.
+ """
+ sandbox_root = get_sandbox_root()
+ return f"{sandbox_root}/{draft_id}/draft"
+
+
+def get_draft_info_file(draft_id: str) -> str:
+ """Return expected path to draft info sidecar file, whether
+ or not it exists.
+
+ Args:
+ draft_id: Id that uniquely identifies a draft within local sandbox.
+
+ Returns:
+ Full path to draft info sidecar file on local disk.
+ """
+ return cleanpath(get_draft_folder(draft_id), ".draft")
+
+
+@trace
+def read_draft_info(draft_id: str) -> DraftInfo:
+ """Read the draft info sidecar file and convert into draft dataclass.
+
+ Args:
+ draft_id: Id that uniquely identifies a draft within local sandbox.
+
+ Returns:
+ Subclass of DraftInfo dataclass.
+
+ Raises:
+ InvalidDraftError
+ """
+ draft_info_file = get_draft_info_file(draft_id)
+ if not os.path.exists(draft_info_file):
+ msg = f"Draft info file not found: {draft_info_file}"
+ raise InvalidDraftError(draft_id=draft_id, details=msg)
+ try:
+ return DraftInfo.read_file(draft_info_file)
+ except FlowError as exc:
+ raise InvalidDraftError(draft_id=draft_id, details=str(exc))
+
+
+@trace
+def is_new_asset(draft_id: str) -> bool:
+ """Return True if the draft id provided points to a new asset that has
+ never been published.
+
+ Args:
+ draft_id: Id that uniquely identifies a draft within local sandbox.
+ """
+ draft_info = read_draft_info(draft_id)
+ return isinstance(draft_info, NewDraftInfo)
+
+
+@trace
+def is_local_draft(draft_id: str) -> bool:
+ """Return True if the draft id provided points to draft in local sandbox.
+
+ Args:
+ draft_id: Id that uniquely identifies a draft within local sandbox.
+ """
+ draft_folder = get_draft_folder(draft_id)
+ return os.path.exists(draft_folder)
+
+
+@trace
+def get_draft_context(draft_path: str) -> str | None:
+ """Given a file path, if it is a draft path in the local sandbox,
+ return the associated draft id. Otherwise return None.
+
+ Args:
+ draft_path: A file path which is potentially belonging to a draft.
+
+ Returns:
+ Draft id or None if path does not belong to a draft.
+
+ Raises:
+ ValueError
+ """
+ sandbox_root = get_sandbox_root()
+ if not is_sub_directory(sandbox_root, draft_path):
+ return None
+
+ draft_path = relpath(sandbox_root, draft_path)
+ try:
+ draft_id, version, filename = draft_path.split("/")
+ except ValueError:
+ return None
+
+ if version != "draft":
+ return None
+
+ return draft_id
+
+
+def get_asset_drafts(asset_id: str) -> list[CheckoutDraftInfo]:
+ """Return all local drafts that exist for the given asset.
+
+ .. note:: Currently, we only support a single draft per asset,
+ however this could change in the future with the introduction
+ of multiple checkouts/sandboxes. Returning a list keeps this
+ utility flexible.
+
+ The returned values will be CheckoutDraftInfo objects which will contain
+ detailed information about each draft.
+
+ .. note:: This function can only be used to retrieve information about
+ checkouts. Please use `get_drafts(draft_type="new")` function
+ to retrieve list of brand new assets that exist in sandbox.
+
+ Args:
+ asset_id: Id of MEDM asset. This can also be a revision or version id
+ from the same asset.
+
+ Returns:
+ List of CheckoutDraftInfo objects.
+ """
+ # Retrieve asset's draft id
+ draft_id = get_draft_id(asset_id)
+ # Read draft info if it exists
+ try:
+ draft_info = read_draft_info(draft_id)
+ except InvalidDraftError:
+ return []
+ return [draft_info]
+
+
+def get_drafts(draft_type: str | None = None) -> list[DraftInfo]:
+ """Return a list of all drafts found in local sandbox.
+
+ Args:
+ draft_type: If specified, filter by given draft type.
+ Accepted values include "new" or "checkout".
+
+ Returns:
+ List of DraftInfo objects that may be either
+ CheckoutDraftInfo or NewDraftInfo types.
+
+ Raise:
+ FlowError
+ """
+ sandbox_root = get_sandbox_root()
+ if not os.path.exists(sandbox_root):
+ raise FlowError(f"Configured sandbox root does not exist: {sandbox_root}")
+
+ drafts = []
+ for item in os.listdir(sandbox_root):
+ if not os.path.isdir(os.path.join(sandbox_root, item)):
+ continue
+ draft_id = item
+ try:
+ draft_info = read_draft_info(draft_id)
+ except InvalidDraftError:
+ continue
+ if draft_type is not None and draft_type != draft_info.draft_type:
+ continue # skip if filter doesn't match
+ drafts.append(draft_info)
+ return drafts
+
+
+# ------------------------------------------
+# SANDBOX UTILITIES
+# ------------------------------------------
+
+
+@trace
+def create_asset_in_sandbox(
+ name: str,
+ description: str,
+ parent_id: str,
+ type_ids: list[str] | None = None,
+ source_path: str = "",
+) -> NewDraftInfo:
+ """Create a workspace for a new asset in sandbox.
+
+ Args:
+ name: String name for asset.
+ description: Description of the asset.
+ parent_id: Id of parent entity, which may be a project, folder or asset.
+ type_ids: List of type ids to be attached to asset.
+ source_path: Optionally provide a source file to be associated with new asset.
+
+ Returns:
+ NewDraftInfo object.
+
+ Raises:
+ EntityNotFoundError
+ """
+
+ # NOTE: This function will "create" an asset in the sense of creating a
+ # draft location for the asset and storing a sidecar file with
+ # relevant metadata, but not create anything on MEDM.
+ # The actual creation of the MEDM asset will be done at publish time.
+
+ # Create unique draft id that is file safe
+ draft_id = get_draft_id()
+
+ # Create a draft folder
+ draft_folder = get_draft_folder(draft_id)
+ if not os.path.exists(draft_folder):
+ os.makedirs(draft_folder)
+
+ # Copy source file to draft folder if applicable
+ # TODO: This is over-simplified, and makes assumptions about a single source file
+ # as well as simple types.
+ # In future, we should accept ComponentSpecs instead which must
+ # be serialized into the draft info file to be re-created at publish time.
+ # We should also allow specification of multiple "input" files.
+ if source_path:
+ draft_path = cleanpath(draft_folder, os.path.basename(source_path))
+ shutil.copyfile(source_path, draft_path)
+ else:
+ draft_path = ""
+
+ # Add a sidecar file to store metadata of new asset
+ # This will ensure that when it's publish time, we have all the info we need
+ # to create the appropriate asset entity.
+ draft_info_file = get_draft_info_file(draft_id)
+ draft_info = NewDraftInfo(
+ draft_id=draft_id,
+ name=name,
+ description=description,
+ parent_id=parent_id,
+ type_ids=type_ids or [],
+ source_path=draft_path,
+ )
+ draft_info.write_file(draft_info_file)
+
+ return draft_info
+
+
+@trace
+def checkout_revision(
+ revision: medm_model.AssetRevision,
+ component_name: str = "",
+ component_purpose: str = "",
+ force: bool = False,
+) -> CheckoutDraftInfo:
+ """Check out the given asset revision into sandbox.
+ If a draft already exists, an exception will be thrown unless force=True.
+
+ Args:
+ revision: AssetRevision object.
+ component_name: If provided, search for component with this name to be checked out.
+ This should be unique within the revision.
+ component_purpose: If provided, search for a component with this purpose to be
+ checked out. There may be multiple components with the same purpose,
+ so the first match will be returned.
+ force: If True, force a re-checkout even if an existing draft is found.
+
+ Returns:
+ A CheckoutDraftInfo object containing all pertinent information
+ of new or existing checkout.
+
+ Raises:
+ DraftExistsError
+ FlowError
+ """
+ logger = get_logger(__name__)
+
+ draft_id = get_draft_id(revision.asset_id)
+ draft_folder = get_draft_folder(draft_id)
+
+ if os.path.exists(draft_folder):
+ if not force:
+ raise DraftExistsError(draft_id=draft_id, draft_folder=draft_folder)
+ else:
+ logger.info("Removing existing draft folder...")
+ # Delete existing draft folder
+ try:
+ shutil.rmtree(draft_folder)
+ except Exception as exc: # pylint: disable=broad-except
+ msg = f"Could not remove existing draft directory: {draft_folder}"
+ raise FlowError(msg) from exc
+
+ # Fetch necessary binaries for revision into primary storage
+ # TODO: Assumption that we are retrieving a single blob every time.
+ # Need to expand this to grab all blobs, or an explicit list.
+ fetch(
+ revision,
+ component_name=component_name,
+ component_purpose=component_purpose,
+ )
+
+ # Create the draft folder so we can copy new files to it
+ logger.info("Creating draft folder...")
+ os.makedirs(draft_folder)
+
+ # Copy from storage into sandbox
+ # NOTE: assuming flat files in asset dir
+ # NOTE: assuming only a single source file
+ cached_path = get_storage_component_path(
+ revision,
+ component_name=component_name,
+ component_purpose=component_purpose,
+ )
+ if cached_path is None:
+ # This indicates that the specified component does not exist
+ # on the revision, so raise an error
+ msg = "Revision does not contain a component fitting the filter: "
+ msg += f"component_name = {component_name}, component_purpose = {component_purpose}. "
+ msg += "No source file(s) to copy. Checkout cannot be completed."
+ raise FlowError(msg)
+
+ draft_path = cleanpath(draft_folder, os.path.basename(cached_path))
+ msg = "Copying files to draft folder...\n"
+ msg += f"{cached_path} -> {draft_path}"
+ logger.info(msg)
+ try:
+ shutil.copy(cached_path, draft_path)
+ except Exception as exc: # pylint: disable=broad-except
+ raise FlowError("Copy failed!") from exc
+
+ # Query the asset to get it's latest info
+ asset = _query_asset(revision.asset_id)
+
+ # Write draft info
+ draft_info_file = get_draft_info_file(draft_id)
+ draft_info = CheckoutDraftInfo(
+ draft_id=draft_id,
+ name=asset.name,
+ asset_id=asset.id,
+ revision=revision.revision_number,
+ latest_revision=asset.revision_number,
+ version=revision.version_number,
+ latest_version=asset.version_number,
+ source_path=draft_path,
+ )
+ draft_info.write_file(draft_info_file)
+ logger.info("Draft metadata updated.")
+
+ return draft_info
+
+
+@trace
+def discard_draft(draft_id: str):
+ """Discard an existing draft of an asset.
+
+ Args:
+ draft_id: Unique id that identifies a draft location in local sandbox.
+
+ Raises:
+ FlowError
+ InvalidDraftError
+ """
+ logger = get_logger(__name__)
+
+ if not is_local_draft(draft_id):
+ msg = f'The draft "{draft_id}" is not in local sandbox.'
+ raise InvalidDraftError(draft_id=draft_id, details=msg)
+
+ draft_folder = get_draft_folder(draft_id)
+ parent_folder = os.path.dirname(draft_folder)
+ logger.info(f"Deleting draft folder: {parent_folder}...")
+ try:
+ # Remove the parent directory above "draft" folder
+ shutil.rmtree(parent_folder)
+ except Exception as exc: # pylint: disable=broad-except
+ msg = f"Draft folder could not deleted: {parent_folder}"
+ raise FlowError(msg) from exc
+
+
+@trace
+def publish_draft(
+ draft_id: str,
+ comment: str = "",
+ components: list[ComponentSpec] | None = None,
+ used_versions: list[str] | None = None,
+ force: bool = False,
+) -> medm_model.Asset:
+ """Publish a new asset or new revision of existing asset from a draft.
+
+ Args:
+ draft_id: Unique local draft id.
+ comment: Description of revision.
+ components: List of component specifications that will be converted into components
+ to be attached to asset revision.
+ (These are used to store binaries and metadata on revisions.)
+ used_versions: List of version ids of other assets used by this asset.
+ (Stored as "uses" relationships with other asset versions.)
+ force: If True, publish without checking for publish conflicts.
+
+ Returns:
+ Updated MEDM asset object.
+
+ Raises:
+ EntityNotFoundError
+ PublishAssetError
+ PublishConflictError
+ """
+ logger = get_logger(__name__)
+
+ # Retrieve draft metadata
+ draft_info = read_draft_info(draft_id)
+
+ # Add comment component to components if not already present
+ components = [] if components is None else list(components)
+ components.append(CommentComponentSpec(comment))
+
+ if draft_info.draft_type == "new":
+ # For new assets, we need to convert the type ids into components
+ # (Names must be kept unique!)
+ for i, type_id in enumerate(draft_info.type_ids):
+ components.append(TypeComponentSpec(type_id=type_id, name=f"Type {i}"))
+
+ # Publish new asset with appropriate metadata
+ asset = publish_new_asset(
+ name=draft_info.name,
+ parent_id=draft_info.parent_id,
+ description=draft_info.description,
+ components=components,
+ used_versions=used_versions,
+ )
+
+ # Rename the draft folder on first publish to replace the temporary draft
+ # id with the asset's permanent storage key
+ new_draft_id = get_storage_key(asset.id)
+ new_source_path = _rename_draft_folder(draft_id, new_draft_id)
+ draft_id = new_draft_id
+
+ else:
+ # Query the asset
+ asset_id = draft_info.asset_id
+ try:
+ asset = _query_asset(asset_id)
+ except FlowError as exc:
+ msg = f"Invalid asset id associated with draft: {asset_id}"
+ raise EntityNotFoundError(entity_id=asset_id, details=msg) from exc
+
+ # For existing assets, we need to preserve the types already assigned
+ # to the asset - add these to the component list
+ for i, type_id in enumerate(_get_type_ids(asset)):
+ components.append(TypeComponentSpec(type_id=type_id, name=f"Type {i}"))
+
+ # Check for conflict before publishing
+ # A conflict arises if newer revisions of the asset have been published
+ # since this asset was checked out.
+ if not force and _check_publish_conflict(draft_info, asset):
+ checkout_revision = draft_info.revision
+ checkout_version = draft_info.version
+ raise PublishConflictError(
+ asset=asset,
+ checkout_version=checkout_version,
+ checkout_revision=checkout_revision,
+ )
+
+ # Update an existing asset by publishing a new revision
+ asset = publish_new_revision(
+ asset_id=asset_id,
+ components=components,
+ used_versions=used_versions,
+ )
+
+ # For publishes of existing assets, the source path should not change
+ new_source_path = draft_info.source_path
+
+ msg = f'Successfully published version {asset.version_number} (r{asset.revision_number}) of asset "{asset.name}" to Flow AM!'
+ logger.info(msg)
+
+ # Update the draft info file to adjust for latest publish.
+ # This is a sidecar file that will contain any relevant "checkout" information
+ # about current draft. After a publish, we will treat the draft has being
+ # checked out from the most recent published version.
+ # NOTE: we can continue to use the same draft id.
+ draft_info_file = get_draft_info_file(draft_id)
+ draft_info = CheckoutDraftInfo(
+ draft_id=draft_id,
+ name=asset.name,
+ asset_id=asset.id,
+ revision=asset.revision_number,
+ version=asset.version_number,
+ latest_revision=asset.revision_number,
+ latest_version=asset.version_number,
+ source_path=new_source_path,
+ )
+ draft_info.write_file(draft_info_file)
+ logger.info("Draft metadata updated.")
+
+ return asset
+
+
+@trace
+def _rename_draft_folder(old_draft_id: str, new_draft_id: str) -> str:
+ """Attempt to rename the draft folder to the new draft id.
+ If it's not possible, create the new draft folder and copy contents over.
+
+ Returns:
+ Path of new path to source file.
+
+ Raises:
+ PublishAssetError
+ """
+ logger = get_logger(__name__)
+ logger.info(f'Renaming draft folder from "{old_draft_id}" -> "{new_draft_id}".')
+ draft_info = read_draft_info(old_draft_id)
+ old_draft_folder = os.path.dirname(get_draft_folder(old_draft_id))
+ new_draft_folder = os.path.dirname(get_draft_folder(new_draft_id))
+ try:
+ os.rename(old_draft_folder, new_draft_folder)
+ except Exception: # pylint: disable=broad-except
+ msg = f"Could not rename draft folder: {old_draft_folder}. "
+ msg += "Creating a copy instead..."
+ logger.warning(msg)
+ try:
+ shutil.copytree(old_draft_folder, new_draft_folder)
+ except Exception as exc: # pylint: disable=broad-except
+ msg = f"Could not create draft folder: {new_draft_folder}"
+ exc_data = {"draft_folder": old_draft_folder}
+ raise PublishAssetError(data=exc_data, details=msg) from exc
+
+ # Determine new source path
+ old_source_path = draft_info.source_path
+ new_source_path = old_source_path.replace(old_draft_folder, new_draft_folder)
+ return new_source_path
+
+
+def _query_asset(asset_id: str) -> medm_model.Asset:
+ """Query an asset based on id.
+
+ Raises:
+ EntityNotFoundError
+ FlowError
+ """
+ logger = get_logger(__name__)
+ client = get_client()
+ q_input = medm_model.AssetsByIdsInput(ids=[asset_id])
+ q_asset = client.service_asset.assets_by_ids(q_input)
+ try:
+ q_asset.call()
+ except GQLAPIError as exc:
+ msg = f"Error querying asset: {asset_id}. {exc}"
+ raise FlowError(msg) from exc
+ if len(q_asset.assets) == 0:
+ msg = "Error retrieving MEDM asset."
+ raise EntityNotFoundError(entity_id=asset_id, details=msg)
+ asset = q_asset.assets[0]
+ logger.info(f'Queried asset "{asset.name}".')
+ return asset
+
+
+def _get_type_ids(asset: medm_model.Asset):
+ """Return list of type ids assigned to asset."""
+ type_ids = []
+ for comp in asset.components:
+ if BASE_TYPE_ID in comp.parent_type_ids:
+ type_ids.append(comp.type_id)
+ return type_ids
+
+
+def _check_publish_conflict(draft_info, asset) -> bool:
+ """Check if there is a publish conflict based on current state of
+ asset and checkout state of asset.
+ """
+ # A conflict is defined as trying to publish an asset which has had
+ # new versions published against it since the asset was checked out
+ #
+ # NOTE: We are making the assumption that within a single "numbered" version,
+ # component and uses relationships stay the same from revision to revision!
+ # This assumption allows us to only consider conflicts with respect to versions.
+ return draft_info.latest_version < asset.version_number
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/schema.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/schema.py
new file mode 100644
index 00000000..7675f120
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/schema.py
@@ -0,0 +1,258 @@
+# -
+# *****************************************************************************
+# Copyright 2026 Autodesk, Inc. All rights reserved.
+#
+# These coded instructions, statements, and computer programs contain
+# unpublished proprietary information written by Autodesk, Inc. and are
+# protected by Federal copyright law. They may not be disclosed to third
+# parties or copied or duplicated in any form, in whole or in part, without
+# the prior written consent of Autodesk, Inc.
+# *****************************************************************************
+#
+
+"""Utilities for querying and caching schema information."""
+
+from __future__ import annotations # needed for python 3.9 support
+
+import json
+import os
+from functools import cache
+
+from tank_vendor.flow_data_sdk.base import model as flow_model
+from tank_vendor.flow_data_sdk.base.exceptions import GQLAPIError
+
+from .exceptions import FlowError
+from .globals import (
+ BASE_TYPE_ID,
+ BINARY_TYPE_ID,
+ COMMENT_TYPE_ID,
+ DER_SOURCE_TYPE_ID,
+ FOLDER_TYPE_ID,
+ get_client,
+ get_session_collection,
+ IMAGE_TYPE_ID,
+)
+from .utils import get_logger, trace
+
+
+# Schema inheritance tree cache
+# -----------------------------
+# Stored globally as an optimization to avoid querying/re-querying
+# parent types of known schema types.
+# Format: key = schema type id, value = list of parent types
+_schema_tree: dict[str, list[str]] = {}
+
+# Hardcode some well known relationships and root types
+_schema_tree[IMAGE_TYPE_ID] = [BINARY_TYPE_ID]
+_schema_tree[FOLDER_TYPE_ID] = [BASE_TYPE_ID]
+_schema_tree[BASE_TYPE_ID] = []
+_schema_tree[BINARY_TYPE_ID] = []
+_schema_tree[COMMENT_TYPE_ID] = []
+_schema_tree[DER_SOURCE_TYPE_ID] = []
+
+
+# Schema type ids cache
+# ---------------------
+# This maps custom type names to full type ids
+# Format: key = type name (e.g. "type.maya.workfile"), value = full type id
+_schema_ids: dict[str, str] = {}
+
+
+# Schema display name cache
+# -------------------------
+# Caches display data of schemas to avoid re-querying.
+# Format: key = type id, value = display name
+_schema_display_names: dict[str, str] = {}
+
+
+def _read_schema_config(config_path: str):
+ """Read the given json config file, and return raw json dictionary."""
+ logger = get_logger(__name__)
+
+ logger.info(f"Reading schema type ids config file: {config_path}")
+ if not os.path.exists(config_path):
+ raise RuntimeError(f"Schema config file not found: {config_path}")
+
+ with open(config_path) as f:
+ try:
+ raw_config = json.loads(f.read())
+ except json.decoder.JSONDecodeError as exc:
+ raise ValueError("Schema config file is invalid.") from exc
+ return raw_config
+
+
+def _compose_schema_id(type_name: str, version: str):
+ """Generate a full type id with info provided."""
+ session_collection = get_session_collection()
+ org_id = session_collection.organization_id
+ group_id = session_collection.group_id
+ namespace = f"{org_id}.{group_id}"
+ return f"{namespace}:{type_name}-{version}"
+
+
+@trace
+def cache_schema_config(config_path: str):
+ """Add types from provided schema config json file into schema cache
+ to optimize queries against them.
+
+ ..note:: `globals.init_session_collection()` must be called before
+ attempting to add to schema cache.
+
+ Args:
+ config_path: Path to json schema config file.
+
+ Raises:
+ RuntimeError
+ ValueError
+ """
+ raw_config = _read_schema_config(config_path)
+
+ # Cache type id and display name info for configured types
+ for schema in raw_config.get("schemas", {}):
+ type_name = schema.get("name", "")
+ type_version = schema.get("version", "")
+ display_name = schema.get("display_name", "")
+ if type_name and type_version:
+ type_id = _compose_schema_id(type_name, type_version)
+ _schema_ids[type_name] = type_id
+ if display_name:
+ _schema_display_names[type_id] = display_name
+
+ # Add configured types to schema tree cache
+ for schema in raw_config.get("schemas", {}):
+ type_name = schema.get("name", "")
+ parent_types = schema.get("inherits", [])
+ # strip "$ref:" suffix
+ parent_types = [pt[5:] for pt in parent_types]
+ # convert to full ids
+ parent_types = [get_schema_id(pt) for pt in parent_types]
+ # always include base type
+ parent_types.append(BASE_TYPE_ID)
+ type_id = get_schema_id(type_name)
+ # store ancestral relationship
+ if type_id:
+ _schema_tree[type_id] = parent_types
+
+
+def get_schema_id(type_name: str) -> str | None:
+ """Return full type id of type name if cached.
+
+ Args:
+ type_name: Base name of schema type (e.g. "type.template").
+
+ Returns:
+ Full id of type, or None if type is not cached.
+ """
+ return _schema_ids.get(type_name, None)
+
+
+@trace
+def get_schema_display_name(type_id: str) -> str | None:
+ """Return display name of schema of given its type id.
+
+ Args:
+ type_id: Schema type id to be queried.
+
+ Returns:
+ Display name of schema if set, or None if not set.
+
+ Raises:
+ FlowError
+ """
+ logger = get_logger(__name__)
+
+ if type_id in _schema_display_names:
+ return _schema_display_names[type_id]
+
+ # Type is not in schema config, must query display name
+ client = get_client()
+ q_input = flow_model.GetSchemaDisplayDataInput(schema_type_id=type_id)
+ q_schema_display = client.service_schema.schema_display_data(q_input)
+ try:
+ logger.info(f"Querying schema display data for type id: {type_id}.")
+ r_schema_display = q_schema_display.call()
+ except GQLAPIError as exc:
+ msg = f"Error querying schema display data for type id: {type_id}. {exc}"
+ raise FlowError(msg) from exc
+ display_data = r_schema_display.schema_display_data
+ if display_data.display_name == flow_model.NOT_SET:
+ return None
+ # Cache display name before returning
+ _schema_display_names[type_id] = display_data.display_name
+ return display_data.display_name
+
+
+@cache
+@trace
+def is_sub_type(base_id: str, type_id: str) -> bool:
+ """Return True if provided type is a sub class of base type.
+
+ Args:
+ base_id: String base type id.
+ type_id: String type id.
+
+ Returns:
+ True if type_id derives from base_id.
+
+ Raises:
+ FlowError
+ """
+ logger = get_logger(__name__)
+
+ if base_id == type_id:
+ return True
+
+ # Check schema tree cache for input type
+ def match_ancestor(base_id, type_id):
+ if type_id not in _schema_tree:
+ raise ValueError("Unregistered type id.")
+ parent_types = _schema_tree[type_id]
+ for parent_type in parent_types:
+ if parent_type == base_id:
+ return True
+ if match_ancestor(base_id, parent_type):
+ return True
+ return False
+
+ try:
+ return match_ancestor(base_id, type_id)
+ except ValueError:
+ # The type in question is not in our config cache
+ # We are forced to make a query
+ msg = f'Querying schema subclasses for base type "{base_id}" to find type "{type_id}".'
+ logger.info(msg)
+ client = get_client()
+ session_collection = get_session_collection()
+ q_input = flow_model.SchemasBySuperTypeInput(
+ collection_id=session_collection.id,
+ type_id=base_id,
+ include_sub_sub_classes=True,
+ )
+ q_schema = client.service_schema.schemas_by_super_type(q_input)
+ try:
+ # NOTE: Iterator wraps q_schema.call() so no need to invoke this
+ # exlicitly.
+ for subtype in q_schema.schema_types_iterator:
+ if type_id == subtype:
+ return True
+ return False
+ except GQLAPIError as exc:
+ msg = f'Error querying subtypes of base type "{base_id}": {exc}'
+ raise FlowError(msg) from exc
+
+
+def get_schema_config_version(config_path: str) -> str:
+ """Retrieve the schema config version from a config.json file.
+
+ Args:
+ config_path: Path to the schema config json file.
+
+ Returns:
+ The schema config version, or 'unknown' if the key is not present.
+
+ Raises:
+ RuntimeError: If the config file is not found.
+ ValueError: If the config file contains invalid JSON.
+ """
+ config = _read_schema_config(config_path)
+ return config.get("version", "unknown")
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/schema_builder.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/schema_builder.py
new file mode 100644
index 00000000..d468af81
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/schema_builder.py
@@ -0,0 +1,614 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+"""Schema builder utilities for creating and managing Flow pipeline schemas."""
+
+from __future__ import annotations # needed for python 3.9 support
+
+from typing import Any
+
+from tank_vendor.flow_data_sdk.base import model as flow_model
+from tank_vendor.flow_data_sdk.base.exceptions import (
+ FlowConnectionError,
+ GQLAPIError,
+ ValidationError,
+)
+
+from .exceptions import (
+ FlowSchemaBuilderError,
+ FlowSchemaDisplayDataError,
+ FlowSchemaError,
+ FlowSchemaLibraryError,
+)
+from .globals import BASE_TYPE_ID, get_client
+from .objects import FlowProject
+from . import schema
+from .schema import get_schema_id
+from .utils import cleanpath, get_logger
+
+
+# Constant for Flow Toolkit Library schema library ID
+FLOW_TOOLKIT_LIBRARY_ID = "FlowToolkitLibrary"
+
+
+class SchemaBuilder:
+ """Utility class to build and validate schemas from a dictionary.
+
+ This class provides methods to validate schema definitions, build new schemas,
+ update schema display names, and retrieve existing schemas from a schema library.
+ """
+
+ def __init__(
+ self,
+ schema_dict: dict,
+ schema_kind: flow_model.SchemaKind,
+ project_id: str,
+ schema_library_id: str,
+ ):
+ """Initialize the SchemaBuilder.
+
+ Args:
+ schema_dict: The dictionary containing the schema definition.
+ schema_kind: The kind of Schema to create (type, component, or property).
+ project_id: The Flow AM project ID.
+ schema_library_id: The ID of the schema library where the schema
+ will be created.
+ """
+ self.schema_dict = schema_dict
+ self.schema_kind = schema_kind
+ self.project_id = project_id
+ self.schema_library_id = schema_library_id
+ self.schema: flow_model.Schema | None = None
+
+ # Validate schema dictionary on initialization
+ self.__validate_schema_dict()
+
+ def __validate_schema_dict(self) -> None:
+ """Validate the incoming schema dictionary.
+
+ Raises:
+ KeyError: If required keys are missing or invalid.
+ ValueError: If key types or values are invalid.
+ """
+
+ # Define a dictionary mapping keys to their expected types
+ key_type_dict = {
+ "name": str,
+ "version": str,
+ "display_name": str,
+ "kind": str,
+ "description": str,
+ "inherits": list,
+ "properties": list,
+ }
+
+ # Check for mandatory keys
+ for key in ["name", "version"]:
+ if key not in self.schema_dict:
+ raise KeyError(
+ f"The custom type schema must contain a '{key}' key of type string."
+ )
+
+ # Validate keys and types
+ for key, value in self.schema_dict.items():
+ if key not in key_type_dict:
+ raise KeyError(f"Invalid key '{key}' in custom type schema.")
+ if not isinstance(value, key_type_dict[key]):
+ raise ValueError(
+ f"The '{key}' key must be of type {key_type_dict[key].__name__}."
+ )
+ if key == "kind":
+ # Validate against flow_model.SchemaKind enum
+ valid_kinds = [kind.value for kind in flow_model.SchemaKind]
+ if value not in valid_kinds:
+ raise ValueError(
+ f"Invalid kind '{value}' in custom type schema. "
+ f"Must be one of {', '.join(valid_kinds)}"
+ )
+ if key == "properties":
+ for property_dict in value:
+ self.__validate_property_dict(property_dict)
+
+ def __validate_property_dict(self, property_dict: dict) -> None:
+ """Validate a schema 'property' dictionary.
+
+ Args:
+ property_dict (dict): The property dictionary to validate.
+
+ Raises:
+ KeyError: If required keys are missing or invalid.
+ ValueError: If key types or values are invalid.
+ """
+
+ # Define a dictionary mapping keys to their expected types
+ key_type_dict = {
+ "name": str,
+ "data_type": str,
+ "properties": list,
+ "default_value": Any,
+ "context": str,
+ "length": int,
+ }
+
+ # Check for mandatory keys
+ for key in ["name", "data_type"]:
+ if key not in property_dict:
+ raise KeyError(f"The property schema must contain a '{key}' key.")
+
+ # Validate keys and types
+ for key, value in property_dict.items():
+ if key not in key_type_dict:
+ raise KeyError(f"Invalid key '{key}' in property schema.")
+
+ if key_type_dict[key] is not Any:
+ if not isinstance(value, key_type_dict[key]): # type: ignore
+ raise ValueError(
+ f"The '{key}' key must be of type "
+ f"{key_type_dict[key].__name__}." # type: ignore
+ )
+
+ if key == "data_type":
+ # Validate that referenced schema exists in config
+ if "$ref:" in value:
+ referenced_schema = value.split(":")[1]
+ referenced_schema_id = get_schema_id(referenced_schema)
+ if not referenced_schema_id:
+ raise ValueError(
+ f"Referenced schema '{referenced_schema}' not found "
+ "in config.json."
+ )
+
+ if key == "properties":
+ for sub_property_dict in value:
+ self.__validate_property_dict(sub_property_dict)
+
+ def _create_property_type_input(
+ self, data_type: str
+ ) -> flow_model.PropertyTypeInput:
+ """Create a PropertyTypeInput from a data type string.
+
+ Args:
+ data_type: Either a primitive type name (e.g., "String") or a
+ custom schema type ID
+
+ Returns:
+ PropertyTypeInput with either primitive_type or custom_type set
+ """
+ primitive_types = [member.value for member in flow_model.PrimitiveType]
+
+ if data_type in primitive_types:
+ primitive_enum = flow_model.PrimitiveType(data_type)
+ return flow_model.PropertyTypeInput(primitive_type=primitive_enum.value)
+
+ return flow_model.PropertyTypeInput(custom_type=data_type)
+
+ def _create_properties_definition_input(
+ self, property_dict: dict
+ ) -> flow_model.PropertiesDefinitionInput:
+ """Recursively create a PropertiesDefinitionInput from a property dictionary."""
+ # Resolve references in data type
+ data_type = property_dict["data_type"]
+
+ if "$ref:" in data_type:
+ # Resolve referenced schema id
+ referenced_schema_name = property_dict["data_type"].split(":")[1]
+ referenced_schema_id = get_schema_id(referenced_schema_name)
+ if not referenced_schema_id:
+ raise ValueError(
+ f"Referenced schema '{referenced_schema_name}' not found "
+ "in config.json."
+ )
+ data_type = referenced_schema_id
+
+ if "$id:" in data_type:
+ data_type = ":".join(data_type.split(":")[1:])
+
+ # Create PropertyTypeInput
+ property_type_input = self._create_property_type_input(data_type)
+
+ # Recursively handle nested properties
+ nested_properties = []
+ if property_dict.get("properties"):
+ for nested_property in property_dict["properties"]:
+ nested_properties.append(
+ self._create_properties_definition_input(nested_property)
+ )
+
+ # Create PropertiesDefinitionInput
+ properties_definition_input = flow_model.PropertiesDefinitionInput(
+ name=property_dict["name"],
+ type=property_type_input,
+ properties=nested_properties if nested_properties else None,
+ )
+
+ # Add optional fields if present
+ if "default_value" in property_dict:
+ properties_definition_input.value = property_dict["default_value"]
+ if "context" in property_dict:
+ properties_definition_input.context = property_dict["context"]
+ if "length" in property_dict:
+ properties_definition_input.length = property_dict["length"]
+
+ return properties_definition_input
+
+ def build(self) -> flow_model.Schema:
+ """Build and commit the custom private Schema.
+
+ Returns:
+ Schema: The created or existing Schema instance.
+
+ Raises:
+ FlowSchemaError: If schema creation or commit fails due to a client error.
+ ValueError: If referenced schemas in 'inherits' or 'properties' are
+ not found.
+ """
+
+ logger = get_logger(__name__)
+ client = get_client()
+
+ # Build inherits list for CreateSchemaInput
+ inherits = []
+ if self.schema_dict.get("inherits"):
+ for inherited_schema in self.schema_dict["inherits"]:
+ # If current schema inherits from another schema
+ if "$ref:" in inherited_schema:
+ # Resolve referenced schema id
+ inherited_schema_name = inherited_schema.split(":")[1]
+ inherited_schema_id = get_schema_id(inherited_schema_name)
+ if not inherited_schema_id:
+ raise ValueError(
+ f"Inherited schema '{inherited_schema_name}' not found "
+ "in config.json."
+ )
+ inherits.append(inherited_schema_id)
+ else:
+ inherits.append(inherited_schema)
+
+ # Build property list for CreateSchemaInput
+ properties = []
+ for property_dict in self.schema_dict.get("properties", []):
+ properties.append(
+ self._create_properties_definition_input(property_dict)
+ )
+
+ try:
+ # Create CreateSchemaInput for the mutation
+ create_schema_input = flow_model.CreateSchemaInput(
+ kind=self.schema_kind.value,
+ name=self.schema_dict["name"],
+ project_id=self.project_id,
+ schema_library_id=self.schema_library_id,
+ version=self.schema_dict["version"],
+ description=self.schema_dict.get("description", ""),
+ inherits=inherits if inherits else None,
+ properties=properties if properties else None,
+ )
+ # Create and call the schema mutation
+ create_schema_query = client.service_schema.create_schema(
+ variables=create_schema_input
+ )
+ query_response = create_schema_query.call()
+ self.schema = query_response.schema
+ logger.info("Created new schema: %s", self.schema.name)
+ except (GQLAPIError, FlowConnectionError, ValidationError) as e:
+ raise FlowSchemaError(
+ details=(
+ f"Failed to create schema '{self.schema_dict['name']}' "
+ f"version '{self.schema_dict['version']}': {e}"
+ )
+ ) from e
+
+ return self.schema
+
+ def get_display_data(self) -> flow_model.SchemaDisplayData | None:
+ """Retrieve the display data object for the current schema.
+
+ This method attempts to fetch the SchemaDisplayData associated with the
+ schema instance. If the schema has not been built, a FlowSchemaBuilderError
+ is raised. If no display data exists for the schema, None is returned.
+
+ Returns:
+ SchemaDisplayData | None: The display data instance if found,
+ otherwise None.
+
+ Raises:
+ FlowSchemaBuilderError: If the schema instance has not been built.
+ """
+
+ if not self.schema:
+ raise FlowSchemaBuilderError(details="Schema instance has not been built yet.")
+
+ client = get_client()
+ schema_display_data = None
+ try:
+ # Create GetSchemaDisplayDataInput for the query
+ input_data = flow_model.GetSchemaDisplayDataInput(
+ schema_type_id=self.schema.type_id
+ )
+ # Create and call the schema query
+ schema_query = client.service_schema.schema_display_data(
+ variables=input_data
+ )
+ query_response = schema_query.call()
+ schema_display_data = query_response.schema_display_data
+ except (GQLAPIError, FlowConnectionError, ValidationError):
+ # TODO: Verify get display data for new created schema's behavior
+ # Two possible scenarios:
+ # 1. Schema type doesn't exist (shouldn't happen if schema was just created)
+ # 2. No display data exists yet (expected for newly created schemas)
+ pass
+
+ return schema_display_data
+
+ def update_display_name(self) -> str | None:
+ """Update the display name of the schema to match the value in the schema
+ definition.
+
+ This method retrieves the current schema's display data and updates its
+ display name if it differs from the desired value. If the display name is
+ successfully updated, the new value is returned. If the display name
+ already matches, it is returned.
+
+ Returns:
+ str | None: The updated display name if successful, or the current
+ display name if it already matches the desired value.
+
+ Raises:
+ FlowSchemaBuilderError: If the schema instance has not been built or the
+ schema definition does not specify a display name.
+ FlowSchemaDisplayDataError: If updating the display name fails due to an
+ error (such as an API failure).
+ """
+
+ logger = get_logger(__name__)
+ client = get_client()
+ display_name = self.schema_dict.get("display_name")
+
+ if not self.schema:
+ raise FlowSchemaBuilderError(details="Schema instance has not been built yet.")
+
+ if not display_name:
+ raise FlowSchemaBuilderError(
+ details="No display name specified in schema definition."
+ )
+
+ if (
+ self.schema.display_data
+ and self.schema.display_data.display_name == display_name
+ ):
+ return display_name
+
+ schema_display_data = self.get_display_data()
+
+ try:
+ if schema_display_data:
+ # Display data exists
+ # Create UpdateSchemaDisplayDataInput for update display name
+ # mutation and call it
+ input_data = flow_model.UpdateSchemaDisplayDataInput(
+ schema_type_id=self.schema.type_id, display_name=display_name
+ )
+ client.service_schema.update_schema_display_data(
+ variables=input_data,
+ ).call()
+ else:
+ # Display data doesn't exist
+ # Create CreateSchemaDisplayDataInput for create display name
+ # mutation and call it
+ input_data = flow_model.CreateSchemaDisplayDataInput(
+ schema_type_id=self.schema.type_id, display_name=display_name
+ )
+ client.service_schema.create_schema_display_data(
+ variables=input_data,
+ ).call()
+
+ logger.debug(
+ "updated display name of schema %s to '%s'",
+ self.schema.name,
+ display_name,
+ )
+ return display_name
+ except (GQLAPIError, FlowConnectionError, ValidationError) as e:
+ raise FlowSchemaDisplayDataError(
+ details=(
+ f"Could not update display data for schema "
+ f"'{self.schema.name}': {e}."
+ )
+ ) from e
+
+
+def _get_schema_library(
+ library_id: str, collection_id: str
+) -> flow_model.SchemaLibrary | None:
+ """Retrieve a schema library by its unique name (ID) if it exists, or None
+ if not found.
+
+ Raises:
+ FlowSchemaLibraryError: If retrieving schema libraries fails due to a client
+ error.
+ """
+
+ logger = get_logger(__name__)
+ client = get_client()
+
+ try:
+ # Create SchemaLibrariesByCollectionIdInput for the query
+ input_data = flow_model.SchemaLibrariesByCollectionIdInput(
+ collection_id=collection_id
+ )
+ # Create and call the schema query
+ schema_query = client.service_schema.schema_libraries_by_collection_id(
+ variables=input_data
+ )
+ schema_libraries = list(schema_query.libraries_iterator or [])
+ except (GQLAPIError, FlowConnectionError, ValidationError) as e:
+ raise FlowSchemaLibraryError(
+ details=f"Failed to retrieve all schema libraries: {e}"
+ ) from e
+
+ for schema_library in schema_libraries:
+ if schema_library.name == library_id:
+ logger.info("Found existing schema library %s", library_id)
+ return schema_library
+
+ logger.info("Schema Library %s not found.", library_id)
+ return None
+
+
+def _create_schema_library(
+ library_id: str,
+ title: str,
+ description: str,
+ project_id: str,
+ collection_id: str,
+) -> flow_model.SchemaLibrary:
+ """Create a schema library if it does not exist, or return the existing one.
+
+ Raises:
+ FlowSchemaLibraryError: If retrieving or creating the schema library fails
+ due to a client error.
+ """
+
+ logger = get_logger(__name__)
+ schema_library = _get_schema_library(library_id, collection_id)
+
+ if schema_library is not None:
+ logger.info("Schema Library %s found. Skipping creation.", library_id)
+ return schema_library
+
+ client = get_client()
+
+ try:
+ # Create CreateSchemaLibraryInput for the mutation
+ input_data = flow_model.CreateSchemaLibraryInput(
+ name=library_id,
+ title=title,
+ description=description,
+ project_id=project_id,
+ )
+ # Create and call the schema mutation
+ schema_query = client.service_schema.create_schema_library(
+ variables=input_data
+ )
+ query_response = schema_query.call()
+ # Extract the created schema library from the response
+ schema_library = query_response.schema_library
+ logger.info("Successfully created a new Schema Library %s", library_id)
+ return schema_library
+
+ except (GQLAPIError, FlowConnectionError, ValidationError) as e:
+ raise FlowSchemaLibraryError(
+ details=f"Failed to create SchemaLibrary '{library_id}': {e}"
+ ) from e
+
+
+def create_pipeline_schemas(project_id: str, config_path: str):
+ """Create pipeline schemas under the schema library 'Flow Toolkit Library'
+ for a CPA collection.
+
+ This utility is only applicable to CPA (provisioned) collections. It is
+ designed to create or use the 'Flow Toolkit Library' and its associated
+ schemas.
+
+ The function performs the following steps:
+ 1. Loads and validates the config.json file.
+ 2. Queries existing schema type ids for the target collection.
+ 3. Creates the schema library if any configured schemas are missing.
+ 4. Creates any missing schemas and sets their display names.
+
+ Args:
+ project_id: The Flow AM project ID.
+ config_path: Path to the schema config json file.
+ Raises:
+ RuntimeError: If schema library creation fails.
+ FileNotFoundError: If the config file does not exist.
+ json.JSONDecodeError: If the config file contains invalid JSON.
+ KeyError: If the config file does not contain a 'schemas' key,
+ or if required keys are missing in the schema definition.
+ ValueError: If schema definition or property values are invalid.
+ FlowSchemaError: If schema creation or retrieval fails.
+ FlowSchemaBuilderError: If schema building fails.
+ FlowSchemaDisplayDataError: If updating display data fails.
+ FlowSchemaLibraryError: If schema library operations fail.
+ """
+ logger = get_logger(__name__)
+
+ config = schema._read_schema_config(config_path)
+ if "schemas" not in config:
+ raise KeyError("The schema config file must contain a 'schemas' key with a list of schemas to create.")
+ client = get_client()
+ collection_id = FlowProject.get_collection_id(project_id)
+
+ # Retrieve all existing published schema type ids for the collection
+ try:
+ # Create SchemasBySuperTypeInput for the query
+ schemas_by_supertype_input = flow_model.SchemasBySuperTypeInput(
+ collection_id=collection_id,
+ type_id=BASE_TYPE_ID,
+ include_sub_sub_classes=True,
+ )
+ # Create and call the schema query
+ schema_query = client.service_schema.schemas_by_super_type(
+ variables=schemas_by_supertype_input
+ )
+ existing_schema_types = set(schema_query.schema_types_iterator)
+ logger.info(
+ f"Retrieved {len(existing_schema_types)} existing schemas for "
+ f"collection {collection_id}."
+ )
+ except (GQLAPIError, FlowConnectionError, ValidationError) as e:
+ raise RuntimeError(f"Failed to retrieve existing schemas: {e}") from e
+
+ # Check if schema listed in config.json already exists
+ # If not, add it to need_to_create list
+ need_to_create = []
+ for schema_dict in config.get("schemas", []):
+ # Get the schema type id for the schema and check if it already exists
+ schema_type_id = get_schema_id(schema_dict["name"])
+ if schema_type_id not in existing_schema_types:
+ logger.info(
+ "Schema '%s' not found. Adding to list to be created.",
+ schema_type_id,
+ )
+ need_to_create.append(schema_dict)
+
+ # Check if need_to_create is not empty
+ # and proceed to create schema library and schemas
+ if len(need_to_create) > 0:
+ logger.info("Attempting to create missing schemas...")
+ library_id = FLOW_TOOLKIT_LIBRARY_ID
+ _create_schema_library(
+ library_id=library_id,
+ title="Flow Toolkit Library",
+ description="Schemas for Flow Toolkit pipelines.",
+ project_id=project_id,
+ collection_id=collection_id,
+ )
+
+ schemas = []
+ # Create schemas if not exist
+ for schema_dict in need_to_create:
+ schema_builder = SchemaBuilder(
+ schema_dict=schema_dict,
+ schema_kind=flow_model.SchemaKind(schema_dict["kind"]),
+ project_id=project_id,
+ schema_library_id=library_id,
+ )
+ schema_builder.build()
+
+ if schema_builder.schema:
+ # NOTE: We need to explicitly pass the display name back because
+ # in newly created schemas, the 'display_name' property is not
+ # updated, therefore can not be reliably used to determine
+ # display name of the schema.
+ display_name = schema_builder.update_display_name()
+ schemas.append((schema_builder.schema, display_name))
+ else:
+ logger.info("All required schemas already exist.")
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/storage.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/storage.py
new file mode 100644
index 00000000..d2c927a1
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/storage.py
@@ -0,0 +1,314 @@
+# -
+# *****************************************************************************
+# Copyright 2026 Autodesk, Inc. All rights reserved.
+#
+# These coded instructions, statements, and computer programs contain
+# unpublished proprietary information written by Autodesk, Inc. and are
+# protected by Federal copyright law. They may not be disclosed to third
+# parties or copied or duplicated in any form, in whole or in part, without
+# the prior written consent of Autodesk, Inc.
+# *****************************************************************************
+# +
+
+"""
+This module provides utilities related to storage locations.
+"""
+
+from __future__ import annotations # needed for python 3.9 support
+
+import os
+import json
+from functools import cache
+
+from tank_vendor.flow_data_sdk.base import model as medm_model
+
+from .exceptions import (
+ ConfigurationError,
+ DirectoryNotCreatedError,
+ FlowError,
+)
+from .utils import (
+ abspath,
+ cleanpath,
+ ensure_dir,
+ get_logger,
+ trace,
+)
+
+
+# Storage roots
+# -------------
+# Environment variables that store key storage root paths.
+# Paths should be configured via set_sandbox_root() and set_storage_root().
+# They should be accessed via get_sandbox_root() and get_storage_root()
+
+# Sandbox storage root - for draft assets/revisions (editable)
+FLOW_SANDBOX_ROOT = "FLOW_SANDBOX_ROOT"
+
+# Primary cache root - for binaries cached locally from remote storage (read-only)
+FLOW_STORAGE_ROOT = "FLOW_STORAGE_ROOT"
+
+
+def set_sandbox_root(sandbox_root: str, create_dir: bool = False):
+ """Set sandbox root location.
+
+ Args:
+ sandbox_root: Path to top-level sandbox directory.
+ create_dir: If True, create this directory if it doesn't exist.
+
+ Raises:
+ DirectoryNotCreatedError
+ """
+ logger = get_logger(__name__)
+
+ # Expand user paths (e.g., ~ to /Users/username)
+ sandbox_root = os.path.expanduser(sandbox_root)
+ # Ensure path is absolute and real
+ sandbox_root = abspath("", sandbox_root)
+ logger.info(f"Setting Flow sandbox directory to: {sandbox_root}")
+ if create_dir:
+ try:
+ # Create directory if necessary
+ ensure_dir(sandbox_root)
+ except DirectoryNotCreatedError as exc:
+ raise ConfigurationError(
+ details=f"Couldn't create directory {sandbox_root} for sandbox root."
+ ) from exc
+
+ os.environ[FLOW_SANDBOX_ROOT] = sandbox_root
+
+
+def set_storage_root(storage_root: str, create_dir: bool = False):
+ """Set primary cache location.
+
+ Args:
+ storage_root: Path to top-level primary cache directory.
+ create_dir: If True, create this directory if it doesn't exist.
+
+ Raises:
+ DirectoryNotCreatedError
+ """
+ logger = get_logger(__name__)
+
+ # Expand user paths (e.g., ~ to /Users/username)
+ storage_root = os.path.expanduser(storage_root)
+ # Ensure path is absolute and real
+ storage_root = abspath("", storage_root)
+ logger.info(f"Setting Flow storage directory to: {storage_root}")
+ if create_dir:
+ try:
+ # Create directory if necessary
+ ensure_dir(storage_root)
+ except DirectoryNotCreatedError as exc:
+ raise ConfigurationError(
+ details=f"Couldn't create directory {storage_root} for primary storage root."
+ ) from exc
+ os.environ[FLOW_STORAGE_ROOT] = storage_root
+
+
+def get_sandbox_root() -> str:
+ """Return the root directory of sandbox location.
+
+ Returns:
+ Full path to configured sandbox root directory.
+ """
+ return os.getenv(FLOW_SANDBOX_ROOT, "")
+
+
+def get_storage_root() -> str:
+ """Return the root directory of primary cache location.
+
+ Returns:
+ Full path to configured storage root directory.
+ """
+ return os.getenv(FLOW_STORAGE_ROOT, "")
+
+
+def get_storage_key(asset_id: str) -> str:
+ """Parse storage key from asset id.
+ This is a unique file-safe id that can be used in storage file paths.
+
+ Args:
+ asset_id: Medm asset id.
+
+ Returns:
+ Storage key associated with that asset.
+ """
+ return asset_id.rsplit(":", maxsplit=1)[-1]
+
+
+def get_storage_asset_dir(asset_id: str) -> str:
+ """Return the full path of asset directory in primary storage
+ (whether or not the directory exists).
+
+ Args:
+ asset_id: Medm asset id.
+
+ Returns:
+ Full path to expected location of primary storage directory on local disk.
+ """
+ return cleanpath(get_storage_root(), get_storage_key(asset_id))
+
+
+def get_storage_revision_dir(asset_id: str, revision_number: int) -> str:
+ """Return the full path of asset directory in primary storage
+ for the given revision of asset (whether or not the directory exists).
+
+ Args:
+ asset_id: Medm asset id.
+ revision_number: Number of revision.
+
+ Returns:
+ Full path to expected location of primary storage directory on local disk.
+ """
+ return cleanpath(get_storage_asset_dir(asset_id), f"r{revision_number}")
+
+
+@trace
+def _cache_asset_info(asset_id: str):
+ """Store relevant, persistent metadata about this asset to its
+ storage directory. This will be very useful for certain lookups.
+
+ In particular, we want to cache the relationship between the asset's
+ storage key and asset id. We do this by saving a sidecar file to the
+ asset's storage directory. Looking this up via the sidecar file will
+ save a very expensive query when mapping storage key back to asset id.
+
+ Args:
+ asset_id: Id of asset whose info should be cached.
+
+ Raises:
+ FlowError
+ """
+ # Check if the info file already exists
+ # If so, check that it is complete. File reads are cheap so doing
+ # this every time an Asset object is constructed should be ok.
+ storage_dir = get_storage_asset_dir(asset_id)
+ file_path = cleanpath(storage_dir, ".info")
+ info = {}
+ if os.path.exists(file_path):
+ with open(file_path, "r") as f:
+ try:
+ info = json.loads(f.read())
+ except json.JSONDecodeError:
+ # We don't care if the file is corrupted, just write a new one
+ pass
+
+ # For now we're only storing asset id
+ expected_keys = ["asset_id"]
+ write_file = False
+ for key in expected_keys:
+ if key not in info:
+ # If the file is incomplete, re-write the file
+ write_file = True
+ break
+
+ if not write_file:
+ return
+
+ info = {"asset_id": asset_id}
+ try:
+ if not os.path.exists(storage_dir):
+ os.makedirs(storage_dir)
+ with open(file_path, "w") as f:
+ f.write(json.dumps(info, indent=4))
+ except Exception as exc: # pylint: disable=broad-except
+ msg = f"Asset info file could not be written: {file_path}"
+ raise FlowError(msg) from exc
+
+
+@cache
+@trace
+def storage_key_to_asset_id(storage_key: str) -> str:
+ """Given an asset storage key, return the corresponding asset id.
+
+ Args:
+ storage_key: String asset storage key.
+
+ Returns:
+ Asset id.
+
+ Raises:
+ FlowError
+ """
+ # Check if storage key to asset id lookup is cached
+ info_file = cleanpath(get_storage_root(), storage_key, ".info")
+ if os.path.exists(info_file):
+ with open(info_file, "r") as f:
+ info = json.loads(f.read())
+ asset_id = info.get("asset_id")
+ if asset_id is not None:
+ return asset_id
+
+ # Fallback plan is to do a very expensive project query
+ # TODO: implement query
+
+ raise FlowError(f"Invalid storage key provided: {storage_key}")
+
+
+def get_storage_component_path(
+ revision: medm_model.AssetRevision,
+ component_name: str = "",
+ component_purpose: str = "",
+ blob_index: int = 0,
+) -> str | None:
+ """Return the full path of the component blob file of the given asset revision
+ in primary storage (whether or not the file exists).
+
+ Args:
+ revision: Revision to which component belongs.
+ component_name: If provided, search for component with this name and return its storage path.
+ This should be unique within the revision.
+ component_purpose: If provided, search for a component with this purpose and return
+ its storage path. There may be multiple components with the same purpose,
+ so the first match will be returned.
+ blob_index: Specific blob from source component to get.
+
+ ..note:: If both component name and purpose are provided, the first intersection
+ of both criteria will be returned.
+
+ Returns:
+ Full path to expected location of cached source file on local disk, or
+ None if the component does not exist on the revision.
+
+ Raise:
+ FlowError
+ """
+ comp = _find_component(revision, name=component_name, purpose=component_purpose)
+ if not comp:
+ return None
+ try:
+ blob_path = comp.data["data"][blob_index]["path"]
+ except KeyError as exc:
+ msg = f'Blob path could not be retrieved for component "{comp.name}". {exc}'
+ raise FlowError(msg)
+ storage_dir = get_storage_revision_dir(revision.asset_id, revision.revision_number)
+ return cleanpath(storage_dir, blob_path)
+
+
+def _find_component(
+ revision: medm_model.AssetRevision,
+ name: str = "",
+ purpose: str = "",
+ type_id: str = "",
+) -> medm_model.Component | None:
+ """Match component on given revision based on criteria.
+ If multiple criteria is provided, the first intersection will be returned.
+
+ Args:
+ revision: Revision to be searched.
+ name: Name of component to match.
+ purpose: Purpose of component to match.
+ NOTE: this is only applicable to binary components.
+ type_id: Type id of component to match.
+ """
+ for comp in revision.components:
+ comp_purpose = comp.data.get("purpose", "")
+ if name and comp.name != name:
+ continue
+ if purpose and comp_purpose != purpose:
+ continue
+ if type_id and comp.type_id != type_id:
+ continue
+ return comp
+ return None
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/transferapi.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/transferapi.py
new file mode 100644
index 00000000..0e1d6caf
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/transferapi.py
@@ -0,0 +1,251 @@
+# -
+# *****************************************************************************
+# Copyright 2026 Autodesk, Inc. All rights reserved.
+#
+# These coded instructions, statements, and computer programs contain
+# unpublished proprietary information written by Autodesk, Inc. and are
+# protected by Federal copyright law. They may not be disclosed to third
+# parties or copied or duplicated in any form, in whole or in part, without
+# the prior written consent of Autodesk, Inc.
+# *****************************************************************************
+# +
+
+"""
+API methods for transfering files to the remote server
+"""
+
+from __future__ import annotations # needed for python 3.9 support
+
+import base64
+import hashlib
+import math
+import os
+import urllib.error
+import urllib.request
+
+from tank_vendor.flow_data_sdk.base import model as medm_model
+from tank_vendor.flow_data_sdk.base.exceptions import GQLAPIError
+
+from .exceptions import FileUploadError
+from .utils import get_logger, trace
+
+
+@trace
+def open_upload_file(client, urn_id: str, upload_uri: str) -> medm_model.UploadFileJob:
+ """Open an asynchronous file upload session.
+
+ Creates an upload job on the server for multipart file upload.
+ The returned job ID is required for subsequent upload operations.
+
+ Args:
+ urn_id: Target blob URN identifier.
+ upload_uri: Server-provided upload URI.
+
+ Returns:
+ An UploadFileJob instance containing the job ID
+
+ Raises:
+ FileUploadError: If the GraphQL API returns an error.
+ """
+ logger = get_logger(__name__)
+
+ try:
+ # Create the open upload file input data
+ input_data = medm_model.OpenUploadFileInput(
+ upload_uri=upload_uri, urn_id=urn_id
+ )
+ # Create the open upload file operation and call it
+ operation = client.service_binary.open_upload_file(variables=input_data)
+ response = operation.call()
+ return response.job
+
+ except GQLAPIError as e:
+ error_details = f"GraphQL API error (status {e.status_code}): {e.message}"
+ logger.error(f"Failed to open upload file session: {error_details}")
+ raise FileUploadError(file_path=upload_uri, details=error_details) from e
+
+
+@trace
+def get_upload_file_part(
+ client, async_job_id: str, part_num: int, md5_hash: str
+) -> medm_model.GetUploadFilePartResponse:
+ """Get upload URL for a specific file part
+
+ Retrieves upload information for a specific part of the multipart upload for the single file upload.
+
+ Args:
+ async_job_id: The async job ID returned from open_upload_file.
+ part_num: The part number to upload (1-based index).
+ md5_hash: Base64-encoded MD5 hash of the part data.
+
+ Returns:
+ UploadFilePart with upload URL and metadata.
+
+ Raises:
+ FileUploadError: If the GraphQL API returns an error.
+ """
+ logger = get_logger(__name__)
+
+ try:
+ # Create the get upload file part input data
+ input_data = medm_model.GetUploadFilePartInput(
+ async_job_id=async_job_id, hash=md5_hash, part_num=part_num
+ )
+ # Create the get upload file part operation and call it
+ operation = client.service_binary.get_upload_file_part(variables=input_data)
+ response = operation.call()
+ return response
+
+ except GQLAPIError as e:
+ error_details = f"GraphQL API error (status {e.status_code}): {e.message}"
+ logger.error(
+ f"Failed to get upload file part {part_num} for job {async_job_id}: {error_details}"
+ )
+ raise FileUploadError(
+ file_path=f"job_{async_job_id}_part_{part_num}", details=error_details
+ ) from e
+
+
+@trace
+def close_upload_file(
+ client, async_job_id: str, state: str, etags: list[str]
+) -> medm_model.UploadFileJob:
+ """Close the asynchronous file upload session.
+
+ Notifies the server to merge all uploaded parts into the final file.
+ Must be called even if the upload failed to clean up server resources.
+
+ Args:
+ async_job_id: The async job ID returned from open_upload_file.
+ state: Final state of the upload. Must be "SUCCEEDED" or "FAILED".
+ etags: List of ETags for each uploaded part.
+
+ Returns:
+ An UploadFileJob instance containing the job ID
+
+ Raises:
+ ValueError: If an invalid state is provided.
+ FileUploadError: If the GraphQL API returns an error.
+ """
+ logger = get_logger(__name__)
+
+ if state not in ["SUCCEEDED", "FAILED"]:
+ raise ValueError(f"Invalid state: {state}. Must be SUCCEEDED or FAILED")
+
+ try:
+ # Create the close upload file input data
+ input_data = medm_model.CloseUploadFileInput(
+ async_job_id=async_job_id,
+ state=state,
+ etags=etags,
+ )
+ # Create the close upload file operation and call it
+ operation = client.service_binary.close_upload_file(variables=input_data)
+ response = operation.call()
+ return response.job
+
+ except GQLAPIError as e:
+ error_details = f"GraphQL API error (status {e.status_code}): {e.message}"
+ logger.error(
+ f"Failed to close upload file session {async_job_id} with state {state}: {error_details}"
+ )
+ raise FileUploadError(
+ file_path=f"job_{async_job_id}", details=error_details
+ ) from e
+
+
+@trace
+def upload_blob(client, file_path, urn_id, upload_uri):
+ """Upload a file to remote storage using multipart upload.
+
+ Upload workflow:
+ 1. Opens an upload session and obtains a job ID
+ 2. Splits the file into chunks and uploads each part
+ 3. Closes the upload session to finalize
+
+ Args:
+ client: The GraphQL client instance
+ file_path: The local filename of the file to be uploaded
+ urn_id: URN of the blob component to upload to
+ upload_uri: URI needed for API transfer
+ """
+ logger = get_logger(__name__)
+
+ # Create the UploadFileJob
+ upload_job = open_upload_file(client=client, urn_id=urn_id, upload_uri=upload_uri)
+ async_job_id = upload_job.id
+ logger.info(f"Opened upload job: {async_job_id} (state: {upload_job.state})")
+
+ # Keep track of etags
+ etags = []
+
+ # Split file into chunks for upload
+ total_size = os.path.getsize(file_path)
+ part_size = max(5 * 1024 * 1024, math.ceil(total_size / 10000))
+ total_parts = math.ceil(total_size / part_size)
+
+ logger.info(f"Uploading {file_path} in {total_parts} chunks of {part_size} bytes")
+
+ upload_state = "SUCCEEDED"
+
+ try:
+ with open(file_path, "rb") as f:
+ for part_num in range(1, total_parts + 1):
+ # Read chunk data
+ chunk_data = f.read(part_size)
+ # MD5 Hash is only used as a checksum so there is
+ # no security risk for using it here
+ md5_hash = base64.b64encode(hashlib.md5(chunk_data).digest()).decode(
+ "utf-8"
+ )
+
+ logger.info(f" - Uploading chunk {part_num} : {md5_hash}")
+
+ # Get the upload URL for this part
+ part_info = get_upload_file_part(
+ client=client,
+ async_job_id=async_job_id,
+ part_num=part_num,
+ md5_hash=md5_hash,
+ )
+ upload_url = part_info.send_url
+ # Confirm that upload url is a web url and not a file:// url
+ # which could present a security breach
+ if not upload_url.startswith("http"):
+ msg = f"Suspicious url returned: {upload_url}. Aborting upload..."
+ raise RuntimeError(msg)
+
+ # Create request and upload the part data
+ headers = {
+ "Content-MD5": md5_hash,
+ "Content-Type": "application/octet-stream",
+ }
+ req = urllib.request.Request(
+ upload_url, data=chunk_data, headers=headers, method="PUT"
+ )
+ with urllib.request.urlopen(req, timeout=120) as response:
+ if response.status not in (200, 201):
+ raise ValueError(
+ f"Failed to upload part {part_num}: HTTP {response.status}"
+ )
+ etag = response.headers.get("ETag")
+ if not etag:
+ raise ValueError(
+ f"ETag not found in response for part {part_num}"
+ )
+ etags.append(etag)
+ logger.info(f" Part {part_num}/{total_parts} uploaded successfully")
+
+ except Exception as e:
+ upload_state = "FAILED"
+
+ raise FileUploadError(file_path=file_path, details=str(e)) from e
+
+ finally:
+ # Finally close the file upload job
+ close_upload_file(
+ client=client,
+ async_job_id=async_job_id,
+ etags=etags,
+ state=upload_state,
+ )
diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/utils.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/utils.py
new file mode 100644
index 00000000..8301f2a9
--- /dev/null
+++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/utils.py
@@ -0,0 +1,386 @@
+# -
+# *****************************************************************************
+# Copyright 2026 Autodesk, Inc. All rights reserved.
+#
+# These coded instructions, statements, and computer programs contain
+# unpublished proprietary information written by Autodesk, Inc. and are
+# protected by Federal copyright law. They may not be disclosed to third
+# parties or copied or duplicated in any form, in whole or in part, without
+# the prior written consent of Autodesk, Inc.
+# *****************************************************************************
+# +
+
+"""
+This module contains general utilities that can be leveraged by Flow integrations.
+"""
+
+from __future__ import annotations # needed for python 3.9 support
+
+import logging
+import math
+import mimetypes
+import os
+import re
+import sys
+import time
+import traceback
+import urllib
+from functools import wraps
+from typing import Callable
+
+from .exceptions import DirectoryNotCreatedError, FlowError
+
+
+# Time in seconds above which profiling info should be printed
+# Setting the value to None will turn off profiling globally
+# Add/Remove the @trace decorator from a function to turn on/off
+# profiling for that function
+PROFILING_THRESHOLD = None
+
+# Logging level - used for default logger only
+LOGGING_LEVEL = "INFO"
+# Callback function used to generate logger object
+# (If unset, default python logger will be used)
+_logger_callback: Callable[[str], logging.Logger] | None = None
+# Logging formatter for default python logger
+_formatter = logging.Formatter(
+ "%(asctime)s - %(module)s.%(funcName)s [%(lineno)d] %(levelname)s: %(message)s"
+)
+
+
+def trace(func):
+ """Decorator to track and record execution time of a function."""
+
+ def profile_output(arg0, t0, t1):
+ """Output profiling info to log."""
+ # List of internal object types we would want to trace
+ supported_objects = [
+ "FlowAsset",
+ "FlowRevision",
+ "FlowBlob",
+ "FlowComponent",
+ "ComponentSpec",
+ "FlowProject",
+ ]
+ msg = "PROFILING: "
+ if arg0.__class__ == type:
+ # Class function
+ msg += f"{arg0.__name__}.{func.__name__}"
+ elif arg0.__class__.__name__ in supported_objects:
+ # Member function
+ msg += f"{arg0.__class__.__name__}.{func.__name__}"
+ else:
+ # Global function
+ msg += func.__name__
+ msg += f": {strtime(t0, t1)}"
+ # Only display execution times above threshold
+ if t1 - t0 >= PROFILING_THRESHOLD:
+ print(msg)
+
+ def strtime(t0, t1):
+ """Convert a time range into MM:SS.remainder string format."""
+ td = t1 - t0
+ minutes = math.floor(td / 60)
+ seconds = td - (minutes * 60)
+ return f"{minutes:02}:{seconds:02}"
+
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ # If profiling is turned off, run function normally
+ if PROFILING_THRESHOLD is None:
+ return func(*args, **kwargs)
+
+ t0 = time.time()
+ try:
+ result = func(*args, **kwargs)
+ except Exception as e:
+ raise e
+ finally:
+ t1 = time.time()
+ arg0 = args[0] if args else None
+ profile_output(arg0, t0, t1)
+ return result
+
+ return wrapper
+
+
+def get_logger(name: str):
+ """Return a logger with the given name.
+ The callback set in _logger_callback will be used to generate the logger.
+ """
+ if _logger_callback is None:
+ # Generate default python logger
+ logger = logging.getLogger(name)
+ if len(logger.handlers) == 0:
+ # Initialize stdout handler
+ handler = logging.StreamHandler(sys.stdout)
+ handler.setFormatter(_formatter)
+ logger.addHandler(handler)
+ logger.setLevel(LOGGING_LEVEL)
+ return logger
+ return _logger_callback(name)
+
+
+def to_regex_safe_wildcard_string(pattern: str) -> str:
+ """Convert a wildcard pattern to a regex-safe pattern string.
+
+ This function escapes all regex special characters in the input pattern
+ and converts wildcard asterisks (*) to regex wildcards (.*), allowing
+ for safe pattern matching where only * acts as a wildcard.
+
+ Args:
+ pattern: Input pattern that may contain wildcards (*) and special
+ regex characters that should be treated as literals.
+
+ Returns:
+ A regex pattern string where:
+ - All regex special characters are escaped (treated as literals)
+ - Asterisks (*) are converted to .* (regex wildcard)
+
+ Examples:
+ >>> to_regex_safe_wildcard_string("file.txt")
+ 'file\\\\.txt'
+ >>> to_regex_safe_wildcard_string("*.txt")
+ '.*\\\\.txt'
+ >>> to_regex_safe_wildcard_string("config[v1.0].data")
+ 'config\\\\[v1\\\\.0\\\\]\\\\.data'
+ >>> to_regex_safe_wildcard_string("*_file_*.txt")
+ '.*_file_.*\\\\.txt'
+ """
+ return re.escape(pattern).replace("\\*", ".*")
+
+
+def ensure_dir(dir_path: str):
+ """Ensure that a directory exists by creating it and all needed parent directories
+
+ Args:
+ dir_path: Full path to local directory
+ """
+ logger = get_logger(__name__)
+
+ if not os.path.exists(dir_path):
+ # Try to create the directory
+ try:
+ logger.info(f"Creating directory {dir_path}")
+ os.makedirs(dir_path)
+ except OSError as exc:
+ # Only complain if the directory wasn't created
+ if not os.path.exists(dir_path):
+ raise DirectoryNotCreatedError(dir_path=dir_path)
+ else:
+ logger.warning(str(exc))
+
+ if not os.path.isdir(dir_path):
+ # This is very unlikely to happen
+ raise DirectoryNotCreatedError(dir_path=dir_path)
+
+
+def cleanpath(path: str, *extra: str) -> str:
+ """Return the same path, normalized and using only front slashes.
+
+ Args:
+ path: String absolute or relative path.
+ *extra: Zero or more string arguments representing extra bits to
+ add to input path in given order.
+
+ Returns:
+ str: Path that is the product of all input parameters joined.
+
+ Examples:
+ >>> cleanpath('c:\\dev\\my_root', 'my_dir', 'my_file.ma')
+ 'c:/dev/my_root/my_dir/my_file.ma'
+ >>> cleanpath('/Users//smith/folder1/file1.txt')
+ '/Users/smith/folder1/file1.txt'
+ >>> cleanpath('C:/temp/some_dir/', '/some_folder/')
+ 'C:/temp/some_dir/some_folder'
+ >>> cleanpath('/Applications', '/\\some_app')
+ '/Applications/some_app'
+ >>> cleanpath('D:', 'MIM_Files')
+ 'D:/MIM_Files'
+ >>> cleanpath('D:\\\\', 'MIM_Files')
+ 'D:/MIM_Files'
+ >>> cleanpath('')
+ ''
+ >>> cleanpath('', 'blah', 'blah')
+ 'blah/blah'
+ >>> cleanpath('/path/to/dir/')
+ '/path/to/dir'
+ >>> cleanpath('/path/./to/../file.txt')
+ '/path/file.txt'
+ """
+ # Add slash if first argument is a drive
+ # (os.path.join will not add one in this case)
+ if path.endswith(":"):
+ path += "/"
+ # Must strip any leading slashes from extra bits
+ extras = []
+ for ext in extra:
+ extras.append(ext.lstrip("/\\"))
+ result = os.path.join(path, *extras)
+ if not result:
+ return ""
+ return os.path.normpath(result).replace("\\", "/")
+
+
+def abspath(basepath: str, relpath: str) -> str:
+ """Return absolute path of a base path plus a path relative to it.
+
+ Args:
+ basepath: Absolute file path.
+ relpath: Input path that is relative to provided base path.
+
+ Examples:
+ >>> abspath('c:/temp', 'dir/myfile.txt')
+ 'C:/temp/dir/myfile.txt'
+ >>> abspath('', 'c:/temp/dir/myfile.txt')
+ 'C:/temp/dir/myfile.txt'
+ >>> abspath('c:/some_folder', '../temp/dir/other_dir')
+ 'C:/temp/dir/other_dir'
+ >>> abspath('c:/temp/dir2/folder', '../../dir/myfile.txt')
+ 'C:/temp/dir/myfile.txt'
+ """
+ return cleanpath(os.path.realpath(os.path.join(basepath, relpath)))
+
+
+def relpath(basepath: str, targetpath: str) -> str:
+ r"""Return the target path as a relative path to base path.
+ Paths *must* be on same drive.
+
+ Args:
+ basepath: Absolute directory path that return path will be relative to.
+ targetpath: Absolute path whose relative value will be returned.
+
+ Examples:
+ >>> relpath('c:/temp', 'c:/temp/dir/myfile.txt')
+ 'dir/myfile.txt'
+ >>> relpath('c:/some_folder', 'c:/temp/dir/other_dir')
+ '../temp/dir/other_dir'
+ >>> relpath('c:/temp/dir2/folder', 'C:/temp/dir/myfile.txt')
+ '../../dir/myfile.txt'
+ >>> relpath(r'c:/temp\dir2/folder', r'C:/temp\dir\myfile.txt') # noqa: W605
+ '../../dir/myfile.txt'
+ """
+ return cleanpath(os.path.relpath(targetpath, basepath))
+
+
+def is_sub_directory(root_path: str, full_path: str) -> bool:
+ """Return True if the full path provided begins with the root path.
+
+ Args:
+ root_path: A root directory.
+ full_path: A full path to a directory or file.
+
+ Examples:
+ >>> is_sub_directory('c:/temp', 'C:/temp/dir/myfile.txt')
+ True
+ >>> is_sub_directory('', 'c:/temp/dir/myfile.txt')
+ False
+ >>> is_sub_directory('c:/some_folder', 'c:/temp/dir/other_dir')
+ False
+ >>> is_sub_directory('c:/temp/dir2/folder', '')
+ False
+ """
+ if not root_path or not full_path:
+ return False
+ abs_root = abspath("", root_path)
+ abs_full = abspath("", full_path)
+ try:
+ # Use commonpath to properly check if full_path is under root_path
+ # If commonpath of both equals the root, then full is under root
+ return os.path.commonpath([abs_root]) == os.path.commonpath(
+ [abs_root, abs_full]
+ )
+ except ValueError:
+ # Raised when paths are on different drives (Windows) or one is relative
+ return False
+
+
+def fileext(filepath: str):
+ """Return extension of given file path without the dot and in lower case.
+ Examples:
+ >>> fileext('c:/temp/file.txt')
+ 'txt'
+ >>> fileext('dir/another_dir/file.PNG')
+ 'png'
+ >>> fileext('dir/another_dir')
+ ''
+ >>> fileext('dir/another.dir/folder')
+ ''
+ >>> fileext('file.backup.tar.gz')
+ 'gz'
+ """
+ filename = os.path.basename(filepath)
+ if "." not in filename:
+ return ""
+ return os.path.splitext(filename)[-1].strip(".").lower()
+
+
+def mimetype(ext: str):
+ """Return the mimetype of the given file extension.
+
+ Args:
+ ext: A file extension which may or may not be preceded by a '.'.
+ A file path is also accepted.
+
+ Returns:
+ String mimetype, or blank string if extension is not recognized.
+
+ Examples:
+ >>> mimetype('jpg')
+ 'image/jpeg'
+ >>> mimetype('.jpeg')
+ 'image/jpeg'
+ >>> mimetype('c:/temp/my_image.jpg')
+ 'image/jpeg'
+ >>> mimetype('.not_a_recognized_file_type')
+ ''
+ """
+ if "." in ext and not ext.startswith("."):
+ ext = os.path.splitext(ext)[1]
+ else:
+ ext = "." + ext.strip(".")
+ try:
+ return mimetypes.types_map[ext]
+ except KeyError:
+ return ""
+
+
+@trace
+def download_file(url, local_filename):
+ """Download a file from the passed in URL and save it in the specified location.
+ Create any folders as necessary.
+
+ Args:
+ url: The remote URL to download
+ local_filename: The full path to the local destination file
+
+ Raises:
+ DirctoryNotCreatedError: If we couldn't create the folder for the download
+ """
+ # Check if path exists, and create it if it doesn't
+ local_dir = os.path.dirname(local_filename)
+
+ # This can raise an exception
+ ensure_dir(local_dir)
+
+ try:
+ with urllib.request.urlopen(url) as r: # nosec B210
+ try:
+ with open(local_filename, "wb") as f:
+ while True:
+ chunk = r.read(8192)
+ if not chunk:
+ break
+ f.write(chunk)
+ except Exception as exc: # pylint: disable=broad-except
+ msg = f"Failed to write to local file: {local_filename}"
+ traceback.print_exc()
+ raise FlowError(msg) from exc
+ except urllib.error.HTTPError as exc:
+ msg = f'HTTP error occurred while accessing url "{url}". {exc}'
+ traceback.print_exc()
+ raise FlowError(msg) from exc
+ except urllib.error.URLError as exc:
+ msg = f'Request to get url "{url}" failed. {exc}'
+ traceback.print_exc()
+ raise FlowError(msg) from exc
diff --git a/python/tk-core/requirements/3.10/frozen_requirements.txt b/python/tk-core/requirements/3.10/frozen_requirements.txt
index 589c0f0a..0ed675c7 100644
--- a/python/tk-core/requirements/3.10/frozen_requirements.txt
+++ b/python/tk-core/requirements/3.10/frozen_requirements.txt
@@ -1,5 +1,7 @@
distro==1.4.0
+Fileseq==2.1.2
packaging==25.0
+PyJWT==2.12.0
PyYAML==6.0.1
ruamel.yaml==0.18.14
ruamel.yaml.clib==0.2.15
diff --git a/python/tk-core/requirements/3.10/pkgs.zip b/python/tk-core/requirements/3.10/pkgs.zip
index 4811a07c..a375cc0d 100644
Binary files a/python/tk-core/requirements/3.10/pkgs.zip and b/python/tk-core/requirements/3.10/pkgs.zip differ
diff --git a/python/tk-core/requirements/3.10/requirements.txt b/python/tk-core/requirements/3.10/requirements.txt
index eed0486f..fc0667fa 100644
--- a/python/tk-core/requirements/3.10/requirements.txt
+++ b/python/tk-core/requirements/3.10/requirements.txt
@@ -1,5 +1,7 @@
distro==1.4.0
+fileseq==2.1.2
packaging==25.0
+pyjwt==2.12.0
pyyaml==6.0.1
ruamel.yaml==0.18.14
shotgun_api3==3.10.0
diff --git a/python/tk-core/requirements/3.11/frozen_requirements.txt b/python/tk-core/requirements/3.11/frozen_requirements.txt
index 284e721c..c12f38dc 100644
--- a/python/tk-core/requirements/3.11/frozen_requirements.txt
+++ b/python/tk-core/requirements/3.11/frozen_requirements.txt
@@ -1,5 +1,7 @@
distro==1.9.0
+Fileseq==2.1.2
packaging==25.0
+PyJWT==2.12.0
PyYAML==6.0.2
ruamel.yaml==0.18.14
ruamel.yaml.clib==0.2.15
diff --git a/python/tk-core/requirements/3.11/pkgs.zip b/python/tk-core/requirements/3.11/pkgs.zip
index 4d502a0e..ad363b23 100644
Binary files a/python/tk-core/requirements/3.11/pkgs.zip and b/python/tk-core/requirements/3.11/pkgs.zip differ
diff --git a/python/tk-core/requirements/3.11/requirements.txt b/python/tk-core/requirements/3.11/requirements.txt
index 4c705e43..d03588ab 100644
--- a/python/tk-core/requirements/3.11/requirements.txt
+++ b/python/tk-core/requirements/3.11/requirements.txt
@@ -1,5 +1,7 @@
distro==1.9.0
+fileseq==2.1.2
packaging==25.0
+pyjwt==2.12.0
pyyaml==6.0.2
ruamel.yaml==0.18.14
shotgun_api3==3.10.0
diff --git a/python/tk-core/requirements/3.13/frozen_requirements.txt b/python/tk-core/requirements/3.13/frozen_requirements.txt
index 284e721c..c12f38dc 100644
--- a/python/tk-core/requirements/3.13/frozen_requirements.txt
+++ b/python/tk-core/requirements/3.13/frozen_requirements.txt
@@ -1,5 +1,7 @@
distro==1.9.0
+Fileseq==2.1.2
packaging==25.0
+PyJWT==2.12.0
PyYAML==6.0.2
ruamel.yaml==0.18.14
ruamel.yaml.clib==0.2.15
diff --git a/python/tk-core/requirements/3.13/pkgs.zip b/python/tk-core/requirements/3.13/pkgs.zip
index 78a1b8f1..57c488af 100644
Binary files a/python/tk-core/requirements/3.13/pkgs.zip and b/python/tk-core/requirements/3.13/pkgs.zip differ
diff --git a/python/tk-core/requirements/3.13/requirements.txt b/python/tk-core/requirements/3.13/requirements.txt
index 4c705e43..d03588ab 100644
--- a/python/tk-core/requirements/3.13/requirements.txt
+++ b/python/tk-core/requirements/3.13/requirements.txt
@@ -1,5 +1,7 @@
distro==1.9.0
+fileseq==2.1.2
packaging==25.0
+pyjwt==2.12.0
pyyaml==6.0.2
ruamel.yaml==0.18.14
shotgun_api3==3.10.0
diff --git a/python/tk-core/requirements/3.7/certs/shotgun_api3/lib/certifi/cacert.pem b/python/tk-core/requirements/3.7/certs/shotgun_api3/lib/certifi/cacert.pem
deleted file mode 100644
index 64c05d7f..00000000
--- a/python/tk-core/requirements/3.7/certs/shotgun_api3/lib/certifi/cacert.pem
+++ /dev/null
@@ -1,4778 +0,0 @@
-
-# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc.
-# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc.
-# Label: "Entrust Root Certification Authority"
-# Serial: 1164660820
-# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4
-# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9
-# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c
------BEGIN CERTIFICATE-----
-MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
-VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
-Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
-KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
-cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
-NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
-NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
-ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
-BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
-KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
-Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
-4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
-KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
-rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
-94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
-sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
-gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
-kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
-vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
-A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
-O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
-AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
-9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
-eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
-0vdXcDazv/wor3ElhVsT/h5/WrQ8
------END CERTIFICATE-----
-
-# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited
-# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited
-# Label: "QuoVadis Root CA 2"
-# Serial: 1289
-# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b
-# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7
-# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86
------BEGIN CERTIFICATE-----
-MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
-GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
-b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV
-BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
-YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa
-GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg
-Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J
-WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB
-rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp
-+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1
-ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i
-Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz
-PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og
-/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH
-oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI
-yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud
-EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2
-A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL
-MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
-ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f
-BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn
-g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl
-fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K
-WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha
-B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc
-hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR
-TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD
-mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z
-ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y
-4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza
-8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
------END CERTIFICATE-----
-
-# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited
-# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited
-# Label: "QuoVadis Root CA 3"
-# Serial: 1478
-# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf
-# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85
-# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35
------BEGIN CERTIFICATE-----
-MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
-GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
-b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV
-BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
-YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM
-V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB
-4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr
-H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd
-8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv
-vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT
-mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe
-btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc
-T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt
-WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ
-c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A
-4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD
-VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG
-CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0
-aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
-aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu
-dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw
-czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G
-A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC
-TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg
-Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0
-7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem
-d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd
-+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B
-4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN
-t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x
-DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57
-k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s
-zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j
-Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT
-mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK
-4SVhM7JZG+Ju1zdXtg2pEto=
------END CERTIFICATE-----
-
-# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
-# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
-# Label: "DigiCert Assured ID Root CA"
-# Serial: 17154717934120587862167794914071425081
-# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72
-# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43
-# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c
------BEGIN CERTIFICATE-----
-MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
-d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
-b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
-EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
-cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
-MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
-JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
-mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
-wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
-VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
-AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
-AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
-BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
-pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
-dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
-fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
-NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
-H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
-+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
------END CERTIFICATE-----
-
-# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com
-# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com
-# Label: "DigiCert Global Root CA"
-# Serial: 10944719598952040374951832963794454346
-# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e
-# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36
-# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61
------BEGIN CERTIFICATE-----
-MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
-d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
-QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
-MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
-b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
-9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
-CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
-nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
-43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
-T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
-gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
-BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
-TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
-DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
-hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
-06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
-PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
-YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
-CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
------END CERTIFICATE-----
-
-# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com
-# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com
-# Label: "DigiCert High Assurance EV Root CA"
-# Serial: 3553400076410547919724730734378100087
-# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a
-# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25
-# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf
------BEGIN CERTIFICATE-----
-MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
-d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
-ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
-MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
-LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
-RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
-+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
-PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
-xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
-Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
-hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
-EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
-MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
-FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
-nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
-eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
-hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
-Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
-vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
-+OkuE6N36B9K
------END CERTIFICATE-----
-
-# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG
-# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG
-# Label: "SwissSign Gold CA - G2"
-# Serial: 13492815561806991280
-# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93
-# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61
-# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95
------BEGIN CERTIFICATE-----
-MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
-BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln
-biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF
-MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT
-d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
-CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8
-76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+
-bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c
-6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE
-emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd
-MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt
-MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y
-MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y
-FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi
-aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM
-gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB
-qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7
-lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn
-8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
-L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6
-45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO
-UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5
-O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC
-bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv
-GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a
-77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC
-hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3
-92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp
-Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w
-ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt
-Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
------END CERTIFICATE-----
-
-# Issuer: CN=SecureTrust CA O=SecureTrust Corporation
-# Subject: CN=SecureTrust CA O=SecureTrust Corporation
-# Label: "SecureTrust CA"
-# Serial: 17199774589125277788362757014266862032
-# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1
-# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11
-# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73
------BEGIN CERTIFICATE-----
-MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI
-MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
-FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz
-MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv
-cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN
-AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz
-Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO
-0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao
-wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj
-7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS
-8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT
-BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
-/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg
-JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC
-NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3
-6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/
-3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm
-D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS
-CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
-3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
------END CERTIFICATE-----
-
-# Issuer: CN=Secure Global CA O=SecureTrust Corporation
-# Subject: CN=Secure Global CA O=SecureTrust Corporation
-# Label: "Secure Global CA"
-# Serial: 9751836167731051554232119481456978597
-# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de
-# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b
-# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69
------BEGIN CERTIFICATE-----
-MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK
-MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
-GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx
-MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg
-Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG
-SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ
-iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa
-/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ
-jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI
-HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7
-sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w
-gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF
-MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw
-KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG
-AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L
-URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO
-H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm
-I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY
-iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
-f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
------END CERTIFICATE-----
-
-# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited
-# Subject: CN=COMODO Certification Authority O=COMODO CA Limited
-# Label: "COMODO Certification Authority"
-# Serial: 104350513648249232941998508985834464573
-# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75
-# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b
-# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66
------BEGIN CERTIFICATE-----
-MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB
-gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
-A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
-BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
-MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
-YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
-RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0
-aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3
-UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI
-2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8
-Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp
-+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+
-DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O
-nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW
-/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g
-PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
-QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY
-SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv
-IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
-RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4
-zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd
-BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB
-ZQ==
------END CERTIFICATE-----
-
-# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited
-# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited
-# Label: "COMODO ECC Certification Authority"
-# Serial: 41578283867086692638256921589707938090
-# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23
-# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11
-# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7
------BEGIN CERTIFICATE-----
-MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
-MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
-BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
-IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
-MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
-ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
-T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
-biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
-FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
-cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
-BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
-BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
-fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
-GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
------END CERTIFICATE-----
-
-# Issuer: CN=Certigna O=Dhimyotis
-# Subject: CN=Certigna O=Dhimyotis
-# Label: "Certigna"
-# Serial: 18364802974209362175
-# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff
-# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97
-# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d
------BEGIN CERTIFICATE-----
-MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
-BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X
-DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ
-BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3
-DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4
-QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny
-gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw
-zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q
-130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2
-JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw
-DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw
-ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT
-AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj
-AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG
-9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h
-bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc
-fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu
-HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w
-t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
-WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
------END CERTIFICATE-----
-
-# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority
-# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority
-# Label: "ePKI Root Certification Authority"
-# Serial: 28956088682735189655030529057352760477
-# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3
-# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0
-# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5
------BEGIN CERTIFICATE-----
-MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
-MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
-ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
-Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw
-IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL
-SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF
-AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH
-SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh
-ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X
-DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1
-TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ
-fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA
-sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU
-WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS
-nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH
-dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip
-NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC
-AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF
-MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
-ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB
-uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl
-PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP
-JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/
-gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2
-j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6
-5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB
-o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS
-/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z
-Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE
-W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D
-hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
------END CERTIFICATE-----
-
-# Issuer: O=certSIGN OU=certSIGN ROOT CA
-# Subject: O=certSIGN OU=certSIGN ROOT CA
-# Label: "certSIGN ROOT CA"
-# Serial: 35210227249154
-# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17
-# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b
-# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb
------BEGIN CERTIFICATE-----
-MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT
-AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD
-QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP
-MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC
-ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do
-0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ
-UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d
-RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ
-OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv
-JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C
-AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O
-BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ
-LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY
-MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ
-44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I
-Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw
-i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN
-9u6wWk5JRFRYX0KD
------END CERTIFICATE-----
-
-# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services)
-# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services)
-# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny"
-# Serial: 80544274841616
-# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88
-# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91
-# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98
------BEGIN CERTIFICATE-----
-MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
-EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
-MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
-cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR
-dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB
-pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM
-b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm
-aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz
-IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
-MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT
-lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz
-AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5
-VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG
-ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2
-BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG
-AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M
-U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh
-bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C
-+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
-bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F
-uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
-XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
------END CERTIFICATE-----
-
-# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd.
-# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd.
-# Label: "Microsec e-Szigno Root CA 2009"
-# Serial: 14014712776195784473
-# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1
-# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e
-# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78
------BEGIN CERTIFICATE-----
-MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
-VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
-ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G
-CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y
-OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx
-FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp
-Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
-dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP
-kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc
-cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U
-fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7
-N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC
-xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1
-+rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
-A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM
-Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG
-SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h
-mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk
-ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
-tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c
-2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t
-HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW
------END CERTIFICATE-----
-
-# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3
-# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3
-# Label: "GlobalSign Root CA - R3"
-# Serial: 4835703278459759426209954
-# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28
-# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad
-# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b
------BEGIN CERTIFICATE-----
-MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
-A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
-Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
-MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
-A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
-hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
-RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
-gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
-KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
-QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
-XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
-DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
-LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
-RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
-jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
-6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
-mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
-Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
-WD9f
------END CERTIFICATE-----
-
-# Issuer: CN=Izenpe.com O=IZENPE S.A.
-# Subject: CN=Izenpe.com O=IZENPE S.A.
-# Label: "Izenpe.com"
-# Serial: 917563065490389241595536686991402621
-# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73
-# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19
-# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f
------BEGIN CERTIFICATE-----
-MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
-MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
-ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
-VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j
-b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq
-scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO
-xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H
-LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX
-uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD
-yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+
-JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q
-rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN
-BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L
-hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB
-QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+
-HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu
-Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg
-QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB
-BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
-MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
-AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA
-A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb
-laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56
-awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo
-JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw
-LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT
-VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk
-LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb
-UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/
-QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+
-naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls
-QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
------END CERTIFICATE-----
-
-# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc.
-# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc.
-# Label: "Go Daddy Root Certificate Authority - G2"
-# Serial: 0
-# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01
-# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b
-# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da
------BEGIN CERTIFICATE-----
-MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
-EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
-EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
-ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
-NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
-EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
-AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
-DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
-E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
-/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
-DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
-GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
-tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
-AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
-FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
-WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
-9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
-gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
-2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
-LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
-4uJEvlz36hz1
------END CERTIFICATE-----
-
-# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc.
-# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc.
-# Label: "Starfield Root Certificate Authority - G2"
-# Serial: 0
-# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96
-# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e
-# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5
------BEGIN CERTIFICATE-----
-MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
-EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
-HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
-ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw
-MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
-b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj
-aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp
-Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
-ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg
-nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1
-HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N
-Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN
-dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0
-HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
-BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G
-CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU
-sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3
-4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg
-8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
-pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
-mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
------END CERTIFICATE-----
-
-# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc.
-# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc.
-# Label: "Starfield Services Root Certificate Authority - G2"
-# Serial: 0
-# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2
-# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f
-# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5
------BEGIN CERTIFICATE-----
-MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx
-EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
-HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs
-ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
-MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD
-VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy
-ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy
-dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
-hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p
-OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2
-8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K
-Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe
-hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk
-6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw
-DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q
-AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI
-bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB
-ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z
-qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
-iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn
-0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN
-sSi6
------END CERTIFICATE-----
-
-# Issuer: CN=AffirmTrust Commercial O=AffirmTrust
-# Subject: CN=AffirmTrust Commercial O=AffirmTrust
-# Label: "AffirmTrust Commercial"
-# Serial: 8608355977964138876
-# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7
-# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7
-# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7
------BEGIN CERTIFICATE-----
-MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE
-BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
-dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL
-MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
-cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
-AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP
-Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr
-ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL
-MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1
-yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr
-VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/
-nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
-KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG
-XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj
-vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt
-Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g
-N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC
-nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
------END CERTIFICATE-----
-
-# Issuer: CN=AffirmTrust Networking O=AffirmTrust
-# Subject: CN=AffirmTrust Networking O=AffirmTrust
-# Label: "AffirmTrust Networking"
-# Serial: 8957382827206547757
-# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f
-# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f
-# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b
------BEGIN CERTIFICATE-----
-MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE
-BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
-dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL
-MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
-cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
-AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y
-YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua
-kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL
-QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp
-6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG
-yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i
-QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
-KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO
-tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu
-QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ
-Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u
-olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48
-x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
------END CERTIFICATE-----
-
-# Issuer: CN=AffirmTrust Premium O=AffirmTrust
-# Subject: CN=AffirmTrust Premium O=AffirmTrust
-# Label: "AffirmTrust Premium"
-# Serial: 7893706540734352110
-# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57
-# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27
-# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a
------BEGIN CERTIFICATE-----
-MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE
-BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz
-dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG
-A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U
-cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf
-qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ
-JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ
-+jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS
-s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5
-HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7
-70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG
-V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S
-qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S
-5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia
-C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX
-OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE
-FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
-BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2
-KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
-Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B
-8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ
-MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc
-0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ
-u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF
-u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH
-YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8
-GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO
-RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e
-KeC2uAloGRwYQw==
------END CERTIFICATE-----
-
-# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust
-# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust
-# Label: "AffirmTrust Premium ECC"
-# Serial: 8401224907861490260
-# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d
-# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb
-# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23
------BEGIN CERTIFICATE-----
-MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC
-VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ
-cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ
-BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt
-VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D
-0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9
-ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G
-A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G
-A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs
-aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I
-flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ==
------END CERTIFICATE-----
-
-# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority
-# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority
-# Label: "Certum Trusted Network CA"
-# Serial: 279744
-# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78
-# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e
-# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e
------BEGIN CERTIFICATE-----
-MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM
-MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D
-ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU
-cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3
-WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg
-Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw
-IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B
-AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH
-UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM
-TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU
-BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM
-kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x
-AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV
-HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV
-HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y
-sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL
-I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8
-J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY
-VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
-03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
------END CERTIFICATE-----
-
-# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA
-# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA
-# Label: "TWCA Root Certification Authority"
-# Serial: 1
-# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79
-# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48
-# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44
------BEGIN CERTIFICATE-----
-MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES
-MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU
-V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz
-WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO
-LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm
-aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
-AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE
-AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH
-K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX
-RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z
-rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx
-3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
-HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq
-hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC
-MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls
-XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D
-lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn
-aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ
-YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
------END CERTIFICATE-----
-
-# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2
-# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2
-# Label: "Security Communication RootCA2"
-# Serial: 0
-# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43
-# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74
-# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6
------BEGIN CERTIFICATE-----
-MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl
-MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe
-U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX
-DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy
-dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj
-YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV
-OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr
-zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM
-VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ
-hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO
-ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw
-awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs
-OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
-DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF
-coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc
-okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8
-t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy
-1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/
-SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
------END CERTIFICATE-----
-
-# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967
-# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967
-# Label: "Actalis Authentication Root CA"
-# Serial: 6271844772424770508
-# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6
-# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac
-# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66
------BEGIN CERTIFICATE-----
-MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE
-BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w
-MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
-IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC
-SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1
-ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB
-MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv
-UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX
-4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9
-KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/
-gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb
-rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ
-51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F
-be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe
-KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F
-v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn
-fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7
-jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz
-ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
-ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL
-e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70
-jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz
-WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V
-SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j
-pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX
-X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok
-fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R
-K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU
-ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU
-LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT
-LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
------END CERTIFICATE-----
-
-# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327
-# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327
-# Label: "Buypass Class 2 Root CA"
-# Serial: 2
-# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29
-# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99
-# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48
------BEGIN CERTIFICATE-----
-MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
-MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
-Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow
-TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
-HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
-BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr
-6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV
-L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91
-1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx
-MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ
-QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB
-arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr
-Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi
-FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS
-P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN
-9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP
-AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz
-uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h
-9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
-A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t
-OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo
-+fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7
-KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2
-DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us
-H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ
-I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7
-5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h
-3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz
-Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=
------END CERTIFICATE-----
-
-# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327
-# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327
-# Label: "Buypass Class 3 Root CA"
-# Serial: 2
-# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec
-# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57
-# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d
------BEGIN CERTIFICATE-----
-MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
-MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
-Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow
-TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
-HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
-BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y
-ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E
-N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9
-tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX
-0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c
-/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X
-KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY
-zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS
-O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D
-34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP
-K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3
-AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv
-Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj
-QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
-cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS
-IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2
-HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa
-O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv
-033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u
-dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE
-kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41
-3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD
-u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq
-4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc=
------END CERTIFICATE-----
-
-# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center
-# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center
-# Label: "T-TeleSec GlobalRoot Class 3"
-# Serial: 1
-# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef
-# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1
-# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd
------BEGIN CERTIFICATE-----
-MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
-KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
-BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
-YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1
-OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
-aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
-ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G
-CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN
-8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/
-RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4
-hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5
-ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM
-EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj
-QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1
-A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy
-WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ
-1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30
-6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT
-91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
-e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p
-TpPDpFQUWw==
------END CERTIFICATE-----
-
-# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH
-# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH
-# Label: "D-TRUST Root Class 3 CA 2 2009"
-# Serial: 623603
-# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f
-# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0
-# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1
------BEGIN CERTIFICATE-----
-MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
-MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
-bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha
-ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM
-HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB
-BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03
-UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42
-tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R
-ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM
-lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp
-/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G
-A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G
-A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj
-dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy
-MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl
-cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js
-L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL
-BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni
-acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
-o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K
-zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8
-PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y
-Johw1+qRzT65ysCQblrGXnRl11z+o+I=
------END CERTIFICATE-----
-
-# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH
-# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH
-# Label: "D-TRUST Root Class 3 CA 2 EV 2009"
-# Serial: 623604
-# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6
-# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83
-# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81
------BEGIN CERTIFICATE-----
-MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF
-MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD
-bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw
-NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV
-BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI
-hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn
-ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0
-3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z
-qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR
-p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8
-HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw
-ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea
-HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw
-Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh
-c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E
-RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt
-dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku
-Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp
-3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
-nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF
-CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na
-xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX
-KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1
------END CERTIFICATE-----
-
-# Issuer: CN=CA Disig Root R2 O=Disig a.s.
-# Subject: CN=CA Disig Root R2 O=Disig a.s.
-# Label: "CA Disig Root R2"
-# Serial: 10572350602393338211
-# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03
-# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71
-# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03
------BEGIN CERTIFICATE-----
-MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV
-BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
-MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy
-MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
-EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw
-ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe
-NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH
-PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I
-x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe
-QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR
-yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO
-QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912
-H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ
-QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD
-i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs
-nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1
-rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
-DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI
-hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
-tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf
-GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb
-lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka
-+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal
-TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i
-nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3
-gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr
-G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os
-zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x
-L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL
------END CERTIFICATE-----
-
-# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV
-# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV
-# Label: "ACCVRAIZ1"
-# Serial: 6828503384748696800
-# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02
-# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17
-# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13
------BEGIN CERTIFICATE-----
-MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE
-AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw
-CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ
-BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND
-VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb
-qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY
-HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo
-G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA
-lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr
-IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/
-0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH
-k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47
-4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO
-m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa
-cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl
-uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI
-KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls
-ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG
-AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
-VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT
-VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG
-CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA
-cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA
-QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA
-7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA
-cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA
-QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA
-czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu
-aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt
-aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud
-DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF
-BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp
-D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU
-JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m
-AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD
-vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms
-tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH
-7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
-I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA
-h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF
-d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H
-pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7
------END CERTIFICATE-----
-
-# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA
-# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA
-# Label: "TWCA Global Root CA"
-# Serial: 3262
-# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96
-# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65
-# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b
------BEGIN CERTIFICATE-----
-MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx
-EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT
-VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5
-NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT
-B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG
-SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF
-10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz
-0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh
-MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH
-zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc
-46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2
-yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi
-laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP
-oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA
-BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE
-qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm
-4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
-/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL
-1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
-LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF
-H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo
-RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+
-nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh
-15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW
-6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW
-nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j
-wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz
-aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy
-KwbQBM0=
------END CERTIFICATE-----
-
-# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera
-# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera
-# Label: "TeliaSonera Root CA v1"
-# Serial: 199041966741090107964904287217786801558
-# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c
-# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37
-# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89
------BEGIN CERTIFICATE-----
-MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw
-NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv
-b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD
-VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2
-MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F
-VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1
-7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X
-Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+
-/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs
-81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm
-dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe
-Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu
-sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4
-pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs
-slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ
-arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD
-VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG
-9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl
-dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
-0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj
-TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed
-Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7
-Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI
-OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7
-vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW
-t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn
-HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx
-SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
------END CERTIFICATE-----
-
-# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center
-# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center
-# Label: "T-TeleSec GlobalRoot Class 2"
-# Serial: 1
-# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a
-# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9
-# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52
------BEGIN CERTIFICATE-----
-MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
-KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
-BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
-YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1
-OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
-aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
-ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G
-CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd
-AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC
-FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi
-1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq
-jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ
-wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj
-QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/
-WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy
-NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC
-uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw
-IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6
-g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
-9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP
-BSeOE6Fuwg==
------END CERTIFICATE-----
-
-# Issuer: CN=Atos TrustedRoot 2011 O=Atos
-# Subject: CN=Atos TrustedRoot 2011 O=Atos
-# Label: "Atos TrustedRoot 2011"
-# Serial: 6643877497813316402
-# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56
-# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21
-# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74
------BEGIN CERTIFICATE-----
-MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE
-AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG
-EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM
-FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC
-REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp
-Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM
-VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+
-SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ
-4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L
-cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi
-eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV
-HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG
-A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3
-DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j
-vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP
-DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc
-maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D
-lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv
-KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
------END CERTIFICATE-----
-
-# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited
-# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited
-# Label: "QuoVadis Root CA 1 G3"
-# Serial: 687049649626669250736271037606554624078720034195
-# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab
-# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67
-# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74
------BEGIN CERTIFICATE-----
-MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL
-BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
-BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00
-MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
-aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG
-SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV
-wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe
-rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341
-68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh
-4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp
-UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o
-abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc
-3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G
-KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt
-hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO
-Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt
-zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
-BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD
-ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
-MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2
-cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN
-qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5
-YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv
-b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2
-8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k
-NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj
-ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp
-q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt
-nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD
------END CERTIFICATE-----
-
-# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited
-# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited
-# Label: "QuoVadis Root CA 2 G3"
-# Serial: 390156079458959257446133169266079962026824725800
-# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06
-# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36
-# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40
------BEGIN CERTIFICATE-----
-MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL
-BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
-BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00
-MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
-aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG
-SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf
-qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW
-n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym
-c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+
-O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1
-o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j
-IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq
-IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz
-8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh
-vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l
-7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG
-cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
-BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD
-ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
-AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC
-roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga
-W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n
-lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE
-+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV
-csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd
-dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg
-KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM
-HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4
-WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M
------END CERTIFICATE-----
-
-# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited
-# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited
-# Label: "QuoVadis Root CA 3 G3"
-# Serial: 268090761170461462463995952157327242137089239581
-# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7
-# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d
-# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46
------BEGIN CERTIFICATE-----
-MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL
-BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
-BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00
-MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
-aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG
-SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR
-/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu
-FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR
-U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c
-ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR
-FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k
-A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw
-eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl
-sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp
-VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q
-A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+
-ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
-BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD
-ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
-KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI
-FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv
-oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg
-u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP
-0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf
-3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl
-8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+
-DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN
-PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/
-ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0
------END CERTIFICATE-----
-
-# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com
-# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com
-# Label: "DigiCert Assured ID Root G2"
-# Serial: 15385348160840213938643033620894905419
-# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d
-# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f
-# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85
------BEGIN CERTIFICATE-----
-MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
-d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
-b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG
-EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
-cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi
-MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA
-n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc
-biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp
-EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA
-bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu
-YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB
-AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW
-BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI
-QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I
-0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni
-lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9
-B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv
-ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
-IhNzbM8m9Yop5w==
------END CERTIFICATE-----
-
-# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com
-# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com
-# Label: "DigiCert Assured ID Root G3"
-# Serial: 15459312981008553731928384953135426796
-# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb
-# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89
-# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2
------BEGIN CERTIFICATE-----
-MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw
-CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
-ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
-RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV
-UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
-Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq
-hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf
-Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q
-RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
-BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD
-AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY
-JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv
-6pZjamVFkpUBtA==
------END CERTIFICATE-----
-
-# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com
-# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com
-# Label: "DigiCert Global Root G2"
-# Serial: 4293743540046975378534879503202253541
-# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44
-# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4
-# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f
------BEGIN CERTIFICATE-----
-MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
-d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
-MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
-MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
-b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
-9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
-2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
-1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
-q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
-tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
-vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
-BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
-5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
-1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
-NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
-Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
-8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
-pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
-MrY=
------END CERTIFICATE-----
-
-# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com
-# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com
-# Label: "DigiCert Global Root G3"
-# Serial: 7089244469030293291760083333884364146
-# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca
-# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e
-# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0
------BEGIN CERTIFICATE-----
-MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
-CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
-ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
-Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw
-EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
-IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF
-K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG
-fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO
-Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd
-BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx
-AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/
-oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8
-sycX
------END CERTIFICATE-----
-
-# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com
-# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com
-# Label: "DigiCert Trusted Root G4"
-# Serial: 7451500558977370777930084869016614236
-# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49
-# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4
-# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88
------BEGIN CERTIFICATE-----
-MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
-d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
-RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
-UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
-Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
-SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
-ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
-xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
-ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
-DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
-jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
-CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
-EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
-fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
-uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
-chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
-9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
-hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
-ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
-SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
-+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
-fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
-sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
-cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
-0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
-4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
-r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
-/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
-gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
------END CERTIFICATE-----
-
-# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited
-# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited
-# Label: "COMODO RSA Certification Authority"
-# Serial: 101909084537582093308941363524873193117
-# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18
-# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4
-# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34
------BEGIN CERTIFICATE-----
-MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
-hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
-A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
-BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
-MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
-EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
-Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
-dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
-6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
-pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
-9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
-/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
-Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
-+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
-qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
-SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
-u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
-Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
-crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
-FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
-/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
-wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
-4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
-2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
-FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
-CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
-boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
-jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
-S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
-QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
-0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
-NVOFBkpdn627G190
------END CERTIFICATE-----
-
-# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network
-# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network
-# Label: "USERTrust RSA Certification Authority"
-# Serial: 2645093764781058787591871645665788717
-# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5
-# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e
-# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2
------BEGIN CERTIFICATE-----
-MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
-iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
-cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
-BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
-MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
-BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
-aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
-dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
-AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
-3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
-tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
-Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
-VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
-79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
-c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
-Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
-c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
-UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
-Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
-BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
-A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
-Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
-VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
-ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
-8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
-iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
-Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
-XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
-qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
-VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
-L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
-jjxDah2nGN59PRbxYvnKkKj9
------END CERTIFICATE-----
-
-# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network
-# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network
-# Label: "USERTrust ECC Certification Authority"
-# Serial: 123013823720199481456569720443997572134
-# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1
-# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0
-# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a
------BEGIN CERTIFICATE-----
-MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL
-MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
-eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
-JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx
-MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
-Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg
-VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm
-aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo
-I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng
-o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G
-A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD
-VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB
-zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW
-RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
------END CERTIFICATE-----
-
-# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5
-# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5
-# Label: "GlobalSign ECC Root CA - R5"
-# Serial: 32785792099990507226680698011560947931244
-# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08
-# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa
-# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24
------BEGIN CERTIFICATE-----
-MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
-MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
-bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
-DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
-QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
-MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc
-8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke
-hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
-VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI
-KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg
-515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO
-xwy8p2Fp8fc74SrL+SvzZpA3
------END CERTIFICATE-----
-
-# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust
-# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust
-# Label: "IdenTrust Commercial Root CA 1"
-# Serial: 13298821034946342390520003877796839426
-# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7
-# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25
-# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae
------BEGIN CERTIFICATE-----
-MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK
-MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu
-VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw
-MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw
-JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG
-SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT
-3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU
-+ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp
-S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1
-bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi
-T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL
-vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK
-Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK
-dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT
-c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv
-l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N
-iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
-/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD
-ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH
-6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt
-LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93
-nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3
-+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK
-W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT
-AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq
-l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG
-4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ
-mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A
-7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H
------END CERTIFICATE-----
-
-# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust
-# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust
-# Label: "IdenTrust Public Sector Root CA 1"
-# Serial: 13298821034946342390521976156843933698
-# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba
-# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd
-# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f
------BEGIN CERTIFICATE-----
-MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN
-MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu
-VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN
-MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0
-MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi
-MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7
-ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy
-RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS
-bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF
-/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R
-3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw
-EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy
-9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V
-GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ
-2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV
-WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD
-W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
-BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN
-AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj
-t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV
-DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9
-TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G
-lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW
-mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df
-WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5
-+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ
-tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA
-GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv
-8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c
------END CERTIFICATE-----
-
-# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only
-# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only
-# Label: "Entrust Root Certification Authority - G2"
-# Serial: 1246989352
-# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2
-# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4
-# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39
------BEGIN CERTIFICATE-----
-MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC
-VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50
-cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs
-IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz
-dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy
-NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu
-dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt
-dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0
-aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj
-YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
-AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T
-RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN
-cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW
-wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1
-U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0
-jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
-BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN
-BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/
-jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
-Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v
-1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R
-nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH
-VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==
------END CERTIFICATE-----
-
-# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only
-# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only
-# Label: "Entrust Root Certification Authority - EC1"
-# Serial: 51543124481930649114116133369
-# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc
-# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47
-# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5
------BEGIN CERTIFICATE-----
-MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG
-A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3
-d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu
-dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq
-RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy
-MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD
-VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
-L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g
-Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD
-ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi
-A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt
-ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH
-Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
-BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC
-R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX
-hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
------END CERTIFICATE-----
-
-# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority
-# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority
-# Label: "CFCA EV ROOT"
-# Serial: 407555286
-# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30
-# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83
-# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd
------BEGIN CERTIFICATE-----
-MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD
-TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y
-aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx
-MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j
-aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP
-T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03
-sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL
-TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5
-/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp
-7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz
-EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt
-hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP
-a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot
-aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg
-TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV
-PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv
-cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL
-tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd
-BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB
-ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT
-ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL
-jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS
-ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy
-P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19
-xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d
-Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN
-5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe
-/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z
-AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
-5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
------END CERTIFICATE-----
-
-# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed
-# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed
-# Label: "OISTE WISeKey Global Root GB CA"
-# Serial: 157768595616588414422159278966750757568
-# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d
-# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed
-# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6
------BEGIN CERTIFICATE-----
-MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt
-MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg
-Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i
-YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x
-CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG
-b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh
-bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3
-HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx
-WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX
-1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk
-u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P
-99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r
-M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw
-AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB
-BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh
-cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5
-gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO
-ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf
-aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic
-Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=
------END CERTIFICATE-----
-
-# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A.
-# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A.
-# Label: "SZAFIR ROOT CA2"
-# Serial: 357043034767186914217277344587386743377558296292
-# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99
-# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de
-# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe
------BEGIN CERTIFICATE-----
-MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL
-BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6
-ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw
-NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L
-cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg
-Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN
-QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT
-3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw
-3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6
-3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5
-BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN
-XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
-AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF
-AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw
-8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG
-nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP
-oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy
-d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg
-LvWpCz/UXeHPhJ/iGcJfitYgHuNztw==
------END CERTIFICATE-----
-
-# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority
-# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority
-# Label: "Certum Trusted Network CA 2"
-# Serial: 44979900017204383099463764357512596969
-# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2
-# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92
-# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04
------BEGIN CERTIFICATE-----
-MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB
-gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu
-QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG
-A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz
-OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ
-VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp
-ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3
-b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA
-DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn
-0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB
-OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE
-fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E
-Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m
-o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i
-sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW
-OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez
-Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS
-adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n
-3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
-AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC
-AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ
-F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf
-CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29
-XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm
-djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/
-WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb
-AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq
-P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko
-b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj
-XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P
-5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi
-DrW5viSP
------END CERTIFICATE-----
-
-# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority
-# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority
-# Label: "Hellenic Academic and Research Institutions RootCA 2015"
-# Serial: 0
-# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce
-# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6
-# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36
------BEGIN CERTIFICATE-----
-MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix
-DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k
-IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT
-N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v
-dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG
-A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh
-ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx
-QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
-dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
-AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA
-4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0
-AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10
-4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C
-ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV
-9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD
-gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6
-Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq
-NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko
-LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc
-Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV
-HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd
-ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I
-XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI
-M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot
-9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V
-Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea
-j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh
-X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ
-l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf
-bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4
-pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK
-e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0
-vm9qp/UsQu0yrbYhnr68
------END CERTIFICATE-----
-
-# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority
-# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority
-# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015"
-# Serial: 0
-# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef
-# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66
-# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33
------BEGIN CERTIFICATE-----
-MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN
-BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
-c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl
-bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv
-b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ
-BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj
-YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5
-MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0
-dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg
-QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa
-jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC
-MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi
-C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep
-lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof
-TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR
------END CERTIFICATE-----
-
-# Issuer: CN=ISRG Root X1 O=Internet Security Research Group
-# Subject: CN=ISRG Root X1 O=Internet Security Research Group
-# Label: "ISRG Root X1"
-# Serial: 172886928669790476064670243504169061120
-# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e
-# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8
-# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6
------BEGIN CERTIFICATE-----
-MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
-TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
-cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
-WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
-ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
-MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
-h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
-0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
-A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
-T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
-B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
-B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
-KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
-OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
-jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
-qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
-rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
-HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
-hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
-ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
-3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
-NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
-ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
-TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
-jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
-oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
-4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
-mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
-emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
------END CERTIFICATE-----
-
-# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM
-# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM
-# Label: "AC RAIZ FNMT-RCM"
-# Serial: 485876308206448804701554682760554759
-# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d
-# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20
-# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa
------BEGIN CERTIFICATE-----
-MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx
-CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ
-WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ
-BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG
-Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/
-yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf
-BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz
-WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF
-tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z
-374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC
-IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL
-mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7
-wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS
-MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2
-ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet
-UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw
-AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H
-YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3
-LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD
-nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1
-RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM
-LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf
-77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N
-JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm
-fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp
-6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp
-1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B
-9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok
-RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv
-uu8wd+RU4riEmViAqhOLUTpPSPaLtrM=
------END CERTIFICATE-----
-
-# Issuer: CN=Amazon Root CA 1 O=Amazon
-# Subject: CN=Amazon Root CA 1 O=Amazon
-# Label: "Amazon Root CA 1"
-# Serial: 143266978916655856878034712317230054538369994
-# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6
-# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16
-# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e
------BEGIN CERTIFICATE-----
-MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
-ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
-b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL
-MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
-b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj
-ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM
-9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw
-IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6
-VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L
-93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm
-jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
-AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA
-A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI
-U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs
-N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv
-o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU
-5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy
-rqXRfboQnoZsG4q5WTP468SQvvG5
------END CERTIFICATE-----
-
-# Issuer: CN=Amazon Root CA 2 O=Amazon
-# Subject: CN=Amazon Root CA 2 O=Amazon
-# Label: "Amazon Root CA 2"
-# Serial: 143266982885963551818349160658925006970653239
-# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66
-# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a
-# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4
------BEGIN CERTIFICATE-----
-MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF
-ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
-b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL
-MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
-b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK
-gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ
-W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg
-1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K
-8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r
-2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me
-z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR
-8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj
-mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz
-7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6
-+XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI
-0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB
-Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm
-UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2
-LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY
-+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS
-k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl
-7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm
-btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl
-urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+
-fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63
-n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE
-76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H
-9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT
-4PsJYGw=
------END CERTIFICATE-----
-
-# Issuer: CN=Amazon Root CA 3 O=Amazon
-# Subject: CN=Amazon Root CA 3 O=Amazon
-# Label: "Amazon Root CA 3"
-# Serial: 143266986699090766294700635381230934788665930
-# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87
-# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e
-# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4
------BEGIN CERTIFICATE-----
-MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5
-MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
-Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
-A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
-Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl
-ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j
-QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr
-ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr
-BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM
-YyRIHN8wfdVoOw==
------END CERTIFICATE-----
-
-# Issuer: CN=Amazon Root CA 4 O=Amazon
-# Subject: CN=Amazon Root CA 4 O=Amazon
-# Label: "Amazon Root CA 4"
-# Serial: 143266989758080763974105200630763877849284878
-# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd
-# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be
-# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92
------BEGIN CERTIFICATE-----
-MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5
-MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
-Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
-A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
-Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi
-9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk
-M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB
-/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB
-MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw
-CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
-1KyLa2tJElMzrdfkviT8tQp21KW8EA==
------END CERTIFICATE-----
-
-# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM
-# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM
-# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1"
-# Serial: 1
-# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49
-# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca
-# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16
------BEGIN CERTIFICATE-----
-MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx
-GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp
-bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w
-KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0
-BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy
-dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG
-EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll
-IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU
-QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT
-TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg
-LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7
-a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr
-LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr
-N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X
-YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/
-iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f
-AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH
-V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
-BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh
-AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf
-IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4
-lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c
-8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf
-lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=
------END CERTIFICATE-----
-
-# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD.
-# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD.
-# Label: "GDCA TrustAUTH R5 ROOT"
-# Serial: 9009899650740120186
-# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4
-# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4
-# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93
------BEGIN CERTIFICATE-----
-MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE
-BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ
-IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0
-MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV
-BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w
-HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF
-AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj
-Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj
-TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u
-KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj
-qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm
-MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12
-ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP
-zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk
-L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC
-jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA
-HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC
-AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB
-/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg
-p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm
-DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5
-COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry
-L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf
-JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg
-IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io
-2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV
-09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ
-XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq
-T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe
-MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g==
------END CERTIFICATE-----
-
-# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation
-# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation
-# Label: "SSL.com Root Certification Authority RSA"
-# Serial: 8875640296558310041
-# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29
-# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb
-# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69
------BEGIN CERTIFICATE-----
-MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE
-BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK
-DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp
-Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz
-OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv
-dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv
-bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN
-AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R
-xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX
-qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC
-C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3
-6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh
-/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF
-YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E
-JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc
-US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8
-ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm
-+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi
-M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV
-HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G
-A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV
-cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc
-Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs
-PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/
-q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0
-cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr
-a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I
-H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y
-K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu
-nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf
-oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY
-Ic2wBlX7Jz9TkHCpBB5XJ7k=
------END CERTIFICATE-----
-
-# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation
-# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation
-# Label: "SSL.com Root Certification Authority ECC"
-# Serial: 8495723813297216424
-# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e
-# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a
-# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65
------BEGIN CERTIFICATE-----
-MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC
-VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
-U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0
-aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz
-WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0
-b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS
-b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB
-BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI
-7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg
-CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud
-EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD
-VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T
-kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+
-gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
------END CERTIFICATE-----
-
-# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation
-# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation
-# Label: "SSL.com EV Root Certification Authority RSA R2"
-# Serial: 6248227494352943350
-# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95
-# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a
-# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c
------BEGIN CERTIFICATE-----
-MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV
-BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE
-CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy
-dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy
-MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G
-A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD
-DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy
-MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq
-M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf
-OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa
-4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9
-HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR
-aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA
-b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ
-Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV
-PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO
-pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu
-UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY
-MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV
-HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4
-9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW
-s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5
-Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg
-cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM
-79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz
-/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt
-ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm
-Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK
-QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ
-w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi
-S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07
-mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==
------END CERTIFICATE-----
-
-# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation
-# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation
-# Label: "SSL.com EV Root Certification Authority ECC"
-# Serial: 3182246526754555285
-# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90
-# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d
-# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8
------BEGIN CERTIFICATE-----
-MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC
-VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
-U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp
-Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx
-NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv
-dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv
-bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49
-AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA
-VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku
-WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP
-MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX
-5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ
-ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg
-h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
------END CERTIFICATE-----
-
-# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
-# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
-# Label: "GlobalSign Root CA - R6"
-# Serial: 1417766617973444989252670301619537
-# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae
-# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1
-# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69
------BEGIN CERTIFICATE-----
-MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg
-MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh
-bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx
-MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET
-MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ
-KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI
-xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k
-ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD
-aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw
-LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw
-1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX
-k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2
-SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h
-bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n
-WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY
-rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce
-MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
-AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu
-bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
-nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt
-Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61
-55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj
-vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf
-cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz
-oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp
-nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs
-pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v
-JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R
-8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4
-5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
------END CERTIFICATE-----
-
-# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
-# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
-# Label: "OISTE WISeKey Global Root GC CA"
-# Serial: 44084345621038548146064804565436152554
-# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23
-# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31
-# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d
------BEGIN CERTIFICATE-----
-MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw
-CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91
-bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg
-Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ
-BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu
-ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS
-b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni
-eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W
-p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E
-BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T
-rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV
-57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg
-Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
------END CERTIFICATE-----
-
-# Issuer: CN=UCA Global G2 Root O=UniTrust
-# Subject: CN=UCA Global G2 Root O=UniTrust
-# Label: "UCA Global G2 Root"
-# Serial: 124779693093741543919145257850076631279
-# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8
-# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a
-# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c
------BEGIN CERTIFICATE-----
-MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9
-MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH
-bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x
-CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds
-b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr
-b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9
-kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm
-VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R
-VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc
-C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj
-tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY
-D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv
-j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl
-NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6
-iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP
-O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/
-BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV
-ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj
-L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
-1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl
-1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU
-b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV
-PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj
-y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb
-EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg
-DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI
-+Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy
-YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX
-UB+K+wb1whnw0A==
------END CERTIFICATE-----
-
-# Issuer: CN=UCA Extended Validation Root O=UniTrust
-# Subject: CN=UCA Extended Validation Root O=UniTrust
-# Label: "UCA Extended Validation Root"
-# Serial: 106100277556486529736699587978573607008
-# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2
-# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a
-# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24
------BEGIN CERTIFICATE-----
-MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH
-MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF
-eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx
-MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV
-BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB
-AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog
-D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS
-sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop
-O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk
-sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi
-c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj
-VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz
-KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/
-TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G
-sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs
-1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD
-fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T
-AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN
-l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
-ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ
-VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5
-c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp
-4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s
-t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj
-2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO
-vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C
-xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx
-cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM
-fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax
------END CERTIFICATE-----
-
-# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
-# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
-# Label: "Certigna Root CA"
-# Serial: 269714418870597844693661054334862075617
-# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77
-# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43
-# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68
------BEGIN CERTIFICATE-----
-MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw
-WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw
-MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x
-MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD
-VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX
-BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
-ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO
-ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M
-CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu
-I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm
-TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh
-C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf
-ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz
-IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT
-Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k
-JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5
-hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB
-GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
-FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
-1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov
-L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo
-dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr
-aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq
-hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L
-6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG
-HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6
-0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB
-lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi
-o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1
-gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v
-faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63
-Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh
-jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw
-3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
------END CERTIFICATE-----
-
-# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
-# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
-# Label: "emSign Root CA - G1"
-# Serial: 235931866688319308814040
-# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac
-# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c
-# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67
------BEGIN CERTIFICATE-----
-MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD
-VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU
-ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH
-MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO
-MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv
-Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN
-BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz
-f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO
-8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq
-d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM
-tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt
-Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB
-o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD
-AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x
-PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM
-wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d
-GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH
-6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby
-RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
-iN66zB+Afko=
------END CERTIFICATE-----
-
-# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
-# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
-# Label: "emSign ECC Root CA - G3"
-# Serial: 287880440101571086945156
-# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40
-# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1
-# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b
------BEGIN CERTIFICATE-----
-MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
-EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
-bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
-RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ
-TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
-b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw
-djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0
-WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS
-fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB
-zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq
-hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB
-CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
-+JbNR6iC8hZVdyR+EhCVBCyj
------END CERTIFICATE-----
-
-# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
-# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
-# Label: "emSign Root CA - C1"
-# Serial: 825510296613316004955058
-# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68
-# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01
-# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f
------BEGIN CERTIFICATE-----
-MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG
-A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg
-SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw
-MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
-biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v
-dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ
-BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ
-HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH
-3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH
-GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c
-xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1
-aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq
-TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
-BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87
-/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4
-kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG
-YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT
-+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo
-WXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
------END CERTIFICATE-----
-
-# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
-# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
-# Label: "emSign ECC Root CA - C3"
-# Serial: 582948710642506000014504
-# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5
-# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66
-# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3
------BEGIN CERTIFICATE-----
-MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG
-EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx
-IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw
-MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
-biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND
-IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci
-MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti
-sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O
-BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
-Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c
-3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J
-0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
------END CERTIFICATE-----
-
-# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post
-# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post
-# Label: "Hongkong Post Root CA 3"
-# Serial: 46170865288971385588281144162979347873371282084
-# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0
-# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02
-# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6
------BEGIN CERTIFICATE-----
-MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL
-BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ
-SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n
-a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5
-NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT
-CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u
-Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
-AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO
-dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI
-VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV
-9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY
-2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY
-vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt
-bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb
-x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+
-l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK
-TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj
-Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP
-BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
-i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw
-DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG
-7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk
-MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr
-gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk
-GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS
-3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm
-Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+
-l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c
-JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP
-L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa
-LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG
-mpv0
------END CERTIFICATE-----
-
-# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation
-# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation
-# Label: "Microsoft ECC Root Certificate Authority 2017"
-# Serial: 136839042543790627607696632466672567020
-# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67
-# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5
-# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02
------BEGIN CERTIFICATE-----
-MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw
-CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD
-VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw
-MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV
-UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy
-b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq
-hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR
-ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb
-hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E
-BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3
-FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV
-L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB
-iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=
------END CERTIFICATE-----
-
-# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation
-# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation
-# Label: "Microsoft RSA Root Certificate Authority 2017"
-# Serial: 40975477897264996090493496164228220339
-# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47
-# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74
-# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0
------BEGIN CERTIFICATE-----
-MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl
-MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw
-NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
-IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG
-EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N
-aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi
-MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ
-Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0
-ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1
-HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm
-gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ
-jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc
-aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG
-YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6
-W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K
-UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH
-+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q
-W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/
-BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC
-NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC
-LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC
-gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6
-tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh
-SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2
-TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3
-pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR
-xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp
-GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9
-dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN
-AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB
-RA+GsCyRxj3qrg+E
------END CERTIFICATE-----
-
-# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd.
-# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd.
-# Label: "e-Szigno Root CA 2017"
-# Serial: 411379200276854331539784714
-# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98
-# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1
-# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99
------BEGIN CERTIFICATE-----
-MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV
-BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk
-LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv
-b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ
-BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg
-THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v
-IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv
-xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H
-Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
-A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB
-eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo
-jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ
-+efcMQ==
------END CERTIFICATE-----
-
-# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2
-# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2
-# Label: "certSIGN Root CA G2"
-# Serial: 313609486401300475190
-# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7
-# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32
-# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05
------BEGIN CERTIFICATE-----
-MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV
-BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g
-Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ
-BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ
-R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF
-dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw
-vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ
-uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp
-n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs
-cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW
-xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P
-rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF
-DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx
-DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy
-LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C
-eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB
-/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ
-d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq
-kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC
-b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl
-qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0
-OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c
-NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk
-ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO
-pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj
-03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk
-PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE
-1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX
-QRBdJ3NghVdJIgc=
------END CERTIFICATE-----
-
-# Issuer: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc.
-# Subject: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc.
-# Label: "Trustwave Global Certification Authority"
-# Serial: 1846098327275375458322922162
-# MD5 Fingerprint: f8:1c:18:2d:2f:ba:5f:6d:a1:6c:bc:c7:ab:91:c7:0e
-# SHA1 Fingerprint: 2f:8f:36:4f:e1:58:97:44:21:59:87:a5:2a:9a:d0:69:95:26:7f:b5
-# SHA256 Fingerprint: 97:55:20:15:f5:dd:fc:3c:87:88:c0:06:94:45:55:40:88:94:45:00:84:f1:00:86:70:86:bc:1a:2b:b5:8d:c8
------BEGIN CERTIFICATE-----
-MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw
-CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x
-ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1
-c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx
-OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI
-SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI
-b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp
-Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
-ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn
-swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu
-7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8
-1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW
-80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP
-JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l
-RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw
-hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10
-coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc
-BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n
-twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud
-EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud
-DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W
-0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe
-uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q
-lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB
-aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE
-sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT
-MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe
-qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh
-VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8
-h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9
-EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK
-yeC2nOnOcXHebD8WpHk=
------END CERTIFICATE-----
-
-# Issuer: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc.
-# Subject: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc.
-# Label: "Trustwave Global ECC P256 Certification Authority"
-# Serial: 4151900041497450638097112925
-# MD5 Fingerprint: 5b:44:e3:8d:5d:36:86:26:e8:0d:05:d2:59:a7:83:54
-# SHA1 Fingerprint: b4:90:82:dd:45:0c:be:8b:5b:b1:66:d3:e2:a4:08:26:cd:ed:42:cf
-# SHA256 Fingerprint: 94:5b:bc:82:5e:a5:54:f4:89:d1:fd:51:a7:3d:df:2e:a6:24:ac:70:19:a0:52:05:22:5c:22:a7:8c:cf:a8:b4
------BEGIN CERTIFICATE-----
-MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD
-VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf
-BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3
-YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x
-NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G
-A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0
-d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF
-Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG
-SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN
-FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w
-DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw
-CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh
-DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7
------END CERTIFICATE-----
-
-# Issuer: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc.
-# Subject: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc.
-# Label: "Trustwave Global ECC P384 Certification Authority"
-# Serial: 2704997926503831671788816187
-# MD5 Fingerprint: ea:cf:60:c4:3b:b9:15:29:40:a1:97:ed:78:27:93:d6
-# SHA1 Fingerprint: e7:f3:a3:c8:cf:6f:c3:04:2e:6d:0e:67:32:c5:9e:68:95:0d:5e:d2
-# SHA256 Fingerprint: 55:90:38:59:c8:c0:c3:eb:b8:75:9e:ce:4e:25:57:22:5f:f5:75:8b:bd:38:eb:d4:82:76:60:1e:1b:d5:80:97
------BEGIN CERTIFICATE-----
-MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD
-VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf
-BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3
-YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x
-NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G
-A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0
-d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF
-Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB
-BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ
-j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF
-1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G
-A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3
-AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC
-MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu
-Sw==
------END CERTIFICATE-----
-
-# Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp.
-# Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp.
-# Label: "NAVER Global Root Certification Authority"
-# Serial: 9013692873798656336226253319739695165984492813
-# MD5 Fingerprint: c8:7e:41:f6:25:3b:f5:09:b3:17:e8:46:3d:bf:d0:9b
-# SHA1 Fingerprint: 8f:6b:f2:a9:27:4a:da:14:a0:c4:f4:8e:61:27:f9:c0:1e:78:5d:d1
-# SHA256 Fingerprint: 88:f4:38:dc:f8:ff:d1:fa:8f:42:91:15:ff:e5:f8:2a:e1:e0:6e:0c:70:c3:75:fa:ad:71:7b:34:a4:9e:72:65
------BEGIN CERTIFICATE-----
-MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM
-BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG
-T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0
-aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx
-CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD
-b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB
-dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA
-iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH
-38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE
-HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz
-kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP
-szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq
-vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf
-nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG
-YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo
-0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a
-CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K
-AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I
-36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
-Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN
-qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj
-cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm
-+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL
-hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe
-lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7
-p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8
-piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR
-LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX
-5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO
-dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul
-9XXeifdy
------END CERTIFICATE-----
-
-# Issuer: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres
-# Subject: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres
-# Label: "AC RAIZ FNMT-RCM SERVIDORES SEGUROS"
-# Serial: 131542671362353147877283741781055151509
-# MD5 Fingerprint: 19:36:9c:52:03:2f:d2:d1:bb:23:cc:dd:1e:12:55:bb
-# SHA1 Fingerprint: 62:ff:d9:9e:c0:65:0d:03:ce:75:93:d2:ed:3f:2d:32:c9:e3:e5:4a
-# SHA256 Fingerprint: 55:41:53:b1:3d:2c:f9:dd:b7:53:bf:be:1a:4e:0a:e0:8d:0a:a4:18:70:58:fe:60:a2:b8:62:b2:e4:b8:7b:cb
------BEGIN CERTIFICATE-----
-MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw
-CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw
-FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S
-Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5
-MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL
-DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS
-QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB
-BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH
-sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK
-Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
-VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu
-SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC
-MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy
-v+c=
------END CERTIFICATE-----
-
-# Issuer: CN=GlobalSign Root R46 O=GlobalSign nv-sa
-# Subject: CN=GlobalSign Root R46 O=GlobalSign nv-sa
-# Label: "GlobalSign Root R46"
-# Serial: 1552617688466950547958867513931858518042577
-# MD5 Fingerprint: c4:14:30:e4:fa:66:43:94:2a:6a:1b:24:5f:19:d0:ef
-# SHA1 Fingerprint: 53:a2:b0:4b:ca:6b:d6:45:e6:39:8a:8e:c4:0d:d2:bf:77:c3:a2:90
-# SHA256 Fingerprint: 4f:a3:12:6d:8d:3a:11:d1:c4:85:5a:4f:80:7c:ba:d6:cf:91:9d:3a:5a:88:b0:3b:ea:2c:63:72:d9:3c:40:c9
------BEGIN CERTIFICATE-----
-MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA
-MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD
-VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy
-MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt
-c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB
-AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ
-OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG
-vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud
-316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo
-0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE
-y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF
-zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE
-+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN
-I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs
-x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa
-ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC
-4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
-HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4
-7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg
-JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti
-2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk
-pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF
-FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt
-rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk
-ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5
-u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP
-4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6
-N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3
-vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6
------END CERTIFICATE-----
-
-# Issuer: CN=GlobalSign Root E46 O=GlobalSign nv-sa
-# Subject: CN=GlobalSign Root E46 O=GlobalSign nv-sa
-# Label: "GlobalSign Root E46"
-# Serial: 1552617690338932563915843282459653771421763
-# MD5 Fingerprint: b5:b8:66:ed:de:08:83:e3:c9:e2:01:34:06:ac:51:6f
-# SHA1 Fingerprint: 39:b4:6c:d5:fe:80:06:eb:e2:2f:4a:bb:08:33:a0:af:db:b9:dd:84
-# SHA256 Fingerprint: cb:b9:c4:4d:84:b8:04:3e:10:50:ea:31:a6:9f:51:49:55:d7:bf:d2:e2:c6:b4:93:01:01:9a:d6:1d:9f:50:58
------BEGIN CERTIFICATE-----
-MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx
-CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD
-ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw
-MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex
-HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA
-IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq
-R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd
-yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
-DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ
-7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8
-+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A=
------END CERTIFICATE-----
-
-# Issuer: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH
-# Subject: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH
-# Label: "GLOBALTRUST 2020"
-# Serial: 109160994242082918454945253
-# MD5 Fingerprint: 8a:c7:6f:cb:6d:e3:cc:a2:f1:7c:83:fa:0e:78:d7:e8
-# SHA1 Fingerprint: d0:67:c1:13:51:01:0c:aa:d0:c7:6a:65:37:31:16:26:4f:53:71:a2
-# SHA256 Fingerprint: 9a:29:6a:51:82:d1:d4:51:a2:e3:7f:43:9b:74:da:af:a2:67:52:33:29:f9:0f:9a:0d:20:07:c3:34:e2:3c:9a
------BEGIN CERTIFICATE-----
-MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG
-A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw
-FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx
-MDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u
-aXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq
-hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b
-RatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z
-YybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3
-QWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw
-yJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+
-BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ
-SaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH
-r96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0
-4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me
-dKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw
-q7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2
-nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
-AQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu
-H9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA
-VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC
-XtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd
-6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf
-+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi
-kvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7
-wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB
-TI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C
-MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn
-4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I
-aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy
-qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==
------END CERTIFICATE-----
-
-# Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz
-# Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz
-# Label: "ANF Secure Server Root CA"
-# Serial: 996390341000653745
-# MD5 Fingerprint: 26:a6:44:5a:d9:af:4e:2f:b2:1d:b6:65:b0:4e:e8:96
-# SHA1 Fingerprint: 5b:6e:68:d0:cc:15:b6:a0:5f:1e:c1:5f:ae:02:fc:6b:2f:5d:6f:74
-# SHA256 Fingerprint: fb:8f:ec:75:91:69:b9:10:6b:1e:51:16:44:c6:18:c5:13:04:37:3f:6c:06:43:08:8d:8b:ef:fd:1b:99:75:99
------BEGIN CERTIFICATE-----
-MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV
-BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk
-YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV
-BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN
-MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF
-UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD
-VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v
-dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj
-cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q
-yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH
-2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX
-H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL
-zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR
-p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz
-W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/
-SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn
-LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3
-n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B
-u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj
-o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO
-BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
-AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L
-9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej
-rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK
-pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0
-vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq
-OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ
-/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9
-2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI
-+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2
-MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo
-tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=
------END CERTIFICATE-----
-
-# Issuer: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority
-# Subject: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority
-# Label: "Certum EC-384 CA"
-# Serial: 160250656287871593594747141429395092468
-# MD5 Fingerprint: b6:65:b3:96:60:97:12:a1:ec:4e:e1:3d:a3:c6:c9:f1
-# SHA1 Fingerprint: f3:3e:78:3c:ac:df:f4:a2:cc:ac:67:55:69:56:d7:e5:16:3c:e1:ed
-# SHA256 Fingerprint: 6b:32:80:85:62:53:18:aa:50:d1:73:c9:8d:8b:da:09:d5:7e:27:41:3d:11:4c:f7:87:a0:f5:d0:6c:03:0c:f6
------BEGIN CERTIFICATE-----
-MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw
-CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw
-JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT
-EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0
-WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT
-LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX
-BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE
-KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm
-Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj
-QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8
-EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J
-UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn
-nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=
------END CERTIFICATE-----
-
-# Issuer: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority
-# Subject: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority
-# Label: "Certum Trusted Root CA"
-# Serial: 40870380103424195783807378461123655149
-# MD5 Fingerprint: 51:e1:c2:e7:fe:4c:84:af:59:0e:2f:f4:54:6f:ea:29
-# SHA1 Fingerprint: c8:83:44:c0:18:ae:9f:cc:f1:87:b7:8f:22:d1:c5:d7:45:84:ba:e5
-# SHA256 Fingerprint: fe:76:96:57:38:55:77:3e:37:a9:5e:7a:d4:d9:cc:96:c3:01:57:c1:5d:31:76:5b:a9:b1:57:04:e1:ae:78:fd
------BEGIN CERTIFICATE-----
-MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6
-MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu
-MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV
-BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw
-MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg
-U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo
-b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG
-SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ
-n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q
-p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq
-NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF
-8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3
-HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa
-mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi
-7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF
-ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P
-qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ
-v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6
-Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1
-vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD
-ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4
-WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo
-zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR
-5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ
-GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf
-5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq
-0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D
-P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM
-qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP
-0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf
-E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
------END CERTIFICATE-----
-
-# Issuer: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique
-# Subject: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique
-# Label: "TunTrust Root CA"
-# Serial: 108534058042236574382096126452369648152337120275
-# MD5 Fingerprint: 85:13:b9:90:5b:36:5c:b6:5e:b8:5a:f8:e0:31:57:b4
-# SHA1 Fingerprint: cf:e9:70:84:0f:e0:73:0f:9d:f6:0c:7f:2c:4b:ee:20:46:34:9c:bb
-# SHA256 Fingerprint: 2e:44:10:2a:b5:8c:b8:54:19:45:1c:8e:19:d9:ac:f3:66:2c:af:bc:61:4b:6a:53:96:0a:30:f7:d0:e2:eb:41
------BEGIN CERTIFICATE-----
-MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL
-BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg
-Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv
-b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG
-EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u
-IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ
-KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ
-n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd
-2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF
-VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ
-GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF
-li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU
-r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2
-eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb
-MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg
-jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB
-7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW
-5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE
-ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0
-90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z
-xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu
-QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4
-FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH
-22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP
-xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn
-dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5
-Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b
-nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ
-CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH
-u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj
-d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
------END CERTIFICATE-----
-
-# Issuer: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA
-# Subject: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA
-# Label: "HARICA TLS RSA Root CA 2021"
-# Serial: 76817823531813593706434026085292783742
-# MD5 Fingerprint: 65:47:9b:58:86:dd:2c:f0:fc:a2:84:1f:1e:96:c4:91
-# SHA1 Fingerprint: 02:2d:05:82:fa:88:ce:14:0c:06:79:de:7f:14:10:e9:45:d7:a5:6d
-# SHA256 Fingerprint: d9:5d:0e:8e:da:79:52:5b:f9:be:b1:1b:14:d2:10:0d:32:94:98:5f:0c:62:d9:fa:bd:9c:d9:99:ec:cb:7b:1d
------BEGIN CERTIFICATE-----
-MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs
-MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
-c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg
-Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL
-MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl
-YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv
-b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l
-mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE
-4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv
-a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M
-pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw
-Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b
-LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY
-AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB
-AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq
-E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr
-W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ
-CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF
-MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE
-AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU
-X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3
-f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja
-H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP
-JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P
-zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt
-jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0
-/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT
-BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79
-aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW
-xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU
-63ZTGI0RmLo=
------END CERTIFICATE-----
-
-# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA
-# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA
-# Label: "HARICA TLS ECC Root CA 2021"
-# Serial: 137515985548005187474074462014555733966
-# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0
-# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48
-# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01
------BEGIN CERTIFICATE-----
-MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw
-CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh
-cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v
-dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG
-A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj
-aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg
-Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7
-KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y
-STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw
-AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD
-AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw
-SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN
-nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
------END CERTIFICATE-----
-
-# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068
-# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068
-# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068"
-# Serial: 1977337328857672817
-# MD5 Fingerprint: 4e:6e:9b:54:4c:ca:b7:fa:48:e4:90:b1:15:4b:1c:a3
-# SHA1 Fingerprint: 0b:be:c2:27:22:49:cb:39:aa:db:35:5c:53:e3:8c:ae:78:ff:b6:fe
-# SHA256 Fingerprint: 57:de:05:83:ef:d2:b2:6e:03:61:da:99:da:9d:f4:64:8d:ef:7e:e8:44:1c:3b:72:8a:fa:9b:cd:e0:f9:b2:6a
------BEGIN CERTIFICATE-----
-MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE
-BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
-cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1
-MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
-Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
-MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
-thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
-cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
-L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
-NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
-X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
-m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
-Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
-EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
-KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
-6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
-OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc
-tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd
-IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j
-b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC
-AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw
-ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m
-iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF
-Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ
-hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P
-Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE
-EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV
-1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t
-CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR
-5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw
-f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9
-ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK
-GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV
------END CERTIFICATE-----
-
-# Issuer: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd.
-# Subject: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd.
-# Label: "vTrus ECC Root CA"
-# Serial: 630369271402956006249506845124680065938238527194
-# MD5 Fingerprint: de:4b:c1:f5:52:8c:9b:43:e1:3e:8f:55:54:17:8d:85
-# SHA1 Fingerprint: f6:9c:db:b0:fc:f6:02:13:b6:52:32:a6:a3:91:3f:16:70:da:c3:e1
-# SHA256 Fingerprint: 30:fb:ba:2c:32:23:8e:2a:98:54:7a:f9:79:31:e5:50:42:8b:9b:3f:1c:8e:eb:66:33:dc:fa:86:c5:b2:7d:d3
------BEGIN CERTIFICATE-----
-MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw
-RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY
-BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz
-MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u
-LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF
-K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0
-v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd
-e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD
-VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw
-V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA
-AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG
-GJTO
------END CERTIFICATE-----
-
-# Issuer: CN=vTrus Root CA O=iTrusChina Co.,Ltd.
-# Subject: CN=vTrus Root CA O=iTrusChina Co.,Ltd.
-# Label: "vTrus Root CA"
-# Serial: 387574501246983434957692974888460947164905180485
-# MD5 Fingerprint: b8:c9:37:df:fa:6b:31:84:64:c5:ea:11:6a:1b:75:fc
-# SHA1 Fingerprint: 84:1a:69:fb:f5:cd:1a:25:34:13:3d:e3:f8:fc:b8:99:d0:c9:14:b7
-# SHA256 Fingerprint: 8a:71:de:65:59:33:6f:42:6c:26:e5:38:80:d0:0d:88:a1:8d:a4:c6:a9:1f:0d:cb:61:94:e2:06:c5:c9:63:87
------BEGIN CERTIFICATE-----
-MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL
-BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x
-FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx
-MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s
-THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD
-ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc
-IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU
-AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+
-GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9
-8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH
-flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt
-J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim
-0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN
-pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ
-UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW
-OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB
-AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E
-BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet
-8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd
-nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j
-bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM
-Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv
-TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS
-S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr
-I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9
-b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB
-UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P
-Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven
-sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s=
------END CERTIFICATE-----
-
-# Issuer: CN=ISRG Root X2 O=Internet Security Research Group
-# Subject: CN=ISRG Root X2 O=Internet Security Research Group
-# Label: "ISRG Root X2"
-# Serial: 87493402998870891108772069816698636114
-# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5
-# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af
-# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70
------BEGIN CERTIFICATE-----
-MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
-CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
-R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
-MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
-ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
-EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
-+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
-ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
-AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
-zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
-tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
-/q4AaOeMSQ+2b1tbFfLn
------END CERTIFICATE-----
-
-# Issuer: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd.
-# Subject: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd.
-# Label: "HiPKI Root CA - G1"
-# Serial: 60966262342023497858655262305426234976
-# MD5 Fingerprint: 69:45:df:16:65:4b:e8:68:9a:8f:76:5f:ff:80:9e:d3
-# SHA1 Fingerprint: 6a:92:e4:a8:ee:1b:ec:96:45:37:e3:29:57:49:cd:96:e3:e5:d2:60
-# SHA256 Fingerprint: f0:15:ce:3c:c2:39:bf:ef:06:4b:e9:f1:d2:c4:17:e1:a0:26:4a:0a:94:be:1f:0c:8d:12:18:64:eb:69:49:cc
------BEGIN CERTIFICATE-----
-MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP
-MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
-ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa
-Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3
-YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx
-MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw
-qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv
-Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6
-lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz
-Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ
-KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK
-FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj
-HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr
-y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ
-/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM
-a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6
-fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
-HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG
-SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi
-7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc
-SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza
-ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc
-XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg
-iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho
-L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF
-Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr
-kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+
-vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU
-YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==
------END CERTIFICATE-----
-
-# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4
-# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4
-# Label: "GlobalSign ECC Root CA - R4"
-# Serial: 159662223612894884239637590694
-# MD5 Fingerprint: 26:29:f8:6d:e1:88:bf:a2:65:7f:aa:c4:cd:0f:7f:fc
-# SHA1 Fingerprint: 6b:a0:b0:98:e1:71:ef:5a:ad:fe:48:15:80:77:10:f4:bd:6f:0b:28
-# SHA256 Fingerprint: b0:85:d7:0b:96:4f:19:1a:73:e4:af:0d:54:ae:7a:0e:07:aa:fd:af:9b:71:dd:08:62:13:8a:b7:32:5a:24:a2
------BEGIN CERTIFICATE-----
-MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD
-VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh
-bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw
-MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g
-UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT
-BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx
-uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV
-HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/
-+wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147
-bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
------END CERTIFICATE-----
-
-# Issuer: CN=GTS Root R1 O=Google Trust Services LLC
-# Subject: CN=GTS Root R1 O=Google Trust Services LLC
-# Label: "GTS Root R1"
-# Serial: 159662320309726417404178440727
-# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40
-# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a
-# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf
------BEGIN CERTIFICATE-----
-MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
-CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
-MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
-MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
-Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
-A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
-27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
-Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
-TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
-qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
-szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
-Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
-MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
-wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
-aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
-VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
-AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
-FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
-C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
-QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
-h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
-7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
-ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
-MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
-Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
-6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
-0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
-2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
-bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
------END CERTIFICATE-----
-
-# Issuer: CN=GTS Root R2 O=Google Trust Services LLC
-# Subject: CN=GTS Root R2 O=Google Trust Services LLC
-# Label: "GTS Root R2"
-# Serial: 159662449406622349769042896298
-# MD5 Fingerprint: 1e:39:c0:53:e6:1e:29:82:0b:ca:52:55:36:5d:57:dc
-# SHA1 Fingerprint: 9a:44:49:76:32:db:de:fa:d0:bc:fb:5a:7b:17:bd:9e:56:09:24:94
-# SHA256 Fingerprint: 8d:25:cd:97:22:9d:bf:70:35:6b:da:4e:b3:cc:73:40:31:e2:4c:f0:0f:af:cf:d3:2d:c7:6e:b5:84:1c:7e:a8
------BEGIN CERTIFICATE-----
-MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw
-CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
-MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
-MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
-Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA
-A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt
-nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY
-6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu
-MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k
-RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg
-f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV
-+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo
-dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW
-Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa
-G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq
-gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID
-AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
-FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H
-vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8
-0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC
-B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u
-NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg
-yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev
-HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6
-xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR
-TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg
-JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV
-7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl
-6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL
------END CERTIFICATE-----
-
-# Issuer: CN=GTS Root R3 O=Google Trust Services LLC
-# Subject: CN=GTS Root R3 O=Google Trust Services LLC
-# Label: "GTS Root R3"
-# Serial: 159662495401136852707857743206
-# MD5 Fingerprint: 3e:e7:9d:58:02:94:46:51:94:e5:e0:22:4a:8b:e7:73
-# SHA1 Fingerprint: ed:e5:71:80:2b:c8:92:b9:5b:83:3c:d2:32:68:3f:09:cd:a0:1e:46
-# SHA256 Fingerprint: 34:d8:a7:3e:e2:08:d9:bc:db:0d:95:65:20:93:4b:4e:40:e6:94:82:59:6e:8b:6f:73:c8:42:6b:01:0a:6f:48
------BEGIN CERTIFICATE-----
-MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD
-VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
-A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
-WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
-IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
-AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G
-jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2
-4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
-BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7
-VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm
-ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X
------END CERTIFICATE-----
-
-# Issuer: CN=GTS Root R4 O=Google Trust Services LLC
-# Subject: CN=GTS Root R4 O=Google Trust Services LLC
-# Label: "GTS Root R4"
-# Serial: 159662532700760215368942768210
-# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8
-# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47
-# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d
------BEGIN CERTIFICATE-----
-MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD
-VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
-A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
-WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
-IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
-AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi
-QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR
-HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
-BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D
-9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8
-p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD
------END CERTIFICATE-----
-
-# Issuer: CN=Telia Root CA v2 O=Telia Finland Oyj
-# Subject: CN=Telia Root CA v2 O=Telia Finland Oyj
-# Label: "Telia Root CA v2"
-# Serial: 7288924052977061235122729490515358
-# MD5 Fingerprint: 0e:8f:ac:aa:82:df:85:b1:f4:dc:10:1c:fc:99:d9:48
-# SHA1 Fingerprint: b9:99:cd:d1:73:50:8a:c4:47:05:08:9c:8c:88:fb:be:a0:2b:40:cd
-# SHA256 Fingerprint: 24:2b:69:74:2f:cb:1e:5b:2a:bf:98:89:8b:94:57:21:87:54:4e:5b:4d:99:11:78:65:73:62:1f:6a:74:b8:2c
------BEGIN CERTIFICATE-----
-MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx
-CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE
-AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1
-NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ
-MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
-ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq
-AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9
-vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9
-lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD
-n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT
-7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o
-6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC
-TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6
-WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R
-DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI
-pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj
-YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy
-rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
-AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ
-8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi
-0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM
-A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS
-SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K
-TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF
-6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er
-3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt
-Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT
-VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW
-ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA
-rBPuUBQemMc=
------END CERTIFICATE-----
-
-# Issuer: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH
-# Subject: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH
-# Label: "D-TRUST BR Root CA 1 2020"
-# Serial: 165870826978392376648679885835942448534
-# MD5 Fingerprint: b5:aa:4b:d5:ed:f7:e3:55:2e:8f:72:0a:f3:75:b8:ed
-# SHA1 Fingerprint: 1f:5b:98:f0:e3:b5:f7:74:3c:ed:e6:b0:36:7d:32:cd:f4:09:41:67
-# SHA256 Fingerprint: e5:9a:aa:81:60:09:c2:2b:ff:5b:25:ba:d3:7d:f3:06:f0:49:79:7c:1f:81:d8:5a:b0:89:e6:57:bd:8f:00:44
------BEGIN CERTIFICATE-----
-MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw
-CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
-VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5
-NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG
-A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB
-BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS
-zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0
-QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/
-VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g
-PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf
-Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l
-dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1
-c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO
-PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW
-wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV
-dWNbFJWcHwHP2NVypw87
------END CERTIFICATE-----
-
-# Issuer: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH
-# Subject: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH
-# Label: "D-TRUST EV Root CA 1 2020"
-# Serial: 126288379621884218666039612629459926992
-# MD5 Fingerprint: 8c:2d:9d:70:9f:48:99:11:06:11:fb:e9:cb:30:c0:6e
-# SHA1 Fingerprint: 61:db:8c:21:59:69:03:90:d8:7c:9c:12:86:54:cf:9d:3d:f4:dd:07
-# SHA256 Fingerprint: 08:17:0d:1a:a3:64:53:90:1a:2f:95:92:45:e3:47:db:0c:8d:37:ab:aa:bc:56:b8:1a:a1:00:dc:95:89:70:db
------BEGIN CERTIFICATE-----
-MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw
-CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
-VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5
-NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG
-A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB
-BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC
-/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD
-wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3
-OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g
-PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf
-Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l
-dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1
-c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO
-PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA
-y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb
-gfM0agPnIjhQW+0ZT0MW
------END CERTIFICATE-----
-
-# Issuer: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc.
-# Subject: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc.
-# Label: "DigiCert TLS ECC P384 Root G5"
-# Serial: 13129116028163249804115411775095713523
-# MD5 Fingerprint: d3:71:04:6a:43:1c:db:a6:59:e1:a8:a3:aa:c5:71:ed
-# SHA1 Fingerprint: 17:f3:de:5e:9f:0f:19:e9:8e:f6:1f:32:26:6e:20:c4:07:ae:30:ee
-# SHA256 Fingerprint: 01:8e:13:f0:77:25:32:cf:80:9b:d1:b1:72:81:86:72:83:fc:48:c6:e1:3b:e9:c6:98:12:85:4a:49:0c:1b:05
------BEGIN CERTIFICATE-----
-MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw
-CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp
-Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2
-MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
-bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG
-ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS
-7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp
-0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS
-B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49
-BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ
-LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4
-DXZDjC5Ty3zfDBeWUA==
------END CERTIFICATE-----
-
-# Issuer: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc.
-# Subject: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc.
-# Label: "DigiCert TLS RSA4096 Root G5"
-# Serial: 11930366277458970227240571539258396554
-# MD5 Fingerprint: ac:fe:f7:34:96:a9:f2:b3:b4:12:4b:e4:27:41:6f:e1
-# SHA1 Fingerprint: a7:88:49:dc:5d:7c:75:8c:8c:de:39:98:56:b3:aa:d0:b2:a5:71:35
-# SHA256 Fingerprint: 37:1a:00:dc:05:33:b3:72:1a:7e:eb:40:e8:41:9e:70:79:9d:2b:0a:0f:2c:1d:80:69:31:65:f7:ce:c4:ad:75
------BEGIN CERTIFICATE-----
-MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN
-MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT
-HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN
-NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs
-IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi
-MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+
-ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0
-2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp
-wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM
-pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD
-nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po
-sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx
-Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd
-Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX
-KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe
-XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL
-tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv
-TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN
-AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw
-GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H
-PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF
-O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ
-REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik
-AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv
-/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+
-p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw
-MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF
-qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK
-ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+
------END CERTIFICATE-----
-
-# Issuer: CN=Certainly Root R1 O=Certainly
-# Subject: CN=Certainly Root R1 O=Certainly
-# Label: "Certainly Root R1"
-# Serial: 188833316161142517227353805653483829216
-# MD5 Fingerprint: 07:70:d4:3e:82:87:a0:fa:33:36:13:f4:fa:33:e7:12
-# SHA1 Fingerprint: a0:50:ee:0f:28:71:f4:27:b2:12:6d:6f:50:96:25:ba:cc:86:42:af
-# SHA256 Fingerprint: 77:b8:2c:d8:64:4c:43:05:f7:ac:c5:cb:15:6b:45:67:50:04:03:3d:51:c6:0c:62:02:a8:e0:c3:34:67:d3:a0
------BEGIN CERTIFICATE-----
-MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw
-PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy
-dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9
-MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0
-YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2
-1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT
-vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed
-aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0
-1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5
-r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5
-cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ
-wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ
-6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA
-2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH
-Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR
-eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB
-/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u
-d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr
-PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d
-8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi
-1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd
-rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di
-taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7
-lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj
-yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn
-Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy
-yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n
-wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6
-OV+KmalBWQewLK8=
------END CERTIFICATE-----
-
-# Issuer: CN=Certainly Root E1 O=Certainly
-# Subject: CN=Certainly Root E1 O=Certainly
-# Label: "Certainly Root E1"
-# Serial: 8168531406727139161245376702891150584
-# MD5 Fingerprint: 0a:9e:ca:cd:3e:52:50:c6:36:f3:4b:a3:ed:a7:53:e9
-# SHA1 Fingerprint: f9:e1:6d:dc:01:89:cf:d5:82:45:63:3e:c5:37:7d:c2:eb:93:6f:2b
-# SHA256 Fingerprint: b4:58:5f:22:e4:ac:75:6a:4e:86:12:a1:36:1c:5d:9d:03:1a:93:fd:84:fe:bb:77:8f:a3:06:8b:0f:c4:2d:c2
------BEGIN CERTIFICATE-----
-MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw
-CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu
-bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ
-BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s
-eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK
-+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2
-QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E
-BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4
-hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm
-ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG
-BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
------END CERTIFICATE-----
-
-# Issuer: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD.
-# Subject: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD.
-# Label: "Security Communication ECC RootCA1"
-# Serial: 15446673492073852651
-# MD5 Fingerprint: 7e:43:b0:92:68:ec:05:43:4c:98:ab:5d:35:2e:7e:86
-# SHA1 Fingerprint: b8:0e:26:a9:bf:d2:b2:3b:c0:ef:46:c9:ba:c7:bb:f6:1d:0d:41:41
-# SHA256 Fingerprint: e7:4f:bd:a5:5b:d5:64:c4:73:a3:6b:44:1a:a7:99:c8:a6:8e:07:74:40:e8:28:8b:9f:a1:e5:0e:4b:ba:ca:11
------BEGIN CERTIFICATE-----
-MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT
-AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD
-VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx
-NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT
-HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5
-IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
-AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl
-dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK
-ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E
-BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu
-9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O
-be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k=
------END CERTIFICATE-----
-
-# Issuer: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY
-# Subject: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY
-# Label: "BJCA Global Root CA1"
-# Serial: 113562791157148395269083148143378328608
-# MD5 Fingerprint: 42:32:99:76:43:33:36:24:35:07:82:9b:28:f9:d0:90
-# SHA1 Fingerprint: d5:ec:8d:7b:4c:ba:79:f4:e7:e8:cb:9d:6b:ae:77:83:10:03:21:6a
-# SHA256 Fingerprint: f3:89:6f:88:fe:7c:0a:88:27:66:a7:fa:6a:d2:74:9f:b5:7a:7f:3e:98:fb:76:9c:1f:a7:b0:9c:2c:44:d5:ae
------BEGIN CERTIFICATE-----
-MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBU
-MQswCQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRI
-T1JJVFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAz
-MTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJF
-SUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2Jh
-bCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFmCL3Z
-xRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZ
-spDyRhySsTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O5
-58dnJCNPYwpj9mZ9S1WnP3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgR
-at7GGPZHOiJBhyL8xIkoVNiMpTAK+BcWyqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll
-5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRjeulumijWML3mG90Vr4Tq
-nMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNnMoH1V6XK
-V0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/
-pj+bOT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZO
-z2nxbkRs1CTqjSShGL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXn
-jSXWgXSHRtQpdaJCbPdzied9v3pKH9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+
-WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMBAAGjQjBAMB0GA1UdDgQWBBTF
-7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
-AwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4
-YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3Kli
-awLwQ8hOnThJdMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u
-+2D2/VnGKhs/I0qUJDAnyIm860Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88
-X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuhTaRjAv04l5U/BXCga99igUOLtFkN
-SoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW4AB+dAb/OMRyHdOo
-P2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmpGQrI
-+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRz
-znfSxqxx4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9
-eVzYH6Eze9mCUAyTF6ps3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2
-YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4SSPfSKcOYKMryMguTjClPPGAyzQWWYezy
-r/6zcCwupvI=
------END CERTIFICATE-----
-
-# Issuer: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY
-# Subject: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY
-# Label: "BJCA Global Root CA2"
-# Serial: 58605626836079930195615843123109055211
-# MD5 Fingerprint: 5e:0a:f6:47:5f:a6:14:e8:11:01:95:3f:4d:01:eb:3c
-# SHA1 Fingerprint: f4:27:86:eb:6e:b8:6d:88:31:67:02:fb:ba:66:a4:53:00:aa:7a:a6
-# SHA256 Fingerprint: 57:4d:f6:93:1e:27:80:39:66:7b:72:0a:fd:c1:60:0f:c2:7e:b6:6d:d3:09:29:79:fb:73:85:64:87:21:28:82
------BEGIN CERTIFICATE-----
-MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQsw
-CQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJ
-VFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgy
-MVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJ
-TkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2JhbCBS
-b290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jlSR9B
-IgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK+
-+kpRuDCK/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJK
-sVF/BvDRgh9Obl+rg/xI1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
-AgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA
-94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8gUXOQwKhbYdDFUDn9hf7B
-43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w==
------END CERTIFICATE-----
-
-# Issuer: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited
-# Subject: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited
-# Label: "Sectigo Public Server Authentication Root E46"
-# Serial: 88989738453351742415770396670917916916
-# MD5 Fingerprint: 28:23:f8:b2:98:5c:37:16:3b:3e:46:13:4e:b0:b3:01
-# SHA1 Fingerprint: ec:8a:39:6c:40:f0:2e:bc:42:75:d4:9f:ab:1c:1a:5b:67:be:d2:9a
-# SHA256 Fingerprint: c9:0f:26:f0:fb:1b:40:18:b2:22:27:51:9b:5c:a2:b5:3e:2c:a5:b3:be:5c:f1:8e:fe:1b:ef:47:38:0c:53:83
------BEGIN CERTIFICATE-----
-MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw
-CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T
-ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN
-MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG
-A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT
-ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA
-IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC
-WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+
-6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B
-Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa
-qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q
-4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw==
------END CERTIFICATE-----
-
-# Issuer: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited
-# Subject: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited
-# Label: "Sectigo Public Server Authentication Root R46"
-# Serial: 156256931880233212765902055439220583700
-# MD5 Fingerprint: 32:10:09:52:00:d5:7e:6c:43:df:15:c0:b1:16:93:e5
-# SHA1 Fingerprint: ad:98:f9:f3:e4:7d:75:3b:65:d4:82:b3:a4:52:17:bb:6e:f5:e4:38
-# SHA256 Fingerprint: 7b:b6:47:a6:2a:ee:ac:88:bf:25:7a:a5:22:d0:1f:fe:a3:95:e0:ab:45:c7:3f:93:f6:56:54:ec:38:f2:5a:06
------BEGIN CERTIFICATE-----
-MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf
-MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD
-Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw
-HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY
-MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp
-YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB
-AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa
-ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz
-SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf
-iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X
-ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3
-IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS
-VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE
-SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu
-+Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt
-8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L
-HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt
-zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P
-AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c
-mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ
-YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52
-gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA
-Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB
-JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX
-DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui
-TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5
-dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65
-LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp
-0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY
-QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL
------END CERTIFICATE-----
-
-# Issuer: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation
-# Subject: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation
-# Label: "SSL.com TLS RSA Root CA 2022"
-# Serial: 148535279242832292258835760425842727825
-# MD5 Fingerprint: d8:4e:c6:59:30:d8:fe:a0:d6:7a:5a:2c:2c:69:78:da
-# SHA1 Fingerprint: ec:2c:83:40:72:af:26:95:10:ff:0e:f2:03:ee:31:70:f6:78:9d:ca
-# SHA256 Fingerprint: 8f:af:7d:2e:2c:b4:70:9b:b8:e0:b3:36:66:bf:75:a5:dd:45:b5:de:48:0f:8e:a8:d4:bf:e6:be:bc:17:f2:ed
------BEGIN CERTIFICATE-----
-MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO
-MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD
-DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX
-DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw
-b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC
-AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP
-L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY
-t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins
-S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3
-PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO
-L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3
-R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w
-dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS
-+YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS
-d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG
-AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f
-gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j
-BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z
-NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt
-hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM
-QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf
-R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ
-DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW
-P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy
-lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq
-bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w
-AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q
-r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji
-Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU
-98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA=
------END CERTIFICATE-----
-
-# Issuer: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation
-# Subject: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation
-# Label: "SSL.com TLS ECC Root CA 2022"
-# Serial: 26605119622390491762507526719404364228
-# MD5 Fingerprint: 99:d7:5c:f1:51:36:cc:e9:ce:d9:19:2e:77:71:56:c5
-# SHA1 Fingerprint: 9f:5f:d9:1a:54:6d:f5:0c:71:f0:ee:7a:bd:17:49:98:84:73:e2:39
-# SHA256 Fingerprint: c3:2f:fd:9f:46:f9:36:d1:6c:36:73:99:09:59:43:4b:9a:d6:0a:af:bb:9e:7c:f3:36:54:f1:44:cc:1b:a1:43
------BEGIN CERTIFICATE-----
-MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw
-CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT
-U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2
-MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh
-dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG
-ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm
-acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN
-SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME
-GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW
-uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp
-15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN
-b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g==
------END CERTIFICATE-----
-
-# Issuer: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos
-# Subject: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos
-# Label: "Atos TrustedRoot Root CA ECC TLS 2021"
-# Serial: 81873346711060652204712539181482831616
-# MD5 Fingerprint: 16:9f:ad:f1:70:ad:79:d6:ed:29:b4:d1:c5:79:70:a8
-# SHA1 Fingerprint: 9e:bc:75:10:42:b3:02:f3:81:f4:f7:30:62:d4:8f:c3:a7:51:b2:dd
-# SHA256 Fingerprint: b2:fa:e5:3e:14:cc:d7:ab:92:12:06:47:01:ae:27:9c:1d:89:88:fa:cb:77:5f:a8:a0:08:91:4e:66:39:88:a8
------BEGIN CERTIFICATE-----
-MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w
-LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w
-CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0
-MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF
-Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI
-zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X
-tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4
-AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2
-KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD
-aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu
-CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo
-9H1/IISpQuQo
------END CERTIFICATE-----
-
-# Issuer: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos
-# Subject: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos
-# Label: "Atos TrustedRoot Root CA RSA TLS 2021"
-# Serial: 111436099570196163832749341232207667876
-# MD5 Fingerprint: d4:d3:46:b8:9a:c0:9c:76:5d:9e:3a:c3:b9:99:31:d2
-# SHA1 Fingerprint: 18:52:3b:0d:06:37:e4:d6:3a:df:23:e4:98:fb:5b:16:fb:86:74:48
-# SHA256 Fingerprint: 81:a9:08:8e:a5:9f:b3:64:c5:48:a6:f8:55:59:09:9b:6f:04:05:ef:bf:18:e5:32:4e:c9:f4:57:ba:00:11:2f
------BEGIN CERTIFICATE-----
-MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM
-MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx
-MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00
-MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD
-QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN
-BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z
-4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv
-Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ
-kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs
-GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln
-nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh
-3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD
-0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy
-geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8
-ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB
-c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI
-pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
-dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
-DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS
-4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs
-o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ
-qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw
-xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM
-rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4
-AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR
-0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY
-o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5
-dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE
-oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ==
------END CERTIFICATE-----
-
-# Issuer: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc.
-# Subject: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc.
-# Label: "TrustAsia Global Root CA G3"
-# Serial: 576386314500428537169965010905813481816650257167
-# MD5 Fingerprint: 30:42:1b:b7:bb:81:75:35:e4:16:4f:53:d2:94:de:04
-# SHA1 Fingerprint: 63:cf:b6:c1:27:2b:56:e4:88:8e:1c:23:9a:b6:2e:81:47:24:c3:c7
-# SHA256 Fingerprint: e0:d3:22:6a:eb:11:63:c2:e4:8f:f9:be:3b:50:b4:c6:43:1b:e7:bb:1e:ac:c5:c3:6b:5d:5e:c5:09:03:9a:08
------BEGIN CERTIFICATE-----
-MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM
-BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp
-ZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe
-Fw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw
-IwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU
-cnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
-DwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS
-T1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK
-AtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1
-nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep
-qq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA
-yB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs
-hH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX
-zhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv
-kV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT
-f9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA
-uPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB
-o2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih
-MBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E
-BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4
-wM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2
-XFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1
-JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j
-ITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV
-VHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx
-xHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on
-AX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d
-7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj
-gKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV
-+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo
-FGWsJwt0ivKH
------END CERTIFICATE-----
-
-# Issuer: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc.
-# Subject: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc.
-# Label: "TrustAsia Global Root CA G4"
-# Serial: 451799571007117016466790293371524403291602933463
-# MD5 Fingerprint: 54:dd:b2:d7:5f:d8:3e:ed:7c:e0:0b:2e:cc:ed:eb:eb
-# SHA1 Fingerprint: 57:73:a5:61:5d:80:b2:e6:ac:38:82:fc:68:07:31:ac:9f:b5:92:5a
-# SHA256 Fingerprint: be:4b:56:cb:50:56:c0:13:6a:52:6d:f4:44:50:8d:aa:36:a0:b5:4f:42:e4:ac:38:f7:2a:f4:70:e4:79:65:4c
------BEGIN CERTIFICATE-----
-MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw
-WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs
-IEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y
-MTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD
-VQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz
-dEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx
-s8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw
-LxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij
-YzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD
-pm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE
-AwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR
-UKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj
-/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA==
------END CERTIFICATE-----
-
-# Issuer: CN=CommScope Public Trust ECC Root-01 O=CommScope
-# Subject: CN=CommScope Public Trust ECC Root-01 O=CommScope
-# Label: "CommScope Public Trust ECC Root-01"
-# Serial: 385011430473757362783587124273108818652468453534
-# MD5 Fingerprint: 3a:40:a7:fc:03:8c:9c:38:79:2f:3a:a2:6c:b6:0a:16
-# SHA1 Fingerprint: 07:86:c0:d8:dd:8e:c0:80:98:06:98:d0:58:7a:ef:de:a6:cc:a2:5d
-# SHA256 Fingerprint: 11:43:7c:da:7b:b4:5e:41:36:5f:45:b3:9a:38:98:6b:0d:e0:0d:ef:34:8e:0c:7b:b0:87:36:33:80:0b:c3:8b
------BEGIN CERTIFICATE-----
-MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMw
-TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t
-bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNa
-Fw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv
-cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDEw
-djAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLxeP0C
-flfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJE
-hRGnSjot6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
-VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggq
-hkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg
-2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liWpDVfG2XqYZpwI7UNo5uS
-Um9poIyNStDuiw7LR47QjRE=
------END CERTIFICATE-----
-
-# Issuer: CN=CommScope Public Trust ECC Root-02 O=CommScope
-# Subject: CN=CommScope Public Trust ECC Root-02 O=CommScope
-# Label: "CommScope Public Trust ECC Root-02"
-# Serial: 234015080301808452132356021271193974922492992893
-# MD5 Fingerprint: 59:b0:44:d5:65:4d:b8:5c:55:19:92:02:b6:d1:94:b2
-# SHA1 Fingerprint: 3c:3f:ef:57:0f:fe:65:93:86:9e:a0:fe:b0:f6:ed:8e:d1:13:c7:e5
-# SHA256 Fingerprint: 2f:fb:7f:81:3b:bb:b3:c8:9a:b4:e8:16:2d:0f:16:d7:15:09:a8:30:cc:9d:73:c2:62:e5:14:08:75:d1:ad:4a
------BEGIN CERTIFICATE-----
-MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMw
-TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t
-bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRa
-Fw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv
-cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDIw
-djAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/MMDAL
-j2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmU
-v4RDsNuESgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
-VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggq
-hkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/n
-ich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs73u1Z/GtMMH9ZzkXpc2AV
-mkzw5l4lIhVtwodZ0LKOag==
------END CERTIFICATE-----
-
-# Issuer: CN=CommScope Public Trust RSA Root-01 O=CommScope
-# Subject: CN=CommScope Public Trust RSA Root-01 O=CommScope
-# Label: "CommScope Public Trust RSA Root-01"
-# Serial: 354030733275608256394402989253558293562031411421
-# MD5 Fingerprint: 0e:b4:15:bc:87:63:5d:5d:02:73:d4:26:38:68:73:d8
-# SHA1 Fingerprint: 6d:0a:5f:f7:b4:23:06:b4:85:b3:b7:97:64:fc:ac:75:f5:33:f2:93
-# SHA256 Fingerprint: 02:bd:f9:6e:2a:45:dd:9b:f1:8f:c7:e1:db:df:21:a0:37:9b:a3:c9:c2:61:03:44:cf:d8:d6:06:fe:c1:ed:81
------BEGIN CERTIFICATE-----
-MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQEL
-BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi
-Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1
-NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t
-U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt
-MDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45FtnYSk
-YZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslh
-suitQDy6uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0al
-DrJLpA6lfO741GIDuZNqihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3Oj
-WiE260f6GBfZumbCk6SP/F2krfxQapWsvCQz0b2If4b19bJzKo98rwjyGpg/qYFl
-P8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/cZip8UlF1y5mO6D1cv547
-KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTifBSeolz7p
-UcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/
-kQO9lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JO
-Hg9O5j9ZpSPcPYeoKFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkB
-Ea801M/XrmLTBQe0MXXgDW1XT2mH+VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6U
-CBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
-A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm45P3luG0wDQYJ
-KoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6
-NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQ
-nmhUQo8mUuJM3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+
-QgvfKNmwrZggvkN80V4aCRckjXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2v
-trV0KnahP/t1MJ+UXjulYPPLXAziDslg+MkfFoom3ecnf+slpoq9uC02EJqxWE2a
-aE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/WNyVntHKLr4W96ioD
-j8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+o/E4
-Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0w
-lREQKC6/oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHn
-YfkUyq+Dj7+vsQpZXdxc1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVoc
-icCMb3SgazNNtQEo/a2tiRc7ppqEvOuM6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw
------END CERTIFICATE-----
-
-# Issuer: CN=CommScope Public Trust RSA Root-02 O=CommScope
-# Subject: CN=CommScope Public Trust RSA Root-02 O=CommScope
-# Label: "CommScope Public Trust RSA Root-02"
-# Serial: 480062499834624527752716769107743131258796508494
-# MD5 Fingerprint: e1:29:f9:62:7b:76:e2:96:6d:f3:d4:d7:0f:ae:1f:aa
-# SHA1 Fingerprint: ea:b0:e2:52:1b:89:93:4c:11:68:f2:d8:9a:ac:22:4c:a3:8a:57:ae
-# SHA256 Fingerprint: ff:e9:43:d7:93:42:4b:4f:7c:44:0c:1c:3d:64:8d:53:63:f3:4b:82:dc:87:aa:7a:9f:11:8f:c5:de:e1:01:f1
------BEGIN CERTIFICATE-----
-MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQEL
-BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi
-Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2
-NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t
-U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt
-MDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3VrCLE
-NQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0
-kyI9p+Kx7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1C
-rWDaSWqVcN3SAOLMV2MCe5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxz
-hkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2WWy09X6GDRl224yW4fKcZgBzqZUPckXk2
-LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rpM9kzXzehxfCrPfp4sOcs
-n/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIfhs1w/tku
-FT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5
-kQMreyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3
-wNemKfrb3vOTlycEVS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6v
-wQcQeKwRoi9C8DfF8rhW3Q5iLc4tVn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs
-5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
-A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7GxcJXvYXowDQYJ
-KoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB
-KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3
-+VGXu6TwYofF1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbyme
-APnCKfWxkxlSaRosTKCL4BWaMS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3Nyq
-pgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xdgSGn2rtO/+YHqP65DSdsu3BaVXoT
-6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2OHG1QAk8mGEPej1WF
-sQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+NmYWvt
-PjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2d
-lklyALKrdVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670
-v64fG9PiO/yzcnMcmyiQiRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17O
-rg3bhzjlP1v9mxnhMUF6cKojawHhRUzNlM47ni3niAIi9G7oyOzWPPO5std3eqx7
------END CERTIFICATE-----
-
-# Issuer: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH
-# Subject: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH
-# Label: "Telekom Security TLS ECC Root 2020"
-# Serial: 72082518505882327255703894282316633856
-# MD5 Fingerprint: c1:ab:fe:6a:10:2c:03:8d:bc:1c:22:32:c0:85:a7:fd
-# SHA1 Fingerprint: c0:f8:96:c5:a9:3b:01:06:21:07:da:18:42:48:bc:e9:9d:88:d5:ec
-# SHA256 Fingerprint: 57:8a:f4:de:d0:85:3f:4e:59:98:db:4a:ea:f9:cb:ea:8d:94:5f:60:b6:20:a3:8d:1a:3c:13:b2:bc:7b:a8:e1
------BEGIN CERTIFICATE-----
-MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw
-CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH
-bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw
-MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx
-JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE
-AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49
-AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O
-tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP
-f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f
-MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA
-MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di
-z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn
-27iQ7t0l
------END CERTIFICATE-----
-
-# Issuer: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH
-# Subject: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH
-# Label: "Telekom Security TLS RSA Root 2023"
-# Serial: 44676229530606711399881795178081572759
-# MD5 Fingerprint: bf:5b:eb:54:40:cd:48:71:c4:20:8d:7d:de:0a:42:f2
-# SHA1 Fingerprint: 54:d3:ac:b3:bd:57:56:f6:85:9d:ce:e5:c3:21:e2:d4:ad:83:d0:93
-# SHA256 Fingerprint: ef:c6:5c:ad:bb:59:ad:b6:ef:e8:4d:a2:23:11:b3:56:24:b7:1b:3b:1e:a0:da:8b:66:55:17:4e:c8:97:86:46
------BEGIN CERTIFICATE-----
-MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj
-MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0
-eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy
-MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC
-REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG
-A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ
-KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9
-cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV
-cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA
-U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6
-Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug
-BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy
-8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J
-co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg
-8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8
-rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12
-mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg
-+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX
-gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2
-p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ
-pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm
-9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw
-M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd
-GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+
-CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t
-xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+
-w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK
-L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj
-X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q
-ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm
-dTdmQRCsu/WU48IxK63nI1bMNSWSs1A=
------END CERTIFICATE-----
-
-# Issuer: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA
-# Subject: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA
-# Label: "FIRMAPROFESIONAL CA ROOT-A WEB"
-# Serial: 65916896770016886708751106294915943533
-# MD5 Fingerprint: 82:b2:ad:45:00:82:b0:66:63:f8:5f:c3:67:4e:ce:a3
-# SHA1 Fingerprint: a8:31:11:74:a6:14:15:0d:ca:77:dd:0e:e4:0c:5d:58:fc:a0:72:a5
-# SHA256 Fingerprint: be:f2:56:da:f2:6e:9c:69:bd:ec:16:02:35:97:98:f3:ca:f7:18:21:a0:3e:01:82:57:c5:3c:65:61:7f:3d:4a
------BEGIN CERTIFICATE-----
-MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQsw
-CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE
-YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB
-IFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2WhcNNDcwMzMxMDkwMTM2WjBuMQsw
-CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE
-YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB
-IFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zf
-e9MEkVz6iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6C
-cyvHZpsKjECcfIr28jlgst7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB
-/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FDY1w8ndYn81LsF7Kpryz3dvgwHQYDVR0O
-BBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO
-PQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgLcFBTApFw
-hVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dG
-XSaQpYXFuXqUPoeovQA=
------END CERTIFICATE-----
-
-# Issuer: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA
-# Subject: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA
-# Label: "TWCA CYBER Root CA"
-# Serial: 85076849864375384482682434040119489222
-# MD5 Fingerprint: 0b:33:a0:97:52:95:d4:a9:fd:bb:db:6e:a3:55:5b:51
-# SHA1 Fingerprint: f6:b1:1c:1a:83:38:e9:7b:db:b3:a8:c8:33:24:e0:2d:9c:7f:26:66
-# SHA256 Fingerprint: 3f:63:bb:28:14:be:17:4e:c8:b6:43:9c:f0:8d:6d:56:f0:b7:c4:05:88:3a:56:48:a3:34:42:4d:6b:3e:c5:58
------BEGIN CERTIFICATE-----
-MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ
-MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290
-IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5
-WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO
-LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg
-Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P
-40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF
-avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/
-34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i
-JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu
-j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf
-Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP
-2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA
-S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA
-oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC
-kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW
-5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD
-VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd
-BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB
-AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t
-tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn
-68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn
-TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t
-RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx
-f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI
-Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz
-8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4
-NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX
-xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6
-t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X
------END CERTIFICATE-----
-
-# Issuer: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd.
-# Subject: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd.
-# Label: "SecureSign Root CA12"
-# Serial: 587887345431707215246142177076162061960426065942
-# MD5 Fingerprint: c6:89:ca:64:42:9b:62:08:49:0b:1e:7f:e9:07:3d:e8
-# SHA1 Fingerprint: 7a:22:1e:3d:de:1b:06:ac:9e:c8:47:70:16:8e:3c:e5:f7:6b:06:f4
-# SHA256 Fingerprint: 3f:03:4b:b5:70:4d:44:b2:d0:85:45:a0:20:57:de:93:eb:f3:90:5f:ce:72:1a:cb:c7:30:c0:6d:da:ee:90:4e
------BEGIN CERTIFICATE-----
-MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL
-BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u
-LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw
-NTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD
-eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS
-b290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3emhF
-KxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mt
-p7JIKwccJ/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zd
-J1M3s6oYwlkm7Fsf0uZlfO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gur
-FzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBFEaCeVESE99g2zvVQR9wsMJvuwPWW0v4J
-hscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1UefNzFJM3IFTQy2VYzxV4+K
-h9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
-AgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsF
-AAOCAQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6Ld
-mmQOmFxv3Y67ilQiLUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJ
-mBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA
-8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV
-55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/
-yOPiZwud9AzqVN/Ssq+xIvEg37xEHA==
------END CERTIFICATE-----
-
-# Issuer: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd.
-# Subject: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd.
-# Label: "SecureSign Root CA14"
-# Serial: 575790784512929437950770173562378038616896959179
-# MD5 Fingerprint: 71:0d:72:fa:92:19:65:5e:89:04:ac:16:33:f0:bc:d5
-# SHA1 Fingerprint: dd:50:c0:f7:79:b3:64:2e:74:a2:b8:9d:9f:d3:40:dd:bb:f0:f2:4f
-# SHA256 Fingerprint: 4b:00:9c:10:34:49:4f:9a:b5:6b:ba:3b:a1:d6:27:31:fc:4d:20:d8:95:5a:dc:ec:10:a9:25:60:72:61:e3:38
------BEGIN CERTIFICATE-----
-MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM
-BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u
-LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw
-NzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD
-eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS
-b290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh1oq/
-FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOg
-vlIfX8xnbacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy
-6pJxaeQp8E+BgQQ8sqVb1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo
-/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9J
-kdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOEkJTRX45zGRBdAuVwpcAQ
-0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSxjVIHvXib
-y8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac
-18izju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs
-0Wq2XSqypWa9a4X0dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIAB
-SMbHdPTGrMNASRZhdCyvjG817XsYAFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVL
-ApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
-AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeqYR3r6/wtbyPk
-86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E
-rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ib
-ed87hwriZLoAymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopT
-zfFP7ELyk+OZpDc8h7hi2/DsHzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHS
-DCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPGFrojutzdfhrGe0K22VoF3Jpf1d+4
-2kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6qnsb58Nn4DSEC5MUo
-FlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/OfVy
-K4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6
-dB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl
-Lor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB
-365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c
-JRNItX+S
------END CERTIFICATE-----
-
-# Issuer: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd.
-# Subject: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd.
-# Label: "SecureSign Root CA15"
-# Serial: 126083514594751269499665114766174399806381178503
-# MD5 Fingerprint: 13:30:fc:c4:62:a6:a9:de:b5:c1:68:af:b5:d2:31:47
-# SHA1 Fingerprint: cb:ba:83:c8:c1:5a:5d:f1:f9:73:6f:ca:d7:ef:28:13:06:4a:07:7d
-# SHA256 Fingerprint: e7:78:f0:f0:95:fe:84:37:29:cd:1a:00:82:17:9e:53:14:a9:c2:91:44:28:05:e1:fb:1d:8f:b6:b8:88:6c:3a
------BEGIN CERTIFICATE-----
-MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw
-UTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM
-dGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy
-NTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJl
-cnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBSb290
-IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5GdCx4
-wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSR
-ZHX+AezB2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMB
-Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT
-9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp
-4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6
-bkU6iYAZezKYVWOr62Nuk22rGwlgMU4=
------END CERTIFICATE-----
-
-# Issuer: CN=D-TRUST BR Root CA 2 2023 O=D-Trust GmbH
-# Subject: CN=D-TRUST BR Root CA 2 2023 O=D-Trust GmbH
-# Label: "D-TRUST BR Root CA 2 2023"
-# Serial: 153168538924886464690566649552453098598
-# MD5 Fingerprint: e1:09:ed:d3:60:d4:56:1b:47:1f:b7:0c:5f:1b:5f:85
-# SHA1 Fingerprint: 2d:b0:70:ee:71:94:af:69:68:17:db:79:ce:58:9f:a0:6b:96:f7:87
-# SHA256 Fingerprint: 05:52:e6:f8:3f:df:65:e8:fa:96:70:e6:66:df:28:a4:e2:13:40:b5:10:cb:e5:25:66:f9:7c:4f:b9:4b:2b:d1
------BEGIN CERTIFICATE-----
-MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBI
-MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE
-LVRSVVNUIEJSIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUw
-OTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi
-MCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN
-AQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCTcfKr
-i3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNE
-gXtRr90zsWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8
-k12b9py0i4a6Ibn08OhZWiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCT
-Rphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl
-2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LULQyReS2tNZ9/WtT5PeB+U
-cSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIvx9gvdhFP
-/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bS
-uREVMweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+
-0bpwHJwh5Q8xaRfX/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4N
-DfTisl01gLmB1IRpkQLLddCNxbU9CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+
-XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZ5Dw1t61
-GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG
-OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y
-XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tI
-FoE9c+CeJyrrd6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67n
-riv6uvw8l5VAk1/DLQOj7aRvU9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTR
-VFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4nj8+AybmTNudX0KEPUUDAxxZiMrc
-LmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdijYQ6qgYF/6FKC0ULn
-4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff/vtD
-hQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsG
-koHU6XCPpz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46
-ls/pdu4D58JDUjxqgejBWoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aS
-Ecr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/5usWDiJFAbzdNpQ0qTUmiteXue4Icr80
-knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ
-hJ65bvspmZDogNOfJA==
------END CERTIFICATE-----
-
-# Issuer: CN=TrustAsia TLS ECC Root CA O=TrustAsia Technologies, Inc.
-# Subject: CN=TrustAsia TLS ECC Root CA O=TrustAsia Technologies, Inc.
-# Label: "TrustAsia TLS ECC Root CA"
-# Serial: 310892014698942880364840003424242768478804666567
-# MD5 Fingerprint: 09:48:04:77:d2:fc:65:93:71:66:b1:11:95:4f:06:8c
-# SHA1 Fingerprint: b5:ec:39:f3:a1:66:37:ae:c3:05:94:57:e2:be:11:be:b7:a1:7f:36
-# SHA256 Fingerprint: c0:07:6b:9e:f0:53:1f:b1:a6:56:d6:7c:4e:be:97:cd:5d:ba:a4:1e:f4:45:98:ac:c2:48:98:78:c9:2d:87:11
------BEGIN CERTIFICATE-----
-MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMw
-WDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs
-IEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQw
-NTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBYMQswCQYDVQQGEwJDTjElMCMGA1UE
-ChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RB
-c2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/pVs/
-AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDp
-guMqWzJ8S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAw
-DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAw
-DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01
-L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15KeAIxAKORh/IRM4PDwYqR
-OkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ==
------END CERTIFICATE-----
-
-# Issuer: CN=TrustAsia TLS RSA Root CA O=TrustAsia Technologies, Inc.
-# Subject: CN=TrustAsia TLS RSA Root CA O=TrustAsia Technologies, Inc.
-# Label: "TrustAsia TLS RSA Root CA"
-# Serial: 160405846464868906657516898462547310235378010780
-# MD5 Fingerprint: 3b:9e:c3:86:0f:34:3c:6b:c5:46:c4:8e:1d:e7:19:12
-# SHA1 Fingerprint: a5:46:50:c5:62:ea:95:9a:1a:a7:04:6f:17:58:c7:29:53:3d:03:fa
-# SHA256 Fingerprint: 06:c0:8d:7d:af:d8:76:97:1e:b1:12:4f:e6:7f:84:7e:c0:c7:a1:58:d3:ea:53:cb:e9:40:e2:ea:97:91:f4:c3
------BEGIN CERTIFICATE-----
-MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEM
-BQAwWDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp
-ZXMsIEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcN
-MjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2WjBYMQswCQYDVQQGEwJDTjElMCMG
-A1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1
-c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
-AgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+
-NmDQDIPNlOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJ
-Q1DNDX3eRA5gEk9bNb2/mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561
-HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fkzv93uMltrOXVmPGZLmzjyUT5tUMnCE32
-ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYozza/+lcK7Fs/6TAWe8Tb
-xNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyrz2I8sMeX
-i9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQ
-UNoyIBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+j
-TnhMmCWr8n4uIF6CFabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DT
-bE3txci3OE9kxJRMT6DNrqXGJyV1J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8
-S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnTq1mt1tve1CuBAgMBAAGjQjBA
-MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZylomkadFK/hT
-MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3
-Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4
-iqME3mmL5Dw8veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt
-7DlK9RME7I10nYEKqG/odv6LTytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp
-2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHxtlotJnMnlvm5P1vQiJ3koP26TpUJ
-g3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp27RIGAAtvKLEiUUj
-pQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87qqA8M
-pugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongP
-XvPKnbwbPKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIwe
-SsCI3zWQzj8C9GRh3sfIB5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0
-ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNzFrwFuHnYWa8G5z9nODmxfKuU4CkUpijy
-323imttUQ/hHWKNddBWcwauwxzQ=
------END CERTIFICATE-----
-
-# Issuer: CN=D-TRUST EV Root CA 2 2023 O=D-Trust GmbH
-# Subject: CN=D-TRUST EV Root CA 2 2023 O=D-Trust GmbH
-# Label: "D-TRUST EV Root CA 2 2023"
-# Serial: 139766439402180512324132425437959641711
-# MD5 Fingerprint: 96:b4:78:09:f0:09:cb:77:eb:bb:1b:4d:6f:36:bc:b6
-# SHA1 Fingerprint: a5:5b:d8:47:6c:8f:19:f7:4c:f4:6d:6b:b6:c2:79:82:22:df:54:8b
-# SHA256 Fingerprint: 8e:82:21:b2:e7:d4:00:78:36:a1:67:2f:0d:cc:29:9c:33:bc:07:d3:16:f1:32:fa:1a:20:6d:58:71:50:f1:ce
------BEGIN CERTIFICATE-----
-MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBI
-MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE
-LVRSVVNUIEVWIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUw
-OTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi
-MCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN
-AQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1sJkK
-F8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE
-7CUXFId/MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFe
-EMbsh2aJgWi6zCudR3Mfvc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6
-lHPTGGkKSv/BAQP/eX+1SH977ugpbzZMlWGG2Pmic4ruri+W7mjNPU0oQvlFKzIb
-RlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3YG14C8qKXO0elg6DpkiV
-jTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq9107PncjLgc
-jmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZx
-TnXonMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+
-ARZZaBhDM7DS3LAaQzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nk
-hbDhezGdpn9yo7nELC7MmVcOIQxFAZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knF
-NXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqvyREBuH
-kV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG
-OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y
-XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14
-QvBukEdHjqOSMo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4
-pZt+UPJ26oUFKidBK7GB0aL2QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q
-3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xDUmPBEcrCRbH0O1P1aa4846XerOhU
-t7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V4U/M5d40VxDJI3IX
-cI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuodNv8
-ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT
-2vFp4LJiTZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs
-7dpn1mKmS00PaaLJvOwiS5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNP
-gofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst
-Nl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh
-XBxvWHZks/wCuPWdCg==
------END CERTIFICATE-----
-
-# Issuer: CN=SwissSign RSA TLS Root CA 2022 - 1 O=SwissSign AG
-# Subject: CN=SwissSign RSA TLS Root CA 2022 - 1 O=SwissSign AG
-# Label: "SwissSign RSA TLS Root CA 2022 - 1"
-# Serial: 388078645722908516278762308316089881486363258315
-# MD5 Fingerprint: 16:2e:e4:19:76:81:85:ba:8e:91:58:f1:15:ef:72:39
-# SHA1 Fingerprint: 81:34:0a:be:4c:cd:ce:cc:e7:7d:cc:8a:d4:57:e2:45:a0:77:5d:ce
-# SHA256 Fingerprint: 19:31:44:f4:31:e0:fd:db:74:07:17:d4:de:92:6a:57:11:33:88:4b:43:60:d3:0e:27:29:13:cb:e6:60:ce:41
------BEGIN CERTIFICATE-----
-MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQEL
-BQAwUTELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UE
-AxMiU3dpc3NTaWduIFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgx
-MTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxT
-d2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0Eg
-MjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmjiC8NX
-vDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7
-LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX
-5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE
-EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt
-/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x
-0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5
-KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM
-0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shd
-OxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrta
-clXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+DdziZaKHG29777YtvTK
-wP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMBAf8EBTAD
-AQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4
-DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL
-BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO3
-10aewCoSPY6WlkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgz
-Hqp41eZUBDqyggmNzhYzWUUo8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQ
-iJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIc
-gC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3CjlvrzG4ngRhZi0Rjn9UM
-ZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6MOuhF
-LhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJp
-zv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td
-Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0
-rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EO
-gLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ
------END CERTIFICATE-----
diff --git a/python/tk-core/requirements/3.7/frozen_requirements.txt b/python/tk-core/requirements/3.7/frozen_requirements.txt
deleted file mode 100644
index fa55883f..00000000
--- a/python/tk-core/requirements/3.7/frozen_requirements.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-distro==1.4.0
-packaging==24.0
-PyYAML==5.4.1
-ruamel.yaml==0.18.13
-ruamel.yaml.clib==0.2.8
-shotgun-api3==3.9.0
diff --git a/python/tk-core/requirements/3.7/pkgs.zip b/python/tk-core/requirements/3.7/pkgs.zip
deleted file mode 100644
index 42f30929..00000000
Binary files a/python/tk-core/requirements/3.7/pkgs.zip and /dev/null differ
diff --git a/python/tk-core/requirements/3.7/requirements.txt b/python/tk-core/requirements/3.7/requirements.txt
deleted file mode 100644
index e4f56efe..00000000
--- a/python/tk-core/requirements/3.7/requirements.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-distro==1.4.0
-packaging==24.0
-pyyaml==5.4.1
-ruamel.yaml==0.18.13
-shotgun_api3==3.9.0
diff --git a/python/tk-core/requirements/3.9/frozen_requirements.txt b/python/tk-core/requirements/3.9/frozen_requirements.txt
index 212246db..f7a436c5 100644
--- a/python/tk-core/requirements/3.9/frozen_requirements.txt
+++ b/python/tk-core/requirements/3.9/frozen_requirements.txt
@@ -1,5 +1,7 @@
distro==1.4.0
+Fileseq==2.1.2
packaging==25.0
+PyJWT==2.12.0
PyYAML==5.4.1
ruamel.yaml==0.18.14
ruamel.yaml.clib==0.2.15
diff --git a/python/tk-core/requirements/3.9/pkgs.zip b/python/tk-core/requirements/3.9/pkgs.zip
index c0043ab1..86e8258b 100644
Binary files a/python/tk-core/requirements/3.9/pkgs.zip and b/python/tk-core/requirements/3.9/pkgs.zip differ
diff --git a/python/tk-core/requirements/3.9/requirements.txt b/python/tk-core/requirements/3.9/requirements.txt
index 4db18269..24a23f3c 100644
--- a/python/tk-core/requirements/3.9/requirements.txt
+++ b/python/tk-core/requirements/3.9/requirements.txt
@@ -1,5 +1,7 @@
distro==1.4.0
+fileseq==2.1.2
packaging==25.0
+pyjwt==2.12.0
pyyaml==5.4.1
ruamel.yaml==0.18.14
shotgun_api3==3.10.0
diff --git a/python/tk-core/requirements/any/flow_data_sdk-beta.zip b/python/tk-core/requirements/any/flow_data_sdk-beta.zip
new file mode 100644
index 00000000..0250e7fe
Binary files /dev/null and b/python/tk-core/requirements/any/flow_data_sdk-beta.zip differ
diff --git a/python/tk-core/requirements/update_python_packages.py b/python/tk-core/requirements/update_python_packages.py
index 77c5e684..abfabba5 100644
--- a/python/tk-core/requirements/update_python_packages.py
+++ b/python/tk-core/requirements/update_python_packages.py
@@ -88,9 +88,14 @@ def main():
]
# Make sure we found as many Python packages as there
- # are packages listed inside frozen_requirements.txt
- # assert len(package_names) == nb_dependencies
- assert len(package_names) >= nb_dependencies
+ # are packages listed inside frozen_requirements.txt.
+ # Count .dist-info directories rather than package directories because
+ # namespace packages (e.g. ruamel.yaml + ruamel.yaml.clib, jaraco.*)
+ # share a single parent directory, making package_names < nb_dependencies.
+ dist_info_count = len(
+ [name for name in os.listdir(temp_dir) if name.endswith(".dist-info")]
+ )
+ assert dist_info_count >= nb_dependencies
# Write out the zip file for python packages. Compress the zip file with ZIP_DEFLATED. Note
# that this requires zlib to decompress when importing. Compression also causes import to
diff --git a/python/tk-core/scripts/tank_cmd.py b/python/tk-core/scripts/tank_cmd.py
index e686eea4..b43e0dbe 100644
--- a/python/tk-core/scripts/tank_cmd.py
+++ b/python/tk-core/scripts/tank_cmd.py
@@ -8,36 +8,36 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import sys
-import os
-from html import escape
+import datetime
import logging
+import os
import string
-import tank
+import sys
import textwrap
-import datetime
+from html import escape
-from tank.errors import TankError, TankInitError
-from tank.commands.tank_command import get_actions, run_action
+import tank
+from tank import LogManager, pipelineconfig_utils
+from tank.authentication import (
+ AuthenticationCancelled,
+ AuthenticationError,
+ CoreDefaultsManager,
+ IncompleteCredentials,
+ ShotgunAuthenticationError,
+ ShotgunAuthenticator,
+)
+from tank.commands import constants as command_constants
+from tank.commands.action_base import Action
from tank.commands.clone_configuration import clone_pipeline_configuration_html
from tank.commands.core_upgrade import TankCoreUpdater
-from tank.commands.action_base import Action
-from tank.util import shotgun
-from tank.util import shotgun_entity
+from tank.commands.tank_command import get_actions, run_action
+from tank.errors import TankError, TankInitError
+from tank.platform import constants as platform_constants
+from tank.platform import engine
from tank.util import is_windows
from tank.util import sgre as re
-from tank.platform import constants as platform_constants
-from tank.authentication import ShotgunAuthenticator
-from tank.authentication import AuthenticationError
-from tank.authentication import ShotgunAuthenticationError
-from tank.authentication import AuthenticationCancelled
-from tank.authentication import IncompleteCredentials
-from tank.authentication import CoreDefaultsManager
-from tank.commands import constants as command_constants
+from tank.util import shotgun, shotgun_entity
from tank_vendor import yaml
-from tank.platform import engine
-from tank import pipelineconfig_utils
-from tank import LogManager
# the logger used by this file is sgtk.tank_cmd
logger = LogManager.get_logger("tank_cmd")
diff --git a/python/tk-core/setup.py b/python/tk-core/setup.py
index 43b4f24a..01dab5fe 100644
--- a/python/tk-core/setup.py
+++ b/python/tk-core/setup.py
@@ -10,12 +10,32 @@
# Basic setup.py so tk-core could be installed as
# a standard Python package
+import glob
import os
import re
+import shutil
import subprocess
import sys
-from setuptools import setup, find_packages
+from setuptools import find_packages, setup
+from setuptools.command.build_py import build_py as _build_py
+
+
+class build_py(_build_py):
+ def run(self):
+ super().run()
+ # Copy requirements/any/*.zip into tank_vendor/vendor_any/ inside the
+ # build tree so pip-installed packages can find version-independent
+ # vendor zips (e.g. flow_data_sdk). Editable installs skip this step
+ # intentionally — they use the source-tree requirements/any/ path.
+ src_dir = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), "requirements", "any"
+ )
+ dst_dir = os.path.join(self.build_lib, "tank_vendor", "vendor_any")
+ if os.path.isdir(src_dir):
+ os.makedirs(dst_dir, exist_ok=True)
+ for zip_path in glob.glob(os.path.join(src_dir, "*.zip")):
+ shutil.copy2(zip_path, dst_dir)
def get_version():
@@ -40,7 +60,7 @@ def get_version():
if re.match(r"v[0-9]*.[0-9]*.[0-9]*", version_git):
return version_git
return "dev"
- except:
+ except Exception:
# Blindly ignore problems, git might be not available, or the user could
# be installing from zip archive, etc...
pass
@@ -97,6 +117,7 @@ def get_install_requires():
f.close()
setup(
+ cmdclass={"build_py": build_py},
name="sgtk",
version=get_version(),
description="Flow Production Tracking Toolkit Core API",
diff --git a/python/tk-core/setup/tank_api_proxy/sgtk/__init__.py b/python/tk-core/setup/tank_api_proxy/sgtk/__init__.py
index d0c107d4..1ac7cd76 100644
--- a/python/tk-core/setup/tank_api_proxy/sgtk/__init__.py
+++ b/python/tk-core/setup/tank_api_proxy/sgtk/__init__.py
@@ -11,6 +11,7 @@
# thin proxy wrapper which finds the real sgtk and replaces itself with that
+import importlib
import os
import sys
@@ -63,4 +64,4 @@
# prepend this to the python path and reload the module
# this way we will load the 'real' tank!
os.sys.path.insert(0, parent_python_path)
-reload(sys.modules["sgtk"])
+importlib.reload(sys.modules["sgtk"])
diff --git a/python/tk-core/setup/tank_api_proxy/tank/__init__.py b/python/tk-core/setup/tank_api_proxy/tank/__init__.py
index a02f7590..15ad0b7c 100644
--- a/python/tk-core/setup/tank_api_proxy/tank/__init__.py
+++ b/python/tk-core/setup/tank_api_proxy/tank/__init__.py
@@ -11,6 +11,7 @@
# thin proxy wrapper which finds the real tank and replaces itself with that
+import importlib
import os
import sys
@@ -62,4 +63,4 @@
# prepend this to the python path and reload the module
# this way we will load the 'real' tank!
os.sys.path.insert(0, parent_python_path)
-reload(sys.modules["tank"])
+importlib.reload(sys.modules["tank"])