das-cli 1.3.15__tar.gz → 1.4.0__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.
- {das_cli-1.3.15/das_cli.egg-info → das_cli-1.4.0}/PKG-INFO +9 -1
- {das_cli-1.3.15 → das_cli-1.4.0}/README.md +8 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/app.py +3 -1
- {das_cli-1.3.15 → das_cli-1.4.0}/das/cli.py +63 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/common/entry_fields_constants.py +1 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/managers/entries_manager.py +75 -6
- das_cli-1.4.0/das/services/das_elastic_search.py +26 -0
- {das_cli-1.3.15 → das_cli-1.4.0/das_cli.egg-info}/PKG-INFO +9 -1
- {das_cli-1.3.15 → das_cli-1.4.0}/das_cli.egg-info/SOURCES.txt +1 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/pyproject.toml +1 -1
- {das_cli-1.3.15 → das_cli-1.4.0}/tests/entries_manager_test.py +21 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/LICENSE +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/MANIFEST.in +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/__init__.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/ai/plugins/dasai.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/ai/plugins/entries/entries_plugin.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/authentication/auth.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/authentication/oauth.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/authentication/secure_input.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/common/api.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/common/config.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/common/enums.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/common/file_utils.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/managers/__init__.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/managers/digital_objects_manager.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/managers/download_manager.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/managers/search_manager.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/services/attributes.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/services/cache.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/services/digital_objects.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/services/downloads.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/services/entries.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/services/entry_fields.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/services/hangfire.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/services/search.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/services/service_base.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das/services/users.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das_cli.egg-info/dependency_links.txt +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das_cli.egg-info/entry_points.txt +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das_cli.egg-info/requires.txt +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/das_cli.egg-info/top_level.txt +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/setup.cfg +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/tests/__init__.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/tests/attributes_test.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/tests/download_manager_test.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/tests/entries_service_test.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/tests/entries_test.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/tests/file_utils_test.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/tests/run_tests.py +0 -0
- {das_cli-1.3.15 → das_cli-1.4.0}/tests/search_manager_test.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: das-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: DAS api client.
|
|
5
5
|
Author: Royal Netherlands Institute for Sea Research
|
|
6
6
|
License-Expression: MIT
|
|
@@ -1201,3 +1201,11 @@ MIT License
|
|
|
1201
1201
|
## Maintainers
|
|
1202
1202
|
|
|
1203
1203
|
This project is maintained by the Royal Netherlands Institute for Sea Research (NIOZ).
|
|
1204
|
+
|
|
1205
|
+
## Debugging and Troubleshooting
|
|
1206
|
+
|
|
1207
|
+
If you need to run the current module use:
|
|
1208
|
+
|
|
1209
|
+
```bash
|
|
1210
|
+
python -m das.cli login --api-url https://api.das-dev.nioz.nl/ --oauth
|
|
1211
|
+
```
|
|
@@ -1178,3 +1178,11 @@ MIT License
|
|
|
1178
1178
|
## Maintainers
|
|
1179
1179
|
|
|
1180
1180
|
This project is maintained by the Royal Netherlands Institute for Sea Research (NIOZ).
|
|
1181
|
+
|
|
1182
|
+
## Debugging and Troubleshooting
|
|
1183
|
+
|
|
1184
|
+
If you need to run the current module use:
|
|
1185
|
+
|
|
1186
|
+
```bash
|
|
1187
|
+
python -m das.cli login --api-url https://api.das-dev.nioz.nl/ --oauth
|
|
1188
|
+
```
|
|
@@ -5,6 +5,7 @@ from das.services.entries import EntriesService
|
|
|
5
5
|
from das.services.entry_fields import EntryFieldsService
|
|
6
6
|
from das.services.hangfire import HangfireService
|
|
7
7
|
from das.services.search import SearchService
|
|
8
|
+
from das.services.das_elastic_search import DasElasticSearch
|
|
8
9
|
from das.common.config import save_api_url
|
|
9
10
|
|
|
10
11
|
# $env:PYTHONPATH="C:\Workspace\das-cli"
|
|
@@ -20,7 +21,8 @@ class Das:
|
|
|
20
21
|
self.entries = EntriesService(base_url)
|
|
21
22
|
self.hangfire = HangfireService(base_url)
|
|
22
23
|
self.entry_fields = EntryFieldsService(base_url)
|
|
23
|
-
self.search = SearchService(base_url)
|
|
24
|
+
self.search = SearchService(base_url)
|
|
25
|
+
self.elastic_search = DasElasticSearch(base_url)
|
|
24
26
|
|
|
25
27
|
def authenticate(self, username: str, password: str) -> str:
|
|
26
28
|
"""
|
|
@@ -395,6 +395,69 @@ def sync_doi(das_ctx, id):
|
|
|
395
395
|
except Exception as e:
|
|
396
396
|
click.secho(f"Error: {e}", fg="red")
|
|
397
397
|
|
|
398
|
+
# Elastic Search commands group
|
|
399
|
+
@cli.group()
|
|
400
|
+
def elastic():
|
|
401
|
+
"""Commands for working with Elastic Search"""
|
|
402
|
+
pass
|
|
403
|
+
|
|
404
|
+
@elastic.command("search-item")
|
|
405
|
+
@click.option('--field', required=True, help='The field to search by (e.g., identifier)')
|
|
406
|
+
@click.option('--value', required=True, help='The value to search for')
|
|
407
|
+
@click.option('--format', 'output_format', type=click.Choice(['table', 'json', 'compact']), default='table', help='Output format (default: table)')
|
|
408
|
+
@pass_das_context
|
|
409
|
+
def elastic_search_item(das_ctx, field, value, output_format):
|
|
410
|
+
"""Search for an item in Elastic Search by field and value"""
|
|
411
|
+
client = das_ctx.get_client()
|
|
412
|
+
try:
|
|
413
|
+
result = client.elastic_search.search_item(field, value)
|
|
414
|
+
|
|
415
|
+
if result is None:
|
|
416
|
+
click.secho(f"No results found for {field}={value}", fg="yellow")
|
|
417
|
+
return
|
|
418
|
+
|
|
419
|
+
if output_format == 'json':
|
|
420
|
+
click.echo(json.dumps(result, indent=2))
|
|
421
|
+
elif output_format == 'compact':
|
|
422
|
+
if isinstance(result, list):
|
|
423
|
+
for item in result:
|
|
424
|
+
if isinstance(item, dict):
|
|
425
|
+
click.echo(', '.join(f"{k}={v}" for k, v in item.items()))
|
|
426
|
+
else:
|
|
427
|
+
click.echo(str(item))
|
|
428
|
+
elif isinstance(result, dict):
|
|
429
|
+
click.echo(', '.join(f"{k}={v}" for k, v in result.items()))
|
|
430
|
+
else:
|
|
431
|
+
click.echo(str(result))
|
|
432
|
+
else:
|
|
433
|
+
# table format (default)
|
|
434
|
+
click.secho(f"\n Search results for {field}={value}", fg="cyan", bold=True)
|
|
435
|
+
click.echo()
|
|
436
|
+
|
|
437
|
+
if isinstance(result, dict):
|
|
438
|
+
max_key_len = max(len(str(k)) for k in result.keys()) if result else 0
|
|
439
|
+
for key, val in result.items():
|
|
440
|
+
if isinstance(val, (dict, list)):
|
|
441
|
+
val_str = json.dumps(val, indent=2)
|
|
442
|
+
click.echo(f" {click.style(str(key).ljust(max_key_len), fg='green')} {val_str}")
|
|
443
|
+
else:
|
|
444
|
+
click.echo(f" {click.style(str(key).ljust(max_key_len), fg='green')} {val}")
|
|
445
|
+
elif isinstance(result, list):
|
|
446
|
+
if result and isinstance(result[0], dict):
|
|
447
|
+
headers = list(result[0].keys())
|
|
448
|
+
rows = [[str(item.get(h, '')) for h in headers] for item in result]
|
|
449
|
+
click.echo(format_table(rows, headers))
|
|
450
|
+
else:
|
|
451
|
+
for item in result:
|
|
452
|
+
click.echo(f" {item}")
|
|
453
|
+
else:
|
|
454
|
+
click.echo(f" {result}")
|
|
455
|
+
|
|
456
|
+
click.echo()
|
|
457
|
+
click.secho(f"✓ Search completed successfully", fg="green")
|
|
458
|
+
except Exception as e:
|
|
459
|
+
click.secho(f"Error: {e}", fg="red")
|
|
460
|
+
|
|
398
461
|
# Entries commands group
|
|
399
462
|
@cli.group()
|
|
400
463
|
def entry():
|
|
@@ -2,9 +2,10 @@ import json
|
|
|
2
2
|
from das.common.config import load_api_url
|
|
3
3
|
from das.managers.search_manager import SearchManager
|
|
4
4
|
from das.services.attributes import AttributesService
|
|
5
|
+
from das.services.das_elastic_search import DasElasticSearch
|
|
5
6
|
from das.services.entry_fields import EntryFieldsService
|
|
6
7
|
from das.services.entries import EntriesService
|
|
7
|
-
from das.common.entry_fields_constants import DIGITAL_OBJECT_INPUT, SELECT_COMBO_INPUT, GROUP_BOX_INPUT
|
|
8
|
+
from das.common.entry_fields_constants import DIGITAL_OBJECT_INPUT, SELECT_COMBO_INPUT, GROUP_BOX_INPUT, SUB_FORM_RELATIONS
|
|
8
9
|
from das.services.search import SearchService
|
|
9
10
|
from das.services.users import UsersService
|
|
10
11
|
|
|
@@ -22,6 +23,7 @@ class EntryManager:
|
|
|
22
23
|
self.search_service = SearchService(base_url)
|
|
23
24
|
self.attribute_service = AttributesService(base_url)
|
|
24
25
|
self.user_service = UsersService(base_url)
|
|
26
|
+
self.das_elastic_search = DasElasticSearch(base_url)
|
|
25
27
|
|
|
26
28
|
def delete(self, id: str = None, code: str = None) -> bool:
|
|
27
29
|
"""Delete an entry by its id or code."""
|
|
@@ -324,8 +326,9 @@ class EntryManager:
|
|
|
324
326
|
elif column_name in entry:
|
|
325
327
|
updated_entry[column_name] = self.__get_value(field, entry[column_name])
|
|
326
328
|
|
|
327
|
-
return self.entry_service.update(attribute_id=attribute_id, entry=updated_entry)
|
|
328
|
-
|
|
329
|
+
return self.entry_service.update(attribute_id=attribute_id, entry=updated_entry)
|
|
330
|
+
|
|
331
|
+
|
|
329
332
|
def __set_group_box_field_value(self, current_entry: dict, field, entry: dict) -> dict:
|
|
330
333
|
"""Helper method to set group box field value."""
|
|
331
334
|
custom_data = field.get('customData', None)
|
|
@@ -347,12 +350,78 @@ class EntryManager:
|
|
|
347
350
|
if field.get('inputType') == SELECT_COMBO_INPUT: # SELECT_COMBO_INPUT
|
|
348
351
|
select_value = self.__get_select_combobox_field_value(field, source)
|
|
349
352
|
return select_value
|
|
350
|
-
elif field.get('inputType') == GROUP_BOX_INPUT:
|
|
351
|
-
# group_box_value = self.__get_group_box_field_value(entry_raw, field)
|
|
353
|
+
elif field.get('inputType') == GROUP_BOX_INPUT:
|
|
352
354
|
return None
|
|
355
|
+
elif field.get('inputType') == SUB_FORM_RELATIONS:
|
|
356
|
+
sub_form_relations_value = self.__get_sub_form_relations_field_value(field, source)
|
|
357
|
+
return sub_form_relations_value
|
|
353
358
|
else:
|
|
354
|
-
return source
|
|
359
|
+
return source
|
|
355
360
|
|
|
361
|
+
def __get_sub_form_relations_field_value(self, field, source: str | list | dict) -> str | list | dict:
|
|
362
|
+
if not source:
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
if isinstance(source, (dict, list)):
|
|
366
|
+
return source
|
|
367
|
+
|
|
368
|
+
# convert all keys to lowercase
|
|
369
|
+
field = {str(k).lower(): v for k, v in field.items()}
|
|
370
|
+
|
|
371
|
+
tag = None
|
|
372
|
+
values = [v.strip() for v in source.split(',') if v.strip()]
|
|
373
|
+
relations = []
|
|
374
|
+
|
|
375
|
+
if field.get('customdata', None) is not None:
|
|
376
|
+
try:
|
|
377
|
+
customdata_string = field.get('customdata').lower()
|
|
378
|
+
customdata = json.loads(customdata_string)
|
|
379
|
+
if customdata is not None and isinstance(customdata, dict):
|
|
380
|
+
if "tag" in customdata:
|
|
381
|
+
tag = customdata.get("tag")
|
|
382
|
+
if "relations" in customdata:
|
|
383
|
+
relations = customdata.get("relations")
|
|
384
|
+
except json.JSONDecodeError:
|
|
385
|
+
raise ValueError(f"Invalid customdata JSON: {field.get('customdata')}")
|
|
386
|
+
|
|
387
|
+
result_list = []
|
|
388
|
+
for value in values:
|
|
389
|
+
response = self.das_elastic_search.search_item(field="identifier", value=value)
|
|
390
|
+
if response.get('item') is not None and isinstance(response.get('item'), dict):
|
|
391
|
+
if response.get('item').get('source') is not None and isinstance(response.get('item').get('source'), dict):
|
|
392
|
+
source = response.get('item').get('source', {})
|
|
393
|
+
# find the attribute id in the relation.attributeid using lambda and check if its the same as the source.attributeid, if not raise an error
|
|
394
|
+
if relations and not any(rel.get('attributeid') == source.get('attributeid') for rel in relations):
|
|
395
|
+
raise ValueError(f"Attribute ID {source.get('attributeid')} for value '{value}' is not allowed in the relations list")
|
|
396
|
+
item = {
|
|
397
|
+
"id": source.get('id'),
|
|
398
|
+
"name": source.get('title'),
|
|
399
|
+
"code": source.get('code'),
|
|
400
|
+
"attributeId": source.get('attributeid'),
|
|
401
|
+
"tag": tag,
|
|
402
|
+
"acronym": None,
|
|
403
|
+
"order": 0
|
|
404
|
+
}
|
|
405
|
+
# get the attribute name from the relations where the attribute id is the same as the source.attributeid using lambda
|
|
406
|
+
attribute_name = next((attr.get('attributealias') for attr in relations if attr.get('attributeid') == source.get('attributeid')), None)
|
|
407
|
+
|
|
408
|
+
# We need to check if the relation item already exists in the result list, if it does we need to append the item to the relations list of that item, if not we need to create a new item in the result list
|
|
409
|
+
existing_item = next((i for i in result_list if i.get('attribute') == attribute_name), None)
|
|
410
|
+
if existing_item:
|
|
411
|
+
existing_item.get('relations', []).append(item)
|
|
412
|
+
continue
|
|
413
|
+
|
|
414
|
+
item_relation = {
|
|
415
|
+
"attribute": attribute_name,
|
|
416
|
+
"relations": [item],
|
|
417
|
+
"tag": tag
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
result_list.append(item_relation)
|
|
421
|
+
|
|
422
|
+
return json.dumps(result_list) if result_list else source
|
|
423
|
+
|
|
424
|
+
|
|
356
425
|
def __get_select_combobox_field_value(self, field, source: str | list | dict) -> str | list | dict:
|
|
357
426
|
"""Helper method to get select combobox field value."""
|
|
358
427
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from das.services.service_base import ServiceBase
|
|
2
|
+
from das.common.api import post_data
|
|
3
|
+
|
|
4
|
+
class DasElasticSearch(ServiceBase):
|
|
5
|
+
def __init__(self, base_url):
|
|
6
|
+
self.base_url = f"{base_url}/api/services/app/DataPortal"
|
|
7
|
+
|
|
8
|
+
def search_item(self, field: str, value: str):
|
|
9
|
+
headers = {**self.set_auth_headers(), "Content-Type": "application/json"}
|
|
10
|
+
url = f"{self.base_url}/SearchItem"
|
|
11
|
+
|
|
12
|
+
payload = {
|
|
13
|
+
"field": field,
|
|
14
|
+
"value": value
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
response = post_data(url, data=payload, headers=headers)
|
|
18
|
+
|
|
19
|
+
if response.get('success') == True:
|
|
20
|
+
return response.get('result')
|
|
21
|
+
else:
|
|
22
|
+
error = response.get('error', 'Unknown error')
|
|
23
|
+
raw_content = response.get('raw_content', '')
|
|
24
|
+
if raw_content:
|
|
25
|
+
raise ValueError(f"{error}\nResponse content: {raw_content}")
|
|
26
|
+
raise ValueError(str(error))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: das-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: DAS api client.
|
|
5
5
|
Author: Royal Netherlands Institute for Sea Research
|
|
6
6
|
License-Expression: MIT
|
|
@@ -1201,3 +1201,11 @@ MIT License
|
|
|
1201
1201
|
## Maintainers
|
|
1202
1202
|
|
|
1203
1203
|
This project is maintained by the Royal Netherlands Institute for Sea Research (NIOZ).
|
|
1204
|
+
|
|
1205
|
+
## Debugging and Troubleshooting
|
|
1206
|
+
|
|
1207
|
+
If you need to run the current module use:
|
|
1208
|
+
|
|
1209
|
+
```bash
|
|
1210
|
+
python -m das.cli login --api-url https://api.das-dev.nioz.nl/ --oauth
|
|
1211
|
+
```
|
|
@@ -78,6 +78,27 @@ class TestEntriesManager(unittest.TestCase):
|
|
|
78
78
|
self.assertIsNotNone(created_event)
|
|
79
79
|
self.assertRegex(created_event[0]['id'], r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')
|
|
80
80
|
|
|
81
|
+
def test_create_analytical_run_entry(self):
|
|
82
|
+
"""Test creating an analytical run entry"""
|
|
83
|
+
|
|
84
|
+
payload = {
|
|
85
|
+
'Alias': 'alias-test',
|
|
86
|
+
'Instrument': 'k.b.db',
|
|
87
|
+
'Date': '2024-06-01',
|
|
88
|
+
'Protocol(s)': 'zc.b.v,zc.b.t',
|
|
89
|
+
'Comment': 'This is a test comment',
|
|
90
|
+
'Operator': 'Wim Boer',
|
|
91
|
+
'Software': 'q.b.b',
|
|
92
|
+
'Sample(s)': "4.b.x3g,4.b.p3g,4.b.w3g,zb.b.bc,zb.b.lk,zb.b.k1",
|
|
93
|
+
'Standard(s)': "wc.b.4,wc.b.k"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
created_entry_id = self.entry_manager.create(attribute="Analytical Run", entry=payload)
|
|
97
|
+
|
|
98
|
+
self.assertIsNotNone(created_entry_id)
|
|
99
|
+
# assert createrd entry id is an guid
|
|
100
|
+
self.assertRegex(created_entry_id[0]['id'], r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')
|
|
101
|
+
|
|
81
102
|
def test_delete_entry(self):
|
|
82
103
|
"""Test deleting an entry"""
|
|
83
104
|
|
|
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
|
|
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
|