rudi-node-write 1.2.1__tar.gz → 1.2.2__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 (48) hide show
  1. {rudi_node_write-1.2.1/src/rudi_node_write.egg-info → rudi_node_write-1.2.2}/PKG-INFO +24 -1
  2. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/pyproject.toml +5 -4
  3. rudi_node_write-1.2.2/requirements-dev.txt +102 -0
  4. rudi_node_write-1.2.2/requirements.txt +6 -0
  5. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/utils/list_utils.py +13 -13
  6. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2/src/rudi_node_write.egg-info}/PKG-INFO +24 -1
  7. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write.egg-info/SOURCES.txt +2 -3
  8. rudi_node_write-1.2.2/src/rudi_node_write.egg-info/requires.txt +24 -0
  9. rudi_node_write-1.2.1/requirements.txt +0 -0
  10. rudi_node_write-1.2.1/src/rudi_node_write/wip/clean_node.py +0 -50
  11. rudi_node_write-1.2.1/src/rudi_node_write/wip/federation_backup.py +0 -145
  12. rudi_node_write-1.2.1/src/rudi_node_write/wip/rudinode_federation.py +0 -412
  13. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/LICENCE.md +0 -0
  14. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/README.md +0 -0
  15. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/setup.cfg +0 -0
  16. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/__init__.py +0 -0
  17. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/conf/meta_defaults.py +0 -0
  18. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/connectors/io_connector.py +0 -0
  19. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/connectors/io_rudi_api_write.py +0 -0
  20. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/connectors/io_rudi_jwt_factory.py +0 -0
  21. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/connectors/io_rudi_manager_write.py +0 -0
  22. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/connectors/io_rudi_media_write.py +0 -0
  23. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/connectors/rudi_node_auth.py +0 -0
  24. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/rudi_node_writer.py +0 -0
  25. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/rudi_types/rudi_const.py +0 -0
  26. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/rudi_types/rudi_contact.py +0 -0
  27. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/rudi_types/rudi_dates.py +0 -0
  28. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/rudi_types/rudi_dictionary_entry.py +0 -0
  29. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/rudi_types/rudi_geo.py +0 -0
  30. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/rudi_types/rudi_licence.py +0 -0
  31. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/rudi_types/rudi_media.py +0 -0
  32. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/rudi_types/rudi_meta.py +0 -0
  33. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/rudi_types/rudi_meta_misc.py +0 -0
  34. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/rudi_types/rudi_org.py +0 -0
  35. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/rudi_types/serializable.py +0 -0
  36. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/utils/dict_utils.py +0 -0
  37. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/utils/err.py +0 -0
  38. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/utils/file_utils.py +0 -0
  39. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/utils/html_utils.py +0 -0
  40. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/utils/jwt.py +0 -0
  41. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/utils/log.py +0 -0
  42. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/utils/str_utils.py +0 -0
  43. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/utils/type_date.py +0 -0
  44. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/utils/typing_utils.py +0 -0
  45. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write/utils/url_utils.py +0 -0
  46. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write.egg-info/dependency_links.txt +0 -0
  47. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/src/rudi_node_write.egg-info/top_level.txt +0 -0
  48. {rudi_node_write-1.2.1 → rudi_node_write-1.2.2}/tests/test_rudi_node_write.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rudi-node-write
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: Use the internal API of a RUDI Producer node
5
5
  Author-email: Olivier Martineau <olivier.martineau@irisa.fr>
6
6
  Maintainer-email: Olivier Martineau <olivier.martineau@irisa.fr>
@@ -17,6 +17,29 @@ Classifier: License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1
17
17
  Requires-Python: >=3.11
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENCE.md
20
+ Requires-Dist: beautifulsoup4==4.12.3
21
+ Requires-Dist: chardet==5.2.0
22
+ Requires-Dist: deepdiff==8.0.1
23
+ Requires-Dist: puremagic==1.28
24
+ Provides-Extra: dev
25
+ Requires-Dist: black==24.10.0; extra == "dev"
26
+ Requires-Dist: build==1.2.2.post1; extra == "dev"
27
+ Requires-Dist: commitizen==3.29.1; extra == "dev"
28
+ Requires-Dist: importlib-resources==6.4.5; extra == "dev"
29
+ Requires-Dist: inflect==7.3.1; extra == "dev"
30
+ Requires-Dist: ipykernel==6.29.5; extra == "dev"
31
+ Requires-Dist: jaraco.collections==5.1.0; extra == "dev"
32
+ Requires-Dist: nbstripout==0.7.1; extra == "dev"
33
+ Requires-Dist: ordered-set==4.1.0; extra == "dev"
34
+ Requires-Dist: pip-autoremove==0.10.0; extra == "dev"
35
+ Requires-Dist: pip-chill==1.0.3; extra == "dev"
36
+ Requires-Dist: pip-upgrade-outdated==1.5; extra == "dev"
37
+ Requires-Dist: pip3-autoremove==1.2.2; extra == "dev"
38
+ Requires-Dist: pre-commit==4.0.1; extra == "dev"
39
+ Requires-Dist: pyright==1.1.386; extra == "dev"
40
+ Requires-Dist: pytest-cov==5.0.0; extra == "dev"
41
+ Requires-Dist: tomli==2.0.2; extra == "dev"
42
+ Requires-Dist: twine==5.1.1; extra == "dev"
20
43
 
21
44
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
22
45
  [![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rudi-node-write"
7
- version = "1.2.1"
7
+ version = "1.2.2"
8
8
  authors = [{ name = "Olivier Martineau", email = "olivier.martineau@irisa.fr" }]
9
9
  maintainers = [{ name = "Olivier Martineau", email = "olivier.martineau@irisa.fr" }]
10
10
  description = "Use the internal API of a RUDI Producer node"
@@ -18,8 +18,8 @@ classifiers = ["Programming Language :: Python :: 3.11",
18
18
  "License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)",
19
19
  ]
20
20
  keywords = ["rudi-node-write", "rudi-node-put", "RUDI", "producer node", "RUDI node", "open-data", "Univ. Rennes"]
21
- dynamic = ["dependencies"]
22
- # dependencies = ["beautifulsoup4", "chardet", "deepdiff", "defusedxml", "puremagic"]
21
+ dynamic = ["dependencies", "optional-dependencies"]
22
+ # dependencies = ["beautifulsoup4", "chardet", "deepdiff", "puremagic"]
23
23
 
24
24
  [project.urls]
25
25
  Homepage = "https://github.com/OlivierMartineau/rudi-node-write"
@@ -34,7 +34,7 @@ target-version = ['py311']
34
34
  # ----- Tool: commitizen
35
35
  [tool.commitizen]
36
36
  name = "cz_conventional_commits"
37
- version = "1.2.1"
37
+ version = "1.2.2"
38
38
  version_files = ["pyproject.toml:version"]
39
39
 
40
40
  # ----- Tool: pytest
@@ -51,6 +51,7 @@ include = ["rudi_node_write*"]
51
51
 
52
52
  [tool.setuptools.dynamic]
53
53
  dependencies = {file = ["requirements.txt"]}
54
+ optional-dependencies = {dev = { file = ["requirements-dev.txt"] }}
54
55
 
55
56
  # ----- Tool: pyright
56
57
  [tool.pyright]
@@ -0,0 +1,102 @@
1
+ black==24.10.0
2
+ build==1.2.2.post1
3
+ commitizen==3.29.1
4
+ importlib-resources==6.4.5
5
+ inflect==7.3.1
6
+ ipykernel==6.29.5
7
+ jaraco.collections==5.1.0
8
+ nbstripout==0.7.1
9
+ ordered-set==4.1.0
10
+ pip-autoremove==0.10.0
11
+ pip-chill==1.0.3
12
+ pip-upgrade-outdated==1.5
13
+ pip3-autoremove==1.2.2
14
+ pre-commit==4.0.1
15
+ pyright==1.1.386
16
+ pytest-cov==5.0.0
17
+ tomli==2.0.2
18
+ twine==5.1.1
19
+ # appnope==0.1.4 # Installed as dependency for ipykernel
20
+ # argcomplete==3.5.1 # Installed as dependency for commitizen
21
+ # asttokens==2.4.1 # Installed as dependency for stack-data
22
+ # attrs==24.2.0 # Installed as dependency for jsonschema, referencing
23
+ # autocommand==2.2.2 # Installed as dependency for jaraco.text
24
+ # backports.tarfile==1.2.0 # Installed as dependency for jaraco.context
25
+ # certifi==2024.8.30 # Installed as dependency for requests
26
+ # cfgv==3.4.0 # Installed as dependency for pre-commit
27
+ # charset-normalizer==3.4.0 # Installed as dependency for commitizen, requests
28
+ # click==8.1.7 # Installed as dependency for black
29
+ # colorama==0.4.6 # Installed as dependency for commitizen
30
+ # comm==0.2.2 # Installed as dependency for ipykernel
31
+ # coverage==7.6.4 # Installed as dependency for pytest-cov
32
+ # debugpy==1.8.7 # Installed as dependency for ipykernel
33
+ # decli==0.6.2 # Installed as dependency for commitizen
34
+ # decorator==5.1.1 # Installed as dependency for ipython
35
+ # distlib==0.3.9 # Installed as dependency for virtualenv
36
+ # docutils==0.21.2 # Installed as dependency for readme-renderer
37
+ # executing==2.1.0 # Installed as dependency for stack-data
38
+ # fastjsonschema==2.20.0 # Installed as dependency for nbformat
39
+ # filelock==3.16.1 # Installed as dependency for virtualenv
40
+ # identify==2.6.1 # Installed as dependency for pre-commit
41
+ # idna==3.10 # Installed as dependency for requests
42
+ # importlib-metadata==8.5.0 # Installed as dependency for keyring, twine
43
+ # iniconfig==2.0.0 # Installed as dependency for pytest
44
+ # ipython==8.28.0 # Installed as dependency for ipykernel
45
+ # jaraco.classes==3.4.0 # Installed as dependency for keyring
46
+ # jaraco.context==6.0.1 # Installed as dependency for jaraco.text, keyring
47
+ # jaraco.functools==4.1.0 # Installed as dependency for jaraco.text, keyring
48
+ # jaraco.text==4.0.0 # Installed as dependency for jaraco.collections
49
+ # jedi==0.19.1 # Installed as dependency for ipython
50
+ # jinja2==3.1.4 # Installed as dependency for commitizen
51
+ # jsonschema==4.23.0 # Installed as dependency for nbformat
52
+ # jsonschema-specifications==2024.10.1 # Installed as dependency for jsonschema
53
+ # jupyter-client==8.6.3 # Installed as dependency for ipykernel
54
+ # jupyter-core==5.7.2 # Installed as dependency for ipykernel, jupyter-client, nbformat
55
+ # keyring==25.4.1 # Installed as dependency for twine
56
+ # markdown-it-py==3.0.0 # Installed as dependency for rich
57
+ # markupsafe==3.0.2 # Installed as dependency for jinja2
58
+ # matplotlib-inline==0.1.7 # Installed as dependency for ipykernel, ipython
59
+ # mdurl==0.1.2 # Installed as dependency for markdown-it-py
60
+ # more-itertools==10.5.0 # Installed as dependency for inflect, jaraco.classes, jaraco.functools, jaraco.text
61
+ # mypy-extensions==1.0.0 # Installed as dependency for black
62
+ # nbformat==5.10.4 # Installed as dependency for nbstripout
63
+ # nest-asyncio==1.6.0 # Installed as dependency for ipykernel
64
+ # nh3==0.2.18 # Installed as dependency for readme-renderer
65
+ # nodeenv==1.9.1 # Installed as dependency for pre-commit, pyright
66
+ # packaging==24.1 # Installed as dependency for black, build, commitizen, ipykernel, pytest
67
+ # parso==0.8.4 # Installed as dependency for jedi
68
+ # pathspec==0.12.1 # Installed as dependency for black
69
+ # pexpect==4.9.0 # Installed as dependency for ipython
70
+ # pkginfo==1.11.2 # Installed as dependency for twine
71
+ # platformdirs==4.3.6 # Installed as dependency for black, jupyter-core, virtualenv
72
+ # pluggy==1.5.0 # Installed as dependency for pytest
73
+ # prompt-toolkit==3.0.48 # Installed as dependency for ipython, questionary
74
+ # psutil==6.1.0 # Installed as dependency for ipykernel
75
+ # ptyprocess==0.7.0 # Installed as dependency for pexpect
76
+ # pure-eval==0.2.3 # Installed as dependency for stack-data
77
+ # pygments==2.18.0 # Installed as dependency for ipython, readme-renderer, rich
78
+ # pyproject-hooks==1.2.0 # Installed as dependency for build
79
+ # pytest==8.3.3 # Installed as dependency for pytest-cov
80
+ # python-dateutil==2.9.0.post0 # Installed as dependency for jupyter-client
81
+ # pyyaml==6.0.2 # Installed as dependency for commitizen, pre-commit
82
+ # pyzmq==26.2.0 # Installed as dependency for ipykernel, jupyter-client
83
+ # questionary==2.0.1 # Installed as dependency for commitizen
84
+ # readme-renderer==44.0 # Installed as dependency for twine
85
+ # referencing==0.35.1 # Installed as dependency for jsonschema, jsonschema-specifications
86
+ # requests==2.32.3 # Installed as dependency for requests-toolbelt, twine
87
+ # requests-toolbelt==1.0.0 # Installed as dependency for twine
88
+ # rfc3986==2.0.0 # Installed as dependency for twine
89
+ # rich==13.9.3 # Installed as dependency for twine
90
+ # rpds-py==0.20.0 # Installed as dependency for jsonschema, referencing
91
+ # six==1.16.0 # Installed as dependency for asttokens, python-dateutil
92
+ # stack-data==0.6.3 # Installed as dependency for ipython
93
+ # termcolor==2.5.0 # Installed as dependency for commitizen
94
+ # tomlkit==0.13.2 # Installed as dependency for commitizen
95
+ # tornado==6.4.1 # Installed as dependency for ipykernel, jupyter-client
96
+ # traitlets==5.14.3 # Installed as dependency for comm, ipykernel, ipython, jupyter-client, jupyter-core, matplotlib-inline, nbformat
97
+ # typeguard==4.3.0 # Installed as dependency for inflect
98
+ # typing-extensions==4.12.2 # Installed as dependency for ipython, pyright, typeguard
99
+ # urllib3==2.2.3 # Installed as dependency for requests, twine
100
+ # virtualenv==20.27.0 # Installed as dependency for pre-commit
101
+ # wcwidth==0.2.13 # Installed as dependency for prompt-toolkit
102
+ # zipp==3.20.2 # Installed as dependency for importlib-metadata
@@ -0,0 +1,6 @@
1
+ beautifulsoup4==4.12.3
2
+ chardet==5.2.0
3
+ deepdiff==8.0.1
4
+ puremagic==1.28
5
+ # orderly-set==5.2.3 # Installed as dependency for deepdiff
6
+ # soupsieve==2.6 # Installed as dependency for beautifulsoup4
@@ -101,16 +101,16 @@ def clean_nones(value):
101
101
  return value
102
102
 
103
103
 
104
- # if __name__ == "__main__": # pragma: no cover
105
- # tests = "tests"
106
- # begin = time()
107
- # a = [1, 2, 3, 4, 5]
108
- # b = [9, 8, 7, 6, {"r": [5, 6]}]
109
- # c = [8, 7, 6, {"r": [5, 6]}, 9]
110
- # print(tests, f"{a} Δ {b}", list_diff(a, b))
111
- # print(tests, f"{a} Δ {c}", list_diff(a, c))
112
- # print(tests, f"{b} Δ {c}", list_diff(b, c))
113
- # print(tests, "b == c", are_list_equal(b, c))
114
- # # print(tests, f'{b} == {c} ->', are_list_equal(b, c))
115
- # # print(tests, f'dict eq ->', {'r': [6, 5]} != {'r': [5, 6]})
116
- # log_d(tests, "exec. time", time() - begin)
104
+ if __name__ == "__main__": # pragma: no cover
105
+ tests = "tests"
106
+ # begin = time()
107
+ a = [1, 2, 3, 4, 5]
108
+ b = [9, 8, 7, 6, {"r": [5, 6]}]
109
+ c = [8, 7, 6, {"r": [5, 6]}, 9]
110
+ print(tests, f"{a} Δ {b}", list_diff(a, b))
111
+ print(tests, f"{a} Δ {c}", list_diff(a, c))
112
+ print(tests, f"{b} Δ {c}", list_diff(b, c))
113
+ print(tests, "b == c", are_list_equal(b, c))
114
+ # print(tests, f'{b} == {c} ->', are_list_equal(b, c))
115
+ # print(tests, f'dict eq ->', {'r': [6, 5]} != {'r': [5, 6]})
116
+ # log_d(tests, "exec. time", time() - begin)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rudi-node-write
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: Use the internal API of a RUDI Producer node
5
5
  Author-email: Olivier Martineau <olivier.martineau@irisa.fr>
6
6
  Maintainer-email: Olivier Martineau <olivier.martineau@irisa.fr>
@@ -17,6 +17,29 @@ Classifier: License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1
17
17
  Requires-Python: >=3.11
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENCE.md
20
+ Requires-Dist: beautifulsoup4==4.12.3
21
+ Requires-Dist: chardet==5.2.0
22
+ Requires-Dist: deepdiff==8.0.1
23
+ Requires-Dist: puremagic==1.28
24
+ Provides-Extra: dev
25
+ Requires-Dist: black==24.10.0; extra == "dev"
26
+ Requires-Dist: build==1.2.2.post1; extra == "dev"
27
+ Requires-Dist: commitizen==3.29.1; extra == "dev"
28
+ Requires-Dist: importlib-resources==6.4.5; extra == "dev"
29
+ Requires-Dist: inflect==7.3.1; extra == "dev"
30
+ Requires-Dist: ipykernel==6.29.5; extra == "dev"
31
+ Requires-Dist: jaraco.collections==5.1.0; extra == "dev"
32
+ Requires-Dist: nbstripout==0.7.1; extra == "dev"
33
+ Requires-Dist: ordered-set==4.1.0; extra == "dev"
34
+ Requires-Dist: pip-autoremove==0.10.0; extra == "dev"
35
+ Requires-Dist: pip-chill==1.0.3; extra == "dev"
36
+ Requires-Dist: pip-upgrade-outdated==1.5; extra == "dev"
37
+ Requires-Dist: pip3-autoremove==1.2.2; extra == "dev"
38
+ Requires-Dist: pre-commit==4.0.1; extra == "dev"
39
+ Requires-Dist: pyright==1.1.386; extra == "dev"
40
+ Requires-Dist: pytest-cov==5.0.0; extra == "dev"
41
+ Requires-Dist: tomli==2.0.2; extra == "dev"
42
+ Requires-Dist: twine==5.1.1; extra == "dev"
20
43
 
21
44
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
22
45
  [![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
@@ -1,12 +1,14 @@
1
1
  LICENCE.md
2
2
  README.md
3
3
  pyproject.toml
4
+ requirements-dev.txt
4
5
  requirements.txt
5
6
  src/rudi_node_write/__init__.py
6
7
  src/rudi_node_write/rudi_node_writer.py
7
8
  src/rudi_node_write.egg-info/PKG-INFO
8
9
  src/rudi_node_write.egg-info/SOURCES.txt
9
10
  src/rudi_node_write.egg-info/dependency_links.txt
11
+ src/rudi_node_write.egg-info/requires.txt
10
12
  src/rudi_node_write.egg-info/top_level.txt
11
13
  src/rudi_node_write/conf/meta_defaults.py
12
14
  src/rudi_node_write/connectors/io_connector.py
@@ -37,7 +39,4 @@ src/rudi_node_write/utils/str_utils.py
37
39
  src/rudi_node_write/utils/type_date.py
38
40
  src/rudi_node_write/utils/typing_utils.py
39
41
  src/rudi_node_write/utils/url_utils.py
40
- src/rudi_node_write/wip/clean_node.py
41
- src/rudi_node_write/wip/federation_backup.py
42
- src/rudi_node_write/wip/rudinode_federation.py
43
42
  tests/test_rudi_node_write.py
@@ -0,0 +1,24 @@
1
+ beautifulsoup4==4.12.3
2
+ chardet==5.2.0
3
+ deepdiff==8.0.1
4
+ puremagic==1.28
5
+
6
+ [dev]
7
+ black==24.10.0
8
+ build==1.2.2.post1
9
+ commitizen==3.29.1
10
+ importlib-resources==6.4.5
11
+ inflect==7.3.1
12
+ ipykernel==6.29.5
13
+ jaraco.collections==5.1.0
14
+ nbstripout==0.7.1
15
+ ordered-set==4.1.0
16
+ pip-autoremove==0.10.0
17
+ pip-chill==1.0.3
18
+ pip-upgrade-outdated==1.5
19
+ pip3-autoremove==1.2.2
20
+ pre-commit==4.0.1
21
+ pyright==1.1.386
22
+ pytest-cov==5.0.0
23
+ tomli==2.0.2
24
+ twine==5.1.1
File without changes
@@ -1,50 +0,0 @@
1
- from abc import ABC
2
- from concurrent.futures import as_completed, ThreadPoolExecutor
3
- from dataclasses import dataclass
4
- from os import cpu_count
5
- from time import sleep, time
6
- from typing import Callable
7
- from urllib.request import urlopen
8
-
9
- from rudi_node_write.connectors.io_connector import HTTP_REQUEST_METHODS
10
- from rudi_node_write.connectors.io_rudi_api_write import RudiNodeApiConnector
11
- from rudi_node_write.connectors.io_rudi_jwt_factory import RudiNodeJwtFactory
12
- from rudi_node_write.connectors.io_rudi_manager_write import RudiNodeManagerConnector
13
- from rudi_node_write.connectors.rudi_node_auth import RudiNodeAuth
14
- from rudi_node_write.rudi_node_writer import RudiNodeWriter
15
- from rudi_node_write.utils.file_utils import check_is_dir, check_is_file, read_json_file, write_json_file
16
- from rudi_node_write.utils.log import log_d, log_e, log_w
17
- from rudi_node_write.utils.str_utils import slash_join
18
- from rudi_node_write.utils.type_date import Date
19
- from rudi_node_write.utils.url_utils import ensure_http, ensure_url_startswith
20
-
21
- if __name__ == "__main__": # pragma: no cover
22
- begin = time()
23
- here = "RudiNodeClean"
24
-
25
- test_dir = check_is_dir("../dwnld")
26
- data_dir = check_is_dir("../data")
27
- creds_file = read_json_file("../creds/creds_release.json")
28
- auth = RudiNodeAuth(b64url_auth=creds_file["b64auth"])
29
- node = RudiNodeWriter(creds_file["pm_url"], auth, keep_connection=True)
30
-
31
- for meta in node.metadata_list:
32
- changed = False
33
- print(meta["global_id"], ":", meta["access_condition"]["confidentiality"]["gdpr_sensitive"])
34
- if meta["access_condition"]["confidentiality"]["gdpr_sensitive"] == None:
35
- meta["access_condition"]["confidentiality"]["gdpr_sensitive"] = False
36
- changed = True
37
- # try:
38
- # if meta["geography"]["geographic_distribution"]["geometry"] == None:
39
- # del meta["geography"]["geographic_distribution"]["geometry"]
40
- # changed = True
41
- # except:
42
- # pass
43
- # try:
44
- # if meta["metadata_info"]["metadata_dates"]["published"] == None:
45
- # del meta["metadata_info"]["metadata_dates"]["published"]
46
- # changed = True
47
- # except:
48
- # pass
49
- if changed:
50
- node.put_metadata(meta)
@@ -1,145 +0,0 @@
1
- from concurrent.futures import as_completed, ThreadPoolExecutor
2
- from os import cpu_count
3
- from time import sleep, time
4
- from urllib.request import urlopen
5
-
6
- from rudi_node_write.connectors.io_rudi_manager_write import RudiNodeManagerConnector
7
- from rudi_node_write.connectors.rudi_node_auth import RudiNodeAuth
8
- from rudi_node_write.rudi_node_writer import RudiNodeWriter
9
- from rudi_node_write.utils.file_utils import check_is_dir, check_is_file, read_json_file, write_json_file
10
- from rudi_node_write.utils.log import log_d, log_e, log_w
11
- from rudi_node_write.utils.str_utils import slash_join
12
- from rudi_node_write.utils.type_date import Date
13
-
14
- federation_data = {}
15
-
16
-
17
- MAX_WORKERS = 0
18
- DEFAULT_WAIT_TIME = 0.3
19
-
20
-
21
- def try_request(req, wait_time=DEFAULT_WAIT_TIME, quit_on_fail=True):
22
- fun = "try_request"
23
- sleep(wait_time)
24
- try:
25
- return req
26
- except Exception as err:
27
- if quit_on_fail:
28
- return "FAIL"
29
- # Let's try one more time
30
- return try_request(req, wait_time, True)
31
-
32
-
33
- def backup_node(node_name, node_info):
34
- node_url = node_info["url"]
35
- pm_url = node_info["pmback"] if node_info.get("pmback") is not None else (slash_join(node_url, "prodmanager"))
36
- if not pm_url.startswith("http"):
37
- pm_url = "https://" + pm_url
38
-
39
- node = RudiNodeWriter(pm_url=pm_url, auth=auth, keep_connection=True)
40
-
41
- node_data = {"node_name": node_name, "pm_url": node.pm_url}
42
- node_data["media_url"] = try_request(node.media_url)
43
- node_data["init_data"] = try_request(node.init_data)
44
- node_data["organization_list"] = try_request(node.organization_list)
45
- node_data["contact_list"] = try_request(node.contact_list)
46
- node_data["metadata_list"] = try_request(node.metadata_list)
47
- node_data["enums"] = try_request(node.enums)
48
- node_data["media_list"] = try_request(node.media_list)
49
-
50
- node.close_connection()
51
- return node_data
52
-
53
-
54
- if __name__ == "__main__": # pragma: no cover
55
- begin = time()
56
- here = "RudiNodeFederation backup"
57
-
58
- test_dir = check_is_dir("../dwnld")
59
- data_dir = check_is_dir("../data")
60
- creds_file = check_is_file("../creds/test_creds_manager.json")
61
- servers_file = check_is_file("../creds/servers.json")
62
-
63
- servers_list = read_json_file(servers_file)
64
- nodes = servers_list.get("nodes")
65
-
66
- # nodes = {"exatow": nodes["exatow"]}
67
-
68
- nb_nodes = len(nodes.keys())
69
-
70
- rudi_node_creds = read_json_file(creds_file)
71
- auth = RudiNodeAuth.from_json(rudi_node_creds)
72
- url = rudi_node_creds["url"]
73
- pm_url = url if url.endswith("prodmanager") else slash_join(url, "prodmanager")
74
- assert isinstance(auth, RudiNodeAuth)
75
- errors = {}
76
- if MAX_WORKERS:
77
- with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
78
- futures = {executor.submit(backup_node, node_name, nodes[node_name]): node_name for node_name in nodes}
79
- for future in as_completed(futures, timeout=120):
80
- try:
81
- future_result = future.result()
82
- except Exception as err:
83
- node_name = futures[future]
84
- log_e(here, "Backup failed for node:", futures[future])
85
- future_result = {}
86
- errors[node_name] = err
87
- node_name = futures[future]
88
- future_result["node_name"] = None
89
- federation_data[node_name] = future_result
90
- else:
91
- # federation_info = {nodes[node_name] for node_name in nodes}
92
-
93
- for node_name in nodes:
94
- log_d(here, "----- node_name", node_name)
95
- node_info = nodes[node_name]
96
- try:
97
- node_data = backup_node(node_name, node_info)
98
- federation_data[node_name] = node_data
99
-
100
- write_json_file(destination_file_path=slash_join(data_dir, node_name + ".json"), json_dict=node_data)
101
- except Exception as e:
102
- errors[node_name] = e
103
- node_data = None
104
-
105
- federation_data[node_name] = node_data
106
-
107
- write_json_file(destination_file_path=slash_join(data_dir, "_federation.json"), json_dict=federation_data)
108
- if len(errors.keys()) > 0:
109
- log_e(here, "Backup failed for some nodes:", errors)
110
- else:
111
- log_d(here, "All nodes were successfully backed up")
112
-
113
- log_d(here, "exec. time", time() - begin)
114
-
115
- # urls = [
116
- # # "http://www.foxnews.com/",
117
- # "http://www.cnn.com/",
118
- # "http://europe.wsj.com/",
119
- # "http://www.bbc.co.uk/",
120
- # "http://some-made-up-domain.com/",
121
- # ]
122
-
123
- # url_dict = {url + "r": url for url in urls}
124
- # print(url_dict)
125
-
126
-
127
- # def load_url(url, timeout):
128
- # with urlopen(url, timeout=timeout) as conn:
129
- # return conn.read()
130
-
131
-
132
- # with ThreadPoolExecutor(max_workers=5) as executor:
133
- # future_to_url = {executor.submit(load_url, url, 60): url for url in urls}
134
- # for future in as_completed(future_to_url):
135
- # url = future_to_url[future]
136
- # try:
137
- # data = future.result()
138
- # except Exception as exc:
139
- # print("%r generated an exception: %s" % (url, exc))
140
- # else:
141
- # print("%r page is %d bytes" % (url, len(data)))
142
-
143
- # print(cpu_count() * 5)
144
- # pool = ThreadPoolExecutor()
145
- # print(pool._max_workers)
@@ -1,412 +0,0 @@
1
- from abc import ABC
2
- from concurrent.futures import as_completed, ThreadPoolExecutor
3
- from dataclasses import dataclass
4
- from os import cpu_count
5
- from time import sleep, time
6
- from typing import Callable
7
- from urllib.request import urlopen
8
-
9
- from rudi_node_write.connectors.io_connector import HTTP_REQUEST_METHODS
10
- from rudi_node_write.connectors.io_rudi_api_write import RudiNodeApiConnector
11
- from rudi_node_write.connectors.io_rudi_jwt_factory import RudiNodeJwtFactory
12
- from rudi_node_write.connectors.io_rudi_manager_write import RudiNodeManagerConnector
13
- from rudi_node_write.connectors.rudi_node_auth import RudiNodeAuth
14
- from rudi_node_write.rudi_node_writer import RudiNodeWriter
15
- from rudi_node_write.utils.file_utils import check_is_dir, check_is_file, read_json_file, write_json_file
16
- from rudi_node_write.utils.log import log_d, log_e, log_w
17
- from rudi_node_write.utils.str_utils import slash_join
18
- from rudi_node_write.utils.type_date import Date
19
- from rudi_node_write.utils.url_utils import ensure_http, ensure_url_startswith
20
-
21
- federation_data = {}
22
-
23
- DEFAULT_WAIT_TIME = 0.3
24
-
25
- NO_PORTAL = "No portal connected"
26
-
27
-
28
- def ensure_url_startswith_api(url):
29
- return ensure_url_startswith(url, "api")
30
-
31
-
32
- class Module:
33
- MANAGER = "manager"
34
- CATALOG = "catalog"
35
- STORAGE = "storage"
36
-
37
-
38
- class HttpMethods:
39
- GET = "GET"
40
- POST = "POST"
41
- PUT = "PUT"
42
- DELETE = "DELETE"
43
-
44
-
45
- @dataclass
46
- class ModuleRequest:
47
- action_name: str
48
- module_name: Module = Module.MANAGER
49
- req_method: HttpMethods = HttpMethods.GET
50
- url_bit: str = "/"
51
- post_treatment: Callable = lambda x: x
52
- can_fail: bool = False
53
- keep_alive: bool = False
54
- should_log_request: bool = False
55
- should_log_response: bool = False
56
-
57
-
58
- # TODO: envoyer une suite/liste de traitements sur les nœuds:
59
- # - quel connecteur cible (node_write ? catalog ? media ?)
60
- # - quelle URL requête (url_bit )
61
- # - quel post-traitement
62
- #
63
- # => Input: objet à définir
64
- # #
65
- # [{
66
- # target: Module.MANAGER | Module.CATALOG | MEDIA,
67
- # url_bit,
68
- # post_treatment on answer (defaulted to x=>x)
69
- # opts: keep_alive, log request, log response (defaulted to False)
70
- # }]
71
- class NodeConnector:
72
- def __init__(
73
- self,
74
- node_name,
75
- catalog_url,
76
- catalog_auth,
77
- manager_url,
78
- manager_auth,
79
- ):
80
- self._node_name = node_name
81
- self._catalog = CatalogConnector(node_name=node_name, module_url=catalog_url, module_auth=catalog_auth)
82
- self._manager = ManagerConnector(node_name=node_name, module_url=manager_url, module_auth=manager_auth)
83
-
84
-
85
- class ModuleConnector(ABC):
86
- def __init__(self, node_name: str, get_connector: Callable, get_headers: Callable):
87
- self._node_name = node_name
88
- self._get_connector = get_connector
89
- self._get_headers = get_headers
90
-
91
- @property
92
- def node_name(self):
93
- return self._node_name
94
-
95
- @property
96
- def module_name(self):
97
- raise NotImplementedError("This function should be implemented")
98
-
99
- @property
100
- def connector(self):
101
- return self._get_connector()
102
-
103
- @property
104
- def headers(self):
105
- return self._get_headers()
106
-
107
- @staticmethod
108
- def create_connector(node_name, module_name, module_url, module_auth):
109
- if module_name == Module.MANAGER:
110
- return ManagerConnector(node_name=node_name, module_url=module_url, module_auth=module_auth)
111
- if module_name == Module.CATALOG:
112
- return CatalogConnector(node_name=node_name, module_url=module_url, module_auth=module_auth)
113
-
114
- def module_request(
115
- self,
116
- request: ModuleRequest,
117
- ):
118
- return self.request(
119
- req_method=request.req_method,
120
- url_bit=request.url_bit,
121
- post_treatment=request.post_treatment,
122
- can_fail=request.can_fail,
123
- keep_alive=request.keep_alive,
124
- should_log_request=request.should_log_request,
125
- should_log_response=request.should_log_response,
126
- )
127
-
128
- def request(
129
- self,
130
- req_method: HttpMethods,
131
- url_bit: str,
132
- post_treatment=lambda x: x,
133
- can_fail: bool = False,
134
- keep_alive: bool = False,
135
- should_log_request: bool = False,
136
- should_log_response: bool = False,
137
- ):
138
- try:
139
- node_res = self.connector.request(
140
- req_method=req_method,
141
- relative_url=ensure_url_startswith_api(url_bit),
142
- headers=self.headers,
143
- keep_alive=keep_alive,
144
- should_log_request=should_log_request,
145
- should_log_response=should_log_response,
146
- )
147
- return post_treatment(node_res)
148
- except Exception as err:
149
- if can_fail:
150
- raise err
151
- return f"ERR: {err}"
152
-
153
-
154
- class ManagerConnector(ModuleConnector):
155
- def __init__(self, node_name, module_url, module_auth):
156
- self._node_name = node_name
157
- self._node_writer = RudiNodeWriter(pm_url=module_url, auth=module_auth, headers_user_agent="ManagerConnector")
158
- super().__init__(
159
- node_name=node_name,
160
- get_connector=lambda: self._node_writer._pm_connector,
161
- get_headers=lambda: self._node_writer._pm_connector._pm_headers,
162
- )
163
-
164
-
165
- class CatalogConnector(ModuleConnector):
166
- def __init__(self, node_name, module_url, module_auth):
167
- self._node_name = node_name
168
- self._catalog_connector = RudiNodeApiConnector(
169
- server_url=module_url, jwt_factory=module_auth, headers_user_agent="CatalogConnector"
170
- )
171
- super().__init__(
172
- node_name=node_name,
173
- get_connector=lambda: self._catalog_connector,
174
- get_headers=lambda: self._catalog_connector._headers,
175
- )
176
-
177
-
178
- class RudiNodeFederation:
179
- def __init__(self, config_file_path, credentials_file_path, local_jwt_factory):
180
- self.jwt_factory = local_jwt_factory
181
- self.load_federation_conf(config_file_path, credentials_file_path)
182
-
183
- def load_federation_conf(self, config_file_path, credentials_file_path):
184
- self._conf = read_json_file(config_file_path)
185
- self._creds = read_json_file(credentials_file_path)
186
- self._connectors = {}
187
-
188
- # self._conf["nodes"] = {"exatow": self._conf["nodes"]["exatow"], "rm": self._conf["nodes"]["rm"]}
189
-
190
- for node_name, node_conf in self.nodes_conf.items():
191
- print(node_name)
192
- node_url = ensure_http(node_conf["url"])
193
-
194
- pm_url = ensure_http(
195
- node_conf["pmback"] if node_conf.get("pmback") is not None else slash_join(node_url, "prodmanager")
196
- )
197
-
198
- self._connectors[node_name] = {
199
- Module.MANAGER: ManagerConnector(
200
- node_name=node_name,
201
- module_url=pm_url,
202
- module_auth=RudiNodeAuth(b64url_auth=self.get_creds(node_name).get("b64auth")),
203
- ),
204
- Module.CATALOG: CatalogConnector(
205
- node_name=node_name, module_url=node_url, module_auth=self.jwt_factory
206
- ),
207
- }
208
-
209
- @property
210
- def nodes_conf(self):
211
- return self.get_node_conf()
212
-
213
- def get_node_conf(self, node_name=None):
214
- if node_name is None:
215
- return self._conf["nodes"]
216
- return self._conf["nodes"].get(node_name)
217
-
218
- def get_creds(self, node_name):
219
- if self._creds.get(node_name):
220
- return self._creds[node_name]
221
- return self._creds["defaults"]
222
-
223
- def request(
224
- self,
225
- node_name,
226
- module_name,
227
- req_method,
228
- url_bit,
229
- post_treatment=lambda x: x,
230
- can_fail=False,
231
- keep_alive=False,
232
- should_log_request=False,
233
- should_log_response=False,
234
- ):
235
- if module_name == Module.MANAGER:
236
- connector = self._managers[node_name]
237
- headers = connector._pm_headers
238
- url_bit = ensure_url_startswith_api(url_bit)
239
- elif module_name == Module.CATALOG:
240
- connector = self._catalogs[node_name]
241
- headers = connector._headers
242
- url_bit = ensure_url_startswith_api(url_bit)
243
- else:
244
- raise NotImplementedError("Not yet available")
245
- try:
246
- node_res = connector.request(
247
- req_method=req_method,
248
- relative_url=url_bit,
249
- headers=headers,
250
- keep_alive=keep_alive,
251
- should_log_request=should_log_request,
252
- should_log_response=should_log_response,
253
- )
254
- return post_treatment(node_res)
255
- except Exception as err:
256
- if can_fail:
257
- raise err
258
- return f"ERR: {err}"
259
-
260
- def batch_request_node(self, connectors, request_list, max_workers=0):
261
- res = {}
262
- err = {}
263
- for request in request_list:
264
- if isinstance(request, ModuleRequest):
265
- res[request.action_name] = connectors[request.module_name].module_request(request)
266
- else:
267
- (action_name, module_name, req_method, url_bit, post_treatment) = request
268
- res[action_name] = connectors[module_name].request(
269
- req_method=req_method,
270
- url_bit=url_bit,
271
- post_treatment=post_treatment,
272
- )
273
- return res
274
-
275
- def batch_requests(self, request_list, max_workers=0):
276
- """
277
- @param request_dict: a dict with module names as keys and a list of actions {action_name: (module_name, req_method , url_bit , post_treatment)}}
278
- """
279
- res = {}
280
- err = {}
281
- if not max_workers:
282
- for node_name, connectors in self._connectors.items():
283
- res[node_name] = self.batch_request_node(connectors, request_list)
284
- else:
285
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
286
- futures = {
287
- executor.submit(connectors[request.module_name].module_request, request): request
288
- for request in request_list
289
- }
290
- for future in as_completed(futures, timeout=120):
291
- try:
292
- future_result = future.result()
293
- except Exception as err:
294
- node_name = futures[future]
295
- log_e(here, "Request failed:", futures[future])
296
- future_result = {}
297
- errors[request.node_name] = err
298
- node_name = futures[future]
299
- future_result["node_name"] = None
300
- federation_data[node_name] = future_result
301
- return res
302
-
303
- def try_request(req, wait_time=DEFAULT_WAIT_TIME, quit_on_fail=True):
304
- fun = "try_request"
305
- sleep(wait_time)
306
- try:
307
- return req
308
- except Exception as err:
309
- if quit_on_fail:
310
- return "FAIL"
311
- # Let's try one more time
312
- return try_request(req, wait_time, True)
313
-
314
- def backup_node(node_name, node_info):
315
- node_url = node_info["url"]
316
- pm_url = node_info["pmback"] if node_info.get("pmback") is not None else (slash_join(node_url, "prodmanager"))
317
- ensure_http(pm_url)
318
-
319
- node = RudiNodeWriter(pm_url=pm_url, auth=auth, keep_connection=True)
320
-
321
- node_data = {"node_name": node_name, "pm_url": node.pm_url}
322
- node_data["media_url"] = try_request(node.media_url)
323
- node_data["init_data"] = try_request(node.init_data)
324
- node_data["organization_list"] = try_request(node.organization_list)
325
- node_data["contact_list"] = try_request(node.contact_list)
326
- node_data["metadata_list"] = try_request(node.metadata_list)
327
- node_data["enums"] = try_request(node.enums)
328
- node_data["media_list"] = try_request(node.media_list)
329
-
330
- node.close_connection()
331
- return node_data
332
-
333
-
334
- if __name__ == "__main__": # pragma: no cover
335
- begin = time()
336
- here = "RudiNodeFederation"
337
-
338
- test_dir = check_is_dir("../dwnld")
339
- data_dir = check_is_dir("../data")
340
- creds_file = check_is_file("../creds/federation_creds.json")
341
- servers_file = check_is_file("../creds/servers.json")
342
-
343
- try:
344
- local_jwt_factory = RudiNodeJwtFactory("http://localhost:4040", {"sub": "rudi_api_pm"})
345
- except:
346
- raise ConnectionError("Local crypto module is apparently not launched!")
347
-
348
- rudi_federation = RudiNodeFederation(
349
- config_file_path=servers_file, credentials_file_path=creds_file, local_jwt_factory=local_jwt_factory
350
- )
351
-
352
- def get_token(x):
353
- if x == NO_PORTAL:
354
- return NO_PORTAL
355
- try:
356
- return x["access_token"].replace(r"[\w-]+(\.[\w-]+){2}", "valid_jwt")
357
- except:
358
- return x
359
-
360
- res = rudi_federation.batch_requests(
361
- [
362
- ModuleRequest(
363
- action_name="portal_url",
364
- module_name=Module.MANAGER,
365
- url_bit="front/portal-url",
366
- can_fail=True,
367
- should_log_request=True,
368
- ),
369
- ModuleRequest(
370
- action_name="portal_token",
371
- module_name=Module.CATALOG,
372
- url_bit="admin/portal/token",
373
- post_treatment=get_token,
374
- ),
375
- ]
376
- )
377
- portal_connections_file = "rudi_node_write/wip/portal_connect.json"
378
- reworked_portal_connect_file = "rudi_node_write/wip/portal_connect_final.json"
379
- # res = read_json_file("rudi_node_write/wip/portal_connect.json")
380
- print(res)
381
- write_json_file(destination_file_path=portal_connections_file, json_dict=res)
382
- final_res = {}
383
- for node_name, node_res in res.items():
384
- portal_url = node_res["portal_url"]
385
- print(portal_url)
386
- portal_connection = (
387
- node_res["portal_token"]
388
- if not node_res["portal_token"].startswith("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9")
389
- else "OK"
390
- )
391
- if final_res.get(portal_url) is None:
392
- final_res[portal_url] = {node_name: portal_connection}
393
- final_res[portal_url][node_name] = portal_connection
394
- # print(res)
395
- # portal_urls = rudi_federation.manager_get("front/portal-url")
396
- # node_catalog_url = rudi_federation.manager_get("front/ext-api-url")
397
- # catalog_portal_token = rudi_federation.catalog_get("portal/token", extract_token_from_http_res)
398
- # node_portals = {}
399
- # for node_name, portal_url in portal_urls.items():
400
- # node_info = {"catalog_url": node_catalog_url[node_name], "portal_token": catalog_portal_token}
401
- # if node_portals.get(portal_url) is None:
402
- # node_portals[portal_url] = {node_name: node_info}
403
- # else:
404
- # node_portals[portal_url][node_name] = node_info
405
-
406
- # print(node_portals)
407
- print(final_res)
408
- write_json_file(destination_file_path=reworked_portal_connect_file, json_dict=final_res)
409
-
410
- print()
411
-
412
- # print(chr(sum(range(ord(min(str(not ())))))))