development-framework-cli 1.7.3__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.
Files changed (71) hide show
  1. development_framework_cli-1.7.3.dist-info/METADATA +141 -0
  2. development_framework_cli-1.7.3.dist-info/RECORD +71 -0
  3. development_framework_cli-1.7.3.dist-info/WHEEL +4 -0
  4. development_framework_cli-1.7.3.dist-info/entry_points.txt +2 -0
  5. development_framework_cli-1.7.3.dist-info/licenses/LICENSE +21 -0
  6. dm_cli/__init__.py +1 -0
  7. dm_cli/cli.py +180 -0
  8. dm_cli/command_group/__init__.py +0 -0
  9. dm_cli/command_group/data_source.py +176 -0
  10. dm_cli/command_group/entities.py +116 -0
  11. dm_cli/dmss.py +104 -0
  12. dm_cli/dmss_api/__init__.py +27 -0
  13. dm_cli/dmss_api/api/__init__.py +3 -0
  14. dm_cli/dmss_api/api/access_control_api.py +349 -0
  15. dm_cli/dmss_api/api/attribute_api.py +175 -0
  16. dm_cli/dmss_api/api/blob_api.py +344 -0
  17. dm_cli/dmss_api/api/blueprint_api.py +326 -0
  18. dm_cli/dmss_api/api/datasource_api.py +448 -0
  19. dm_cli/dmss_api/api/default_api.py +4439 -0
  20. dm_cli/dmss_api/api/document_api.py +922 -0
  21. dm_cli/dmss_api/api/entity_api.py +464 -0
  22. dm_cli/dmss_api/api/export_api.py +313 -0
  23. dm_cli/dmss_api/api/file_api.py +197 -0
  24. dm_cli/dmss_api/api/health_check_api.py +160 -0
  25. dm_cli/dmss_api/api/lookup_table_api.py +323 -0
  26. dm_cli/dmss_api/api/meta_api.py +185 -0
  27. dm_cli/dmss_api/api/personal_access_token_api.py +433 -0
  28. dm_cli/dmss_api/api/reference_api.py +174 -0
  29. dm_cli/dmss_api/api/search_api.py +183 -0
  30. dm_cli/dmss_api/api/whoami_api.py +163 -0
  31. dm_cli/dmss_api/api_client.py +896 -0
  32. dm_cli/dmss_api/apis/__init__.py +32 -0
  33. dm_cli/dmss_api/configuration.py +482 -0
  34. dm_cli/dmss_api/exceptions.py +158 -0
  35. dm_cli/dmss_api/model/__init__.py +5 -0
  36. dm_cli/dmss_api/model/access_control_list.py +287 -0
  37. dm_cli/dmss_api/model/access_level.py +290 -0
  38. dm_cli/dmss_api/model/acl.py +287 -0
  39. dm_cli/dmss_api/model/data_source_information.py +282 -0
  40. dm_cli/dmss_api/model/data_source_request.py +281 -0
  41. dm_cli/dmss_api/model/dependency.py +294 -0
  42. dm_cli/dmss_api/model/entity.py +275 -0
  43. dm_cli/dmss_api/model/error_response.py +278 -0
  44. dm_cli/dmss_api/model/export_meta_response.py +277 -0
  45. dm_cli/dmss_api/model/get_blueprint_response.py +280 -0
  46. dm_cli/dmss_api/model/lookup.py +283 -0
  47. dm_cli/dmss_api/model/pat_data.py +299 -0
  48. dm_cli/dmss_api/model/recipe.py +311 -0
  49. dm_cli/dmss_api/model/recipe_attribute.py +304 -0
  50. dm_cli/dmss_api/model/reference.py +280 -0
  51. dm_cli/dmss_api/model/reference_entity.py +288 -0
  52. dm_cli/dmss_api/model/repository.py +317 -0
  53. dm_cli/dmss_api/model/repository_type.py +290 -0
  54. dm_cli/dmss_api/model/storage_attribute.py +295 -0
  55. dm_cli/dmss_api/model/storage_data_types.py +292 -0
  56. dm_cli/dmss_api/model/storage_recipe.py +293 -0
  57. dm_cli/dmss_api/model_utils.py +2057 -0
  58. dm_cli/dmss_api/models/__init__.py +29 -0
  59. dm_cli/dmss_api/rest.py +358 -0
  60. dm_cli/domain.py +166 -0
  61. dm_cli/enums.py +48 -0
  62. dm_cli/import_entity.py +128 -0
  63. dm_cli/import_package.py +173 -0
  64. dm_cli/package_tree_from_zip.py +120 -0
  65. dm_cli/state.py +12 -0
  66. dm_cli/utils/__init__.py +0 -0
  67. dm_cli/utils/file_structure.py +31 -0
  68. dm_cli/utils/reference.py +129 -0
  69. dm_cli/utils/resolve_local_ids.py +142 -0
  70. dm_cli/utils/utils.py +161 -0
  71. dm_cli/utils/zip.py +70 -0
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: development-framework-cli
3
+ Version: 1.7.3
4
+ Summary: CLI tool for working with Data Modelling Storage Service
5
+ Project-URL: Homepage, https://github.com/equinor/dm-cli
6
+ Project-URL: Repository, https://github.com/equinor/dm-cli
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Python: >=3.13
14
+ Requires-Dist: emoji>=2.15.0
15
+ Requires-Dist: frozendict>=2.4.7
16
+ Requires-Dist: python-dateutil>=2.9.0.post0
17
+ Requires-Dist: requests>=2.32.5
18
+ Requires-Dist: tenacity>=9.1.4
19
+ Requires-Dist: tqdm>=4.67.3
20
+ Requires-Dist: typer[all]>=0.24.1
21
+ Requires-Dist: typing-extensions>=4.15.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pre-commit>=4.5.1; extra == 'dev'
24
+ Requires-Dist: pytest>=9.0.2; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Data Modelling CLI Tool [![PyPi version](https://img.shields.io/pypi/v/development-framework-cli)](https://pypi.org/project/development-framework-cli) [![PyPi downloads](https://img.shields.io/pypi/dm/development-framework-cli)](https://pypi.org/project/development-framework-cli) ![Visitors](https://api.visitorbadge.io/api/visitors?path=equinor%2Fdm-cli&countColor=%23263759&style=flat)
28
+
29
+ CLI tool for working with [Data Modelling Storage Service](https://github.com/equinor/data-modelling-storage-service)
30
+
31
+ ## Installing
32
+ > [!NOTE]
33
+ > Requires Python version >= 3.13
34
+
35
+ Install the tool via pip
36
+ ```sh
37
+ $ pip3 install development-framework-cli
38
+ ```
39
+
40
+ After a successful install, the program `'dm'` will be available in your python environment
41
+
42
+ ## Usage
43
+
44
+ ```txt
45
+ Usage: dm [OPTIONS] COMMAND [ARGS]...
46
+
47
+ Command Line Interface (CLI) tool for working with the Data Modelling Framework. This tool is mainly used to upload data source definitions, models and
48
+ entities, and creating RecipeLink-tables.
49
+
50
+ ╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
51
+ │ --force -f Force the operation. Overwriting and potentially deleting data. │
52
+ │ --url -u TEXT URL to the Data Modelling Storage Service (DMSS). [default: http://localhost:5000] │
53
+ │ --token -t TEXT Token for authentication against DMSS. [default: no-token] │
54
+ │ --debug -d Print stack trace of suppressed exceptions │
55
+ │ --version -v Print version and exit │
56
+ │ --install-completion Install completion for the current shell. │
57
+ │ --show-completion Show completion for the current shell, to copy it or customize the installation. │
58
+ │ --help Show this message and exit. │
59
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
60
+ ╭─ Commands ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
61
+ │ create-lookup Create a named Ui-/StorageRecipe-lookup-table from all RecipeLinks in a package existing in DMSS (requires admin privileges). │
62
+ │ ds Import and reset data sources │
63
+ │ entities Import, delete, or validate entities and/or blueprints │
64
+ │ export Export one or more entities. │
65
+ │ import-plugin-blueprints Import blueprints from a plugin into the standard location 'system/Plugins/<plugin-name>'. │
66
+ │ reset Reset all data sources (deletes and re-uploads all packages to DMSS). │
67
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
68
+ ```
69
+
70
+ > [!TIP]
71
+ > For each of the `commands` listed above, you can run `dm <COMMAND> --help` to see subcommand-specific help messages, e.g. `dm ds import --help` or `dm pkg --help`
72
+
73
+ ### Required directory structure
74
+ Certain commands expect a specific directory structure, such as the commands `dm reset`, `dm ds init`, and `dm ds reset`.
75
+ For these commands, the `path` argument must be the path to a directory with two subdirectories, `data_sources` and `data`.
76
+
77
+ ```sh
78
+ $ tree app
79
+ app
80
+ ├── data
81
+ └── DemoApplicationDataSource
82
+ ├── instances
83
+ │ └── demoApplication.json
84
+ | └── models
85
+ | └── DemoApplication.json
86
+ └── data_sources
87
+ └── DemoApplicationDataSource.json
88
+ ```
89
+
90
+ To add meta information to a package (for example to the models root package), a file with name "package.json" can be placed inside the folder.
91
+
92
+
93
+ ### Supported reference syntax
94
+ The CLI tool will understand and resolve the following address formats during import.
95
+
96
+
97
+ ```bash
98
+ # URI - Full absolute path prefixed with protocol
99
+ dmss://datasource/package/entity
100
+ dmss://datasource/package/subfolder/entity
101
+ dmss://datasource/$123-456.attributeName
102
+
103
+ # Alias - Require dependencies to be defined somewhere in the source tree
104
+ ALIAS:package/entity
105
+ ALIAS:entity
106
+
107
+ # Data Source - Relative from the destination data source
108
+ /package/entity
109
+ /package/subfolder/entity
110
+ /$123-456.attributeName
111
+
112
+ # Package - Relative from the source package
113
+ entity
114
+ subfolder/entity
115
+
116
+ # Dotted - Relative from the file (UNIX directory traversal)
117
+ ./../entity
118
+ ../subfolder/entity
119
+ ../../subfolder/entity
120
+ ```
121
+
122
+ ## Development
123
+
124
+ ```sh
125
+ $ python3 -m venv .venv
126
+ $ source .venv/bin/activate
127
+ # Install the package from local files in edit mode (-e)
128
+ $ pip3 install -e .
129
+ $ dm --help
130
+ ```
131
+
132
+ ### Testing
133
+
134
+ 1. Install the package with dev dependencies: `pip3 install -e ".[dev]"`
135
+ 2. Run the tests: `pytest`
136
+
137
+ ## Feedback
138
+ Please feel free to leave feedback in issues/PRs.
139
+
140
+ ## Contributing
141
+ If you would like to contribute, please read our [Contribution guide](https://equinor.github.io/dm-docs/contributing/).
@@ -0,0 +1,71 @@
1
+ dm_cli/__init__.py,sha256=2c0WxZjtTHwotq4Jkk-B28UcCDeX2TNO-BSbdbD95x8,46
2
+ dm_cli/cli.py,sha256=mpxL7qeJQlpTeFlM7Fqho-6ASYrfcgIYfqxnVmvMZHI,7004
3
+ dm_cli/dmss.py,sha256=3yzKc3Hb3iXE_Pq7-Dop2eQ61KLc2WcEsMOzfp-JM-g,3066
4
+ dm_cli/domain.py,sha256=BMBK-WOQwBvDBEr_AoywdXAUPXMHIKIZLnMHpkuzqPs,5650
5
+ dm_cli/enums.py,sha256=7PFPV1u5mexc-p6s8gG-wb7B5SmAORh5esmPQ-zxUtg,1236
6
+ dm_cli/import_entity.py,sha256=SJn9IxUBWS9iTkafYAfZfkk7bZsip4TfZmQZRbHe3LI,4275
7
+ dm_cli/import_package.py,sha256=wBXqF0rJrRusCFsLQkFBCyYbrLJdHQ8756oW04v9ggM,7526
8
+ dm_cli/package_tree_from_zip.py,sha256=bpr9BoRaYKaowaP-zDif6km7mYJLbbby0f0kZHH6DTc,4624
9
+ dm_cli/state.py,sha256=F2L_0bj0P840WDvCmqR0TCsko0AsVyjiOP8SVKocG7s,198
10
+ dm_cli/command_group/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ dm_cli/command_group/data_source.py,sha256=pYVPicGlabf1HRUNqoKQcSlQfVRtzJjp2xqmWb_9kKA,7651
12
+ dm_cli/command_group/entities.py,sha256=7pPL_Ejkm2He6CDjVfSz39QDGWBVNCtkU4dVox5du24,4198
13
+ dm_cli/dmss_api/__init__.py,sha256=rO1FT4B25_nBww-fLBlehjkOfqgkDV3EWbG5ZJcO7cA,731
14
+ dm_cli/dmss_api/api_client.py,sha256=AZz3BN28Jk1AUeS5ZTizJ0ZW8bGOCPlVF2MapfX1uMQ,39037
15
+ dm_cli/dmss_api/configuration.py,sha256=mToiL-ruKriKSfuhJr2CJNnQEA2ASGviNS1vatHxe34,17323
16
+ dm_cli/dmss_api/exceptions.py,sha256=dbM7LyJ7_cBPWLhUyTEwU1VcLNF9WRG0ZiwaztZ5G7c,5055
17
+ dm_cli/dmss_api/model_utils.py,sha256=fyT0M2tzu1Uc9kNp8rqVVImBChh1DTyunEkECFtYwKA,82475
18
+ dm_cli/dmss_api/rest.py,sha256=GpRcHbwGnXiu9HbiObaNss9-rrdGtmELxGhAHdL3J1k,14265
19
+ dm_cli/dmss_api/api/__init__.py,sha256=12fUKkdPETk9RckbCl32EL1X6OhwMwwWMmCOmPnls94,225
20
+ dm_cli/dmss_api/api/access_control_api.py,sha256=I4o0cqQMUDmyDKOAV53jGBSKZwYGLwY2DbOMSPwAGXc,13451
21
+ dm_cli/dmss_api/api/attribute_api.py,sha256=tgD5OjbCbVNmPAIFtF_4X-XtKnJhgbzU4KJuoDg-bEw,6459
22
+ dm_cli/dmss_api/api/blob_api.py,sha256=j1u11vmysi9FYmmSHtqDH7gGriUT83uahELoLFFzhHc,12976
23
+ dm_cli/dmss_api/api/blueprint_api.py,sha256=Y2ksyFgNo6uzdFZUQW7cFlw1VGrXHjotA3QMvY1KkfU,12515
24
+ dm_cli/dmss_api/api/datasource_api.py,sha256=KXvY--XsJ65rtXDd7IiX8AvIS_--sjKDMz8FGmmRP8E,17726
25
+ dm_cli/dmss_api/api/default_api.py,sha256=WkHiGQZPyTp19OLC_0ZA3VIWVkkAb_aIaTMZkG97S2M,182276
26
+ dm_cli/dmss_api/api/document_api.py,sha256=q1hJ6usJh6b55OiJrHY_6f_DY43tVhu1TsoZQ7Z9JLU,37828
27
+ dm_cli/dmss_api/api/entity_api.py,sha256=bKdPS-_9PgNxPiC4nvy4vbSzy5vB-xiUNiz75QD5B64,18900
28
+ dm_cli/dmss_api/api/export_api.py,sha256=kAGwTLJY6qSUri7waCIvysg1IGxVzr4efq3s-EZADag,12271
29
+ dm_cli/dmss_api/api/file_api.py,sha256=wZpQD83P5JpHG2IQzkXVUGruD3Ax56eH7WRjVc75-R8,7285
30
+ dm_cli/dmss_api/api/health_check_api.py,sha256=4siOuShRIz43zNF4G0oH_-Er3niHjuYpOb1pNwr7G1c,5768
31
+ dm_cli/dmss_api/api/lookup_table_api.py,sha256=uEy92V59eJpsxtngHoQoH9AyUryqCEIiTac6VYQc0R0,12639
32
+ dm_cli/dmss_api/api/meta_api.py,sha256=P6DqHw2Sa37H6cmZSc9Kzu6yMry5wG7Dd-ntt1jdvdc,6975
33
+ dm_cli/dmss_api/api/personal_access_token_api.py,sha256=a8nIs-Lbf9VBQAT8OzJr7VDWhx1wXAomY_rq3-nP_Zs,16745
34
+ dm_cli/dmss_api/api/reference_api.py,sha256=8uQnY69rKR4HfoweFElmgnhYGMRbL6MyKMU5nLe9hNk,6203
35
+ dm_cli/dmss_api/api/search_api.py,sha256=NDOl0f3Ck5DxLrDqO58M-daGLtzUozMM56Hrf0ItOpY,7870
36
+ dm_cli/dmss_api/api/whoami_api.py,sha256=bI7imyDYqedM-up20y7UfJ4RS2-MM0yC6AcXkji3Ihg,6077
37
+ dm_cli/dmss_api/apis/__init__.py,sha256=6ZAbQnCEUI8MECJMbXVvaP3O-iZy3iQLBoSXlP-tRg0,1368
38
+ dm_cli/dmss_api/model/__init__.py,sha256=U9808NsoSPJPzCkNdXn1lsoaUXKJu6LeAYzjE6xq5Kc,349
39
+ dm_cli/dmss_api/model/access_control_list.py,sha256=ytBc-1fHcFgVFyxQ5N-WNHlDzj6WqQA_gPS1A29MjE4,12675
40
+ dm_cli/dmss_api/model/access_level.py,sha256=AMhP_AS7G_k6ar903IECo-G3FkuJqHU8SwP_3bNwpOg,12009
41
+ dm_cli/dmss_api/model/acl.py,sha256=ogczX9AszMg9dSpk1IUAWUDnkdjLhkzQ8fskCwIvEGo,12632
42
+ dm_cli/dmss_api/model/data_source_information.py,sha256=BeTOHx_NGGkL9JCAfDIDy1Tl6MtHy6pbEdOfHya5fZg,11976
43
+ dm_cli/dmss_api/model/data_source_request.py,sha256=6TmSFJ6V2beqL6qiX5Q3SzUldOyZA6SDhNiDAxF5D1A,11935
44
+ dm_cli/dmss_api/model/dependency.py,sha256=es0ly3G-qXxO3G8hcJqCXbvUOMP9rRSwhucskGHMnCo,12472
45
+ dm_cli/dmss_api/model/entity.py,sha256=CjQDalkvAWk7Lmm30j7T5OfYuYZwSLlNYhBKogkmY_M,11602
46
+ dm_cli/dmss_api/model/error_response.py,sha256=SZCPbiE_vfWPesCEehIlP_nhs3UnywQkXwdv484Nusg,13086
47
+ dm_cli/dmss_api/model/export_meta_response.py,sha256=Bc95wErM0AKbym27G62vftJF0QCXcb6cYDtt6O-hg04,12363
48
+ dm_cli/dmss_api/model/get_blueprint_response.py,sha256=rTlCzAohS8UuktzhDQWkxAGa9v4AZqNuGKu1OZKAm8c,12675
49
+ dm_cli/dmss_api/model/lookup.py,sha256=breMaW0pXGXPNx6luEQO5B9Co4fMpTMFm4P9G5zboyc,12625
50
+ dm_cli/dmss_api/model/pat_data.py,sha256=gkQdIQSmVnR4pAKw7Y3UmXr2kST0RH-c-imEyayYg5U,12923
51
+ dm_cli/dmss_api/model/recipe.py,sha256=hNbyp0wa4xzeD1naiE2KjLg9vomXqZp8RbkxP9pDYZs,14427
52
+ dm_cli/dmss_api/model/recipe_attribute.py,sha256=f2-P-bF5LGNia08T86Sc9V73jnAH-zhKo9aE_7J0_48,13522
53
+ dm_cli/dmss_api/model/reference.py,sha256=fxp8Z66wSVaKgxFJ4R2fiB_CKbq72aD9bQ7yhem5Cg4,11764
54
+ dm_cli/dmss_api/model/reference_entity.py,sha256=ITan_axXrDu9W7qTybA6p3OQB5mjIQFtkJQ8XhlFaBc,12151
55
+ dm_cli/dmss_api/model/repository.py,sha256=W1T7QfqXpk1DDPSkLvpB_TgvbzV8LoE9ZmMeVsV81m8,13879
56
+ dm_cli/dmss_api/model/repository_type.py,sha256=zeqC9WRY9PETTnXcUCx8JIWgCgxkuyYgkkdIKSYN3M8,12168
57
+ dm_cli/dmss_api/model/storage_attribute.py,sha256=xRML8nAgZ4GkfhHEVCG496IAU6O2AnRbY9froqbtMlU,13333
58
+ dm_cli/dmss_api/model/storage_data_types.py,sha256=TElPaXKgiZ_YUqXAbjqw3tkmGn271uc1NkClSzSoRqg,12198
59
+ dm_cli/dmss_api/model/storage_recipe.py,sha256=uZh_zFithPxO3_lXuiHchhf2ZRX3_pJd-xP3HVFaDm0,13218
60
+ dm_cli/dmss_api/models/__init__.py,sha256=7pzdYw1ktxZRe29ZN_ZIdid8hPu_0JZx0KPaGjV0fR0,1516
61
+ dm_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
+ dm_cli/utils/file_structure.py,sha256=efIcl7kr7LGm9-2AKt6M6uXe0L8NHNBg0Icy1xO8rSM,1020
63
+ dm_cli/utils/reference.py,sha256=V11edpj8mJSCbXlWlv8HW2zO4AwHRqDnmwdxKv5_qpA,5593
64
+ dm_cli/utils/resolve_local_ids.py,sha256=I9kXVZTQgqr07eu4CghGQVU99MAEXCdn6hacVX0AYOY,5726
65
+ dm_cli/utils/utils.py,sha256=T-O4mwMOJH-qwGFEVDbogb8RVXfloEt0wA8A5fRWCSc,6882
66
+ dm_cli/utils/zip.py,sha256=Zdq6Z978i_1iDAvbrqZwJNsw4QersVtK0HeqFiRagdA,2781
67
+ development_framework_cli-1.7.3.dist-info/METADATA,sha256=JDFFza5Yjl5Mm1mkN8tMiuKJcKUnQ5bNDx7h9UCOyDU,8174
68
+ development_framework_cli-1.7.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
69
+ development_framework_cli-1.7.3.dist-info/entry_points.txt,sha256=PAOXUTc_jhfY9Hfs7BNFzh4TlbrOGYU7HgTM-P7h074,38
70
+ development_framework_cli-1.7.3.dist-info/licenses/LICENSE,sha256=zefthAHZDOYIcHUXefkN_Fvuy4s-nYfoLX_dsycVDxc,1064
71
+ development_framework_cli-1.7.3.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ dm = dm_cli.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Equinor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
dm_cli/__init__.py ADDED
@@ -0,0 +1 @@
1
+ VERSION = "1.7.3" # x-release-please-version
dm_cli/cli.py ADDED
@@ -0,0 +1,180 @@
1
+ #! /usr/bin/env python
2
+ import io
3
+ import os
4
+ from pathlib import Path
5
+ from typing import List, Optional
6
+ from zipfile import ZipFile
7
+
8
+ import emoji
9
+ import typer
10
+ from typing_extensions import Annotated
11
+
12
+ from dm_cli import VERSION
13
+ from dm_cli.command_group.data_source import data_source_app, reset_data_source
14
+ from dm_cli.command_group.entities import entities_app, import_entity
15
+ from dm_cli.dmss import dmss_api, dmss_exception_wrapper, export
16
+ from dm_cli.state import state
17
+ from dm_cli.utils.file_structure import get_app_dir_structure, get_json_files_in_dir
18
+ from dm_cli.utils.utils import (
19
+ get_root_packages_in_data_sources,
20
+ validate_entities_in_data_sources,
21
+ )
22
+ from dm_cli.utils.zip import save_as_zip_file, unpack_and_save_zipfile
23
+
24
+ app = typer.Typer(pretty_exceptions_short=True)
25
+ app.add_typer(data_source_app, name="ds")
26
+ app.add_typer(entities_app, name="entities")
27
+
28
+
29
+ def version_callback(print_version: bool):
30
+ if print_version:
31
+ print(VERSION)
32
+ raise typer.Exit()
33
+
34
+
35
+ @app.callback()
36
+ def main(
37
+ force: bool = typer.Option(
38
+ False, "--force", "-f", help="Force the operation. Overwriting and potentially deleting data."
39
+ ),
40
+ dmss_url: str = typer.Option(
41
+ "http://localhost:5000", "--url", "-u", help="URL to the Data Modelling Storage Service (DMSS)."
42
+ ),
43
+ token: str = typer.Option("no-token", "--token", "-t", help="Token for authentication against DMSS."),
44
+ debug: bool = typer.Option(False, "--debug", "-d", help="Print stack trace of suppressed exceptions"),
45
+ version: Optional[bool] = typer.Option(
46
+ None, "--version", "-v", callback=version_callback, is_eager=True, help="Print version and exit"
47
+ ),
48
+ ):
49
+ """
50
+ Command Line Interface (CLI) tool for working with the Data Modelling Framework.
51
+ This tool is mainly used to upload data source definitions, models and entities, and creating RecipeLink-tables.
52
+ """
53
+ state.force = force
54
+ state.dmss_url = dmss_url
55
+ state.token = token
56
+ state.debug = debug
57
+
58
+ dmss_api.api_client.default_headers["Authorization"] = f"Bearer {token}"
59
+ dmss_api.api_client.configuration.host = dmss_url
60
+
61
+
62
+ @app.command("import-plugin-blueprints")
63
+ def import_plugin_blueprints(
64
+ path: Annotated[
65
+ str,
66
+ typer.Argument(
67
+ help="Path to file or folder on local filesystem to import. Trailing '/' will result in the content being imported instead of the folder itself."
68
+ ),
69
+ ],
70
+ validate: Annotated[bool, typer.Option(help="if True, all entities uploaded will be validated.")] = True,
71
+ ):
72
+ """
73
+ Import blueprints from a plugin into the standard location 'system/Plugins/<plugin-name>'.
74
+ """
75
+ state.force = True
76
+ dmss_exception_wrapper(
77
+ import_entity, source=f"{path}/blueprints/", destination=f"system/Plugins/{Path(path).name}", validate=validate
78
+ )
79
+
80
+
81
+ @app.command("create-lookup")
82
+ def create_lookup(
83
+ name: Annotated[str, typer.Argument(help="Name of the lookup (application context) to create.")],
84
+ paths: Annotated[
85
+ List[Path],
86
+ typer.Argument(
87
+ help="one or more remote location for recursively looking for RecipeLinks. "
88
+ "A remote location is a path in DMSS on the format <dataSource>/<rootPackage>/<subPacakge>/..."
89
+ ),
90
+ ],
91
+ ):
92
+ """
93
+ Create a named Ui-/StorageRecipe-lookup-table from all RecipeLinks in a package existing in DMSS (requires admin privileges).
94
+ """
95
+ # TODO change type of paths argument to be list of str to avoid this path as strings conversion below
96
+ paths_as_strings = [str(path) for path in paths]
97
+ print(f"Creating lookup table from paths: {paths_as_strings}")
98
+ dmss_exception_wrapper(dmss_api.create_lookup, recipe_package=paths_as_strings, application=name)
99
+
100
+
101
+ @app.command("export")
102
+ def export_entity(
103
+ target: Annotated[str, typer.Argument(help="Address to the entity to export. Format: <dataSource>/<path>.")],
104
+ export_location: Annotated[
105
+ str,
106
+ typer.Argument(
107
+ help="Path on local filesystem to store the exported document(s) to. If not provided, will export to current folder."
108
+ ),
109
+ ] = "",
110
+ unpack: Annotated[
111
+ bool,
112
+ typer.Option(help="Whether or not to unpack the zip file containing the exported entities(s)."),
113
+ ] = False,
114
+ ):
115
+ """
116
+ Export one or more entities.
117
+
118
+ Example: if you have a data_source "DemoApplicationDataSource" with the root package "models" and a sub package "Windmill",
119
+ the entire "Windmill" package can be exported using: target = "DemoApplicationDataSource/models/Windmill".
120
+ The 'target' parameter can either point to a single entity or to a package.
121
+
122
+ If the file or folder to export already exists on local disk, an exception is raised.
123
+ """
124
+ response = dmss_exception_wrapper(export, target)
125
+
126
+ if not export_location:
127
+ export_location = os.getcwd()
128
+ with ZipFile(io.BytesIO(response.content), "r") as zip_file:
129
+ zip_file.filename = f"{target.split('/')[-1]}.zip"
130
+ if unpack:
131
+ unpack_and_save_zipfile(export_location=export_location, zip_file=zip_file)
132
+ else:
133
+ save_as_zip_file(export_location=export_location, filename=zip_file.filename, data=response.content)
134
+
135
+
136
+ @app.command("reset")
137
+ def reset(
138
+ path: Annotated[
139
+ str,
140
+ typer.Argument(help="The path on local file system to the data source you want to reset."),
141
+ ],
142
+ validate_entities: Annotated[
143
+ bool, typer.Option(help="if True, all entities uploaded to DMSS will be validated.")
144
+ ] = True,
145
+ resolve_local_ids: Annotated[
146
+ bool, typer.Option(help="if True, will resolve all local ids found in all entities")
147
+ ] = False,
148
+ ):
149
+ """
150
+ Reset all data sources (deletes and re-uploads all packages to DMSS).
151
+ """
152
+ # Check for presence of expected directories, 'data_sources' and 'data'
153
+ data_sources_dir, data_dir = get_app_dir_structure(Path(path))
154
+
155
+ data_source_definitions = get_json_files_in_dir(data_sources_dir)
156
+ if not data_source_definitions:
157
+ print(emoji.emojize(f"\t:warning: No data source definitions were found in '{data_sources_dir}'."))
158
+
159
+ for data_source_definition_filename in data_source_definitions:
160
+ data_source_name = data_source_definition_filename.replace(".json", "")
161
+
162
+ data_source_data_dir = data_dir.joinpath(data_source_name)
163
+ if not data_source_data_dir.is_dir():
164
+ print(
165
+ emoji.emojize(
166
+ f"\t:warning: No data source data directory was found by the name '{data_source_name}' in '{data_dir}'."
167
+ )
168
+ )
169
+ continue
170
+
171
+ dmss_exception_wrapper(
172
+ reset_data_source, data_source=data_source_name, path=path, resolve_local_ids=resolve_local_ids
173
+ )
174
+ data_source_contents = get_root_packages_in_data_sources(path)
175
+ if validate_entities:
176
+ dmss_exception_wrapper(validate_entities_in_data_sources, data_source_contents)
177
+
178
+
179
+ if __name__ == "__main__":
180
+ app()
File without changes
@@ -0,0 +1,176 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import emoji
5
+ import typer
6
+ from rich import print
7
+ from tenacity import (
8
+ retry,
9
+ retry_if_exception_type,
10
+ stop_after_attempt,
11
+ wait_random_exponential,
12
+ )
13
+ from typing_extensions import Annotated
14
+
15
+ from dm_cli.dmss import ApplicationException, dmss_api, dmss_exception_wrapper
16
+ from dm_cli.dmss_api.exceptions import ServiceException
17
+ from dm_cli.import_entity import import_folder_entity, remove_by_path_ignore_404
18
+ from dm_cli.utils.file_structure import get_app_dir_structure, get_json_files_in_dir
19
+ from dm_cli.utils.utils import (
20
+ get_root_packages_in_data_sources,
21
+ validate_entities_in_data_sources,
22
+ )
23
+
24
+ data_source_app = typer.Typer(help="Import and reset data sources")
25
+
26
+
27
+ @data_source_app.command("import", help="Import a single data source definition")
28
+ def import_data_source(
29
+ path: Annotated[Path, typer.Argument(help="Path on local filesystem to a data source JSON file.")],
30
+ ):
31
+ """
32
+ Import a single data source definition to DMSS.
33
+ """
34
+
35
+ @retry(
36
+ wait=wait_random_exponential(multiplier=1, max=60),
37
+ stop=stop_after_attempt(5),
38
+ reraise=True,
39
+ retry=retry_if_exception_type(ServiceException),
40
+ )
41
+ def retry_wrapper():
42
+ data_source_path = Path(path)
43
+ if not data_source_path.is_file():
44
+ raise FileNotFoundError(f"The path '{path}' is not a file.")
45
+
46
+ print(f"IMPORTING DATA SOURCE '{data_source_path.name}'")
47
+
48
+ # Read the data source definition
49
+ with open(data_source_path) as file:
50
+ document = json.load(file)
51
+ existing_data_sources = dmss_exception_wrapper(dmss_api.data_source_get_all)
52
+ if any(existing_document["name"] == document["name"] for existing_document in existing_data_sources):
53
+ print(f"WARNING: data source {document['name']} already exists. Updating existing data source.")
54
+
55
+ dmss_exception_wrapper(dmss_api.data_source_save, document["name"], document)
56
+ print(f"\tImported data source '{document['name']}' ✓")
57
+
58
+ retry_wrapper()
59
+
60
+
61
+ @data_source_app.command("import-all", help="Import all datasources found in the directory given by 'path'")
62
+ def import_all_data_sources(
63
+ path: Annotated[
64
+ Path, typer.Argument(help="Path on local filesystem to the folder containing the data sources to import.")
65
+ ],
66
+ ):
67
+ """
68
+ Import all data source definitions to DMSS.
69
+ """
70
+ data_sources_dir = Path(path)
71
+ if not data_sources_dir.is_dir():
72
+ raise FileNotFoundError(f"The path '{path}' is not a directory.")
73
+
74
+ print("IMPORTING DATA SOURCES")
75
+
76
+ # List all data source definitions under <data_sources_dir>
77
+ data_sources = get_json_files_in_dir(data_sources_dir)
78
+ if not data_sources:
79
+ print(emoji.emojize(f"\t:warning: No data source definitions were found in '{data_sources_dir}'"))
80
+
81
+ for filename in data_sources:
82
+ # Import the data source definition
83
+ filepath = data_sources_dir.joinpath(filename)
84
+ import_data_source(filepath)
85
+
86
+
87
+ @data_source_app.command("init")
88
+ def initialize_data_source(
89
+ path: Annotated[Path, typer.Argument(help="Path on local filesystem to data source folder.")],
90
+ validate_entities: Annotated[
91
+ bool, typer.Option(help="If True, all entities uploaded to DMSS will be validated.")
92
+ ] = True,
93
+ ):
94
+ """
95
+ Initialize the data sources and import all packages.
96
+ The remote packages in a data source will be deleted before new data sources are imported.
97
+ """
98
+ # Check for presence of expected directories, 'data_sources' and 'data'
99
+ data_sources_dir, data_dir = get_app_dir_structure(Path(path))
100
+
101
+ data_source_definitions = get_json_files_in_dir(data_sources_dir)
102
+ if not data_source_definitions:
103
+ print(emoji.emojize(f"\t:warning: No data source definitions were found in '{data_sources_dir}'."))
104
+ for data_source_definition_filename in data_source_definitions:
105
+ import_data_source_file(data_sources_dir, data_dir, data_source_definition_filename, False)
106
+ data_source_contents = get_root_packages_in_data_sources(path)
107
+ if validate_entities:
108
+ dmss_exception_wrapper(validate_entities_in_data_sources, data_source_contents)
109
+
110
+
111
+ def import_data_source_file(
112
+ data_sources_dir: str, data_dir: str, data_source_definition_filename: str, resolve_local_ids: bool
113
+ ):
114
+ data_source_definition_filepath = Path(data_sources_dir).joinpath(data_source_definition_filename)
115
+ data_source_name = data_source_definition_filename.replace(".json", "")
116
+
117
+ data_source_data_dir = data_dir / data_source_name
118
+ if not data_source_data_dir.is_dir():
119
+ print(
120
+ emoji.emojize(
121
+ f"\t:warning: No data source data directory was found by the name '{data_source_name}' in '{data_dir}'."
122
+ )
123
+ )
124
+ else:
125
+ import_data_source(data_source_definition_filepath)
126
+ with open(data_source_definition_filepath) as file:
127
+ data_source_document = json.load(file)
128
+ global_folders = data_source_document.get("global_folders", [])
129
+ root_packages = [f for f in data_source_data_dir.iterdir() if f.is_dir() and f.name not in global_folders]
130
+ # Remove existing root packages from the data source.
131
+ # This will also remove any files in the global folders that are references from files in the root packages.
132
+ for root_package in root_packages:
133
+ dmss_exception_wrapper(remove_by_path_ignore_404, f"/{data_source_name}/{root_package.name}")
134
+
135
+ # Import all root packages
136
+ for root_package in root_packages:
137
+ print(f"Importing PACKAGE '{data_source_data_dir / root_package.name}' --> '{data_source_name}'")
138
+ dmss_exception_wrapper(
139
+ import_folder_entity,
140
+ source_path=data_source_data_dir / root_package.name,
141
+ destination=data_source_name,
142
+ # Use the document raw endpoint,
143
+ # so that uploaded packages will not be resolved,
144
+ # this is to support uploading core blueprints.
145
+ raw_package_import=True,
146
+ resolve_local_ids=resolve_local_ids,
147
+ )
148
+
149
+
150
+ @data_source_app.command("reset")
151
+ def reset_data_source(
152
+ data_source: Annotated[str, typer.Argument(help="Name of data source to reset")],
153
+ path: Annotated[Path, typer.Argument(help="Path on local filesystem to data source folder.")],
154
+ resolve_local_ids: Annotated[bool, typer.Argument(help="Resolve local ids")] = False,
155
+ ):
156
+ """
157
+ Reset a single data source (deletes and re-uploads root-packages)
158
+ """
159
+ app_dir = Path(path)
160
+ if not app_dir.is_dir():
161
+ raise FileNotFoundError(f"The path '{path}' is not a directory.")
162
+
163
+ # Check for presence of expected directories, 'data_sources' and 'data'
164
+ data_sources_dir, data_dir = get_app_dir_structure(app_dir)
165
+
166
+ # Check for the data source definition file (json)
167
+ data_source_path = data_sources_dir.joinpath(f"{data_source}.json")
168
+ if not data_source_path.is_file():
169
+ raise FileNotFoundError(f"There is no data source definition for '{data_source}' in '{data_sources_dir}'.")
170
+ # Check for the data source data directory (contains packages)
171
+ data_source_data_dir = data_dir.joinpath(data_source)
172
+ if not data_source_data_dir.is_dir():
173
+ raise FileNotFoundError(f"There is no data source directory for '{data_source}' in '{data_dir}'.")
174
+
175
+ # Import all packages in the data source
176
+ import_data_source_file(data_sources_dir, data_dir, f"{data_source}.json", resolve_local_ids)