dmart 1.4.39__py3-none-any.whl → 1.4.40.post1__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.
dmart/cli.py ADDED
@@ -0,0 +1,1103 @@
1
+ #!/usr/bin/env python
2
+
3
+ import os
4
+ from prompt_toolkit import PromptSession
5
+ import sys
6
+
7
+ from prompt_toolkit.filters import has_completions, completion_is_selected
8
+ from prompt_toolkit.key_binding import KeyBindings
9
+ from rich import pretty
10
+ from rich import print
11
+ from rich.console import Console
12
+ from pydantic_settings import BaseSettings, SettingsConfigDict
13
+ import requests
14
+ import re
15
+
16
+ # from rich.tree import Tree
17
+ from rich.table import Table
18
+ from dataclasses import dataclass
19
+ from prompt_toolkit.styles import Style
20
+ from prompt_toolkit.formatted_text import HTML
21
+ from prompt_toolkit.completion import Completer, Completion # , FuzzyCompleter
22
+ from prompt_toolkit.history import FileHistory
23
+ from prompt_toolkit.cursor_shapes import CursorShape
24
+ from rich.traceback import install
25
+ import json
26
+ import pathlib
27
+ from enum import Enum
28
+ import termios
29
+ import tty
30
+ from dataclasses import field
31
+ from pathlib import Path
32
+
33
+ install(show_locals=False)
34
+
35
+ KEYWORDS = [
36
+ "ls",
37
+ "switch",
38
+ "mv",
39
+ "cat",
40
+ "cd",
41
+ "rm",
42
+ "print",
43
+ "pwd",
44
+ "help",
45
+ "mkdir",
46
+ "schema",
47
+ "create",
48
+ "attach",
49
+ "request",
50
+ "csv",
51
+ "progress",
52
+ ]
53
+
54
+ style = Style.from_dict(
55
+ {
56
+ "completion-menu.completion": "bg:#008888 #ffffff",
57
+ "completion-menu.completion.current": "bg:#00aaaa #000000",
58
+ "scrollbar.background": "bg:#88aaaa",
59
+ "scrollbar.button": "bg:#222222",
60
+ "prompt": "#dddd22 bold",
61
+ }
62
+ )
63
+
64
+ console = Console()
65
+ pretty.install()
66
+
67
+
68
+ class Settings(BaseSettings):
69
+ url: str = "http://localhost:8282"
70
+ shortname: str = "dmart"
71
+ password: str = "password"
72
+ query_limit: int = 100
73
+ retrieve_json_payload: bool = True
74
+ default_space: str = "management"
75
+ pagination: int = 5
76
+
77
+ model_config = SettingsConfigDict(env_file = os.getenv("BACKEND_ENV", os.path.dirname(os.path.realpath(__file__)) + "/config.ini"), env_file_encoding = "utf-8")
78
+
79
+
80
+ settings = Settings()
81
+
82
+
83
+ class CLI_MODE(str, Enum):
84
+ REPL = "REPL"
85
+ CMD = "CMD"
86
+ SCRIPT = "SCRIPT"
87
+
88
+
89
+ mode = CLI_MODE.REPL
90
+
91
+
92
+ class SpaceManagmentType(str, Enum):
93
+ CREATE = "create"
94
+ UPDATE = "update"
95
+ DELETE = "delete"
96
+
97
+
98
+ @dataclass
99
+ class DMart:
100
+ session = requests.Session()
101
+ headers = {"Content-Type": "application/json"}
102
+ dmart_spaces : list = field(default_factory=lambda: [])
103
+ space_names : list[str] = field(default_factory=list[str])
104
+ current_space: str = settings.default_space
105
+ current_subpath: str = "/"
106
+ current_subpath_entries : list = field(default_factory=list)
107
+
108
+ def __dmart_api(self, endpoint, json=None):
109
+ url = f"{settings.url}{endpoint}"
110
+
111
+ if json:
112
+ response = self.session.post(url, headers=self.headers, json=json)
113
+ else:
114
+ response = self.session.get(url)
115
+
116
+ response_body = response.json()
117
+ if response.status_code != 200:
118
+ print(f"[red]{endpoint}", end="\r")
119
+ return response_body
120
+
121
+ def create_content(self, endpoint, json):
122
+ url = f"{settings.url}{endpoint}"
123
+
124
+ response = self.session.post(url, headers=self.headers, json=json)
125
+
126
+ if response.status_code != 200:
127
+ print(endpoint, response.json())
128
+ return response.json().get('status', None)
129
+
130
+ def delete(self, spacename, subpath, shortname, resource_type):
131
+ endpoint = "/managed/request"
132
+ json = {
133
+ "space_name": spacename,
134
+ "request_type": "delete",
135
+ "records": [
136
+ {
137
+ "resource_type": resource_type,
138
+ "subpath": subpath,
139
+ "shortname": shortname,
140
+ "attributes": {},
141
+ }
142
+ ],
143
+ }
144
+ return self.__dmart_api(endpoint, json)
145
+
146
+ def register(self, invitation):
147
+ json = {
148
+ "resource_type": "user",
149
+ "spacename": "management",
150
+ "subpath": "/users",
151
+ "shortname": settings.shortname,
152
+ "attributes": {"invitation": invitation, "password": settings.password},
153
+ }
154
+ self.__dmart_api("/user/create", json)
155
+
156
+ def login(self):
157
+ json = {"shortname": settings.shortname, "password": settings.password}
158
+ response = self.__dmart_api("/user/login", json)
159
+ if response["status"] == "success":
160
+ self.headers = {
161
+ **self.headers,
162
+ "Authorization": f'Bearer {response["records"][0]["attributes"]["access_token"]}',
163
+ }
164
+ return response
165
+
166
+ def profile(self):
167
+ self.__dmart_api("/user/profile")
168
+
169
+ def spaces(self, force: bool = False):
170
+ if force or not self.dmart_spaces:
171
+ json = {
172
+ "type": "spaces",
173
+ "space_name": "management",
174
+ "subpath": "/",
175
+ }
176
+
177
+ response = self.__dmart_api("/managed/query", json)
178
+ self.dmart_spaces = response["records"]
179
+ self.space_names = [one["shortname"] for one in self.dmart_spaces]
180
+ return self.dmart_spaces
181
+
182
+ def query(self, json):
183
+ json["limit"] = settings.query_limit
184
+ json["retrieve_json_payload"] = settings.retrieve_json_payload
185
+ return self.__dmart_api("/managed/query", json)
186
+
187
+ def meta(self, resource_type, space_name, subpath, shortname):
188
+ endpoint = "managed/meta"
189
+ url = f"{settings.url}/{endpoint}/{resource_type}/{space_name}/{subpath}/{shortname}"
190
+ response = self.session.get(url)
191
+
192
+ if response.status_code != 200:
193
+ print(endpoint, response.json())
194
+ return response.json()
195
+
196
+ def payload(self, resource_type, space_name, subpath, shortname):
197
+ endpoint = "managed/payload"
198
+ url = f"{settings.url}/{endpoint}/{resource_type}/{space_name}/{subpath}/{shortname}.json"
199
+ response = self.session.get(url)
200
+
201
+ # if response.status_code != 200:
202
+ # print(endpoint, response.json())
203
+ return response.status_code, response.json()
204
+
205
+ def print_spaces(self):
206
+ print_spaces = "Available spaces: "
207
+ for one in self.space_names:
208
+ if self.current_space == one:
209
+ one = f" [bold yellow]{one}[/] "
210
+ else:
211
+ one = f" [blue]{one}[/] "
212
+ print_spaces += one
213
+ print(print_spaces)
214
+
215
+ def list(self):
216
+ json = {
217
+ "space_name": dmart.current_space,
218
+ "type": "subpath",
219
+ "subpath": dmart.current_subpath.replace("//", "/"),
220
+ "retrieve_json_payload": True,
221
+ "limit": 100,
222
+ }
223
+
224
+ ret = self.query(json)
225
+ self.current_subpath_entries.clear()
226
+ if "records" in ret:
227
+ for one in ret["records"]:
228
+ self.current_subpath_entries.append(one)
229
+
230
+ def get_mime_type(self, ext):
231
+ match ext:
232
+ case ".jfif" | ".jfif-tbnl" | ".jpe" | ".jpeg" | ".jpg":
233
+ return "image/jpeg"
234
+ case ".png":
235
+ return "image/png"
236
+ case ".json":
237
+ return "application/json"
238
+
239
+ def create_folder(self, shortname):
240
+ json = {
241
+ "space_name": self.current_space,
242
+ "request_type": "create",
243
+ "records": [
244
+ {
245
+ "resource_type": "folder",
246
+ "subpath": self.current_subpath,
247
+ "shortname": shortname,
248
+ "attributes": {"is_active": True},
249
+ }
250
+ ],
251
+ }
252
+ endpoint = "/managed/request"
253
+ return self.__dmart_api(endpoint, json)
254
+
255
+ def manage_space(self, spacename, mode: SpaceManagmentType):
256
+ endpoint = "/managed/space"
257
+ json = {
258
+ "space_name": spacename,
259
+ "request_type": mode.value,
260
+ "records": [
261
+ {
262
+ "resource_type": "space",
263
+ "subpath": "/",
264
+ "shortname": spacename,
265
+ "attributes": {},
266
+ }
267
+ ],
268
+ }
269
+ result = self.__dmart_api(endpoint, json)
270
+ self.spaces(force=True)
271
+ return result
272
+
273
+ def create_entry(self, shortname, resource_type):
274
+ endpoint = "/managed/request"
275
+ json = {
276
+ "space_name": self.current_space,
277
+ "request_type": "create",
278
+ "records": [
279
+ {
280
+ "resource_type": resource_type,
281
+ "subpath": self.current_subpath,
282
+ "shortname": shortname,
283
+ "attributes": {"is_active": True},
284
+ }
285
+ ],
286
+ }
287
+ return self.__dmart_api(endpoint, json)
288
+
289
+ def create_attachments(self, request_record: dict, payload_file):
290
+ endpoint = f"{settings.url}/managed/resource_with_payload"
291
+ with open("temp.json", "w") as request_record_file:
292
+ json.dump(request_record, request_record_file)
293
+
294
+ with open("temp.json", "rb") as request_file:
295
+ with open(payload_file, "rb") as media_file:
296
+ request_file.seek(0)
297
+ data = [
298
+ (
299
+ "request_record",
300
+ ("record.json", request_file, "application/json"),
301
+ ),
302
+ (
303
+ "payload_file",
304
+ (
305
+ media_file.name.split("/")[-1],
306
+ media_file,
307
+ self.get_mime_type(pathlib.Path(payload_file).suffix),
308
+ ),
309
+ ),
310
+ ]
311
+ response = self.session.post(
312
+ endpoint,
313
+ data={"space_name": self.current_space},
314
+ files=data,
315
+ headers=self.headers,
316
+ )
317
+ if response.status_code != 200:
318
+ print(endpoint, response.json())
319
+
320
+ os.remove("temp.json")
321
+ return response.json()
322
+
323
+ def upload_csv(self, resource_type, subpath, schema_shortname, payload_file):
324
+ with open(payload_file, "rb") as media_file:
325
+ endpoint = f"{settings.url}/managed/resources_from_csv/{resource_type}/{dmart.current_space}/{subpath}/{schema_shortname}"
326
+ headers = {**self.headers}
327
+ del headers["Content-Type"]
328
+ response = self.session.post(
329
+ endpoint,
330
+ files=[
331
+ ('resources_file', ('file', media_file, 'text/csv'))
332
+ ],
333
+ headers=headers,
334
+ )
335
+ if response.status_code != 200:
336
+ print(endpoint, response.json())
337
+
338
+ return response.json()
339
+
340
+ def upload_schema(self, shortname, payload_file_path):
341
+ data = {
342
+ "resource_type": "schema",
343
+ "subpath": "schema",
344
+ "shortname": shortname,
345
+ "attributes": {"schema_shortname": "meta_schema", "is_active": True},
346
+ }
347
+ endpoint = f"{settings.url}/managed/resource_with_payload"
348
+
349
+ with open("temp.json", "w") as request_record_file:
350
+ json.dump(data, request_record_file)
351
+
352
+ with open("temp.json", "rb") as request_record:
353
+ with open(payload_file_path, "rb") as payload_file:
354
+ files = {
355
+ "space_name": (None, self.current_space),
356
+ "request_record": request_record,
357
+ "payload_file": payload_file,
358
+ }
359
+ headers = {**self.headers}
360
+ del headers["Content-Type"]
361
+ response = self.session.post(
362
+ endpoint,
363
+ files=files,
364
+ headers=headers,
365
+ )
366
+
367
+ if response.status_code != 200:
368
+ print(endpoint, response.json())
369
+
370
+ return response.json()
371
+
372
+ def upload_folder(self, folder_schema_path):
373
+ endpoint = f"{settings.url}/managed/request"
374
+ with open(folder_schema_path, "r") as folder_schema:
375
+ response = self.session.post(
376
+ url=endpoint,
377
+ data=folder_schema,
378
+ headers=self.headers,
379
+ )
380
+ if response.status_code != 200:
381
+ print(endpoint, response.json())
382
+ else:
383
+ print(response.json())
384
+ return response.json()
385
+
386
+ def move(
387
+ self,
388
+ resource_type,
389
+ source_path,
390
+ source_shortname,
391
+ destination_path,
392
+ destination_shortname,
393
+ ):
394
+ endpoint = "/managed/request"
395
+ data = {
396
+ "space_name": self.current_space,
397
+ "request_type": "move",
398
+ "records": [
399
+ {
400
+ "resource_type": resource_type,
401
+ "subpath": self.current_subpath,
402
+ "shortname": source_shortname,
403
+ "attributes": {
404
+ "src_subpath": source_path,
405
+ "src_shortname": source_shortname,
406
+ "dest_subpath": destination_path,
407
+ "dest_shortname": destination_shortname,
408
+ },
409
+ }
410
+ ],
411
+ }
412
+ return self.__dmart_api(endpoint, data)
413
+
414
+ def import_zip(self, zip_file_path):
415
+ endpoint = f"{settings.url}/managed/import"
416
+ with open(zip_file_path, "rb") as zip_file:
417
+ headers = {**self.headers}
418
+ del headers["Content-Type"]
419
+ response = self.session.post(
420
+ endpoint,
421
+ files={"zip_file": zip_file},
422
+ headers=headers,
423
+ )
424
+ if response.status_code != 200:
425
+ print(endpoint, response.json())
426
+ return response.json()
427
+
428
+ def export_json(self, query_json_file):
429
+ output_file_name = "/export.zip"
430
+ os_downloads_path = str(Path.home()/"Downloads")
431
+ output_file_path = os_downloads_path + output_file_name
432
+ with open(query_json_file, "r") as f:
433
+ data = json.load(f)
434
+ endpoint = f"{settings.url}/managed/export"
435
+ response = self.session.post(endpoint, headers=self.headers, json=data, stream=True)
436
+ if response.status_code != 200:
437
+ print(endpoint, response.json())
438
+ return response.json()
439
+ with open(output_file_path, "wb") as out_file:
440
+ for chunk in response.iter_content(chunk_size=8192):
441
+ if chunk:
442
+ out_file.write(chunk)
443
+ print(f"Exported to {os_downloads_path}")
444
+ return {"status": "success", "file": output_file_path}
445
+
446
+
447
+ dmart = DMart()
448
+
449
+
450
+ class CustomCompleter(Completer):
451
+ def get_completions(self, document, complete_event):
452
+ cmd = document.text
453
+ arg = document.get_word_under_cursor()
454
+ if len(cmd) < 2 or re.match(r"^\s*$", cmd):
455
+ for one in KEYWORDS:
456
+ if one.startswith(arg):
457
+ yield Completion(one, start_position=-len(arg))
458
+ elif re.match(r"^\s*\w+\s+", cmd):
459
+ if cmd.startswith("s"):
460
+ for one in dmart.space_names:
461
+ if one.startswith(arg):
462
+ yield Completion(one, start_position=-len(arg))
463
+ else:
464
+ for one in dmart.current_subpath_entries:
465
+ if one[ "shortname" ].startswith(
466
+ arg
467
+ ):
468
+ if "cd" in cmd and one["resource_type"] == "folder":
469
+ yield Completion(
470
+ one["shortname"],
471
+ start_position=-len(arg),
472
+ display_meta=one["resource_type"],
473
+ )
474
+ elif "cd" not in cmd:
475
+ yield Completion(
476
+ one["shortname"],
477
+ start_position=-len(arg),
478
+ display_meta=one["resource_type"],
479
+ )
480
+
481
+
482
+
483
+ def bottom_toolbar():
484
+ return HTML(
485
+ f'<b>{dmart.current_space}</b> : <b><style bg="ansired">{dmart.current_subpath}</style></b>'
486
+ )
487
+
488
+
489
+ def check_update_space(space, subpath=None):
490
+ for one in dmart.space_names:
491
+ if one.startswith(space) and not one.startswith(dmart.current_space):
492
+ dmart.current_space = one
493
+ print("Current space switched to:", dmart.current_space)
494
+ if subpath is not None:
495
+ dmart.current_subpath = subpath
496
+ dmart.list()
497
+ dmart.print_spaces()
498
+ break
499
+
500
+
501
+ def action(text: str):
502
+ old_space, old_subpath = dmart.current_space, dmart.current_subpath
503
+ match text.split():
504
+ case ["h" | "help" | "?"]:
505
+ table = Table(title="Help")
506
+ table.add_column("Command")
507
+ table.add_column("Description")
508
+ table.add_row(
509
+ r"[blue]s[paces][/] [green]\[space_name][/]",
510
+ "List availble spaces or switch to space",
511
+ )
512
+ table.add_row(
513
+ r"[blue]ls[/] [green]\[folder_name][/]",
514
+ "List entries under current subpath",
515
+ )
516
+ table.add_row("[blue]pwd[/]", "Print current subpath")
517
+ table.add_row("[blue]cd[/] [green]folder_name[/]", "Enter the folder")
518
+ table.add_row("[blue]cd ..[/]", "Go one-level up with the subpath")
519
+ table.add_row(
520
+ r"[blue]p\[rint][/blue] [green]shortname[/]",
521
+ "Print meta data for the entry shortname under current subpath",
522
+ )
523
+ table.add_row(
524
+ r"[blue]c\[at][/] [green]shortname[/]",
525
+ "Print the json payload (only) for th entry shortnae under current subpath",
526
+ )
527
+ table.add_row(
528
+ "[blue]rm[/] [green]shortname|*[/]",
529
+ "Delete the shortname (entry or attachment)",
530
+ )
531
+ table.add_row(
532
+ "[blue]create [space|folder] <shortname>[/]", "Create space/folder for current space"
533
+ )
534
+ table.add_row(
535
+ "[blue]upload csv <resource_type> <shortname> <schema_shortname> <csv_file>[/]",
536
+ "Upload data to the current space with schema validation",
537
+ )
538
+ table.add_row(
539
+ "[blue]upload schema <shortname> <json_file>[/]",
540
+ "Upload schema to the current space",
541
+ )
542
+ table.add_row(
543
+ "[blue]request <resource_type> <json_file>[/]",
544
+ "Add/Manage a resource to/in the space",
545
+ )
546
+ table.add_row(
547
+ "[blue]progress <subpath> <shortname> <action>[/]",
548
+ "Progress a ticket into a new state using an action",
549
+ )
550
+ table.add_row(
551
+ "[blue]move <resource_type> <source> <destination>[/]", "Move resource"
552
+ )
553
+ table.add_row("[blue]import <zip_file>[/]", "Import a ZIP file")
554
+ table.add_row("[blue]export <query_json> [/]", "Export a ZIP file")
555
+ table.add_row("[blue]exit|Ctrl+d[/]", "Exit the app")
556
+ table.add_row("[blue]help|h|?[/]", "Show this help")
557
+ print(table)
558
+ return None
559
+ case ["mkdir", dir_shortname]:
560
+ print(dmart.create_folder(dir_shortname))
561
+ return None
562
+ case ["attach", *args]:
563
+ shortname = None
564
+ entry_shortname = None
565
+ payload_file = None
566
+ payload_type = None
567
+
568
+ shortname = args[0]
569
+ entry_shortname = args[1]
570
+ payload_type = args[2]
571
+ payload_file = args[3]
572
+
573
+ print(
574
+ dmart.create_attachments(
575
+ {
576
+ "shortname": shortname,
577
+ "resource_type": payload_type,
578
+ "subpath": f"{dmart.current_subpath}/{entry_shortname}",
579
+ "attributes": {"is_active": True},
580
+ },
581
+ payload_file,
582
+ )
583
+ )
584
+ return None
585
+ case ["upload", "schema", *args]:
586
+ shortname = None
587
+ payload_file = None
588
+
589
+ if len(args) == 3:
590
+ search = re.search(r"@\w+", args[2])
591
+ if not search:
592
+ print("[red]Malformated Command")
593
+ return None
594
+ space = search.group()
595
+ space = space.replace("@", "")
596
+ check_update_space(space)
597
+ dmart.current_subpath = args[2].replace(f"@{space}/", "")
598
+ dmart.list()
599
+ shortname = args[0]
600
+ payload_file = args[1]
601
+
602
+ dmart.upload_schema(shortname, payload_file)
603
+ check_update_space(old_space)
604
+ dmart.current_subpath = old_subpath
605
+ dmart.list()
606
+ return None
607
+ case ["create", *args]:
608
+ shortname = None
609
+ resource_type = None
610
+ if len(args) == 3:
611
+ search = re.search(r"@\w+", args[2])
612
+ if not search:
613
+ print("[red]Malformated Command")
614
+ return None
615
+ space = search.group()
616
+ space = space.replace("@", "")
617
+ check_update_space(space)
618
+ dmart.current_subpath = args[2].replace(f"@{space}", "")
619
+ dmart.list()
620
+ shortname = args[0]
621
+ resource_type = args[1]
622
+
623
+ if args[0] == "space":
624
+ print(dmart.manage_space(args[1], SpaceManagmentType.CREATE))
625
+ else:
626
+ print(dmart.create_entry(resource_type, shortname))
627
+
628
+ check_update_space(old_space)
629
+ dmart.current_subpath = old_subpath
630
+ dmart.list()
631
+ return None
632
+ case ["move", type, source, destination]:
633
+ if not source.startswith("/"):
634
+ source += f"{dmart.current_subpath}/{source}"
635
+ if not destination.startswith("/"):
636
+ destination += f"{dmart.current_subpath}/{destination}"
637
+ source_path = "/".join(source.split("/")[:-1])
638
+ source_shortname = source.split("/")[-1]
639
+ destination_path = "/".join(destination.split("/")[:-1])
640
+ destination_shortname = destination.split("/")[-1]
641
+ print(
642
+ dmart.move(
643
+ type,
644
+ source_path,
645
+ source_shortname,
646
+ destination_path,
647
+ destination_shortname,
648
+ )
649
+ )
650
+ return None
651
+
652
+ case ["progress", ticket_subpath, ticket_shortname, new_state]:
653
+ endpoint = f"{settings.url}/managed/progress-ticket/{dmart.current_space}/{ticket_subpath}/{ticket_shortname}/{new_state}"
654
+ response = dmart.session.put(
655
+ url=endpoint,
656
+ data={},
657
+ headers=dmart.headers,
658
+ )
659
+ print(response.json())
660
+ return None
661
+ case ["request", *args]:
662
+ if len(args) != 1:
663
+ print("[red]Malformated Command")
664
+ return None
665
+
666
+ with open(args[0]) as f:
667
+ return print(
668
+ dmart.create_content("/managed/request", json.load(f)), end="\r"
669
+ )
670
+
671
+ check_update_space(old_space)
672
+ dmart.current_subpath = old_subpath
673
+ dmart.list()
674
+ return None
675
+ case ["query", shortname]:
676
+ action(f"query {shortname}")
677
+ action("ls")
678
+ action("cd ..")
679
+ return None
680
+ case ["upload", "csv", *args]:
681
+ if len(args) == 4:
682
+ print(dmart.upload_csv(args[0], args[1], args[2], args[3]))
683
+ elif len(args) == 3:
684
+ print("[red]Malformated Command")
685
+ return None
686
+ check_update_space(old_space)
687
+ dmart.current_subpath = old_subpath
688
+ dmart.list()
689
+ return None
690
+ case ["rm", "*"]:
691
+ dmart.list()
692
+ for one in dmart.current_subpath_entries:
693
+ shortname = one["shortname"]
694
+ resource_type = one["resource_type"]
695
+ if shortname and resource_type:
696
+ print(
697
+ shortname,
698
+ dmart.delete(
699
+ dmart.current_space,
700
+ dmart.current_subpath,
701
+ shortname,
702
+ resource_type,
703
+ ),
704
+ )
705
+ return None
706
+ case ["rm", *content]:
707
+ if content[0] == "space":
708
+ print(dmart.manage_space(content[1], SpaceManagmentType.DELETE))
709
+ else:
710
+ content = content[0]
711
+ if content.startswith("@"):
712
+ path = content[1]
713
+ search = re.search(r"@\w+", content)
714
+ if not search:
715
+ print("[red]Malformated Command")
716
+ return None
717
+ space = search.group()
718
+ space = space.replace("@", "")
719
+ check_update_space(space)
720
+ dmart.list()
721
+ content = content.replace(f"@{space}/", "")
722
+ # print(dmart.current_subpath_entries)
723
+ shortname = ""
724
+ resource_type = ""
725
+ for one in dmart.current_subpath_entries:
726
+ if one["shortname"] == content:
727
+ shortname = one["shortname"]
728
+ resource_type = one["resource_type"]
729
+ if shortname and resource_type:
730
+ print(
731
+ dmart.delete(
732
+ dmart.current_space,
733
+ dmart.current_subpath,
734
+ shortname,
735
+ resource_type,
736
+ )
737
+ )
738
+ else:
739
+ print("item not found")
740
+ check_update_space(old_space)
741
+ dmart.list()
742
+ case ["pwd"]:
743
+ print(f"{dmart.current_space}:{dmart.current_subpath}")
744
+ return None
745
+ case ["cd"]:
746
+ dmart.current_subpath = "/"
747
+ dmart.list()
748
+ print(f"[yellow]Switched subpath to:[/] [green]{dmart.current_subpath}[/]")
749
+ return None
750
+ case ["cd", ".."]:
751
+ if dmart.current_subpath != "/":
752
+ dmart.current_subpath = "/".join(dmart.current_subpath.split("/")[:-1])
753
+ if not dmart.current_subpath:
754
+ dmart.current_subpath = "/"
755
+ dmart.list()
756
+ print(f"[yellow]Switched subpath to:[/] [green]{dmart.current_subpath}[/]")
757
+ return None
758
+ case ["p" | "print", content]:
759
+ shortname = ""
760
+ resource_type = ""
761
+ shortname = content
762
+ for one in dmart.current_subpath_entries:
763
+ if one["shortname"].startswith(shortname):
764
+ shortname = one["shortname"]
765
+ resource_type = one["resource_type"]
766
+ if shortname and resource_type:
767
+ print(
768
+ dmart.meta(
769
+ resource_type,
770
+ dmart.current_space,
771
+ dmart.current_subpath,
772
+ shortname,
773
+ )
774
+ )
775
+ else:
776
+ print("item not found")
777
+ case ["cd", directory]:
778
+ if directory.startswith("@"):
779
+ search = re.search(r"@\w+", directory)
780
+ if not search:
781
+ print("[red]Malformated Command")
782
+ return None
783
+ space = search.group()
784
+ space = space.replace("@", "")
785
+ check_update_space(space)
786
+ dmart.current_subpath = directory.replace(f"@{space}/", "")
787
+ dmart.list()
788
+ directory = dmart.current_subpath
789
+ new_subpath = ""
790
+ for one in dmart.current_subpath_entries:
791
+ if (
792
+ one["shortname"].startswith(directory)
793
+ and one["resource_type"] == "folder"
794
+ ):
795
+ new_subpath = (
796
+ ""
797
+ if dmart.current_subpath == "/" or dmart.current_subpath == ""
798
+ else dmart.current_subpath
799
+ )
800
+ if new_subpath != "":
801
+ new_subpath += "/"
802
+ new_subpath += one["shortname"]
803
+ dmart.current_subpath = new_subpath
804
+ dmart.list()
805
+ print(
806
+ f"[yellow]Switched subpath to:[/] [green]{dmart.current_subpath}[/]"
807
+ )
808
+ break
809
+ return None
810
+ return None
811
+ case ["c" | "cat", *extra_shortname]:
812
+ old_path = dmart.current_subpath
813
+ if "/" in extra_shortname[0]:
814
+ dmart.current_subpath = "/".join(extra_shortname[0].split("/")[:-1])
815
+ extra_shortname[0] = "".join(extra_shortname[0].split("/")[-1])
816
+ dmart.list()
817
+ dmart.current_subpath = old_path
818
+
819
+ record = {}
820
+ if extra_shortname[0] == "*":
821
+ for one in dmart.current_subpath_entries:
822
+ print(one)
823
+ elif len(extra_shortname) > 0:
824
+ shortname = extra_shortname[0]
825
+ for one in dmart.current_subpath_entries:
826
+ if one["shortname"].startswith(shortname):
827
+ record = one
828
+ if record is not None:
829
+ print(record)
830
+ else:
831
+ print("[yellow]Item is not found[/]")
832
+ case ["ls", *_extra_subpath]:
833
+ if len(_extra_subpath) >= 2:
834
+ print("Too many args passed !")
835
+ return None
836
+
837
+ extra_subpath = ""
838
+ if len(_extra_subpath) == 1 and not _extra_subpath[0].isnumeric():
839
+ if _extra_subpath[0].startswith("/"):
840
+ _extra_subpath[0] = _extra_subpath[0][1:]
841
+ if _extra_subpath[0].endswith("/"):
842
+ _extra_subpath[0] = _extra_subpath[0][:-1]
843
+
844
+ if _extra_subpath[0].startswith("@"):
845
+ search = re.search(r"@\w+", _extra_subpath[0])
846
+ if not search:
847
+ print("[red]Malformated Command")
848
+ return None
849
+ space = search.group()
850
+ space = space.replace("@", "")
851
+ check_update_space(space)
852
+ dmart.current_subpath = _extra_subpath[0].replace(f"@{space}/", "")
853
+ dmart.list()
854
+ else:
855
+ dmart.current_subpath = _extra_subpath[0]
856
+ dmart.list()
857
+
858
+
859
+ if extra_subpath == "":
860
+ for one in dmart.current_subpath_entries:
861
+ if one["shortname"].startswith(extra_subpath):
862
+ extra_subpath = one["shortname"]
863
+
864
+ path = f"[yellow]{dmart.current_space}[/]:[blue]{dmart.current_subpath}"
865
+ if dmart.current_subpath != "/":
866
+ path = f"{path}/{extra_subpath}"
867
+ else:
868
+ path = f"{path}{extra_subpath}"
869
+ # tree = Tree(path)
870
+
871
+ pagination_length = 0
872
+ if len(_extra_subpath) >= 1 and _extra_subpath[-1].isnumeric():
873
+ pagination_length = int(_extra_subpath[-1])
874
+ else:
875
+ pagination_length = settings.pagination
876
+
877
+ pagination_bucket : list[list] = []
878
+ bucket = []
879
+ idx = 0
880
+ for one in dmart.current_subpath_entries:
881
+ icon = ":page_facing_up:"
882
+ extra = ""
883
+ if one["resource_type"] == "folder":
884
+ icon = ":file_folder:"
885
+ if (
886
+ isinstance(one, dict)
887
+ and "attributes" in one
888
+ and isinstance(one["attributes"], dict)
889
+ and "payload" in one["attributes"]
890
+ and isinstance(one["attributes"]["payload"], dict)
891
+ and "content_type" in one["attributes"]["payload"]
892
+ ):
893
+ if "schema_shortname" in one["attributes"]["payload"]:
894
+ schema = f",schema= {one["attributes"]["payload"].get("schema_shortname", "N/A")}"
895
+ extra = f"[yellow](payload:type={one['attributes']['payload']['content_type']}{schema})[/]"
896
+
897
+ idx += 1
898
+ bucket.append(f"{icon} [green]{one['shortname']}[/] {extra}")
899
+ if idx == pagination_length:
900
+ idx = 0
901
+ pagination_bucket.append(bucket)
902
+ bucket = []
903
+
904
+ if len(bucket) != 0:
905
+ pagination_bucket.append(bucket)
906
+ idx = 0
907
+ c = ""
908
+ if len(pagination_bucket) == 1:
909
+ for bucket in pagination_bucket[0]:
910
+ print(bucket)
911
+ else:
912
+ while True:
913
+ if len(pagination_bucket) == 0:
914
+ break
915
+
916
+ for bucket in pagination_bucket[idx]:
917
+ print(bucket)
918
+
919
+ if idx >= len(pagination_bucket) - 1:
920
+ break
921
+
922
+ print("q: quite, n: next")
923
+ fd = sys.stdin.fileno()
924
+ old_settings = termios.tcgetattr(fd)
925
+ try:
926
+ tty.setraw(fd)
927
+ c = sys.stdin.readline(1)
928
+
929
+ if c == "q":
930
+ break
931
+ if c == "n" or c == "\r":
932
+ idx += 1
933
+ finally:
934
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
935
+ return None
936
+ # c = input("q: quite, b: previous, n: next = ")
937
+ # print(tree)
938
+ dmart.current_space, dmart.current_subpath = old_space, old_subpath
939
+ dmart.list()
940
+ return None
941
+ case ["s" | "switch", *space]:
942
+ if len(space) == 0:
943
+ dmart.print_spaces()
944
+ return None
945
+ match = False
946
+ for one in dmart.space_names:
947
+ if space and one.startswith(space[0]):
948
+ print(
949
+ f"Switching current space from {dmart.current_space} to {one} / {space}"
950
+ )
951
+ dmart.current_space = one
952
+ dmart.list()
953
+ match = True
954
+ dmart.print_spaces()
955
+ break
956
+ if not match:
957
+ print(f"Requested space {space} not found")
958
+ return None
959
+ return None
960
+ case ["import", zip_file]:
961
+ print(dmart.import_zip(zip_file))
962
+ return None
963
+ case ["export", query_json_file]:
964
+ print(dmart.export_json(query_json_file))
965
+ return None
966
+ case _:
967
+ print(f"[red]Command[/] [yello]{text}[/] [red]unknown[/]")
968
+ return None
969
+
970
+
971
+ var : dict = {}
972
+
973
+
974
+ def parsing_variables(sliced_command):
975
+ for i in range(len(sliced_command)):
976
+ search = re.search(r"\$\w*", sliced_command[i])
977
+ if search is not None:
978
+ if v := search[0]:
979
+ sliced_command[i] = sliced_command[i].replace(v, var.get(v, ""))
980
+ return sliced_command
981
+
982
+
983
+ key_bindings = KeyBindings()
984
+ filter = has_completions & ~completion_is_selected
985
+ @key_bindings.add("enter", filter=filter)
986
+ def _(event):
987
+ event.current_buffer.go_to_completion(0)
988
+ event.current_buffer.validate_and_handle()
989
+
990
+
991
+ if __name__ == "__main__":
992
+ try :
993
+ # print(Panel.fit("For help, type : [bold]?[/]", title="DMart Cli"))
994
+ print("[bold][green]DMART[/] [yellow]Command line interface[/][/]")
995
+ print(
996
+ f"Connecting to [yellow]{settings.url}[/] user: [yellow]{settings.shortname}[/]"
997
+ )
998
+
999
+ ret = dmart.login()
1000
+ if ret["status"] == "failed":
1001
+ print("Login failed")
1002
+ exit(1)
1003
+ dmart.profile()
1004
+ spaces = dmart.spaces()
1005
+ dmart.current_space = settings.default_space # dmart.space_names[0]
1006
+
1007
+ dmart.list()
1008
+ # print("Available spaces:", space_names)
1009
+ # print("Current space:", current_space)
1010
+ dmart.print_spaces()
1011
+ print("[red]Type [bold]?[/] for help[/]")
1012
+ # current_subpath = "/"
1013
+ # session = PromptSession(lexer=PygmentsLexer(DmartLexer), completer=dmart_completer, style=style)
1014
+
1015
+ session = PromptSession(
1016
+ style=style,
1017
+ completer=CustomCompleter(),
1018
+ history=FileHistory(".cli_history"),
1019
+ key_bindings=key_bindings
1020
+ )
1021
+
1022
+ if len(sys.argv) >= 2:
1023
+ if sys.argv[1] == "s":
1024
+ mode = CLI_MODE.SCRIPT
1025
+ elif sys.argv[1] == "c":
1026
+ mode = CLI_MODE.CMD
1027
+ if sys.argv[3].startswith("/"):
1028
+ check_update_space(sys.argv[2], sys.argv[3])
1029
+ del sys.argv[2]
1030
+ del sys.argv[2]
1031
+ else:
1032
+ check_update_space(sys.argv[2])
1033
+ del sys.argv[2]
1034
+ del sys.argv[0]
1035
+ del sys.argv[0]
1036
+
1037
+ if mode == CLI_MODE.CMD:
1038
+ print(sys.argv)
1039
+ action(" ".join(sys.argv))
1040
+ elif mode == CLI_MODE.SCRIPT:
1041
+ with open(sys.argv[0], "r") as commands:
1042
+ is_comment_block = False
1043
+ for command in commands:
1044
+ if (
1045
+ command.startswith("#")
1046
+ or command.startswith("//")
1047
+ or command == "\n"
1048
+ ):
1049
+ continue
1050
+ elif command.startswith("/*"):
1051
+ is_comment_block = True
1052
+ continue
1053
+ elif command.startswith("*/"):
1054
+ is_comment_block = False
1055
+ continue
1056
+ elif is_comment_block:
1057
+ continue
1058
+
1059
+ print("[green]> ", command)
1060
+ sliced_command = command.split()
1061
+
1062
+ if sliced_command[0] == "VAR":
1063
+ var[sliced_command[1]] = sliced_command[2]
1064
+ continue
1065
+
1066
+ sliced_command = parsing_variables(sliced_command)
1067
+
1068
+ action(" ".join(sliced_command))
1069
+ elif mode == CLI_MODE.REPL:
1070
+ while True:
1071
+ try:
1072
+ text = session.prompt(
1073
+ [("class:prompt", "❯ ")], # ≻≻
1074
+ # cursor=CursorShape.BLINKING_BLOCK,
1075
+ # cursor=CursorShape.BLINKING_BEAM,
1076
+ cursor=CursorShape.BLINKING_UNDERLINE,
1077
+ bottom_toolbar=bottom_toolbar,
1078
+ complete_in_thread=True,
1079
+ complete_while_typing=True,
1080
+ )
1081
+ # remove whitespaces
1082
+ text = re.sub(r"^\s+", "", text) # leading
1083
+ text = re.sub(r"\s+$", "", text) # trailing
1084
+ text = re.sub(
1085
+ r"\s+", " ", text
1086
+ ) # replace multiple inline whitespaces with one
1087
+ if text in ["exit", "q", "quit"]:
1088
+ break
1089
+ else:
1090
+ if text == "":
1091
+ continue
1092
+ action(text)
1093
+ except KeyboardInterrupt as ex:
1094
+ print(repr(ex))
1095
+ continue
1096
+ except EOFError as _:
1097
+ print("[green]Exiting ...[/]")
1098
+ break
1099
+ # else:
1100
+ # print('You entered:', repr(text))
1101
+ print("[yellow]Good bye![/]")
1102
+ except requests.exceptions.ConnectionError as _ :
1103
+ print("[yellow]Connection error[/]")
@@ -0,0 +1,7 @@
1
+ url = "http://localhost:8282"
2
+ shortname = "dmart"
3
+ password = "xxxx"
4
+ query_limit = 50
5
+ retrieve_json_payload = True
6
+ default_space = "management"
7
+ pagination = 50
dmart/dmart.py CHANGED
@@ -441,27 +441,26 @@ def main():
441
441
 
442
442
  # Try to find cli.py in the package or nearby
443
443
  try:
444
+ # When installed as a package, cli.py is in the same directory as dmart.py
445
+ dmart_dir = Path(__file__).resolve().parent
446
+ if str(dmart_dir) not in sys.path:
447
+ sys.path.append(str(dmart_dir))
444
448
  import cli
445
449
  cli.main()
446
- except ImportError:
447
- # Check if it's in the same directory as dmart.py (installed via MANIFEST.in)
448
- dmart_dir = Path(__file__).resolve().parent
449
- cli_path = dmart_dir / "cli.py"
450
+ except (ImportError, AttributeError):
451
+ # Fallback for local development
452
+ cli_path = Path(__file__).resolve().parent.parent / "cli"
450
453
  if cli_path.exists():
451
- if str(dmart_dir) not in sys.path:
452
- sys.path.append(str(dmart_dir))
453
- import cli
454
- cli.main()
455
- else:
456
- # Fallback for local development
457
- cli_path = dmart_dir.parent / "cli"
458
- if cli_path.exists():
459
- sys.path.append(str(cli_path))
454
+ sys.path.append(str(cli_path))
455
+ try:
460
456
  import cli
461
457
  cli.main()
462
- else:
463
- print("Error: cli.py not found.")
458
+ except (ImportError, AttributeError):
459
+ print("Error: cli.py found but main() not found or import failed.")
464
460
  sys.exit(1)
461
+ else:
462
+ print("Error: cli.py not found.")
463
+ sys.exit(1)
465
464
  case "serve":
466
465
  open_cxb = False
467
466
  if "--open-cxb" in sys.argv:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dmart
3
- Version: 1.4.39
3
+ Version: 1.4.40.post1
4
4
  Requires-Python: >=3.11
5
5
  Requires-Dist: fastapi
6
6
  Requires-Dist: pydantic
@@ -1,11 +1,13 @@
1
1
  dmart/__init__.py,sha256=xHodcATn-JYaoi3_TVDimR_UWG_ux9uLePUNQDzHhsY,122
2
2
  dmart/alembic.ini,sha256=wQweByyHQm-EI8BQkE0SHNRjULJ6Xn5jqgvv88IT5Sg,3738
3
3
  dmart/bundler.py,sha256=so8ZJResb1PcOH5vboa_mpFAdYr_T8u8DbbFXd570Lg,1704
4
+ dmart/cli.py,sha256=aE3_2uHcEiQJA_f6viGRwUmGPB-OaNfzSgDgLR5gf40,40447
4
5
  dmart/config.env.sample,sha256=CKd4KIBeUatoFcO2IefmrVNBohpaVMQMFcoPkNBvCeI,696
6
+ dmart/config.ini.sample,sha256=AADMdwG07zRV_cAGMoOorHQaqzTTL2WEIC_mPWjIslE,159
5
7
  dmart/conftest.py,sha256=0ry_zeCmdBNLbm5q115b-pkOrUFYxdsOUXbIMkr7E5Y,362
6
8
  dmart/curl.sh,sha256=lmHSFVr5ft-lc5Aq9LfvKyWfntrfYbnirhzx1EGjp_A,15743
7
9
  dmart/data_generator.py,sha256=CnE-VHEeX7-lAXtqCgbRqR9WHjTuOgeiZcviYrHAmho,2287
8
- dmart/dmart.py,sha256=Hmejqz79xFMJAJztnTEtqYm7XA1B1blIHryuZ48D8AI,24377
10
+ dmart/dmart.py,sha256=Ax4GRara7_21T2Jcbl8EwGfdezr9NTz7UucW_oVw-pA,24419
9
11
  dmart/get_settings.py,sha256=Sbe2WCoiK398E7HY4SNLfDN_GmE8knR4M-YJWF31jcg,153
10
12
  dmart/hypercorn_config.toml,sha256=-eryppEG8HBOM_KbFc4dTQePnpyfoW9ZG5aUATU_6yM,50
11
13
  dmart/info.json,sha256=5Yz4tc5dEt5eVrM2LzLhXgN8vGvE0aDbbbEEaGDsD5k,123
@@ -278,8 +280,8 @@ dmart/utils/ticket_sys_utils.py,sha256=9QAlW2iiy8KyxQRBDj_WmzS5kKb0aYJmGwd4qzmGV
278
280
  dmart/utils/web_notifier.py,sha256=QM87VVid2grC5lK3NdS1yzz0z1wXljr4GChJOeK86W4,843
279
281
  dmart/utils/templates/activation.html.j2,sha256=XAMKCdoqONoc4ZQucD0yV-Pg5DlHHASZrTVItNS-iBE,640
280
282
  dmart/utils/templates/reminder.html.j2,sha256=aoS8bTs56q4hjAZKsb0jV9c-PIURBELuBOpT_qPZNVU,639
281
- dmart-1.4.39.dist-info/METADATA,sha256=GZGdPHieT1FqO4Baoab0MvxcAUm3DNXsnywyPMH6c08,2571
282
- dmart-1.4.39.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
283
- dmart-1.4.39.dist-info/entry_points.txt,sha256=N832M4wG8d2GDw1xztKRVM3TnxpY2QDzvdFE8XaWaG8,43
284
- dmart-1.4.39.dist-info/top_level.txt,sha256=zJo4qk9fUW0BGIR9f4DCfpxaXbvQXH9izIOom6JsyAo,6
285
- dmart-1.4.39.dist-info/RECORD,,
283
+ dmart-1.4.40.post1.dist-info/METADATA,sha256=dRF5fMCJ7hyZsIKXGG6o5e5H00JxS8JkqucCNR4aINc,2577
284
+ dmart-1.4.40.post1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
285
+ dmart-1.4.40.post1.dist-info/entry_points.txt,sha256=N832M4wG8d2GDw1xztKRVM3TnxpY2QDzvdFE8XaWaG8,43
286
+ dmart-1.4.40.post1.dist-info/top_level.txt,sha256=zJo4qk9fUW0BGIR9f4DCfpxaXbvQXH9izIOom6JsyAo,6
287
+ dmart-1.4.40.post1.dist-info/RECORD,,