vortex-cli 4.10.1__tar.gz → 4.11.1__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.
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/PKG-INFO +1 -1
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/setup.cfg +1 -1
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/cli.py +10 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/clone.py +8 -3
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/copy.py +1 -1
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/main.py +6 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/models.py +17 -9
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/soap.py +17 -13
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/spinner.py +5 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/workspace.py +18 -21
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex_cli.egg-info/PKG-INFO +1 -1
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/LICENSE +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/README.md +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/setup.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/__init__.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/__main__.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/colour.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/__init__.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/clean.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/code.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/config.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/db.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/delete.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/docs.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/execute.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/find.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/grep.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/list.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/log.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/new.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/commands/watch.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/constants.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/docs/Blackbook.pdf +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/lib/puakma.jar +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/logging.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex/util.py +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex_cli.egg-info/SOURCES.txt +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex_cli.egg-info/dependency_links.txt +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex_cli.egg-info/entry_points.txt +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex_cli.egg-info/requires.txt +0 -0
- {vortex_cli-4.10.1 → vortex_cli-4.11.1}/vortex_cli.egg-info/top_level.txt +0 -0
|
@@ -248,6 +248,16 @@ def add_clone_parser(
|
|
|
248
248
|
),
|
|
249
249
|
type=Path,
|
|
250
250
|
)
|
|
251
|
+
clone_parser.add_argument(
|
|
252
|
+
"--timeout",
|
|
253
|
+
"-t",
|
|
254
|
+
type=int,
|
|
255
|
+
default=100,
|
|
256
|
+
help=(
|
|
257
|
+
"Set the timeout duration in seconds when using --export. "
|
|
258
|
+
"Default is %(default)s."
|
|
259
|
+
),
|
|
260
|
+
)
|
|
251
261
|
_add_server_option(clone_parser)
|
|
252
262
|
return clone_parser
|
|
253
263
|
|
|
@@ -25,13 +25,16 @@ async def _aexport_pmx(
|
|
|
25
25
|
server: PuakmaServer,
|
|
26
26
|
app_ids: list[int],
|
|
27
27
|
output_dir: Path,
|
|
28
|
+
timeout: int, # noqa: ASYNC109
|
|
28
29
|
) -> int:
|
|
29
30
|
tasks = []
|
|
30
31
|
|
|
31
32
|
async with server as s:
|
|
32
33
|
await s.server_designer.ainitiate_connection()
|
|
33
34
|
for app_id in app_ids:
|
|
34
|
-
task = asyncio.create_task(
|
|
35
|
+
task = asyncio.create_task(
|
|
36
|
+
_aexport_app_pmx(server, app_id, output_dir, timeout)
|
|
37
|
+
)
|
|
35
38
|
tasks.append(task)
|
|
36
39
|
|
|
37
40
|
ret = 0
|
|
@@ -55,8 +58,9 @@ async def _aexport_app_pmx(
|
|
|
55
58
|
server: PuakmaServer,
|
|
56
59
|
app_id: int,
|
|
57
60
|
output_dir: Path,
|
|
61
|
+
timeout: int, # noqa: ASYNC109
|
|
58
62
|
) -> int:
|
|
59
|
-
ret_bytes = await server.download_designer.adownload_pmx(app_id, True)
|
|
63
|
+
ret_bytes = await server.download_designer.adownload_pmx(app_id, True, timeout)
|
|
60
64
|
now = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
|
61
65
|
output_path = output_dir / f"{now}_{server.host}_{app_id}.pmx"
|
|
62
66
|
|
|
@@ -268,6 +272,7 @@ def clone(
|
|
|
268
272
|
server: PuakmaServer,
|
|
269
273
|
app_ids: list[int],
|
|
270
274
|
*,
|
|
275
|
+
timeout: int,
|
|
271
276
|
get_resources: bool = False,
|
|
272
277
|
open_urls: bool = False,
|
|
273
278
|
reclone: bool = False,
|
|
@@ -289,7 +294,7 @@ def clone(
|
|
|
289
294
|
elif not export_path.is_dir():
|
|
290
295
|
logger.error(f"'{export_path}' is not a directory.")
|
|
291
296
|
return 1
|
|
292
|
-
ret = asyncio.run(_aexport_pmx(server, app_ids, export_path))
|
|
297
|
+
ret = asyncio.run(_aexport_pmx(server, app_ids, export_path, timeout))
|
|
293
298
|
else:
|
|
294
299
|
ret = asyncio.run(
|
|
295
300
|
_aclone_apps(workspace, server, app_ids, get_resources, open_urls)
|
|
@@ -75,7 +75,7 @@ async def _acopy_obj(
|
|
|
75
75
|
spinner.stop()
|
|
76
76
|
print(f"Design Object {obj} already exists in {obj.app}.")
|
|
77
77
|
print("Only the Design data/source will be updated.")
|
|
78
|
-
if input("[Y/y] to continue:") not in ["Y", "y"]:
|
|
78
|
+
if input("[Y/y] to continue:") not in ["Y", "y"]: # noqa: ASYNC250
|
|
79
79
|
logger.error("Operation Cancelled")
|
|
80
80
|
spinner.start()
|
|
81
81
|
return 1
|
|
@@ -81,6 +81,11 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
81
81
|
action="store_true",
|
|
82
82
|
help="Initialise the workspace and config files, if they don't already exist",
|
|
83
83
|
)
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
"--no-spin",
|
|
86
|
+
action="store_true",
|
|
87
|
+
help="Disable the spinner",
|
|
88
|
+
)
|
|
84
89
|
|
|
85
90
|
command_parser = parser.add_subparsers(dest="command")
|
|
86
91
|
cli.add_list_parser(command_parser)
|
|
@@ -173,6 +178,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
173
178
|
open_urls=args.open_urls,
|
|
174
179
|
reclone=args.reclone,
|
|
175
180
|
export_path=args.export_path,
|
|
181
|
+
timeout=args.timeout,
|
|
176
182
|
)
|
|
177
183
|
elif args.command == "watch":
|
|
178
184
|
return watch(workspace, server)
|
|
@@ -140,9 +140,14 @@ class PuakmaServer:
|
|
|
140
140
|
self.soap_path = soap_path
|
|
141
141
|
self.webdesign_path = webdesign_path
|
|
142
142
|
self.puakma_db_conn_id = puakma_db_conn_id
|
|
143
|
-
self.username =
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
self.username = (
|
|
144
|
+
username or os.getenv("VORTEX_USERNAME") or input("Enter your Username: ")
|
|
145
|
+
)
|
|
146
|
+
self.password = (
|
|
147
|
+
password
|
|
148
|
+
or os.getenv("VORTEX_PASSWORD")
|
|
149
|
+
or getpass.getpass("Enter your Password: ")
|
|
150
|
+
)
|
|
146
151
|
self._aclient = httpx.AsyncClient(auth=self.auth)
|
|
147
152
|
self._client = httpx.Client(auth=self.auth)
|
|
148
153
|
self.app_designer = AppDesigner(self)
|
|
@@ -322,8 +327,9 @@ class PuakmaApplication:
|
|
|
322
327
|
return hash((self.host, self.id))
|
|
323
328
|
|
|
324
329
|
@property
|
|
325
|
-
def
|
|
326
|
-
|
|
330
|
+
def path(self) -> Path:
|
|
331
|
+
# the 'puakma' application doesn't have a group
|
|
332
|
+
return Path(self.host, self.group or "_", self.name)
|
|
327
333
|
|
|
328
334
|
@property
|
|
329
335
|
def url(self) -> str:
|
|
@@ -520,7 +526,7 @@ class DesignObject:
|
|
|
520
526
|
def design_path(self, workspace: Workspace) -> DesignPath:
|
|
521
527
|
return DesignPath(
|
|
522
528
|
workspace,
|
|
523
|
-
workspace.path / self.app.
|
|
529
|
+
workspace.path / self.app.path / self.design_dir / self.file_name,
|
|
524
530
|
)
|
|
525
531
|
|
|
526
532
|
async def aupload(
|
|
@@ -630,7 +636,7 @@ class InvalidDesignPathError(Exception):
|
|
|
630
636
|
class DesignPath:
|
|
631
637
|
"""
|
|
632
638
|
Represents a path to a Design Object. Expects format:
|
|
633
|
-
/path/to/workspace/app_dir/design_dir/.../obj
|
|
639
|
+
/path/to/workspace/server/group/app_dir/design_dir/.../obj
|
|
634
640
|
otherwise a InvalidDesignPathError is raised
|
|
635
641
|
"""
|
|
636
642
|
|
|
@@ -639,8 +645,10 @@ class DesignPath:
|
|
|
639
645
|
) -> None:
|
|
640
646
|
try:
|
|
641
647
|
rel_path = path.relative_to(workspace.path)
|
|
642
|
-
app_dir, design_dir, _rem = str(rel_path).split(
|
|
643
|
-
|
|
648
|
+
server_dir, group_dir, app_dir, design_dir, _rem = str(rel_path).split(
|
|
649
|
+
os.path.sep, maxsplit=4
|
|
650
|
+
)
|
|
651
|
+
app_dir_path = workspace.path / server_dir / group_dir / app_dir
|
|
644
652
|
self.app = PuakmaApplication.from_dir(app_dir_path)
|
|
645
653
|
if must_exist and not path.exists():
|
|
646
654
|
raise ValueError(f"Design Object '{path}' does not exist")
|
|
@@ -33,13 +33,16 @@ _XSI_NULL: Literal["xsi:null"] = "xsi:null"
|
|
|
33
33
|
class _SOAPParam(NamedTuple):
|
|
34
34
|
name: str
|
|
35
35
|
value: Any
|
|
36
|
-
xsi_type:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
xsi_type: (
|
|
37
|
+
Literal[
|
|
38
|
+
"xsd:integer",
|
|
39
|
+
"xsd:string",
|
|
40
|
+
"xsd:boolean",
|
|
41
|
+
"xsd:base64Binary",
|
|
42
|
+
"xsd:int",
|
|
43
|
+
]
|
|
44
|
+
| None
|
|
45
|
+
) = None
|
|
43
46
|
array_type: Literal["xsd:string"] | None = None
|
|
44
47
|
|
|
45
48
|
|
|
@@ -179,7 +182,7 @@ class _PuakmaSOAPService(ABC):
|
|
|
179
182
|
operation: str,
|
|
180
183
|
params: list[_SOAPParam] | None = None,
|
|
181
184
|
*,
|
|
182
|
-
timeout: int = 20,
|
|
185
|
+
timeout: int = 20, # noqa: ASYNC109
|
|
183
186
|
) -> ET.Element:
|
|
184
187
|
"""
|
|
185
188
|
Builds and sends a SOAP envelope to the service endpoint and returns the parsed
|
|
@@ -343,9 +346,9 @@ class DatabaseDesigner(_PuakmaSOAPService):
|
|
|
343
346
|
for row in resp.findall(".//row"):
|
|
344
347
|
ret.append(
|
|
345
348
|
{
|
|
346
|
-
col_lookup[int(col.attrib["index"]) - 1]:
|
|
347
|
-
|
|
348
|
-
|
|
349
|
+
col_lookup[int(col.attrib["index"]) - 1]: (
|
|
350
|
+
col.text if col.text else ""
|
|
351
|
+
)
|
|
349
352
|
for col in row
|
|
350
353
|
}
|
|
351
354
|
)
|
|
@@ -400,6 +403,7 @@ class DownloadDesigner(_PuakmaSOAPService):
|
|
|
400
403
|
self,
|
|
401
404
|
app_id: int,
|
|
402
405
|
include_source: bool = False,
|
|
406
|
+
timeout: int = 100, # noqa: ASYNC109
|
|
403
407
|
) -> bytes:
|
|
404
408
|
"""Returns bytes xml"""
|
|
405
409
|
operation = "downloadPmx"
|
|
@@ -408,9 +412,9 @@ class DownloadDesigner(_PuakmaSOAPService):
|
|
|
408
412
|
_SOAPParam("p2", include_source, _XSD_BOOLEAN),
|
|
409
413
|
]
|
|
410
414
|
# This operation takes a while for big applications, set timeout to 100
|
|
411
|
-
resp = await self._apost(operation, params, timeout=
|
|
415
|
+
resp = await self._apost(operation, params, timeout=timeout)
|
|
412
416
|
if resp.text is not None:
|
|
413
|
-
return base64.b64decode(resp.text)
|
|
417
|
+
return base64.b64decode(resp.text, validate=True)
|
|
414
418
|
else:
|
|
415
419
|
return b""
|
|
416
420
|
|
|
@@ -17,6 +17,7 @@ class Spinner:
|
|
|
17
17
|
self.delay = 0.1
|
|
18
18
|
self.running = False
|
|
19
19
|
self.thread: threading.Thread | None = None
|
|
20
|
+
self.disabled = not sys.stdout.isatty()
|
|
20
21
|
|
|
21
22
|
def _spin(self) -> None:
|
|
22
23
|
while self.running:
|
|
@@ -25,6 +26,8 @@ class Spinner:
|
|
|
25
26
|
time.sleep(0.1)
|
|
26
27
|
|
|
27
28
|
def start(self) -> None:
|
|
29
|
+
if self.disabled:
|
|
30
|
+
return
|
|
28
31
|
self.running = True
|
|
29
32
|
self.thread = threading.Thread(target=self._spin)
|
|
30
33
|
self.thread.start()
|
|
@@ -34,6 +37,8 @@ class Spinner:
|
|
|
34
37
|
sys.stdout.flush()
|
|
35
38
|
|
|
36
39
|
def stop(self) -> None:
|
|
40
|
+
if self.disabled:
|
|
41
|
+
return
|
|
37
42
|
self.running = False
|
|
38
43
|
if self.thread:
|
|
39
44
|
self.thread.join()
|
|
@@ -141,16 +141,16 @@ class Workspace:
|
|
|
141
141
|
def mkdir(self, app: PuakmaApplication, force_recreate: bool = False) -> Path:
|
|
142
142
|
"""
|
|
143
143
|
Creates a .PuakmaApplication.pickle file within a newly created app directory
|
|
144
|
-
with the format '
|
|
144
|
+
with the format 'host/group/name' inside the workspace.
|
|
145
145
|
Returns the full path to the new app directory
|
|
146
146
|
"""
|
|
147
|
-
|
|
148
|
-
if
|
|
149
|
-
shutil.rmtree(
|
|
150
|
-
|
|
151
|
-
with open(
|
|
147
|
+
full_path = self.path / app.path
|
|
148
|
+
if full_path.exists() and force_recreate:
|
|
149
|
+
shutil.rmtree(full_path)
|
|
150
|
+
full_path.mkdir(exist_ok=True, parents=True)
|
|
151
|
+
with open(full_path / app.MANIFEST_FILE, "wb") as f:
|
|
152
152
|
pickle.dump(app, f)
|
|
153
|
-
return
|
|
153
|
+
return full_path
|
|
154
154
|
|
|
155
155
|
def listdir(
|
|
156
156
|
self, server: PuakmaServer | None = None, *, strict: bool = True
|
|
@@ -163,20 +163,17 @@ class Workspace:
|
|
|
163
163
|
contain a .PuakmaApplication.pickle file.
|
|
164
164
|
"""
|
|
165
165
|
ret = []
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if not MANIFEST_FILE.exists():
|
|
178
|
-
continue
|
|
179
|
-
ret.append(sub_dir)
|
|
166
|
+
pattern = "**/.pma"
|
|
167
|
+
if server:
|
|
168
|
+
pattern = f"{server.host}/**/.pma"
|
|
169
|
+
for app_file in self.path.glob(pattern):
|
|
170
|
+
app_dir = app_file.parent
|
|
171
|
+
if strict:
|
|
172
|
+
try:
|
|
173
|
+
PuakmaApplication.from_dir(app_dir)
|
|
174
|
+
except ValueError:
|
|
175
|
+
continue
|
|
176
|
+
ret.append(app_dir)
|
|
180
177
|
return ret
|
|
181
178
|
|
|
182
179
|
def lookup_design_obj(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|