iris-pex-embedded-python 3.7.2b2__py3-none-any.whl → 4.0.0__py3-none-any.whl
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.
- iop/__init__.py +276 -18
- iop/__main__.py +3 -3
- iop/cli/__init__.py +1 -0
- iop/cli/formatting.py +45 -0
- iop/{_cli.py → cli/main.py} +238 -216
- iop/cli/parser.py +165 -0
- iop/cli/types.py +73 -0
- iop/cls/IOP/Common.cls +11 -2
- iop/cls/IOP/PrivateSession/Duplex.cls +1 -1
- iop/cls/IOP/Service/Remote/Rest/v1.cls +152 -5
- iop/cls/IOP/Utils.cls +814 -7
- iop/cls/IOP/Wrapper.cls +1 -1
- iop/components/__init__.py +16 -0
- iop/{_async_request.py → components/async_request.py} +7 -7
- iop/components/business_host.py +363 -0
- iop/components/business_operation.py +84 -0
- iop/{_business_process.py → components/business_process.py} +145 -125
- iop/components/business_service.py +106 -0
- iop/{_common.py → components/common.py} +160 -164
- iop/{_debugpy.py → components/debugpy.py} +11 -11
- iop/{_generator_request.py → components/generator_request.py} +4 -4
- iop/{_inbound_adapter.py → components/inbound_adapter.py} +10 -18
- iop/{_log_manager.py → components/log_manager.py} +1 -1
- iop/{_outbound_adapter.py → components/outbound_adapter.py} +1 -1
- iop/components/polling_business_service.py +48 -0
- iop/{_private_session_duplex.py → components/private_session_duplex.py} +7 -9
- iop/{_private_session_process.py → components/private_session_process.py} +2 -2
- iop/{_settings.py → components/settings.py} +23 -3
- iop/messages/__init__.py +7 -0
- iop/messages/base.py +61 -0
- iop/{_decorators.py → messages/decorators.py} +8 -2
- iop/messages/dispatch.py +351 -0
- iop/{_persistent_message.py → messages/persistent.py} +24 -19
- iop/{_serialization.py → messages/serialization.py} +34 -21
- iop/{_message_validator.py → messages/validation.py} +15 -13
- iop/migration/__init__.py +1 -0
- iop/migration/io.py +61 -0
- iop/migration/manifest.py +534 -0
- iop/migration/plans.py +80 -0
- iop/migration/utils.py +1126 -0
- iop/production/__init__.py +55 -0
- iop/production/actions.py +525 -0
- iop/production/common.py +103 -0
- iop/production/component.py +234 -0
- iop/production/declarations.py +190 -0
- iop/production/declarative.py +214 -0
- iop/production/diff.py +327 -0
- iop/production/import_.py +296 -0
- iop/production/inspection.py +92 -0
- iop/production/model.py +1143 -0
- iop/production/planning.py +678 -0
- iop/production/reconstruction.py +341 -0
- iop/production/rendering.py +610 -0
- iop/production/runtime.py +91 -0
- iop/production/source_inference.py +729 -0
- iop/production/types.py +546 -0
- iop/production/validation.py +452 -0
- iop/runtime/__init__.py +1 -0
- iop/runtime/director.py +415 -0
- iop/runtime/environment.py +59 -0
- iop/{_iris.py → runtime/iris.py} +2 -2
- iop/runtime/local.py +145 -0
- iop/{_director_protocol.py → runtime/protocol.py} +25 -8
- iop/runtime/remote/__init__.py +12 -0
- iop/runtime/remote/client.py +90 -0
- iop/runtime/remote/director.py +283 -0
- iop/runtime/remote/migration.py +57 -0
- iop/runtime/remote/settings.py +69 -0
- iop/runtime/remote/setup.py +72 -0
- {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/METADATA +11 -2
- iris_pex_embedded_python-4.0.0.dist-info/RECORD +99 -0
- iris_pex_embedded_python-4.0.0.dist-info/entry_points.txt +2 -0
- iris_pex_embedded_python-4.0.0.dist-info/top_level.txt +1 -0
- grongier/__init__.py +0 -0
- grongier/cls/Grongier/PEX/BusinessOperation.cls +0 -8
- grongier/cls/Grongier/PEX/BusinessProcess.cls +0 -13
- grongier/cls/Grongier/PEX/BusinessService.cls +0 -8
- grongier/cls/Grongier/PEX/Common.cls +0 -10
- grongier/cls/Grongier/PEX/Director.cls +0 -10
- grongier/cls/Grongier/PEX/Duplex/Operation.cls +0 -4
- grongier/cls/Grongier/PEX/Duplex/Process.cls +0 -13
- grongier/cls/Grongier/PEX/Duplex/Service.cls +0 -4
- grongier/cls/Grongier/PEX/InboundAdapter.cls +0 -8
- grongier/cls/Grongier/PEX/Message.cls +0 -13
- grongier/cls/Grongier/PEX/OutboundAdapter.cls +0 -8
- grongier/cls/Grongier/PEX/PickleMessage.cls +0 -13
- grongier/cls/Grongier/PEX/PrivateSession/Duplex.cls +0 -8
- grongier/cls/Grongier/PEX/PrivateSession/Message/Ack.cls +0 -14
- grongier/cls/Grongier/PEX/PrivateSession/Message/Poll.cls +0 -14
- grongier/cls/Grongier/PEX/PrivateSession/Message/Start.cls +0 -14
- grongier/cls/Grongier/PEX/PrivateSession/Message/Stop.cls +0 -14
- grongier/cls/Grongier/PEX/Test.cls +0 -10
- grongier/cls/Grongier/PEX/Utils.cls +0 -10
- grongier/cls/Grongier/Service/WSGI.cls +0 -4
- grongier/pex/__init__.py +0 -75
- grongier/pex/__main__.py +0 -4
- grongier/pex/_business_host.py +0 -1
- grongier/pex/_cli.py +0 -4
- grongier/pex/_common.py +0 -1
- grongier/pex/_director.py +0 -1
- grongier/pex/_utils.py +0 -1
- grongier/pex/wsgi/handlers.py +0 -104
- iop/_business_host.py +0 -322
- iop/_business_operation.py +0 -77
- iop/_business_service.py +0 -67
- iop/_director.py +0 -333
- iop/_dispatch.py +0 -194
- iop/_local.py +0 -106
- iop/_message.py +0 -45
- iop/_polling_business_service.py +0 -6
- iop/_remote.py +0 -473
- iop/_utils.py +0 -912
- iop/cls/IOP/Service/WSGI.cls +0 -310
- iop/wsgi/handlers.py +0 -112
- iris_pex_embedded_python-3.7.2b2.dist-info/RECORD +0 -96
- iris_pex_embedded_python-3.7.2b2.dist-info/entry_points.txt +0 -2
- iris_pex_embedded_python-3.7.2b2.dist-info/top_level.txt +0 -2
- {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/WHEEL +0 -0
- {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/licenses/LICENSE +0 -0
iop/{_cli.py → cli/main.py}
RENAMED
|
@@ -1,64 +1,23 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
|
-
import requests
|
|
6
5
|
import os
|
|
7
|
-
from dataclasses import dataclass
|
|
8
|
-
from enum import Enum, auto
|
|
9
6
|
import sys
|
|
10
|
-
from typing import Optional
|
|
11
7
|
from importlib.metadata import version
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
RESTART = auto()
|
|
26
|
-
STATUS = auto()
|
|
27
|
-
TEST = auto()
|
|
28
|
-
VERSION = auto()
|
|
29
|
-
EXPORT = auto()
|
|
30
|
-
MIGRATE = auto()
|
|
31
|
-
LOG = auto()
|
|
32
|
-
INIT = auto()
|
|
33
|
-
HELP = auto()
|
|
34
|
-
UPDATE = auto()
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@dataclass
|
|
38
|
-
class CommandArgs:
|
|
39
|
-
"""Container for parsed command arguments"""
|
|
40
|
-
|
|
41
|
-
default: Optional[str] = None
|
|
42
|
-
list: bool = False
|
|
43
|
-
start: Optional[str] = None
|
|
44
|
-
detach: bool = False
|
|
45
|
-
stop: bool = False
|
|
46
|
-
kill: bool = False
|
|
47
|
-
restart: bool = False
|
|
48
|
-
status: bool = False
|
|
49
|
-
migrate: Optional[str] = None
|
|
50
|
-
export: Optional[str] = None
|
|
51
|
-
version: bool = False
|
|
52
|
-
log: Optional[str] = None
|
|
53
|
-
init: Optional[str] = None
|
|
54
|
-
test: Optional[str] = None
|
|
55
|
-
classname: Optional[str] = None
|
|
56
|
-
body: Optional[str] = None
|
|
57
|
-
namespace: Optional[str] = None
|
|
58
|
-
force_local: bool = False
|
|
59
|
-
remote_settings: Optional[str] = None
|
|
60
|
-
update: bool = False
|
|
61
|
-
migration_plan: bool = False
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from ..migration import utils as migration_utils
|
|
12
|
+
from ..production import Production, ProductionChangePlan
|
|
13
|
+
from ..runtime.local import _LocalDirector
|
|
14
|
+
from ..runtime.protocol import DirectorProtocol
|
|
15
|
+
from ..runtime.remote import _RemoteDirector, get_remote_settings
|
|
16
|
+
from .formatting import format_test_response
|
|
17
|
+
from .parser import create_parser
|
|
18
|
+
from .types import CommandArgs, CommandType
|
|
19
|
+
|
|
20
|
+
_format_test_response = format_test_response
|
|
62
21
|
|
|
63
22
|
|
|
64
23
|
class Command:
|
|
@@ -114,12 +73,23 @@ class Command:
|
|
|
114
73
|
self.args.export,
|
|
115
74
|
self.args.migrate,
|
|
116
75
|
self.args.log,
|
|
76
|
+
self.args.queue,
|
|
117
77
|
self.args.init,
|
|
78
|
+
self.args.bindings,
|
|
79
|
+
self.args.unbind is not None,
|
|
118
80
|
self.args.update,
|
|
81
|
+
self.args.plan,
|
|
82
|
+
self.args.review_plan,
|
|
83
|
+
self.args.apply_plan,
|
|
84
|
+
self.args.verify_plan,
|
|
85
|
+
self.args.rollback_backup,
|
|
119
86
|
]
|
|
120
87
|
)
|
|
121
88
|
|
|
122
89
|
def execute(self) -> None:
|
|
90
|
+
if self.args.unused and not self.args.bindings:
|
|
91
|
+
raise ValueError("--unused can only be used with --bindings.")
|
|
92
|
+
|
|
123
93
|
if self.args.namespace == "not_set" and not self._has_primary_command():
|
|
124
94
|
print(os.getenv("IRISNAMESPACE", "not set"))
|
|
125
95
|
return
|
|
@@ -138,9 +108,17 @@ class Command:
|
|
|
138
108
|
CommandType.EXPORT: self._handle_export,
|
|
139
109
|
CommandType.MIGRATE: self._handle_migrate,
|
|
140
110
|
CommandType.LOG: self._handle_log,
|
|
111
|
+
CommandType.QUEUE: self._handle_queue,
|
|
141
112
|
CommandType.INIT: self._handle_init,
|
|
113
|
+
CommandType.BINDINGS: self._handle_bindings,
|
|
114
|
+
CommandType.UNBIND: self._handle_unbind,
|
|
142
115
|
CommandType.HELP: self._handle_help,
|
|
143
116
|
CommandType.UPDATE: self._handle_update,
|
|
117
|
+
CommandType.PLAN: self._handle_plan,
|
|
118
|
+
CommandType.REVIEW_PLAN: self._handle_review_plan,
|
|
119
|
+
CommandType.APPLY_PLAN: self._handle_apply_plan,
|
|
120
|
+
CommandType.VERIFY_PLAN: self._handle_verify_plan,
|
|
121
|
+
CommandType.ROLLBACK_BACKUP: self._handle_rollback_backup,
|
|
144
122
|
}
|
|
145
123
|
handler = command_handlers.get(command_type)
|
|
146
124
|
if handler:
|
|
@@ -171,10 +149,26 @@ class Command:
|
|
|
171
149
|
return CommandType.MIGRATE
|
|
172
150
|
if self.args.log:
|
|
173
151
|
return CommandType.LOG
|
|
152
|
+
if self.args.queue:
|
|
153
|
+
return CommandType.QUEUE
|
|
174
154
|
if self.args.init:
|
|
175
155
|
return CommandType.INIT
|
|
156
|
+
if self.args.bindings:
|
|
157
|
+
return CommandType.BINDINGS
|
|
158
|
+
if self.args.unbind is not None:
|
|
159
|
+
return CommandType.UNBIND
|
|
176
160
|
if self.args.update:
|
|
177
161
|
return CommandType.UPDATE
|
|
162
|
+
if self.args.plan:
|
|
163
|
+
return CommandType.PLAN
|
|
164
|
+
if self.args.review_plan:
|
|
165
|
+
return CommandType.REVIEW_PLAN
|
|
166
|
+
if self.args.apply_plan:
|
|
167
|
+
return CommandType.APPLY_PLAN
|
|
168
|
+
if self.args.verify_plan:
|
|
169
|
+
return CommandType.VERIFY_PLAN
|
|
170
|
+
if self.args.rollback_backup:
|
|
171
|
+
return CommandType.ROLLBACK_BACKUP
|
|
178
172
|
return CommandType.HELP
|
|
179
173
|
|
|
180
174
|
def _handle_default(self) -> None:
|
|
@@ -230,13 +224,13 @@ class Command:
|
|
|
230
224
|
filepath = body[1:]
|
|
231
225
|
if not os.path.isabs(filepath):
|
|
232
226
|
filepath = os.path.join(os.getcwd(), filepath)
|
|
233
|
-
with open(filepath,
|
|
227
|
+
with open(filepath, encoding="utf-8") as fh:
|
|
234
228
|
body = fh.read()
|
|
235
229
|
|
|
236
230
|
response = self.director.test_component(
|
|
237
231
|
test_name, classname=classname, body=body
|
|
238
232
|
)
|
|
239
|
-
print(
|
|
233
|
+
print(format_test_response(response))
|
|
240
234
|
|
|
241
235
|
def _handle_version(self) -> None:
|
|
242
236
|
print(version("iris-pex-embedded-python"))
|
|
@@ -247,7 +241,25 @@ class Command:
|
|
|
247
241
|
if self.args.export == "not_set"
|
|
248
242
|
else self.args.export
|
|
249
243
|
)
|
|
250
|
-
|
|
244
|
+
export_format = self.args.export_format or "json"
|
|
245
|
+
if export_format == "json":
|
|
246
|
+
print(json.dumps(self.director.export_production(export_name), indent=4))
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
production = Production.from_iris(export_name, director=self.director)
|
|
250
|
+
if export_format == "python":
|
|
251
|
+
print(production.to_python(), end="")
|
|
252
|
+
return
|
|
253
|
+
if export_format == "class":
|
|
254
|
+
print(production.to_class(), end="")
|
|
255
|
+
return
|
|
256
|
+
if export_format == "graph":
|
|
257
|
+
print(production.graph())
|
|
258
|
+
return
|
|
259
|
+
if export_format == "mermaid":
|
|
260
|
+
print(production.to_mermaid(), end="")
|
|
261
|
+
return
|
|
262
|
+
raise ValueError(f"Unsupported export format: {export_format}")
|
|
251
263
|
|
|
252
264
|
def _handle_migrate(self) -> None:
|
|
253
265
|
migrate_path = self.args.migrate
|
|
@@ -257,21 +269,37 @@ class Command:
|
|
|
257
269
|
mode = "REMOTE" if self._is_remote else "LOCAL"
|
|
258
270
|
if self.args.migration_plan:
|
|
259
271
|
print(
|
|
260
|
-
|
|
261
|
-
migrate_path,
|
|
272
|
+
migration_utils.explain_migration(
|
|
273
|
+
migrate_path,
|
|
274
|
+
mode=mode,
|
|
275
|
+
namespace=self.director.namespace,
|
|
276
|
+
strict_production_validation=(
|
|
277
|
+
self.args.strict_production_validation
|
|
278
|
+
),
|
|
262
279
|
)
|
|
263
280
|
)
|
|
264
281
|
return
|
|
265
282
|
if self._is_remote:
|
|
266
283
|
print(
|
|
267
|
-
|
|
268
|
-
migrate_path,
|
|
284
|
+
migration_utils.explain_migration(
|
|
285
|
+
migrate_path,
|
|
286
|
+
mode=mode,
|
|
287
|
+
namespace=self.director.namespace,
|
|
288
|
+
strict_production_validation=(
|
|
289
|
+
self.args.strict_production_validation
|
|
290
|
+
),
|
|
269
291
|
)
|
|
270
292
|
)
|
|
271
|
-
self.
|
|
293
|
+
if self.args.strict_production_validation:
|
|
294
|
+
self.director.migrate(
|
|
295
|
+
migrate_path,
|
|
296
|
+
strict_production_validation=True,
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
self.director.migrate(migrate_path)
|
|
272
300
|
if self._is_remote:
|
|
273
301
|
print(
|
|
274
|
-
|
|
302
|
+
migration_utils.format_migration_success(
|
|
275
303
|
migrate_path, namespace=self.director.namespace
|
|
276
304
|
)
|
|
277
305
|
)
|
|
@@ -282,10 +310,92 @@ class Command:
|
|
|
282
310
|
elif self.args.log is not None:
|
|
283
311
|
self.director.log_production_top(int(self.args.log))
|
|
284
312
|
|
|
313
|
+
def _handle_queue(self) -> None:
|
|
314
|
+
production_name = (
|
|
315
|
+
self.director.get_default_production()
|
|
316
|
+
if self.args.queue == "not_set"
|
|
317
|
+
else self.args.queue
|
|
318
|
+
)
|
|
319
|
+
if not production_name or production_name == "Not defined":
|
|
320
|
+
print(
|
|
321
|
+
"Error: no production name provided and no default production is defined.",
|
|
322
|
+
file=sys.stderr,
|
|
323
|
+
)
|
|
324
|
+
sys.exit(1)
|
|
325
|
+
print(
|
|
326
|
+
json.dumps(
|
|
327
|
+
self.director.export_production_queue_info(production_name),
|
|
328
|
+
indent=4,
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
|
|
285
332
|
def _handle_init(self) -> None:
|
|
286
333
|
path = None if self.args.init == "not_set" else self.args.init
|
|
287
334
|
self.director.setup(path)
|
|
288
335
|
|
|
336
|
+
def _handle_bindings(self) -> None:
|
|
337
|
+
print(
|
|
338
|
+
json.dumps(
|
|
339
|
+
self.director.list_bindings(unused_only=self.args.unused),
|
|
340
|
+
indent=4,
|
|
341
|
+
)
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def _handle_unbind(self) -> None:
|
|
345
|
+
if not self.args.unbind:
|
|
346
|
+
raise ValueError("IRIS class name is required.")
|
|
347
|
+
self.director.unbind_component(self.args.unbind)
|
|
348
|
+
print(f"Removed binding {self.args.unbind}.")
|
|
349
|
+
|
|
350
|
+
def _handle_plan(self) -> None:
|
|
351
|
+
production = self._load_production_from_settings(
|
|
352
|
+
self.args.plan,
|
|
353
|
+
self.args.production,
|
|
354
|
+
)
|
|
355
|
+
plan = production.plan()
|
|
356
|
+
if self.args.out:
|
|
357
|
+
plan.save(self._absolute_path(self.args.out))
|
|
358
|
+
print(f"Production change plan written to {self.args.out}")
|
|
359
|
+
else:
|
|
360
|
+
print(plan)
|
|
361
|
+
|
|
362
|
+
def _handle_review_plan(self) -> None:
|
|
363
|
+
plan = ProductionChangePlan.load(self._absolute_path(self.args.review_plan))
|
|
364
|
+
print(plan)
|
|
365
|
+
|
|
366
|
+
def _handle_apply_plan(self) -> None:
|
|
367
|
+
if not self.args.settings:
|
|
368
|
+
raise ValueError("--apply-plan requires --settings.")
|
|
369
|
+
plan = ProductionChangePlan.load(self._absolute_path(self.args.apply_plan))
|
|
370
|
+
production = self._load_production_from_settings(
|
|
371
|
+
self.args.settings,
|
|
372
|
+
self.args.production or plan.production_name,
|
|
373
|
+
)
|
|
374
|
+
result = production.apply(
|
|
375
|
+
plan,
|
|
376
|
+
allow_destructive=self.args.allow_destructive,
|
|
377
|
+
backup_dir=self.args.backup_dir,
|
|
378
|
+
)
|
|
379
|
+
print(result)
|
|
380
|
+
|
|
381
|
+
def _handle_verify_plan(self) -> None:
|
|
382
|
+
plan = ProductionChangePlan.load(self._absolute_path(self.args.verify_plan))
|
|
383
|
+
production = Production(
|
|
384
|
+
self.args.production or plan.production_name,
|
|
385
|
+
namespace=self.director.namespace,
|
|
386
|
+
director=self.director,
|
|
387
|
+
)
|
|
388
|
+
print(production.verify(plan))
|
|
389
|
+
|
|
390
|
+
def _handle_rollback_backup(self) -> None:
|
|
391
|
+
result = Production.rollback_backup(
|
|
392
|
+
self._absolute_path(self.args.rollback_backup),
|
|
393
|
+
director=self.director,
|
|
394
|
+
namespace=self.director.namespace,
|
|
395
|
+
allow_destructive=self.args.allow_destructive,
|
|
396
|
+
)
|
|
397
|
+
print(result)
|
|
398
|
+
|
|
289
399
|
def _handle_help(self) -> None:
|
|
290
400
|
create_parser().print_help()
|
|
291
401
|
if self._is_remote:
|
|
@@ -296,161 +406,73 @@ class Command:
|
|
|
296
406
|
except Exception:
|
|
297
407
|
logging.warning("Could not retrieve default production.")
|
|
298
408
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if isinstance(response, dict):
|
|
309
|
-
parts = []
|
|
310
|
-
if response.get("error"):
|
|
311
|
-
return f"Error: {response['error']}"
|
|
312
|
-
if response.get("classname"):
|
|
313
|
-
parts.append(f"classname: {response['classname']}")
|
|
314
|
-
body = response.get("body", "")
|
|
315
|
-
if body:
|
|
316
|
-
try:
|
|
317
|
-
parsed = json.loads(body)
|
|
318
|
-
parts.append("body:\n" + json.dumps(parsed, indent=4))
|
|
319
|
-
except (json.JSONDecodeError, TypeError):
|
|
320
|
-
parts.append(f"body: {body}")
|
|
321
|
-
if response.get("truncated"):
|
|
322
|
-
parts.append("(response body was truncated)")
|
|
323
|
-
return "\n".join(parts) if parts else str(response)
|
|
324
|
-
|
|
325
|
-
if isinstance(response, str):
|
|
326
|
-
# Try to detect the "ClassName : {json_body}" pattern from local mode
|
|
327
|
-
if " : " in response:
|
|
328
|
-
classname_part, _, body_part = response.partition(" : ")
|
|
329
|
-
try:
|
|
330
|
-
parsed = json.loads(body_part)
|
|
331
|
-
return (
|
|
332
|
-
f"classname: {classname_part.strip()}\n"
|
|
333
|
-
f"body:\n{json.dumps(parsed, indent=4)}"
|
|
334
|
-
)
|
|
335
|
-
except (json.JSONDecodeError, TypeError):
|
|
336
|
-
pass
|
|
337
|
-
# Plain string — try JSON pretty-print
|
|
409
|
+
def _load_production_from_settings(
|
|
410
|
+
self,
|
|
411
|
+
settings_file: str | None,
|
|
412
|
+
production_name: str | None,
|
|
413
|
+
) -> Production:
|
|
414
|
+
if not settings_file:
|
|
415
|
+
raise ValueError("A settings.py file is required.")
|
|
416
|
+
settings_path = self._absolute_path(settings_file)
|
|
417
|
+
settings, path_added = migration_utils._load_settings(settings_path)
|
|
338
418
|
try:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
"
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
"
|
|
394
|
-
)
|
|
395
|
-
parser.add_argument("-v", "--version", help="display version", action="store_true")
|
|
396
|
-
parser.add_argument("-L", "--log", help="display log", nargs="?", const="not_set")
|
|
397
|
-
parser.add_argument(
|
|
398
|
-
"-i", "--init", help="init the pex module in iris", nargs="?", const="not_set"
|
|
399
|
-
)
|
|
400
|
-
parser.add_argument(
|
|
401
|
-
"-t", "--test", help="test the pex module in iris", nargs="?", const="not_set"
|
|
402
|
-
)
|
|
403
|
-
parser.add_argument(
|
|
404
|
-
"-u", "--update", help="update a production", action="store_true"
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
# Command groups
|
|
408
|
-
start = main_parser.add_argument_group("start arguments")
|
|
409
|
-
start.add_argument(
|
|
410
|
-
"-D", "--detach", help="start a production in detach mode", action="store_true"
|
|
411
|
-
)
|
|
412
|
-
|
|
413
|
-
test = main_parser.add_argument_group("test arguments")
|
|
414
|
-
test.add_argument(
|
|
415
|
-
"-C", "--classname", help="test classname", nargs="?", const="not_set"
|
|
416
|
-
)
|
|
417
|
-
test.add_argument(
|
|
418
|
-
"-B",
|
|
419
|
-
"--body",
|
|
420
|
-
help="test body (JSON string or @path/to/file.json)",
|
|
421
|
-
nargs="?",
|
|
422
|
-
const="not_set",
|
|
423
|
-
)
|
|
424
|
-
|
|
425
|
-
migrate = main_parser.add_argument_group("migrate arguments")
|
|
426
|
-
migrate.add_argument(
|
|
427
|
-
"--force-local",
|
|
428
|
-
help="force local mode, skip remote even if REMOTE_SETTINGS or IOP_URL is present",
|
|
429
|
-
action="store_true",
|
|
430
|
-
)
|
|
431
|
-
migrate.add_argument(
|
|
432
|
-
"--dry-run",
|
|
433
|
-
"--explain",
|
|
434
|
-
dest="migration_plan",
|
|
435
|
-
help="show the migration plan and validation messages without writing to IRIS",
|
|
436
|
-
action="store_true",
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
remote = main_parser.add_argument_group("remote arguments")
|
|
440
|
-
remote.add_argument(
|
|
441
|
-
"-R",
|
|
442
|
-
"--remote-settings",
|
|
443
|
-
help="path to a settings.py containing REMOTE_SETTINGS (overrides IOP_SETTINGS env var)",
|
|
444
|
-
metavar="FILE",
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
namespace = main_parser.add_argument_group("namespace arguments")
|
|
448
|
-
namespace.add_argument(
|
|
449
|
-
"-n", "--namespace", help="set namespace", nargs="?", const="not_set"
|
|
419
|
+
production = _select_production(
|
|
420
|
+
getattr(settings, "PRODUCTIONS", None),
|
|
421
|
+
production_name,
|
|
422
|
+
director=self.director,
|
|
423
|
+
namespace=self.director.namespace,
|
|
424
|
+
)
|
|
425
|
+
production.with_director(self.director)
|
|
426
|
+
if not production.namespace:
|
|
427
|
+
production.in_namespace(self.director.namespace)
|
|
428
|
+
return production
|
|
429
|
+
finally:
|
|
430
|
+
migration_utils._cleanup_sys_path(path_added)
|
|
431
|
+
|
|
432
|
+
@staticmethod
|
|
433
|
+
def _absolute_path(path: str | None) -> str:
|
|
434
|
+
if not path:
|
|
435
|
+
raise ValueError("Path is required.")
|
|
436
|
+
if os.path.isabs(path):
|
|
437
|
+
return path
|
|
438
|
+
return os.path.join(os.getcwd(), path)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _select_production(
|
|
442
|
+
productions,
|
|
443
|
+
production_name: str | None,
|
|
444
|
+
*,
|
|
445
|
+
director: DirectorProtocol,
|
|
446
|
+
namespace: str | None,
|
|
447
|
+
) -> Production:
|
|
448
|
+
if not isinstance(productions, list):
|
|
449
|
+
raise ValueError("settings.py must define PRODUCTIONS as a list.")
|
|
450
|
+
candidates: list[Production] = []
|
|
451
|
+
for entry in productions:
|
|
452
|
+
if isinstance(entry, Production):
|
|
453
|
+
candidate = entry
|
|
454
|
+
elif isinstance(entry, dict):
|
|
455
|
+
candidate = Production.from_dict(
|
|
456
|
+
entry,
|
|
457
|
+
director=director,
|
|
458
|
+
namespace=namespace,
|
|
459
|
+
)
|
|
460
|
+
else:
|
|
461
|
+
continue
|
|
462
|
+
candidates.append(candidate)
|
|
463
|
+
if production_name:
|
|
464
|
+
for candidate in candidates:
|
|
465
|
+
if candidate.name == production_name:
|
|
466
|
+
return candidate
|
|
467
|
+
raise ValueError(f"Production {production_name!r} not found in settings.")
|
|
468
|
+
if len(candidates) == 1:
|
|
469
|
+
return candidates[0]
|
|
470
|
+
names = ", ".join(candidate.name for candidate in candidates) or "none"
|
|
471
|
+
raise ValueError(
|
|
472
|
+
"Production name is required when settings.py contains multiple "
|
|
473
|
+
f"productions ({names})."
|
|
450
474
|
)
|
|
451
475
|
|
|
452
|
-
return main_parser
|
|
453
|
-
|
|
454
476
|
|
|
455
477
|
def main(argv=None) -> None:
|
|
456
478
|
parser = create_parser()
|
|
@@ -472,7 +494,7 @@ def main(argv=None) -> None:
|
|
|
472
494
|
except requests.exceptions.HTTPError as exc:
|
|
473
495
|
print(f"Error: {exc}", file=sys.stderr)
|
|
474
496
|
sys.exit(1)
|
|
475
|
-
except RuntimeError as exc:
|
|
497
|
+
except (RuntimeError, ValueError) as exc:
|
|
476
498
|
print(f"Error: {exc}", file=sys.stderr)
|
|
477
499
|
sys.exit(1)
|
|
478
500
|
sys.exit(0)
|