iris-pex-embedded-python 3.5.6b1__tar.gz → 3.5.6b2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {iris_pex_embedded_python-3.5.6b1/src/iris_pex_embedded_python.egg-info → iris_pex_embedded_python-3.5.6b2}/PKG-INFO +1 -1
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/pyproject.toml +8 -2
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_cli.py +143 -28
- iris_pex_embedded_python-3.5.6b2/src/iop/_local.py +79 -0
- iris_pex_embedded_python-3.5.6b2/src/iop/_remote.py +293 -0
- iris_pex_embedded_python-3.5.6b2/src/iop/cls/IOP/Service/Remote/Rest/v1.cls +374 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2/src/iris_pex_embedded_python.egg-info}/PKG-INFO +1 -1
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iris_pex_embedded_python.egg-info/SOURCES.txt +1 -0
- iris_pex_embedded_python-3.5.6b1/src/iop/_remote.py +0 -91
- iris_pex_embedded_python-3.5.6b1/src/iop/cls/IOP/Service/Remote/Rest/v1.cls +0 -97
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/LICENSE +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/README.md +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/setup.cfg +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/setup.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/__init__.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/BusinessOperation.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/BusinessProcess.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/BusinessService.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/Common.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/Director.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/Duplex/Operation.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/Duplex/Process.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/Duplex/Service.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/InboundAdapter.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/Message.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/OutboundAdapter.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/PickleMessage.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/PrivateSession/Duplex.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/PrivateSession/Message/Ack.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/PrivateSession/Message/Poll.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/PrivateSession/Message/Start.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/PrivateSession/Message/Stop.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/Test.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/PEX/Utils.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/cls/Grongier/Service/WSGI.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/pex/__init__.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/pex/__main__.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/pex/_business_host.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/pex/_cli.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/pex/_common.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/pex/_director.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/pex/_utils.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/grongier/pex/wsgi/handlers.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/__init__.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/__main__.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_async_request.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_business_host.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_business_operation.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_business_process.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_business_service.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_common.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_debugpy.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_decorators.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_director.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_dispatch.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_generator_request.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_inbound_adapter.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_iris.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_log_manager.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_message.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_message_validator.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_outbound_adapter.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_private_session_duplex.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_private_session_process.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_serialization.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/_utils.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/BusinessOperation.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/BusinessProcess.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/BusinessService.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Common.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Director.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Duplex/Operation.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Duplex/Process.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Duplex/Service.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Generator/Message/Ack.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Generator/Message/Poll.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Generator/Message/Start.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Generator/Message/StartPickle.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Generator/Message/Stop.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/InboundAdapter.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Message/JSONSchema.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Message.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/OutboundAdapter.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/PickleMessage.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/PrivateSession/Duplex.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/PrivateSession/Message/Ack.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/PrivateSession/Message/Poll.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/PrivateSession/Message/Start.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/PrivateSession/Message/Stop.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Projection.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Service/Remote/Handler.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Service/WSGI.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Test.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Utils.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/cls/IOP/Wrapper.cls +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iop/wsgi/handlers.py +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iris_pex_embedded_python.egg-info/dependency_links.txt +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iris_pex_embedded_python.egg-info/entry_points.txt +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iris_pex_embedded_python.egg-info/requires.txt +0 -0
- {iris_pex_embedded_python-3.5.6b1 → iris_pex_embedded_python-3.5.6b2}/src/iris_pex_embedded_python.egg-info/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"]
|
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "iris_pex_embedded_python"
|
|
6
|
-
version = "3.5.
|
|
6
|
+
version = "3.5.6b2"
|
|
7
7
|
description = "Iris Interoperability based on Embedded Python"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
authors = [
|
|
@@ -54,4 +54,10 @@ exclude = ["tests*"]
|
|
|
54
54
|
"*" = ["*.cls"]
|
|
55
55
|
|
|
56
56
|
[tool.pytest.ini_options]
|
|
57
|
-
asyncio_default_fixture_loop_scope = "class"
|
|
57
|
+
asyncio_default_fixture_loop_scope = "class"
|
|
58
|
+
testpaths = ["src/tests"]
|
|
59
|
+
markers = [
|
|
60
|
+
"unit: pure unit test - no IRIS instance required (all IRIS calls mocked)",
|
|
61
|
+
"e2e_local: end-to-end test requiring a local IRIS instance",
|
|
62
|
+
"e2e_remote: end-to-end test requiring a remote IRIS instance via REST API",
|
|
63
|
+
]
|
|
@@ -6,10 +6,11 @@ import os
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from enum import Enum, auto
|
|
8
8
|
import sys
|
|
9
|
-
from typing import Optional
|
|
9
|
+
from typing import Optional
|
|
10
10
|
from importlib.metadata import version
|
|
11
11
|
|
|
12
|
-
from .
|
|
12
|
+
from ._local import _LocalDirector
|
|
13
|
+
from ._remote import _RemoteDirector, get_remote_settings
|
|
13
14
|
from ._utils import _Utils
|
|
14
15
|
|
|
15
16
|
|
|
@@ -51,6 +52,7 @@ class CommandArgs:
|
|
|
51
52
|
body: Optional[str] = None
|
|
52
53
|
namespace: Optional[str] = None
|
|
53
54
|
force_local: bool = False
|
|
55
|
+
remote_settings: Optional[str] = None
|
|
54
56
|
update: bool = False
|
|
55
57
|
|
|
56
58
|
class Command:
|
|
@@ -61,6 +63,36 @@ class Command:
|
|
|
61
63
|
# set environment variable IRISNAMESPACE
|
|
62
64
|
os.environ['IRISNAMESPACE'] = self.args.namespace
|
|
63
65
|
|
|
66
|
+
# Resolve director: remote when IOP_URL / IOP_SETTINGS env vars are set
|
|
67
|
+
# or when the -m settings.py file contains REMOTE_SETTINGS.
|
|
68
|
+
# --force-local overrides everything and always uses the local director.
|
|
69
|
+
if self.args.force_local:
|
|
70
|
+
self.director: _LocalDirector | _RemoteDirector = _LocalDirector()
|
|
71
|
+
self._is_remote = False
|
|
72
|
+
else:
|
|
73
|
+
# Resolve absolute paths for --remote-settings and -m so
|
|
74
|
+
# get_remote_settings can load them regardless of cwd.
|
|
75
|
+
explicit_path = self.args.remote_settings
|
|
76
|
+
if explicit_path and not os.path.isabs(explicit_path):
|
|
77
|
+
explicit_path = os.path.join(os.getcwd(), explicit_path)
|
|
78
|
+
|
|
79
|
+
migrate_path = self.args.migrate
|
|
80
|
+
if migrate_path and not os.path.isabs(migrate_path):
|
|
81
|
+
migrate_path = os.path.join(os.getcwd(), migrate_path)
|
|
82
|
+
|
|
83
|
+
remote_settings = get_remote_settings(
|
|
84
|
+
explicit_settings_path=explicit_path,
|
|
85
|
+
fallback_settings_path=migrate_path,
|
|
86
|
+
)
|
|
87
|
+
if remote_settings:
|
|
88
|
+
if self.args.namespace and self.args.namespace != 'not_set':
|
|
89
|
+
remote_settings['namespace'] = self.args.namespace
|
|
90
|
+
self.director = _RemoteDirector(remote_settings)
|
|
91
|
+
self._is_remote = True
|
|
92
|
+
else:
|
|
93
|
+
self.director = _LocalDirector()
|
|
94
|
+
self._is_remote = False
|
|
95
|
+
|
|
64
96
|
def _has_primary_command(self) -> bool:
|
|
65
97
|
return any([
|
|
66
98
|
self.args.default,
|
|
@@ -125,53 +157,62 @@ class Command:
|
|
|
125
157
|
|
|
126
158
|
def _handle_default(self) -> None:
|
|
127
159
|
if self.args.default == 'not_set':
|
|
128
|
-
print(
|
|
160
|
+
print(self.director.get_default_production())
|
|
129
161
|
elif self.args.default is not None:
|
|
130
|
-
|
|
162
|
+
self.director.set_default_production(self.args.default)
|
|
131
163
|
|
|
132
164
|
def _handle_list(self) -> None:
|
|
133
|
-
dikt =
|
|
165
|
+
dikt = self.director.list_productions()
|
|
134
166
|
print(json.dumps(dikt, indent=4))
|
|
135
167
|
|
|
136
168
|
def _handle_start(self) -> None:
|
|
137
|
-
production_name = self.args.start if self.args.start != 'not_set' else
|
|
169
|
+
production_name = self.args.start if self.args.start != 'not_set' else self.director.get_default_production()
|
|
138
170
|
if self.args.detach:
|
|
139
|
-
|
|
171
|
+
self.director.start_production(production_name)
|
|
140
172
|
print(f"Production {production_name} started")
|
|
141
173
|
else:
|
|
142
|
-
|
|
174
|
+
self.director.start_production_with_log(production_name)
|
|
143
175
|
|
|
144
176
|
def _handle_stop(self) -> None:
|
|
145
|
-
|
|
146
|
-
print(f"Production {
|
|
177
|
+
self.director.stop_production()
|
|
178
|
+
print(f"Production {self.director.get_default_production()} stopped")
|
|
147
179
|
|
|
148
180
|
def _handle_kill(self) -> None:
|
|
149
|
-
|
|
181
|
+
self.director.shutdown_production()
|
|
150
182
|
|
|
151
183
|
def _handle_restart(self) -> None:
|
|
152
|
-
|
|
184
|
+
self.director.restart_production()
|
|
153
185
|
|
|
154
186
|
def _handle_status(self) -> None:
|
|
155
|
-
print(json.dumps(
|
|
187
|
+
print(json.dumps(self.director.status_production(), indent=4))
|
|
156
188
|
|
|
157
189
|
def _handle_update(self) -> None:
|
|
158
|
-
|
|
190
|
+
self.director.update_production()
|
|
159
191
|
|
|
160
192
|
def _handle_test(self) -> None:
|
|
161
193
|
test_name = None if self.args.test == 'not_set' else self.args.test
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
194
|
+
classname = self.args.classname if self.args.classname != 'not_set' else None
|
|
195
|
+
body = self.args.body if self.args.body != 'not_set' else None
|
|
196
|
+
|
|
197
|
+
# Support @filename.json body expansion
|
|
198
|
+
if body and body.startswith('@'):
|
|
199
|
+
filepath = body[1:]
|
|
200
|
+
if not os.path.isabs(filepath):
|
|
201
|
+
filepath = os.path.join(os.getcwd(), filepath)
|
|
202
|
+
with open(filepath, 'r', encoding='utf-8') as fh:
|
|
203
|
+
body = fh.read()
|
|
204
|
+
|
|
205
|
+
response = self.director.test_component(
|
|
206
|
+
test_name, classname=classname, body=body
|
|
166
207
|
)
|
|
167
|
-
print(response)
|
|
208
|
+
print(_format_test_response(response))
|
|
168
209
|
|
|
169
210
|
def _handle_version(self) -> None:
|
|
170
211
|
print(version('iris-pex-embedded-python'))
|
|
171
212
|
|
|
172
213
|
def _handle_export(self) -> None:
|
|
173
|
-
export_name =
|
|
174
|
-
print(json.dumps(
|
|
214
|
+
export_name = self.director.get_default_production() if self.args.export == 'not_set' else self.args.export
|
|
215
|
+
print(json.dumps(self.director.export_production(export_name), indent=4))
|
|
175
216
|
|
|
176
217
|
def _handle_migrate(self) -> None:
|
|
177
218
|
migrate_path = self.args.migrate
|
|
@@ -182,24 +223,95 @@ class Command:
|
|
|
182
223
|
|
|
183
224
|
def _handle_log(self) -> None:
|
|
184
225
|
if self.args.log == 'not_set':
|
|
185
|
-
|
|
226
|
+
self.director.log_production()
|
|
186
227
|
elif self.args.log is not None:
|
|
187
|
-
|
|
228
|
+
self.director.log_production_top(int(self.args.log))
|
|
188
229
|
|
|
189
230
|
def _handle_init(self) -> None:
|
|
231
|
+
if self._is_remote:
|
|
232
|
+
logging.warning("'init' is a local-only command and cannot be run remotely.")
|
|
233
|
+
return
|
|
190
234
|
_Utils.setup(None)
|
|
191
235
|
|
|
192
236
|
def _handle_help(self) -> None:
|
|
193
237
|
create_parser().print_help()
|
|
238
|
+
if self._is_remote:
|
|
239
|
+
print(f"\nMode: REMOTE ({os.environ.get('IOP_URL', 'via IOP_SETTINGS')})")
|
|
194
240
|
try:
|
|
195
|
-
print(f"\nDefault production: {
|
|
196
|
-
|
|
241
|
+
print(f"\nDefault production: {self.director.get_default_production()}")
|
|
242
|
+
ns = (self.director._namespace # type: ignore[union-attr]
|
|
243
|
+
if self._is_remote else os.getenv('IRISNAMESPACE', 'not set'))
|
|
244
|
+
print(f"\nNamespace: {ns}")
|
|
197
245
|
except Exception:
|
|
198
246
|
logging.warning("Could not retrieve default production.")
|
|
199
247
|
|
|
248
|
+
def _format_test_response(response) -> str:
|
|
249
|
+
"""Pretty-print any test_component() return value.
|
|
250
|
+
|
|
251
|
+
Handles three cases:
|
|
252
|
+
- dict : remote response with ``classname`` / ``body`` keys
|
|
253
|
+
- str : local response in ``"ClassName : {json}"`` format
|
|
254
|
+
- other : Python dataclass / object returned by the local director
|
|
255
|
+
"""
|
|
256
|
+
if isinstance(response, dict):
|
|
257
|
+
parts = []
|
|
258
|
+
if response.get("error"):
|
|
259
|
+
return f"Error: {response['error']}"
|
|
260
|
+
if response.get("classname"):
|
|
261
|
+
parts.append(f"classname: {response['classname']}")
|
|
262
|
+
body = response.get("body", "")
|
|
263
|
+
if body:
|
|
264
|
+
try:
|
|
265
|
+
parsed = json.loads(body)
|
|
266
|
+
parts.append("body:\n" + json.dumps(parsed, indent=4))
|
|
267
|
+
except (json.JSONDecodeError, TypeError):
|
|
268
|
+
parts.append(f"body: {body}")
|
|
269
|
+
if response.get("truncated"):
|
|
270
|
+
parts.append("(response body was truncated)")
|
|
271
|
+
return "\n".join(parts) if parts else str(response)
|
|
272
|
+
|
|
273
|
+
if isinstance(response, str):
|
|
274
|
+
# Try to detect the "ClassName : {json_body}" pattern from local mode
|
|
275
|
+
if " : " in response:
|
|
276
|
+
classname_part, _, body_part = response.partition(" : ")
|
|
277
|
+
try:
|
|
278
|
+
parsed = json.loads(body_part)
|
|
279
|
+
return (
|
|
280
|
+
f"classname: {classname_part.strip()}\n"
|
|
281
|
+
f"body:\n{json.dumps(parsed, indent=4)}"
|
|
282
|
+
)
|
|
283
|
+
except (json.JSONDecodeError, TypeError):
|
|
284
|
+
pass
|
|
285
|
+
# Plain string — try JSON pretty-print
|
|
286
|
+
try:
|
|
287
|
+
return json.dumps(json.loads(response), indent=4)
|
|
288
|
+
except (json.JSONDecodeError, TypeError):
|
|
289
|
+
return response
|
|
290
|
+
|
|
291
|
+
# Python dataclass / arbitrary object
|
|
292
|
+
try:
|
|
293
|
+
import dataclasses
|
|
294
|
+
if dataclasses.is_dataclass(response):
|
|
295
|
+
return json.dumps(dataclasses.asdict(response), indent=4)
|
|
296
|
+
except Exception:
|
|
297
|
+
pass
|
|
298
|
+
return str(response)
|
|
299
|
+
|
|
300
|
+
|
|
200
301
|
def create_parser() -> argparse.ArgumentParser:
|
|
201
302
|
"""Create and configure argument parser"""
|
|
202
|
-
main_parser = argparse.ArgumentParser(
|
|
303
|
+
main_parser = argparse.ArgumentParser(
|
|
304
|
+
epilog=(
|
|
305
|
+
"Remote mode: set IOP_URL (e.g. http://localhost:8080) to run all commands\n"
|
|
306
|
+
"against a remote IRIS instance via its REST API. Optional env vars:\n"
|
|
307
|
+
" IOP_USERNAME, IOP_PASSWORD, IOP_NAMESPACE (default: USER),\n"
|
|
308
|
+
" IOP_VERIFY_SSL (set to 0 to disable TLS verification).\n"
|
|
309
|
+
"Alternatively use -R /path/to/settings.py or set IOP_SETTINGS=\n"
|
|
310
|
+
"(file must contain a REMOTE_SETTINGS dict with at least 'url').\n"
|
|
311
|
+
"Use --force-local to suppress remote mode entirely."
|
|
312
|
+
),
|
|
313
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
314
|
+
)
|
|
203
315
|
parser = main_parser.add_mutually_exclusive_group()
|
|
204
316
|
|
|
205
317
|
# Main commands
|
|
@@ -224,10 +336,13 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
224
336
|
|
|
225
337
|
test = main_parser.add_argument_group('test arguments')
|
|
226
338
|
test.add_argument('-C', '--classname', help='test classname', nargs='?', const='not_set')
|
|
227
|
-
test.add_argument('-B', '--body', help='test body', nargs='?', const='not_set')
|
|
339
|
+
test.add_argument('-B', '--body', help='test body (JSON string or @path/to/file.json)', nargs='?', const='not_set')
|
|
228
340
|
|
|
229
341
|
migrate = main_parser.add_argument_group('migrate arguments')
|
|
230
|
-
migrate.add_argument('--force-local', help='force local
|
|
342
|
+
migrate.add_argument('--force-local', help='force local mode, skip remote even if REMOTE_SETTINGS or IOP_URL is present', action='store_true')
|
|
343
|
+
|
|
344
|
+
remote = main_parser.add_argument_group('remote arguments')
|
|
345
|
+
remote.add_argument('-R', '--remote-settings', help='path to a settings.py containing REMOTE_SETTINGS (overrides IOP_SETTINGS env var)', metavar='FILE')
|
|
231
346
|
|
|
232
347
|
namespace = main_parser.add_argument_group('namespace arguments')
|
|
233
348
|
namespace.add_argument('-n', '--namespace', help='set namespace', nargs='?', const='not_set')
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Local director: thin instance-method wrapper around static _Director calls.
|
|
2
|
+
|
|
3
|
+
This gives the CLI a uniform interface so it can swap between
|
|
4
|
+
_LocalDirector and _RemoteDirector without any branching.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from ._director import _Director
|
|
12
|
+
from ._utils import _Utils
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _LocalDirector:
|
|
16
|
+
|
|
17
|
+
# ------------------------------------------------------------------
|
|
18
|
+
# Production lifecycle
|
|
19
|
+
# ------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
def get_default_production(self) -> str:
|
|
22
|
+
return _Director.get_default_production()
|
|
23
|
+
|
|
24
|
+
def set_default_production(self, production_name: str = "") -> None:
|
|
25
|
+
_Director.set_default_production(production_name)
|
|
26
|
+
|
|
27
|
+
def list_productions(self) -> dict:
|
|
28
|
+
return _Director.list_productions()
|
|
29
|
+
|
|
30
|
+
def status_production(self) -> dict:
|
|
31
|
+
return _Director.status_production()
|
|
32
|
+
|
|
33
|
+
def start_production(self, production_name: Optional[str] = None) -> None:
|
|
34
|
+
_Director.start_production(production_name)
|
|
35
|
+
|
|
36
|
+
def start_production_with_log(self, production_name: Optional[str] = None) -> None:
|
|
37
|
+
_Director.start_production_with_log(production_name)
|
|
38
|
+
|
|
39
|
+
def stop_production(self) -> None:
|
|
40
|
+
_Director.stop_production()
|
|
41
|
+
|
|
42
|
+
def shutdown_production(self) -> None:
|
|
43
|
+
_Director.shutdown_production()
|
|
44
|
+
|
|
45
|
+
def restart_production(self) -> None:
|
|
46
|
+
_Director.restart_production()
|
|
47
|
+
|
|
48
|
+
def update_production(self) -> None:
|
|
49
|
+
_Director.update_production()
|
|
50
|
+
|
|
51
|
+
# ------------------------------------------------------------------
|
|
52
|
+
# Logging
|
|
53
|
+
# ------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
def log_production_top(self, top: int = 10) -> None:
|
|
56
|
+
_Director.log_production_top(top)
|
|
57
|
+
|
|
58
|
+
def log_production(self) -> None:
|
|
59
|
+
_Director.log_production()
|
|
60
|
+
|
|
61
|
+
# ------------------------------------------------------------------
|
|
62
|
+
# Test
|
|
63
|
+
# ------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
def test_component(
|
|
66
|
+
self,
|
|
67
|
+
target: Optional[str],
|
|
68
|
+
message=None,
|
|
69
|
+
classname: Optional[str] = None,
|
|
70
|
+
body: Optional[str] = None,
|
|
71
|
+
):
|
|
72
|
+
return _Director.test_component(target, message, classname, body)
|
|
73
|
+
|
|
74
|
+
# ------------------------------------------------------------------
|
|
75
|
+
# Export
|
|
76
|
+
# ------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
def export_production(self, production_name: str) -> dict:
|
|
79
|
+
return _Utils.export_production(production_name)
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""Remote director: mirrors _Director's interface over the IOP REST API.
|
|
2
|
+
|
|
3
|
+
Configure via environment variables:
|
|
4
|
+
IOP_URL Required. e.g. http://localhost:8080
|
|
5
|
+
IOP_USERNAME Optional. Default: ""
|
|
6
|
+
IOP_PASSWORD Optional. Default: ""
|
|
7
|
+
IOP_NAMESPACE Optional. Default: "USER"
|
|
8
|
+
IOP_VERIFY_SSL Optional. "0"/"false" to disable TLS verification.
|
|
9
|
+
|
|
10
|
+
Or pass a RemoteSettings dict directly (same shape as REMOTE_SETTINGS in settings.py).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import signal
|
|
18
|
+
import time
|
|
19
|
+
from typing import Any, Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
import requests
|
|
22
|
+
import urllib3
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _RemoteDirector:
|
|
26
|
+
"""Implements the same interface as _Director but dispatches over HTTP."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, remote_settings: Dict[str, Any]) -> None:
|
|
29
|
+
self._base = remote_settings["url"].rstrip("/") + "/api/iop"
|
|
30
|
+
self._auth = (
|
|
31
|
+
remote_settings.get("username", ""),
|
|
32
|
+
remote_settings.get("password", ""),
|
|
33
|
+
)
|
|
34
|
+
self._namespace: str = remote_settings.get("namespace", "USER")
|
|
35
|
+
self._verify: bool = remote_settings.get("verify_ssl", True)
|
|
36
|
+
if not self._verify:
|
|
37
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
38
|
+
|
|
39
|
+
# ------------------------------------------------------------------
|
|
40
|
+
# Internal helpers
|
|
41
|
+
# ------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
def _get(self, path: str, params: Optional[dict] = None) -> Any:
|
|
44
|
+
p = {"namespace": self._namespace, **(params or {})}
|
|
45
|
+
resp = requests.get(
|
|
46
|
+
f"{self._base}{path}", params=p, auth=self._auth,
|
|
47
|
+
verify=self._verify, timeout=30,
|
|
48
|
+
)
|
|
49
|
+
resp.raise_for_status()
|
|
50
|
+
return resp.json()
|
|
51
|
+
|
|
52
|
+
def _post(self, path: str, body: Optional[dict] = None) -> Any:
|
|
53
|
+
b = {"namespace": self._namespace, **(body or {})}
|
|
54
|
+
resp = requests.post(
|
|
55
|
+
f"{self._base}{path}", json=b, auth=self._auth,
|
|
56
|
+
verify=self._verify, timeout=30,
|
|
57
|
+
)
|
|
58
|
+
resp.raise_for_status()
|
|
59
|
+
return resp.json()
|
|
60
|
+
|
|
61
|
+
def _put(self, path: str, body: Optional[dict] = None) -> Any:
|
|
62
|
+
b = {"namespace": self._namespace, **(body or {})}
|
|
63
|
+
resp = requests.put(
|
|
64
|
+
f"{self._base}{path}", json=b, auth=self._auth,
|
|
65
|
+
verify=self._verify, timeout=30,
|
|
66
|
+
)
|
|
67
|
+
resp.raise_for_status()
|
|
68
|
+
return resp.json()
|
|
69
|
+
|
|
70
|
+
def _check_error(self, data: Any) -> Any:
|
|
71
|
+
if isinstance(data, dict) and "error" in data:
|
|
72
|
+
raise RuntimeError(data["error"])
|
|
73
|
+
return data
|
|
74
|
+
|
|
75
|
+
# ------------------------------------------------------------------
|
|
76
|
+
# Production lifecycle
|
|
77
|
+
# ------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
def get_default_production(self) -> str:
|
|
80
|
+
data = self._check_error(self._get("/default"))
|
|
81
|
+
return data.get("production") or "Not defined"
|
|
82
|
+
|
|
83
|
+
def set_default_production(self, production_name: str = "") -> None:
|
|
84
|
+
self._check_error(self._put("/default", {"production": production_name}))
|
|
85
|
+
|
|
86
|
+
def list_productions(self) -> dict:
|
|
87
|
+
return self._check_error(self._get("/list"))
|
|
88
|
+
|
|
89
|
+
def status_production(self) -> dict:
|
|
90
|
+
data = self._check_error(self._get("/status"))
|
|
91
|
+
if not data.get("production"):
|
|
92
|
+
data["production"] = self.get_default_production()
|
|
93
|
+
return data
|
|
94
|
+
|
|
95
|
+
def start_production(self, production_name: Optional[str] = None) -> None:
|
|
96
|
+
body: dict = {}
|
|
97
|
+
if production_name:
|
|
98
|
+
body["production"] = production_name
|
|
99
|
+
self._check_error(self._post("/start", body))
|
|
100
|
+
|
|
101
|
+
def start_production_with_log(self, production_name: Optional[str] = None) -> None:
|
|
102
|
+
"""Start remotely then stream the log until Ctrl-C (which also stops)."""
|
|
103
|
+
self.start_production(production_name)
|
|
104
|
+
prod = production_name or self.get_default_production()
|
|
105
|
+
print(f"Production '{prod}' started. Streaming log — Ctrl-C to stop.")
|
|
106
|
+
running = True
|
|
107
|
+
|
|
108
|
+
def _sigint(sig, frame): # pragma: no cover
|
|
109
|
+
nonlocal running
|
|
110
|
+
running = False
|
|
111
|
+
|
|
112
|
+
signal.signal(signal.SIGINT, _sigint)
|
|
113
|
+
|
|
114
|
+
last_id = 0
|
|
115
|
+
for entry in self._get_log_entries(top=10):
|
|
116
|
+
_print_log_entry(entry)
|
|
117
|
+
last_id = max(last_id, entry.get("id", 0))
|
|
118
|
+
|
|
119
|
+
while running:
|
|
120
|
+
time.sleep(1)
|
|
121
|
+
entries = self._get_log_entries(since_id=last_id)
|
|
122
|
+
for entry in entries:
|
|
123
|
+
_print_log_entry(entry)
|
|
124
|
+
last_id = max(last_id, entry.get("id", 0))
|
|
125
|
+
|
|
126
|
+
self.stop_production()
|
|
127
|
+
|
|
128
|
+
def stop_production(self) -> None:
|
|
129
|
+
self._check_error(self._post("/stop"))
|
|
130
|
+
|
|
131
|
+
def shutdown_production(self) -> None:
|
|
132
|
+
self._check_error(self._post("/kill"))
|
|
133
|
+
|
|
134
|
+
def restart_production(self) -> dict:
|
|
135
|
+
return self._check_error(self._post("/restart"))
|
|
136
|
+
|
|
137
|
+
def update_production(self) -> dict:
|
|
138
|
+
return self._check_error(self._post("/update"))
|
|
139
|
+
|
|
140
|
+
# ------------------------------------------------------------------
|
|
141
|
+
# Logging
|
|
142
|
+
# ------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
def _get_log_entries(
|
|
145
|
+
self,
|
|
146
|
+
top: int = 10,
|
|
147
|
+
since_id: Optional[int] = None,
|
|
148
|
+
) -> List[dict]:
|
|
149
|
+
params: dict = {}
|
|
150
|
+
if since_id is not None:
|
|
151
|
+
params["since_id"] = since_id
|
|
152
|
+
else:
|
|
153
|
+
params["top"] = top
|
|
154
|
+
data = self._check_error(self._get("/log", params))
|
|
155
|
+
return data if isinstance(data, list) else []
|
|
156
|
+
|
|
157
|
+
def log_production_top(self, top: int = 10) -> None:
|
|
158
|
+
entries = self._get_log_entries(top=top)
|
|
159
|
+
for entry in reversed(entries):
|
|
160
|
+
_print_log_entry(entry)
|
|
161
|
+
|
|
162
|
+
def log_production(self) -> None:
|
|
163
|
+
"""Stream log continuously until Ctrl-C."""
|
|
164
|
+
running = True
|
|
165
|
+
|
|
166
|
+
def _sigint(sig, frame): # pragma: no cover
|
|
167
|
+
nonlocal running
|
|
168
|
+
running = False
|
|
169
|
+
|
|
170
|
+
signal.signal(signal.SIGINT, _sigint)
|
|
171
|
+
|
|
172
|
+
last_id = 0
|
|
173
|
+
for entry in self._get_log_entries(top=10):
|
|
174
|
+
_print_log_entry(entry)
|
|
175
|
+
last_id = max(last_id, entry.get("id", 0))
|
|
176
|
+
|
|
177
|
+
while running:
|
|
178
|
+
time.sleep(1)
|
|
179
|
+
entries = self._get_log_entries(since_id=last_id)
|
|
180
|
+
for entry in entries:
|
|
181
|
+
_print_log_entry(entry)
|
|
182
|
+
last_id = max(last_id, entry.get("id", 0))
|
|
183
|
+
|
|
184
|
+
# ------------------------------------------------------------------
|
|
185
|
+
# Test
|
|
186
|
+
# ------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
def test_component(
|
|
189
|
+
self,
|
|
190
|
+
target: Optional[str],
|
|
191
|
+
message=None, # ignored remotely — not serialisable over HTTP
|
|
192
|
+
classname: Optional[str] = None,
|
|
193
|
+
body: Optional[str] = None,
|
|
194
|
+
) -> dict:
|
|
195
|
+
"""Returns a dict: {"classname": "...", "body": "...", "truncated": false}"""
|
|
196
|
+
payload: dict = {"target": target or ""}
|
|
197
|
+
if classname:
|
|
198
|
+
payload["classname"] = classname
|
|
199
|
+
if body:
|
|
200
|
+
payload["body"] = body
|
|
201
|
+
try:
|
|
202
|
+
return self._check_error(self._post("/test", payload))
|
|
203
|
+
except requests.exceptions.HTTPError as exc:
|
|
204
|
+
raise RuntimeError(str(exc)) from exc
|
|
205
|
+
|
|
206
|
+
# ------------------------------------------------------------------
|
|
207
|
+
# Export
|
|
208
|
+
# ------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
def export_production(self, production_name: str) -> dict:
|
|
211
|
+
import xmltodict # already required by _utils
|
|
212
|
+
|
|
213
|
+
data = self._check_error(
|
|
214
|
+
self._get("/export", {"production": production_name})
|
|
215
|
+
)
|
|
216
|
+
xml = data.get("xml", "")
|
|
217
|
+
if not xml:
|
|
218
|
+
return {}
|
|
219
|
+
|
|
220
|
+
def _postprocessor(path, key, value):
|
|
221
|
+
return key, "" if value is None else value
|
|
222
|
+
|
|
223
|
+
return xmltodict.parse(xml, postprocessor=_postprocessor)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ------------------------------------------------------------------
|
|
227
|
+
# Shared helpers
|
|
228
|
+
# ------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
def _print_log_entry(entry: dict) -> None:
|
|
231
|
+
print(
|
|
232
|
+
entry.get("time_logged", ""),
|
|
233
|
+
entry.get("type", ""),
|
|
234
|
+
entry.get("config_name", ""),
|
|
235
|
+
entry.get("job", ""),
|
|
236
|
+
entry.get("message_id", ""),
|
|
237
|
+
entry.get("session_id", ""),
|
|
238
|
+
entry.get("source_class", ""),
|
|
239
|
+
entry.get("source_method", ""),
|
|
240
|
+
entry.get("text", ""),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _load_remote_settings_from_file(settings_path: str) -> Optional[Dict[str, Any]]:
|
|
245
|
+
"""Load a ``REMOTE_SETTINGS`` dict from an arbitrary settings.py file."""
|
|
246
|
+
try:
|
|
247
|
+
import importlib.util
|
|
248
|
+
spec = importlib.util.spec_from_file_location("_iop_settings_remote", settings_path)
|
|
249
|
+
mod = importlib.util.module_from_spec(spec) # type: ignore[arg-type]
|
|
250
|
+
spec.loader.exec_module(mod) # type: ignore[union-attr]
|
|
251
|
+
remote = getattr(mod, "REMOTE_SETTINGS", None)
|
|
252
|
+
if isinstance(remote, dict) and "url" in remote:
|
|
253
|
+
return remote
|
|
254
|
+
except Exception:
|
|
255
|
+
pass
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def get_remote_settings(
|
|
260
|
+
explicit_settings_path: Optional[str] = None,
|
|
261
|
+
fallback_settings_path: Optional[str] = None,
|
|
262
|
+
) -> Optional[Dict[str, Any]]:
|
|
263
|
+
"""Detect remote settings from the environment or an explicit file.
|
|
264
|
+
|
|
265
|
+
Priority:
|
|
266
|
+
1. ``IOP_URL`` env var (direct inline configuration).
|
|
267
|
+
2. ``explicit_settings_path`` — the file supplied via ``--remote-settings``.
|
|
268
|
+
3. ``IOP_SETTINGS`` env var pointing to a settings.py with ``REMOTE_SETTINGS``.
|
|
269
|
+
4. ``fallback_settings_path`` — e.g. the file passed via ``-m settings.py``;
|
|
270
|
+
its ``REMOTE_SETTINGS`` dict is used when present.
|
|
271
|
+
"""
|
|
272
|
+
url = os.environ.get("IOP_URL")
|
|
273
|
+
if url:
|
|
274
|
+
verify_raw = os.environ.get("IOP_VERIFY_SSL", "1")
|
|
275
|
+
return {
|
|
276
|
+
"url": url,
|
|
277
|
+
"username": os.environ.get("IOP_USERNAME", ""),
|
|
278
|
+
"password": os.environ.get("IOP_PASSWORD", ""),
|
|
279
|
+
"namespace": os.environ.get("IOP_NAMESPACE", "USER"),
|
|
280
|
+
"verify_ssl": verify_raw.lower() not in ("0", "false"),
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
for path in filter(None, [
|
|
284
|
+
explicit_settings_path,
|
|
285
|
+
os.environ.get("IOP_SETTINGS"),
|
|
286
|
+
fallback_settings_path,
|
|
287
|
+
]):
|
|
288
|
+
result = _load_remote_settings_from_file(path)
|
|
289
|
+
if result:
|
|
290
|
+
return result
|
|
291
|
+
|
|
292
|
+
return None
|
|
293
|
+
|