cmem-cmemc 24.2.0rc1__tar.gz → 24.3.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.
Files changed (56) hide show
  1. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/LICENSE +1 -1
  2. cmem_cmemc-24.3.0/PKG-INFO +89 -0
  3. cmem_cmemc-24.3.0/README-public.md +36 -0
  4. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/__init__.py +7 -12
  5. cmem_cmemc-24.3.0/cmem_cmemc/command.py +20 -0
  6. cmem_cmemc-24.3.0/cmem_cmemc/command_group.py +70 -0
  7. cmem_cmemc-24.3.0/cmem_cmemc/commands/__init__.py +1 -0
  8. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/acl.py +118 -62
  9. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/admin.py +46 -35
  10. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/client.py +2 -1
  11. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/config.py +3 -1
  12. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/dataset.py +27 -24
  13. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/graph.py +160 -19
  14. cmem_cmemc-24.3.0/cmem_cmemc/commands/metrics.py +340 -0
  15. cmem_cmemc-24.3.0/cmem_cmemc/commands/migration.py +267 -0
  16. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/project.py +62 -17
  17. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/python.py +56 -25
  18. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/query.py +23 -14
  19. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/resource.py +10 -2
  20. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/scheduler.py +10 -2
  21. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/store.py +118 -14
  22. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/user.py +8 -2
  23. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/validation.py +304 -113
  24. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/variable.py +10 -2
  25. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/vocabulary.py +48 -29
  26. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/workflow.py +86 -59
  27. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/commands/workspace.py +27 -8
  28. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/completion.py +190 -140
  29. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/constants.py +2 -0
  30. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/context.py +88 -42
  31. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/manual_helper/graph.py +1 -0
  32. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/manual_helper/multi_page.py +3 -1
  33. cmem_cmemc-24.3.0/cmem_cmemc/migrations/__init__.py +1 -0
  34. cmem_cmemc-24.3.0/cmem_cmemc/migrations/abc.py +84 -0
  35. cmem_cmemc-24.3.0/cmem_cmemc/migrations/access_conditions_243.py +122 -0
  36. cmem_cmemc-24.3.0/cmem_cmemc/migrations/bootstrap_data.py +28 -0
  37. cmem_cmemc-24.3.0/cmem_cmemc/migrations/shapes_widget_integrations_243.py +274 -0
  38. cmem_cmemc-24.3.0/cmem_cmemc/migrations/workspace_configurations.py +28 -0
  39. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/object_list.py +53 -22
  40. cmem_cmemc-24.3.0/cmem_cmemc/parameter_types/__init__.py +1 -0
  41. cmem_cmemc-24.3.0/cmem_cmemc/parameter_types/path.py +69 -0
  42. cmem_cmemc-24.3.0/cmem_cmemc/smart_path/__init__.py +94 -0
  43. cmem_cmemc-24.3.0/cmem_cmemc/smart_path/clients/__init__.py +63 -0
  44. cmem_cmemc-24.3.0/cmem_cmemc/smart_path/clients/http.py +65 -0
  45. cmem_cmemc-24.3.0/cmem_cmemc/string_processor.py +83 -0
  46. cmem_cmemc-24.3.0/cmem_cmemc/title_helper.py +41 -0
  47. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/utils.py +100 -45
  48. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/pyproject.toml +52 -31
  49. cmem_cmemc-24.2.0rc1/PKG-INFO +0 -69
  50. cmem_cmemc-24.2.0rc1/README-public.md +0 -23
  51. cmem_cmemc-24.2.0rc1/cmem_cmemc/commands/__init__.py +0 -82
  52. cmem_cmemc-24.2.0rc1/cmem_cmemc/commands/metrics.py +0 -224
  53. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/_cmemc.zsh +0 -0
  54. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/exceptions.py +0 -0
  55. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/manual_helper/__init__.py +0 -0
  56. {cmem_cmemc-24.2.0rc1 → cmem_cmemc-24.3.0}/cmem_cmemc/manual_helper/single_page.py +0 -0
@@ -199,4 +199,4 @@
199
199
  distributed under the License is distributed on an "AS IS" BASIS,
200
200
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
201
  See the License for the specific language governing permissions and
202
- limitations under the License.
202
+ limitations under the License.
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.1
2
+ Name: cmem-cmemc
3
+ Version: 24.3.0
4
+ Summary: Command line client for eccenca Corporate Memory
5
+ Home-page: https://eccenca.com/go/cmemc
6
+ License: Apache-2.0
7
+ Author: eccenca
8
+ Author-email: cmempy-developer@eccenca.com
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Customer Service
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Information Technology
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: License :: OSI Approved :: Apache Software License
18
+ Classifier: Natural Language :: English
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Topic :: Database
26
+ Classifier: Topic :: Software Development :: Testing
27
+ Classifier: Topic :: Utilities
28
+ Requires-Dist: beautifulsoup4 (>=4.12.3,<5.0.0)
29
+ Requires-Dist: certifi (>=2024.2.2)
30
+ Requires-Dist: click (>=8.1.7,<9.0.0)
31
+ Requires-Dist: click-didyoumean (>=0.3.1,<0.4.0)
32
+ Requires-Dist: click-help-colors (>=0.9.4,<0.10.0)
33
+ Requires-Dist: cmem-cmempy (==24.3.0)
34
+ Requires-Dist: configparser (>=6.0.1,<7.0.0)
35
+ Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
36
+ Requires-Dist: junit-xml (>=1.9,<2.0)
37
+ Requires-Dist: natsort (>=8.4.0,<9.0.0)
38
+ Requires-Dist: packaging (>=24.2,<25.0)
39
+ Requires-Dist: pip (>=24.3.1,<25.0.0)
40
+ Requires-Dist: prometheus-client (>=0.21.0,<0.22.0)
41
+ Requires-Dist: pygments (>=2.18.0,<3.0.0)
42
+ Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
43
+ Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
44
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
45
+ Requires-Dist: rich (>=13.9.1,<14.0.0)
46
+ Requires-Dist: six (>=1.16.0,<2.0.0)
47
+ Requires-Dist: smart-open (>=7.0.5,<8.0.0)
48
+ Requires-Dist: timeago (>=1.0.16,<2.0.0)
49
+ Requires-Dist: treelib (>=1.7.0,<2.0.0)
50
+ Requires-Dist: urllib3 (>=2.2.3,<3.0.0)
51
+ Description-Content-Type: text/markdown
52
+
53
+ # cmemc
54
+
55
+ cmemc is the official command line client for [eccenca Corporate Memory](https://documentation.eccenca.com/).
56
+
57
+ [![version][version-shield]][changelog] [![Python Versions][python-shield]][pypi] [![eccenca Corporate Memory][cmem-shield]][cmem]
58
+
59
+ [![teaser][teaser-image]][cmemc-docu]
60
+
61
+ ## Installation
62
+
63
+ In order to install the cmemc, run:
64
+
65
+ pipx install cmem-cmemc
66
+
67
+ Of course you can install cmemc also with pip, but we recommend [pipx](https://pypa.github.io/pipx/) for normal desktop usage.
68
+
69
+ ## Configuration and Usage
70
+
71
+ cmemc is intended for System Administrators and Linked Data Expert, who wants to automate and remote control activities on eccenca Corporate Memory.
72
+
73
+ The cmemc manual including basic usage pattern, configuration as well as a command reference is available at:
74
+
75
+ [https://eccenca.com/go/cmemc](https://eccenca.com/go/cmemc)
76
+
77
+ cmemc only works with Python 3 and refuses to work with Python 2.x.
78
+ In addition to that, cmemc will warn you in case an untested Python environment is used.
79
+
80
+
81
+ [version-shield]: https://badge.fury.io/py/cmem-cmemc.svg
82
+ [changelog]: https://pypi.org/project/cmem-cmemc/#history
83
+ [python-shield]: https://img.shields.io/pypi/pyversions/cmem-cmemc.svg
84
+ [pypi]: https://pypi.org/project/cmem-cmemc/
85
+ [cmem]: https://documentation.eccenca.com
86
+ [cmem-shield]: https://img.shields.io/badge/made%20for-eccenca%20Corporate%20Memory-blue?logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgaWQ9IkxheWVyXzEiCiAgIGRhdGEtbmFtZT0iTGF5ZXIgMSIKICAgdmlld0JveD0iMCAwIDgxLjI5MDAwMSA4Mi4yODk4NiIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iODEuMjkwMDAxIgogICBoZWlnaHQ9IjgyLjI4OTg2NCIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzODI2Ij4KICAgIDxzdHlsZQogICAgICAgaWQ9InN0eWxlODI0Ij4KICAgICAgLmNscy0xIHsKICAgICAgICBmaWxsOiAjZjM5MjAwOwogICAgICB9CgogICAgICAuY2xzLTIgewogICAgICAgIGZpbGw6IG5vbmU7CiAgICAgICAgc3Ryb2tlOiAjZjM5MjAwOwogICAgICAgIHN0cm9rZS13aWR0aDogMS41cHg7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDxnCiAgICAgaWQ9Imc4NDAiCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMwLjc2LC0zMS4xNDAxMzkpIj4KICAgIDxwYXRoCiAgICAgICBjbGFzcz0iY2xzLTEiCiAgICAgICBkPSJNIDU1LjksODUuMTkgQSAyMC4xNCwyMC4xNCAwIDEgMCAzNS43Niw2NS4wNSAyMC4xNCwyMC4xNCAwIDAgMCA1NS45LDg1LjE5IFoiCiAgICAgICBpZD0icGF0aDgyOCIgLz4KICAgIDxwYXRoCiAgICAgICBjbGFzcz0iY2xzLTEiCiAgICAgICBkPSJtIDk4LDU0LjE0IGEgOSw5IDAgMSAwIC04Ljk1LC05IDguOTUsOC45NSAwIDAgMCA4Ljk1LDkgeiIKICAgICAgIGlkPSJwYXRoODMwIiAvPgogICAgPHBhdGgKICAgICAgIGNsYXNzPSJjbHMtMSIKICAgICAgIGQ9Ik0gODguMzUsMTA4LjQzIEEgMTIuMzEsMTIuMzEgMCAxIDAgNzYsOTYuMTIgMTIuMzEsMTIuMzEgMCAwIDAgODguMzEsMTA4LjQzIFoiCiAgICAgICBpZD0icGF0aDgzMiIgLz4KICAgIDxsaW5lCiAgICAgICBjbGFzcz0iY2xzLTIiCiAgICAgICB4MT0iODYuOTcwMDAxIgogICAgICAgeTE9IjkyLjA1OTk5OCIKICAgICAgIHgyPSI1OC43Nzk5OTkiCiAgICAgICB5Mj0iNjcuMzYwMDAxIgogICAgICAgaWQ9ImxpbmU4MzQiIC8+CiAgICA8bGluZQogICAgICAgY2xhc3M9ImNscy0yIgogICAgICAgeDE9Ijk5LjE4IgogICAgICAgeTE9IjQ1Ljg0IgogICAgICAgeDI9IjU1LjQ4IgogICAgICAgeTI9IjY2LjEyMDAwMyIKICAgICAgIGlkPSJsaW5lODM2IiAvPgogICAgPGxpbmUKICAgICAgIGNsYXNzPSJjbHMtMiIKICAgICAgIHgxPSI5Ny45ODk5OTgiCiAgICAgICB5MT0iNDQuNjUwMDAyIgogICAgICAgeDI9Ijg4LjM0OTk5OCIKICAgICAgIHkyPSI5Mi44Mzk5OTYiCiAgICAgICBpZD0ibGluZTgzOCIgLz4KICA8L2c+Cjwvc3ZnPgo=
87
+ [teaser-image]: https://documentation.eccenca.com/24.1/automate/cmemc-command-line-interface/configuration/completion-setup/22.1-cmemc-create-dataset.gif
88
+ [cmemc-docu]: https://eccenca.com/go/cmemc
89
+
@@ -0,0 +1,36 @@
1
+ # cmemc
2
+
3
+ cmemc is the official command line client for [eccenca Corporate Memory](https://documentation.eccenca.com/).
4
+
5
+ [![version][version-shield]][changelog] [![Python Versions][python-shield]][pypi] [![eccenca Corporate Memory][cmem-shield]][cmem]
6
+
7
+ [![teaser][teaser-image]][cmemc-docu]
8
+
9
+ ## Installation
10
+
11
+ In order to install the cmemc, run:
12
+
13
+ pipx install cmem-cmemc
14
+
15
+ Of course you can install cmemc also with pip, but we recommend [pipx](https://pypa.github.io/pipx/) for normal desktop usage.
16
+
17
+ ## Configuration and Usage
18
+
19
+ cmemc is intended for System Administrators and Linked Data Expert, who wants to automate and remote control activities on eccenca Corporate Memory.
20
+
21
+ The cmemc manual including basic usage pattern, configuration as well as a command reference is available at:
22
+
23
+ [https://eccenca.com/go/cmemc](https://eccenca.com/go/cmemc)
24
+
25
+ cmemc only works with Python 3 and refuses to work with Python 2.x.
26
+ In addition to that, cmemc will warn you in case an untested Python environment is used.
27
+
28
+
29
+ [version-shield]: https://badge.fury.io/py/cmem-cmemc.svg
30
+ [changelog]: https://pypi.org/project/cmem-cmemc/#history
31
+ [python-shield]: https://img.shields.io/pypi/pyversions/cmem-cmemc.svg
32
+ [pypi]: https://pypi.org/project/cmem-cmemc/
33
+ [cmem]: https://documentation.eccenca.com
34
+ [cmem-shield]: https://img.shields.io/badge/made%20for-eccenca%20Corporate%20Memory-blue?logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgaWQ9IkxheWVyXzEiCiAgIGRhdGEtbmFtZT0iTGF5ZXIgMSIKICAgdmlld0JveD0iMCAwIDgxLjI5MDAwMSA4Mi4yODk4NiIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iODEuMjkwMDAxIgogICBoZWlnaHQ9IjgyLjI4OTg2NCIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzODI2Ij4KICAgIDxzdHlsZQogICAgICAgaWQ9InN0eWxlODI0Ij4KICAgICAgLmNscy0xIHsKICAgICAgICBmaWxsOiAjZjM5MjAwOwogICAgICB9CgogICAgICAuY2xzLTIgewogICAgICAgIGZpbGw6IG5vbmU7CiAgICAgICAgc3Ryb2tlOiAjZjM5MjAwOwogICAgICAgIHN0cm9rZS13aWR0aDogMS41cHg7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDxnCiAgICAgaWQ9Imc4NDAiCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMwLjc2LC0zMS4xNDAxMzkpIj4KICAgIDxwYXRoCiAgICAgICBjbGFzcz0iY2xzLTEiCiAgICAgICBkPSJNIDU1LjksODUuMTkgQSAyMC4xNCwyMC4xNCAwIDEgMCAzNS43Niw2NS4wNSAyMC4xNCwyMC4xNCAwIDAgMCA1NS45LDg1LjE5IFoiCiAgICAgICBpZD0icGF0aDgyOCIgLz4KICAgIDxwYXRoCiAgICAgICBjbGFzcz0iY2xzLTEiCiAgICAgICBkPSJtIDk4LDU0LjE0IGEgOSw5IDAgMSAwIC04Ljk1LC05IDguOTUsOC45NSAwIDAgMCA4Ljk1LDkgeiIKICAgICAgIGlkPSJwYXRoODMwIiAvPgogICAgPHBhdGgKICAgICAgIGNsYXNzPSJjbHMtMSIKICAgICAgIGQ9Ik0gODguMzUsMTA4LjQzIEEgMTIuMzEsMTIuMzEgMCAxIDAgNzYsOTYuMTIgMTIuMzEsMTIuMzEgMCAwIDAgODguMzEsMTA4LjQzIFoiCiAgICAgICBpZD0icGF0aDgzMiIgLz4KICAgIDxsaW5lCiAgICAgICBjbGFzcz0iY2xzLTIiCiAgICAgICB4MT0iODYuOTcwMDAxIgogICAgICAgeTE9IjkyLjA1OTk5OCIKICAgICAgIHgyPSI1OC43Nzk5OTkiCiAgICAgICB5Mj0iNjcuMzYwMDAxIgogICAgICAgaWQ9ImxpbmU4MzQiIC8+CiAgICA8bGluZQogICAgICAgY2xhc3M9ImNscy0yIgogICAgICAgeDE9Ijk5LjE4IgogICAgICAgeTE9IjQ1Ljg0IgogICAgICAgeDI9IjU1LjQ4IgogICAgICAgeTI9IjY2LjEyMDAwMyIKICAgICAgIGlkPSJsaW5lODM2IiAvPgogICAgPGxpbmUKICAgICAgIGNsYXNzPSJjbHMtMiIKICAgICAgIHgxPSI5Ny45ODk5OTgiCiAgICAgICB5MT0iNDQuNjUwMDAyIgogICAgICAgeDI9Ijg4LjM0OTk5OCIKICAgICAgIHkyPSI5Mi44Mzk5OTYiCiAgICAgICBpZD0ibGluZTgzOCIgLz4KICA8L2c+Cjwvc3ZnPgo=
35
+ [teaser-image]: https://documentation.eccenca.com/24.1/automate/cmemc-command-line-interface/configuration/completion-setup/22.1-cmemc-create-dataset.gif
36
+ [cmemc-docu]: https://eccenca.com/go/cmemc
@@ -1,4 +1,5 @@
1
1
  """The main command line interface."""
2
+
2
3
  import contextlib
3
4
  import os
4
5
  import sys
@@ -10,8 +11,8 @@ import click
10
11
  import requests.exceptions
11
12
 
12
13
  from cmem_cmemc import completion
14
+ from cmem_cmemc.command_group import CmemcGroup
13
15
  from cmem_cmemc.commands import (
14
- CmemcGroup,
15
16
  admin,
16
17
  config,
17
18
  dataset,
@@ -26,7 +27,7 @@ from cmem_cmemc.exceptions import InvalidConfigurationError
26
27
  from cmem_cmemc.manual_helper.graph import print_manual_graph
27
28
  from cmem_cmemc.manual_helper.multi_page import create_multi_page_documentation
28
29
  from cmem_cmemc.manual_helper.single_page import print_manual
29
- from cmem_cmemc.utils import extract_error_message, get_version
30
+ from cmem_cmemc.utils import check_python_version, extract_error_message, get_version
30
31
 
31
32
  CMEMC_VERSION = get_version()
32
33
 
@@ -38,16 +39,10 @@ if os.environ.get("_CMEMC_COMPLETE", "") == "zsh_source":
38
39
 
39
40
  version = sys.version_info
40
41
  PYTHON_VERSION = f"{version.major}.{version.minor}.{version.micro}"
41
- PYTHON_EXPECTED = "3.11"
42
- PYTHON_GOT = f"{version.major}.{version.minor}"
43
- if PYTHON_EXPECTED != PYTHON_GOT and not CONTEXT.is_completing():
44
- CONTEXT.echo_warning(
45
- "Warning: You are running cmemc under a non-tested python "
46
- f"environment (expected {PYTHON_EXPECTED}, got {PYTHON_GOT})"
47
- )
42
+ check_python_version(ctx=CONTEXT)
48
43
 
49
44
  # set the user-agent environment for the http request headers
50
- os.environ["CMEM_USER_AGENT"] = f"cmemc/{CMEMC_VERSION} " f"(Python {PYTHON_VERSION})"
45
+ os.environ["CMEM_USER_AGENT"] = f"cmemc/{CMEMC_VERSION} (Python {PYTHON_VERSION})"
51
46
 
52
47
  # https://github.com/pallets/click/blob/master/examples/complex/complex/cli.py
53
48
  CONTEXT_SETTINGS = {"auto_envvar_prefix": "CMEMC", "help_option_names": ["-h", "--help"]}
@@ -123,11 +118,11 @@ def cli(
123
118
  ctx.obj.set_config_file(config_file)
124
119
  try:
125
120
  ctx.obj.set_connection(connection)
126
- except InvalidConfigurationError as error:
121
+ except InvalidConfigurationError:
127
122
  # if config is broken still allow for "config edit"
128
123
  # means: do not forward this exception if "config edit"
129
124
  if " ".join(sys.argv).find("config edit") == -1:
130
- raise ValueError("Broken config") from error
125
+ raise
131
126
 
132
127
 
133
128
  cli.add_command(admin.admin)
@@ -0,0 +1,20 @@
1
+ """cmemc Click Command"""
2
+
3
+ from click_help_colors import HelpColorsCommand
4
+
5
+
6
+ class CmemcCommand(HelpColorsCommand):
7
+ """Wrapper click.Command class to have a single extension point.
8
+
9
+ Currently, wrapped click extensions and additional group features:#
10
+ - click-help-colors: https://github.com/click-contrib/click-help-colors
11
+ """
12
+
13
+ color_for_headers = "yellow"
14
+ color_for_options = "green"
15
+
16
+ def __init__(self, *args, **kwargs): # noqa: ANN002, ANN003
17
+ """Init a cmemc group command."""
18
+ kwargs.setdefault("help_headers_color", self.color_for_headers)
19
+ kwargs.setdefault("help_options_color", self.color_for_options)
20
+ super().__init__(*args, **kwargs)
@@ -0,0 +1,70 @@
1
+ """cmemc Click Command Group"""
2
+
3
+ from click_didyoumean import DYMGroup
4
+ from click_help_colors import HelpColorsGroup
5
+
6
+
7
+ class CmemcGroup(HelpColorsGroup, DYMGroup):
8
+ """Wrapper click.Group class to have a single extension point.
9
+
10
+ Currently, wrapped click extensions and additional group features:#
11
+ - click-help-colors: https://github.com/click-contrib/click-help-colors
12
+ """
13
+
14
+ color_for_command_groups = "white"
15
+ color_for_writing_commands = "red"
16
+ color_for_headers = "yellow"
17
+ color_for_options = "green"
18
+
19
+ def __init__(self, *args, **kwargs): # noqa: ANN002, ANN003
20
+ """Init a cmemc group command group."""
21
+ # set default colors
22
+ kwargs.setdefault("help_headers_color", self.color_for_headers)
23
+ kwargs.setdefault("help_options_color", self.color_for_options)
24
+ kwargs.setdefault(
25
+ "help_options_custom_colors",
26
+ {
27
+ "bootstrap": self.color_for_writing_commands,
28
+ "showcase": self.color_for_writing_commands,
29
+ "delete": self.color_for_writing_commands,
30
+ "password": self.color_for_writing_commands,
31
+ "secret": self.color_for_writing_commands,
32
+ "upload": self.color_for_writing_commands,
33
+ "import": self.color_for_writing_commands,
34
+ "create": self.color_for_writing_commands,
35
+ "enable": self.color_for_writing_commands,
36
+ "disable": self.color_for_writing_commands,
37
+ "execute": self.color_for_writing_commands,
38
+ "replay": self.color_for_writing_commands,
39
+ "io": self.color_for_writing_commands,
40
+ "install": self.color_for_writing_commands,
41
+ "uninstall": self.color_for_writing_commands,
42
+ "reload": self.color_for_writing_commands,
43
+ "update": self.color_for_writing_commands,
44
+ "eval": self.color_for_writing_commands,
45
+ "cancel": self.color_for_writing_commands,
46
+ "admin": self.color_for_command_groups,
47
+ "user": self.color_for_command_groups,
48
+ "store": self.color_for_command_groups,
49
+ "metrics": self.color_for_command_groups,
50
+ "config": self.color_for_command_groups,
51
+ "dataset": self.color_for_command_groups,
52
+ "graph": self.color_for_command_groups,
53
+ "project": self.color_for_command_groups,
54
+ "query": self.color_for_command_groups,
55
+ "scheduler": self.color_for_command_groups,
56
+ "vocabulary": self.color_for_command_groups,
57
+ "workflow": self.color_for_command_groups,
58
+ "workspace": self.color_for_command_groups,
59
+ "python": self.color_for_command_groups,
60
+ "cache": self.color_for_command_groups,
61
+ "resource": self.color_for_command_groups,
62
+ "acl": self.color_for_command_groups,
63
+ "client": self.color_for_command_groups,
64
+ "variable": self.color_for_command_groups,
65
+ "validation": self.color_for_command_groups,
66
+ "migrate": self.color_for_writing_commands,
67
+ "migrations": self.color_for_command_groups,
68
+ },
69
+ )
70
+ super().__init__(*args, **kwargs)
@@ -0,0 +1 @@
1
+ """cmemc commands."""
@@ -2,7 +2,7 @@
2
2
 
3
3
  import click
4
4
  import requests.exceptions
5
- from click import Option, UsageError
5
+ from click import Option
6
6
  from cmem.cmempy.dp.authorization.conditions import (
7
7
  create_access_condition,
8
8
  delete_access_condition,
@@ -14,25 +14,36 @@ from cmem.cmempy.dp.authorization.conditions import (
14
14
  from cmem.cmempy.keycloak.user import get_user_by_username, user_groups
15
15
 
16
16
  from cmem_cmemc import completion
17
- from cmem_cmemc.commands import CmemcCommand, CmemcGroup
18
- from cmem_cmemc.constants import NS_ACL, NS_GROUP, NS_USER
17
+ from cmem_cmemc.command import CmemcCommand
18
+ from cmem_cmemc.command_group import CmemcGroup
19
+ from cmem_cmemc.constants import NS_ACL, NS_ACTION, NS_GROUP, NS_USER
19
20
  from cmem_cmemc.context import ApplicationContext
20
- from cmem_cmemc.utils import convert_iri_to_qname, convert_qname_to_iri, struct_to_table
21
+ from cmem_cmemc.utils import (
22
+ convert_iri_to_qname,
23
+ convert_qname_to_iri,
24
+ get_query_text,
25
+ struct_to_table,
26
+ )
21
27
 
22
28
  # option descriptions
23
29
  HELP_TEXTS = {
24
- "name": "A short name or label.",
30
+ "name": "A optional name.",
25
31
  "id": "An optional ID (will be an UUID otherwise).",
26
32
  "description": "An optional description.",
27
33
  "user": "A specific user account required by the access condition.",
28
- "group": "A membership in a user group required by the access condition",
34
+ "group": "A membership in a user group required by the access condition.",
29
35
  "read_graph": "Grants read access to a graph.",
30
36
  "write_graph": "Grants write access to a graph (includes read access).",
31
37
  "action": "Grants usage permissions to an action / functionality.",
38
+ "query": "Dynamic access condition query (file or the query catalog IRI).",
32
39
  }
33
40
 
34
- PUBLIC_USER_URI = "urn:elds-backend-anonymous-user"
35
- PUBLIC_GROUP_URI = "urn:elds-backend-public-group"
41
+ WARNING_UNKNOWN_USER = "Unknown User or no access to get user info."
42
+ WARNING_NO_GROUP_ACCESS = "You do not have the permission to retrieve user groups"
43
+ WARNING_USE_GROUP = "Use the --group option to assign groups manually (what-if-scenario)."
44
+
45
+ PUBLIC_USER_URI = "https://vocab.eccenca.com/auth/AnonymousUser"
46
+ PUBLIC_GROUP_URI = "https://vocab.eccenca.com/auth/PublicGroup"
36
47
 
37
48
  KNOWN_ACCESS_CONDITION_URLS = [PUBLIC_USER_URI, PUBLIC_GROUP_URI]
38
49
 
@@ -55,12 +66,10 @@ def _value_to_acl_url(
55
66
 
56
67
  or not, if it is already a known URI .... or None
57
68
  """
58
- if value in KNOWN_ACCESS_CONDITION_URLS:
69
+ if value == "" or value is None:
70
+ return value
71
+ if value.startswith(("http://", "https://")):
59
72
  return value
60
- if value == "":
61
- return ""
62
- if value is None:
63
- return None
64
73
  match param.name:
65
74
  case "groups":
66
75
  return f"{NS_GROUP}{value}"
@@ -69,8 +78,10 @@ def _value_to_acl_url(
69
78
  return f"{NS_ACL}{value}"
70
79
 
71
80
 
72
- def generate_acl_name(user: str | None, groups: list[str]) -> str:
81
+ def generate_acl_name(user: str | None, groups: list[str], query: str | None) -> str:
73
82
  """Create an access condition name based on user and group assignments."""
83
+ if query is not None:
84
+ return "Query based Dynamic Access Condition"
74
85
  if len(groups) > 0:
75
86
  group_term = "groups" if len(groups) > 1 else "group"
76
87
  groups_labels = ", ".join(
@@ -113,7 +124,13 @@ def list_command(app: ApplicationContext, raw: bool, id_only: bool) -> None:
113
124
  (convert_iri_to_qname(iri=_.get("iri"), default_ns=NS_ACL), _.get("name", "-"))
114
125
  for _ in acls
115
126
  ]
116
- app.echo_info_table(table, headers=["URI", "Name"], sort_column=0)
127
+ app.echo_info_table(
128
+ table,
129
+ headers=["URI", "Name"],
130
+ sort_column=0,
131
+ empty_table_message="No access conditions found. "
132
+ "Use the `admin acl create` command to create a new access condition.",
133
+ )
117
134
 
118
135
 
119
136
  @click.command(cls=CmemcCommand, name="inspect")
@@ -137,24 +154,6 @@ def inspect_command(app: ApplicationContext, access_condition_id: str, raw: bool
137
154
 
138
155
 
139
156
  @click.command(cls=CmemcCommand, name="create")
140
- @click.option(
141
- "--name",
142
- "name",
143
- type=click.STRING,
144
- help=HELP_TEXTS["name"],
145
- )
146
- @click.option(
147
- "--id",
148
- "id_",
149
- type=click.STRING,
150
- help=HELP_TEXTS["id"],
151
- )
152
- @click.option(
153
- "--description",
154
- "description",
155
- type=click.STRING,
156
- help=HELP_TEXTS["description"],
157
- )
158
157
  @click.option(
159
158
  "--user",
160
159
  type=click.STRING,
@@ -195,6 +194,31 @@ def inspect_command(app: ApplicationContext, access_condition_id: str, raw: bool
195
194
  shell_complete=completion.acl_actions,
196
195
  help=HELP_TEXTS["action"],
197
196
  )
197
+ @click.option(
198
+ "--query",
199
+ "query",
200
+ type=click.STRING,
201
+ shell_complete=completion.remote_queries_and_sparql_files,
202
+ help=HELP_TEXTS["query"],
203
+ )
204
+ @click.option(
205
+ "--id",
206
+ "id_",
207
+ type=click.STRING,
208
+ help=HELP_TEXTS["id"],
209
+ )
210
+ @click.option(
211
+ "--name",
212
+ "name",
213
+ type=click.STRING,
214
+ help=HELP_TEXTS["name"],
215
+ )
216
+ @click.option(
217
+ "--description",
218
+ "description",
219
+ type=click.STRING,
220
+ help=HELP_TEXTS["description"],
221
+ )
198
222
  @click.pass_obj
199
223
  # pylint: disable-msg=too-many-arguments
200
224
  def create_command( # noqa: PLR0913
@@ -207,20 +231,43 @@ def create_command( # noqa: PLR0913
207
231
  read_graphs: tuple[str],
208
232
  write_graphs: tuple[str],
209
233
  actions: tuple[str],
234
+ query: str,
210
235
  ) -> None:
211
- """Create an access condition."""
212
- if not read_graphs and not write_graphs and not actions:
236
+ """Create an access condition.
237
+
238
+ With this command, new access conditions can be created.
239
+
240
+ An access condition captures information about WHO gets access to WHAT.
241
+ In order to specify WHO gets access, use the `--user` and / or `--group` options.
242
+ In order to specify WHAT an account get access to, use the `--read-graph`,
243
+ `--write-graph` and `--action` options.`
244
+
245
+ In addition to that, you can specify a name, a description and an ID (all optional).
246
+
247
+ A special case are dynamic access conditions, based on a SPARQL query: Here you
248
+ have to provide a query with the projection variables `user`, `group` `readGraph`
249
+ and `writeGraph` to create multiple grants at once. You can either provide a query file
250
+ or a query URL from the query catalog.
251
+
252
+ Note: Queries for dynamic access conditions are copied into the ACL, so changing the
253
+ query in the query catalog does not change it in the access condition.
254
+
255
+ Example: cmemc admin acl create --group local-users --write-graph https://example.org/
256
+ """
257
+ if not read_graphs and not write_graphs and not actions and not query:
213
258
  raise click.UsageError(
214
259
  "Missing access / usage grant. Use at least one of the following options: "
215
- "--read-graph, --write-graph or --action."
216
- )
217
- if not user and not groups:
218
- app.echo_warning(
219
- "Access conditions without a user and without a group assignment " "affect ALL users."
260
+ "--read-graph, --write-graph, --action or --query."
220
261
  )
262
+ query_str = None
263
+ if query:
264
+ query_str = get_query_text(query, {"user", "group", "readGraph", "writeGraph"})
265
+
266
+ if not user and not groups and not query:
267
+ app.echo_warning("Access conditions without a user or group assignment affect ALL users.")
221
268
 
222
269
  if not name:
223
- name = generate_acl_name(user=user, groups=groups)
270
+ name = generate_acl_name(user=user, groups=groups, query=query)
224
271
 
225
272
  if not description:
226
273
  description = "This access condition was created with cmemc."
@@ -229,6 +276,7 @@ def create_command( # noqa: PLR0913
229
276
  f"Creating access condition '{name}' ... ",
230
277
  nl=False,
231
278
  )
279
+
232
280
  create_access_condition(
233
281
  name=name,
234
282
  static_id=id_,
@@ -237,7 +285,8 @@ def create_command( # noqa: PLR0913
237
285
  groups=groups,
238
286
  read_graphs=list(read_graphs),
239
287
  write_graphs=list(write_graphs),
240
- actions=list(actions),
288
+ actions=[convert_qname_to_iri(qname=_, default_ns=NS_ACTION) for _ in actions],
289
+ query=query_str,
241
290
  )
242
291
  app.echo_success("done")
243
292
 
@@ -302,6 +351,13 @@ def create_command( # noqa: PLR0913
302
351
  shell_complete=completion.acl_actions,
303
352
  help=HELP_TEXTS["action"],
304
353
  )
354
+ @click.option(
355
+ "--query",
356
+ "query",
357
+ type=click.STRING,
358
+ shell_complete=completion.remote_queries_and_sparql_files,
359
+ help=HELP_TEXTS["query"],
360
+ )
305
361
  @click.pass_obj
306
362
  # pylint: disable-msg=too-many-arguments
307
363
  def update_command( # noqa: PLR0913
@@ -314,6 +370,7 @@ def update_command( # noqa: PLR0913
314
370
  read_graphs: tuple[str],
315
371
  write_graphs: tuple[str],
316
372
  actions: tuple[str],
373
+ query: str,
317
374
  ) -> None:
318
375
  """Update an access condition.
319
376
 
@@ -326,6 +383,9 @@ def update_command( # noqa: PLR0913
326
383
  f"Updating access condition {payload['name']} ... ",
327
384
  nl=False,
328
385
  )
386
+ query_str = None
387
+ if query:
388
+ query_str = get_query_text(query, {"user", "group", "readGraph", "writeGraph"})
329
389
 
330
390
  update_access_condition(
331
391
  iri=iri,
@@ -335,7 +395,8 @@ def update_command( # noqa: PLR0913
335
395
  groups=groups,
336
396
  read_graphs=read_graphs,
337
397
  write_graphs=write_graphs,
338
- actions=actions,
398
+ actions=[convert_qname_to_iri(qname=_, default_ns=NS_ACTION) for _ in actions],
399
+ query=query_str,
339
400
  )
340
401
  app.echo_success("done")
341
402
 
@@ -394,10 +455,9 @@ def review_command(app: ApplicationContext, raw: bool, user: str, groups: list[s
394
455
  """Review grants for a given account.
395
456
 
396
457
  This command has two working modes: (1) You can review the access conditions
397
- of an actual account - this needs access to keycloak and the access condition API,
458
+ of an actual account,
398
459
  (2) You can review the access conditions of an imaginary account with a set of
399
- freely added groups (what-if-scenario) - this only needs access to the access
400
- condition API.
460
+ freely added groups (what-if-scenario).
401
461
 
402
462
  The output of the command is a list of grants the account has based on your input
403
463
  and all access conditions loaded in the store. In addition to that, some metadata
@@ -407,23 +467,19 @@ def review_command(app: ApplicationContext, raw: bool, user: str, groups: list[s
407
467
  app.echo_debug("Trying to fetch groups from keycloak.")
408
468
  keycloak_user = get_user_by_username(username=user)
409
469
  if not keycloak_user:
410
- raise UsageError(
411
- "Unknown User or no access to get user info.\n"
412
- "Use the --group option to assign groups manually (what-if-scenario)."
413
- )
414
- try:
415
- keycloak_user_groups = user_groups(user_id=keycloak_user[0]["id"])
416
- groups = [f"{NS_GROUP}{_['name']}" for _ in keycloak_user_groups]
417
- except requests.exceptions.HTTPError as error:
418
- raise UsageError(
419
- f"You do not have the permission to retrieve the groups for user {user}"
420
- " from Keycloak.\n"
421
- "Use the --group option to assign groups manually (what-if-scenario)."
422
- ) from error
470
+ if user != PUBLIC_USER_URI:
471
+ app.echo_warning(WARNING_UNKNOWN_USER)
472
+ app.echo_warning(WARNING_USE_GROUP)
473
+ else:
474
+ try:
475
+ keycloak_user_groups = user_groups(user_id=keycloak_user[0]["id"])
476
+ groups = [f"{NS_GROUP}{_['name']}" for _ in keycloak_user_groups]
477
+ except (requests.exceptions.HTTPError, IndexError):
478
+ app.echo_warning(WARNING_NO_GROUP_ACCESS)
479
+ app.echo_warning(WARNING_USE_GROUP)
423
480
  app.echo_debug(f"Got groups: {groups}")
424
- review_info: dict = review_graph_rights(
425
- account_iri=f"{NS_USER}{user}", group_iris=groups
426
- ).json()
481
+ account_iri = f"{NS_USER}{user}" if user != PUBLIC_USER_URI else PUBLIC_USER_URI
482
+ review_info: dict = review_graph_rights(account_iri=account_iri, group_iris=groups).json()
427
483
  review_info["groupIri"] = groups
428
484
  if raw:
429
485
  app.echo_info_json(review_info)