pyicat-plus 2.2.0__tar.gz → 2.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 (116) hide show
  1. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/PKG-INFO +6 -10
  2. pyicat_plus-2.3.0/README.md +10 -0
  3. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/pyproject.toml +29 -14
  4. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/deserializers/xml.py +1 -1
  5. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/apps/store_processed.py +3 -2
  6. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/apps/store_raw.py +6 -0
  7. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/defaults.py +1 -1
  8. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/icatplus_restricted.py +40 -12
  9. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/interface.py +34 -5
  10. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/main.py +16 -2
  11. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/messaging.py +2 -2
  12. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/null.py +3 -0
  13. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/types.py +10 -0
  14. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/metadata/definitions.py +2 -3
  15. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/conftest.py +0 -1
  16. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_icat_investigations.py +1 -1
  17. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_icat_restricted.py +45 -3
  18. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/utils/compare.py +2 -2
  19. pyicat_plus-2.3.0/src/pyicat_plus/tests/utils/generate.py +5 -0
  20. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus.egg-info/PKG-INFO +6 -10
  21. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus.egg-info/SOURCES.txt +17 -25
  22. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus.egg-info/entry_points.txt +3 -1
  23. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus.egg-info/requires.txt +1 -9
  24. pyicat_plus-2.3.0/src/pyicat_plus.egg-info/top_level.txt +2 -0
  25. {pyicat_plus-2.2.0/src/pyicat_plus/tests/fixtures → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/proc.py +6 -2
  26. pyicat_plus-2.2.0/src/pyicat_plus/tests/fixtures/icat.py → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin/pytest_plugin.py +20 -19
  27. {pyicat_plus-2.2.0/src/pyicat_plus/tests → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/servers/activemq_rest_server.py +6 -3
  28. {pyicat_plus-2.2.0/src/pyicat_plus/tests → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/servers/coilmq/pkg_resources.py +4 -1
  29. {pyicat_plus-2.2.0/src/pyicat_plus/tests → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/servers/icat_db.py +1 -1
  30. {pyicat_plus-2.2.0/src/pyicat_plus/tests → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/servers/icat_session.py +1 -1
  31. {pyicat_plus-2.2.0/src/pyicat_plus/tests → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/servers/icatplus_server.py +17 -2
  32. {pyicat_plus-2.2.0/src/pyicat_plus/tests → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/servers/stomp_publisher.py +1 -1
  33. {pyicat_plus-2.2.0/src/pyicat_plus/tests → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/servers/stomp_subscriber.py +3 -2
  34. {pyicat_plus-2.2.0/src/pyicat_plus/tests/fixtures → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/tcp.py +5 -4
  35. pyicat_plus-2.2.0/README.md +0 -7
  36. pyicat_plus-2.2.0/src/pyicat_plus/apps/sync_raw.py +0 -584
  37. pyicat_plus-2.2.0/src/pyicat_plus/client/bliss.py +0 -38
  38. pyicat_plus-2.2.0/src/pyicat_plus/tests/test_icat_sync.py +0 -747
  39. pyicat_plus-2.2.0/src/pyicat_plus/tests/test_raw_data.py +0 -57
  40. pyicat_plus-2.2.0/src/pyicat_plus/tests/utils/generate.py +0 -5
  41. pyicat_plus-2.2.0/src/pyicat_plus/utils/path_utils.py +0 -25
  42. pyicat_plus-2.2.0/src/pyicat_plus/utils/raw_data.py +0 -238
  43. pyicat_plus-2.2.0/src/pyicat_plus/utils/sync_store.py +0 -381
  44. pyicat_plus-2.2.0/src/pyicat_plus/utils/sync_types.py +0 -394
  45. pyicat_plus-2.2.0/src/pyicat_plus.egg-info/top_level.txt +0 -1
  46. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/LICENSE.md +0 -0
  47. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/setup.cfg +0 -0
  48. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/__init__.py +0 -0
  49. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/__init__.py +0 -0
  50. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/deserializers/__init__.py +0 -0
  51. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/models/__init__.py +0 -0
  52. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/models/xsd/__init__.py +0 -0
  53. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/models/xsd/dataset.py +0 -0
  54. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/models/xsd/investigation.py +0 -0
  55. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/serializers/__init__.py +0 -0
  56. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/serializers/dataset.py +0 -0
  57. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/serializers/dataset_parameters/__init__.py +0 -0
  58. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/serializers/dataset_parameters/fallback.py +0 -0
  59. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/serializers/dataset_parameters/main.py +0 -0
  60. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/serializers/dataset_parameters/pydantic.py +0 -0
  61. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/serializers/investigation.py +0 -0
  62. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/serializers/xml.py +0 -0
  63. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/utils/__init__.py +0 -0
  64. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/utils/constants.py +0 -0
  65. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/_icat_messaging/utils/datetime_utils.py +0 -0
  66. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/apps/__init__.py +0 -0
  67. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/apps/icat_as_nexus.py +0 -0
  68. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/apps/store_from_file.py +0 -0
  69. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/__init__.py +0 -0
  70. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/add_files.py +0 -0
  71. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/archive.py +0 -0
  72. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/deprecation_utils.py +0 -0
  73. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/elogbook.py +0 -0
  74. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/investigation.py +0 -0
  75. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/metadata.py +0 -0
  76. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/models/__init__.py +0 -0
  77. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/models/session.py +0 -0
  78. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/client/update_metadata.py +0 -0
  79. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/concurrency.py +0 -0
  80. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/errors.py +0 -0
  81. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/metadata/__init__.py +0 -0
  82. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/metadata/drac_report_builder.py +0 -0
  83. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/metadata/hdf5_cfg.xml +0 -0
  84. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/metadata/namespace_wrapper.py +0 -0
  85. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/metadata/nexus.py +0 -0
  86. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/__init__.py +0 -0
  87. {pyicat_plus-2.2.0/src/pyicat_plus/tests/fixtures → pyicat_plus-2.3.0/src/pyicat_plus/tests/icat_messaging}/__init__.py +0 -0
  88. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/icat_messaging/test_dataset.py +0 -0
  89. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/icat_messaging/test_fallback.py +0 -0
  90. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/icat_messaging/test_investigation.py +0 -0
  91. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_cli.py +0 -0
  92. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_drac_report_builder.py +0 -0
  93. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_elogbook.py +0 -0
  94. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_icat_add_files.py +0 -0
  95. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_icat_archive.py +0 -0
  96. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_icat_datasets.py +0 -0
  97. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_icat_definitions.py +0 -0
  98. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_icat_mockup.py +0 -0
  99. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_icat_nexus.py +0 -0
  100. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_icat_update_metadata.py +0 -0
  101. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_maxsizedict.py +0 -0
  102. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_namespace.py +0 -0
  103. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/tests/test_url_utils.py +0 -0
  104. {pyicat_plus-2.2.0/src/pyicat_plus/tests/icat_messaging → pyicat_plus-2.3.0/src/pyicat_plus/tests/utils}/__init__.py +0 -0
  105. {pyicat_plus-2.2.0/src/pyicat_plus/tests/servers → pyicat_plus-2.3.0/src/pyicat_plus/utils}/__init__.py +0 -0
  106. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/utils/html_utils.py +0 -0
  107. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/utils/log_utils.py +0 -0
  108. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/utils/maxsizedict.py +0 -0
  109. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus/utils/url.py +0 -0
  110. {pyicat_plus-2.2.0 → pyicat_plus-2.3.0}/src/pyicat_plus.egg-info/dependency_links.txt +0 -0
  111. {pyicat_plus-2.2.0/src/pyicat_plus/tests/servers/coilmq → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/__init__.py +0 -0
  112. {pyicat_plus-2.2.0/src/pyicat_plus/tests/fixtures → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/misc.py +0 -0
  113. {pyicat_plus-2.2.0/src/pyicat_plus/tests/utils → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin/servers}/__init__.py +0 -0
  114. {pyicat_plus-2.2.0/src/pyicat_plus/utils → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin/servers/coilmq}/__init__.py +0 -0
  115. {pyicat_plus-2.2.0/src/pyicat_plus/tests → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/servers/coilmq/__main__.py +0 -0
  116. {pyicat_plus-2.2.0/src/pyicat_plus/tests → pyicat_plus-2.3.0/src/pyicat_plus_pytest_plugin}/servers/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyicat-plus
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Python client to ICAT+
5
5
  Author-email: ESRF <dau-pydev@esrf.fr>
6
6
  License: GNU Lesser General Public License
@@ -186,14 +186,10 @@ Requires-Dist: numpy
186
186
  Requires-Dist: querypool
187
187
  Requires-Dist: requests
188
188
  Requires-Dist: stompest
189
- Requires-Dist: tqdm
190
189
  Requires-Dist: pillow
191
190
  Requires-Dist: xsdata==24.9; python_version == "3.8"
192
191
  Requires-Dist: xsdata; python_version >= "3.9"
193
- Provides-Extra: bliss
194
- Requires-Dist: blissdata[beacon]; extra == "bliss"
195
192
  Provides-Extra: test
196
- Requires-Dist: pyicat-plus[bliss]; extra == "test"
197
193
  Requires-Dist: pytest>=7; extra == "test"
198
194
  Requires-Dist: pytest-timeout>=2.1; extra == "test"
199
195
  Requires-Dist: numpy>=1.15; extra == "test"
@@ -203,10 +199,7 @@ Requires-Dist: stomp.py>=7; extra == "test"
203
199
  Requires-Dist: h5py>=3.5; extra == "test"
204
200
  Provides-Extra: dev
205
201
  Requires-Dist: pyicat-plus[test]; extra == "dev"
206
- Requires-Dist: black>=25; extra == "dev"
207
- Requires-Dist: flake8>=4; extra == "dev"
208
- Requires-Dist: pep8-naming; extra == "dev"
209
- Requires-Dist: isort; extra == "dev"
202
+ Requires-Dist: ruff; extra == "dev"
210
203
  Requires-Dist: xsdata[cli]; extra == "dev"
211
204
  Requires-Dist: pre-commit; extra == "dev"
212
205
  Provides-Extra: doc
@@ -217,7 +210,10 @@ Requires-Dist: pydata-sphinx-theme; extra == "doc"
217
210
  Requires-Dist: sphinx-copybutton; extra == "doc"
218
211
  Dynamic: license-file
219
212
 
220
- # PyIcat-Plus
213
+ <h1>
214
+ <img src="doc/_static/images/pyicatplus-logo.svg" alt="PyIcat-Plus logo" height="52">
215
+ PyIcat-Plus
216
+ </h1>
221
217
 
222
218
  A python client for ICAT+.
223
219
 
@@ -0,0 +1,10 @@
1
+ <h1>
2
+ <img src="doc/_static/images/pyicatplus-logo.svg" alt="PyIcat-Plus logo" height="52">
3
+ PyIcat-Plus
4
+ </h1>
5
+
6
+ A python client for ICAT+.
7
+
8
+ ## Documentation
9
+
10
+ https://pyicat-plus.readthedocs.io/
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "pyicat-plus"
9
- version = "2.2.0"
9
+ version = "2.3.0"
10
10
  authors = [{name = "ESRF", email = "dau-pydev@esrf.fr"}]
11
11
  description = "Python client to ICAT+"
12
12
  readme = {file = "README.md", content-type = "text/markdown"}
@@ -26,7 +26,6 @@ dependencies = [
26
26
  "querypool",
27
27
  "requests",
28
28
  "stompest",
29
- "tqdm",
30
29
  "pillow",
31
30
  "xsdata==24.9; python_version == '3.8'", # latest version that supports Python 3.8
32
31
  "xsdata; python_version >= '3.9'",
@@ -40,11 +39,7 @@ Issues = "https://gitlab.esrf.fr/icat/pyicat-plus/issues"
40
39
  Changelog = "https://gitlab.esrf.fr/icat/pyicat-plus/-/blob/main/CHANGELOG.md"
41
40
 
42
41
  [project.optional-dependencies]
43
- bliss = [
44
- "blissdata[beacon]",
45
- ]
46
42
  test = [
47
- "pyicat-plus[bliss]",
48
43
  "pytest >=7",
49
44
  "pytest-timeout >=2.1",
50
45
  "numpy >=1.15",
@@ -55,10 +50,7 @@ test = [
55
50
  ]
56
51
  dev = [
57
52
  "pyicat-plus[test]",
58
- "black >=25",
59
- "flake8 >=4",
60
- "pep8-naming",
61
- "isort",
53
+ "ruff",
62
54
  "xsdata[cli]",
63
55
  "pre-commit",
64
56
  ]
@@ -83,13 +75,36 @@ where = ["src"]
83
75
  [tool.coverage.run]
84
76
  omit = ['*/tests/*']
85
77
 
86
- [tool.isort]
87
- profile = "black"
88
- force_single_line = true
78
+ [tool.ruff]
79
+ line-length = 88
80
+
81
+ [tool.ruff.lint]
82
+ select = [
83
+ "E", # pycodestyle errors
84
+ "F", # pyflakes
85
+ "I", # isort
86
+ "N", # pep8-naming
87
+ "S", # flake8-bandit
88
+ ]
89
+ ignore = ["E501"]
90
+
91
+ [tool.ruff.lint.per-file-ignores]
92
+ "**/tests/**" = [
93
+ "S101", # allow asserts
94
+ ]
95
+ "src/pyicat_plus_pytest_plugin/**" = [
96
+ "S101", # allow asserts
97
+ ]
98
+
99
+ [tool.ruff.lint.isort]
100
+ force-single-line = true
101
+ known-first-party = ["pyicat_plus", "pyicat_plus_pytest_plugin"]
102
+
103
+ [project.entry-points."pytest11"]
104
+ "pyicat_plus" = "pyicat_plus_pytest_plugin.pytest_plugin"
89
105
 
90
106
  [project.scripts]
91
107
  icat-store-raw = "pyicat_plus.apps.store_raw:main"
92
- icat-sync-raw = "pyicat_plus.apps.sync_raw:main"
93
108
  icat-store-processed = "pyicat_plus.apps.store_processed:main"
94
109
  icat-store-from-file = "pyicat_plus.apps.store_from_file:main"
95
110
  icat-nexus-definitions = "pyicat_plus.apps.icat_as_nexus:main"
@@ -16,7 +16,7 @@ def deserialize_message(data: bytes) -> Tuple[str, Dict[str, Any]]:
16
16
  """
17
17
  :raises xml.etree.ElementTree.ParseError:
18
18
  """
19
- root = ElementTree.fromstring(data)
19
+ root = ElementTree.fromstring(data) # noqa: S314 -- trusted internal ICAT messaging broker
20
20
  root_tag = root.tag.split("}", 1)[-1]
21
21
 
22
22
  if root_tag == "dataset":
@@ -9,7 +9,7 @@ def main(argv=None):
9
9
  if argv is None:
10
10
  argv = sys.argv
11
11
 
12
- parser = argparse.ArgumentParser(description="Register raw data with ICAT")
12
+ parser = argparse.ArgumentParser(description="Register processed data with ICAT")
13
13
 
14
14
  store_raw.add_store_parameters(parser)
15
15
  add_process_parameters(parser)
@@ -27,6 +27,7 @@ def main(argv=None):
27
27
  path=args.path,
28
28
  metadata=args.metadata,
29
29
  raw=args.raw,
30
+ store_filename=args.store_filename,
30
31
  )
31
32
 
32
33
  client.disconnect()
@@ -37,7 +38,7 @@ def add_process_parameters(parser):
37
38
  "--raw",
38
39
  action="append",
39
40
  required=True,
40
- help="Raw dataset directories",
41
+ help="Input dataset directories, raw or processed",
41
42
  )
42
43
 
43
44
 
@@ -25,6 +25,7 @@ def main(argv=None):
25
25
  dataset=args.dataset,
26
26
  path=args.path,
27
27
  metadata=args.metadata,
28
+ store_filename=args.store_filename,
28
29
  )
29
30
  client.disconnect()
30
31
 
@@ -64,6 +65,11 @@ def add_store_parameters(parser):
64
65
  default=[],
65
66
  )
66
67
 
68
+ parser.add_argument(
69
+ "--store-filename",
70
+ help="Store in an XML file instead of sending to ActiveMQ",
71
+ )
72
+
67
73
 
68
74
  def apply_store_parameters(args):
69
75
  if args.metadatafile:
@@ -1,6 +1,6 @@
1
1
  from typing import List
2
2
 
3
- ELOGBOOK_TOKEN: str = "elogbook-00000000-0000-0000-0000-000000000000"
3
+ ELOGBOOK_TOKEN: str = "elogbook-00000000-0000-0000-0000-000000000000" # noqa: S105 -- placeholder default, not a secret
4
4
 
5
5
  METADATA_QUEUE: str = "icatIngest"
6
6
  METADATA_BROKERS: List[str] = ["bcu-mq-01.esrf.fr:61613", "bcu-mq-02.esrf.fr:61613"]
@@ -8,6 +8,8 @@ import requests
8
8
 
9
9
  from ..utils.url import normalize_url
10
10
  from .deprecation_utils import deprecated_argument
11
+ from .deprecation_utils import deprecated_method
12
+ from .types import InvestigationType
11
13
  from .types import InvestigationUser
12
14
  from .types import SampleMetadata
13
15
 
@@ -41,6 +43,7 @@ class IcatPlusRestrictedClient:
41
43
 
42
44
  def _init_urls(self, url: str):
43
45
  self._investigation_url = urljoin(url, "catalogue/{session_id}/investigation")
46
+ self._investigation_types_url = urljoin(url, "catalogue/investigationtypes")
44
47
  self._sample_url = urljoin(url, "catalogue/{session_id}/samples")
45
48
  self._dataset_url = urljoin(url, "catalogue/datasets")
46
49
  self._parcel_url = urljoin(url, "tracking/{session_id}/parcel")
@@ -65,7 +68,7 @@ class IcatPlusRestrictedClient:
65
68
  }
66
69
  if username is not None:
67
70
  credentials["username"] = username
68
- response = requests.post(self._authentication_url, json=credentials)
71
+ response = requests.post(self._authentication_url, json=credentials) # noqa: S113 -- trusted internal ICAT+ service
69
72
  response.raise_for_status()
70
73
  authentication_response = response.json()
71
74
  self._session_id = authentication_response["sessionId"]
@@ -115,7 +118,12 @@ class IcatPlusRestrictedClient:
115
118
  params = {k: v for k, v in params.items() if v is not None}
116
119
 
117
120
  url = self._investigation_url.format(session_id=self.session_id)
118
- response = requests.get(url, params=params)
121
+ response = requests.get(url, params=params) # noqa: S113 -- trusted internal ICAT+ service
122
+ response.raise_for_status()
123
+ return response.json()
124
+
125
+ def get_investigation_types(self) -> List[InvestigationType]:
126
+ response = requests.get(self._investigation_types_url) # noqa: S113 -- trusted internal ICAT+ service
119
127
  response.raise_for_status()
120
128
  return response.json()
121
129
 
@@ -127,7 +135,7 @@ class IcatPlusRestrictedClient:
127
135
  params = {"investigationId": investigation_id}
128
136
 
129
137
  url = self._parcel_url.format(session_id=self.session_id)
130
- response = requests.get(url, params=params)
138
+ response = requests.get(url, params=params) # noqa: S113 -- trusted internal ICAT+ service
131
139
  response.raise_for_status()
132
140
  return response.json()
133
141
 
@@ -161,7 +169,7 @@ class IcatPlusRestrictedClient:
161
169
  "Authorization": f"Bearer {self.session_id}",
162
170
  }
163
171
  url = self._dataset_url
164
- response = requests.get(url, params=params, headers=headers)
172
+ response = requests.get(url, params=params, headers=headers) # noqa: S113 -- trusted internal ICAT+ service
165
173
  response.raise_for_status()
166
174
  return response.json()
167
175
 
@@ -173,7 +181,7 @@ class IcatPlusRestrictedClient:
173
181
  :raises RuntimeError: No session ID is available.
174
182
  """
175
183
  url = self._session_info_url.format(session_id=self.session_id)
176
- response = requests.get(url)
184
+ response = requests.get(url) # noqa: S113 -- trusted internal ICAT+ service
177
185
  response.raise_for_status()
178
186
  return response.json()
179
187
 
@@ -209,7 +217,7 @@ class IcatPlusRestrictedClient:
209
217
  params = {k: v for k, v in params.items() if v is not None}
210
218
 
211
219
  url = self._sample_url.format(session_id=self.session_id)
212
- response = requests.get(url, params=params)
220
+ response = requests.get(url, params=params) # noqa: S113 -- trusted internal ICAT+ service
213
221
  response.raise_for_status()
214
222
  return response.json()
215
223
 
@@ -227,15 +235,35 @@ class IcatPlusRestrictedClient:
227
235
  params["parameters"] = filter
228
236
 
229
237
  url = self._sample_metadata_url.format(session_id=self.session_id)
230
- response = requests.get(url, params=params)
238
+ response = requests.get(url, params=params) # noqa: S113 -- trusted internal ICAT+ service
231
239
  response.raise_for_status()
232
240
  return response.json()
233
241
 
242
+ @deprecated_method
234
243
  def get_sample_files_information_by(self, sample_id: str) -> dict:
235
- params = {"sampleId": sample_id}
244
+ """DEPRECATED: This method is deprecated and may be removed in future versions.
245
+ Use `get_sample_information_list_by` instead.
246
+
247
+ Returns a single sample's file information, or an empty dict when not found.
248
+ """
249
+ results = self.get_sample_information_list_by(sample_id=sample_id)
250
+ return results[0] if results else {}
251
+
252
+ def get_sample_information_list_by(
253
+ self,
254
+ sample_id: Optional[str] = None,
255
+ investigation_id: Optional[str] = None,
256
+ ) -> List[dict]:
257
+ if sample_id is None and investigation_id is None:
258
+ raise ValueError(
259
+ "Either 'sample_id' or 'investigation_id' must be provided."
260
+ )
261
+
262
+ params = {"sampleId": sample_id, "investigationId": investigation_id}
263
+ params = {k: v for k, v in params.items() if v is not None}
236
264
 
237
265
  url = self._sample_files_url.format(session_id=self.session_id)
238
- response = requests.get(url, params=params)
266
+ response = requests.get(url, params=params) # noqa: S113 -- trusted internal ICAT+ service
239
267
  response.raise_for_status()
240
268
  return response.json()
241
269
 
@@ -250,7 +278,7 @@ class IcatPlusRestrictedClient:
250
278
 
251
279
  url = self._download_file_url.format(session_id=self.session_id)
252
280
  if use_chunks:
253
- with requests.get(url, params=params, stream=True) as response:
281
+ with requests.get(url, params=params, stream=True) as response: # noqa: S113 -- trusted internal ICAT+ service
254
282
  response.raise_for_status()
255
283
  return b"".join(
256
284
  chunk
@@ -258,7 +286,7 @@ class IcatPlusRestrictedClient:
258
286
  if chunk
259
287
  )
260
288
  else:
261
- response = requests.get(url, params=params)
289
+ response = requests.get(url, params=params) # noqa: S113 -- trusted internal ICAT+ service
262
290
  response.raise_for_status()
263
291
  return response.content
264
292
 
@@ -269,7 +297,7 @@ class IcatPlusRestrictedClient:
269
297
  url = self._investigation_users_url.format(
270
298
  session_id=self.session_id, investigation_id=investigation_id
271
299
  )
272
- response = requests.get(url)
300
+ response = requests.get(url) # noqa: S113 -- trusted internal ICAT+ service
273
301
  response.raise_for_status()
274
302
  return response.json()
275
303
 
@@ -14,6 +14,7 @@ from .types import ArchiveStatusType as StatusType # noqa F401 deprecated
14
14
  from .types import Dataset
15
15
  from .types import DatasetId
16
16
  from .types import DatasetMetadata # noqa F401
17
+ from .types import InvestigationType
17
18
  from .types import InvestigationUser
18
19
  from .types import SampleMetadata
19
20
 
@@ -143,7 +144,7 @@ class IcatClientInterface:
143
144
  store_filename: Optional[str] = None,
144
145
  ):
145
146
  """
146
- Request icat to store raw dataset.
147
+ Request icat to store a raw dataset, i.e. a dataset without other datasets as input.
147
148
 
148
149
  :param beamline str: beamline name like id01, id15a, bm18...
149
150
  :param proposal str: proposal name like in1169, blc14795, ihma429...
@@ -166,14 +167,14 @@ class IcatClientInterface:
166
167
  store_filename: Optional[str] = None,
167
168
  ):
168
169
  """
169
- Request icat to store a processed dataset.
170
+ Request icat to store a processed dataset, i.e. a dataset with other datasets as input.
170
171
 
171
172
  :param beamline str: beamline name like id01, id15a, bm18...
172
173
  :param proposal str: proposal name like in1169, blc14795, ihma429...
173
174
  :param str dataset: dataset name like sample_XYZ.
174
175
  :param str path: path to the processed dataset to store. Can be a file or a folder.
175
176
  :param dict metadata: metadata to associate with the dataset. Must contain keys defined by the appropriate application definition, available at https://icat-esrf-definitions.readthedocs.io.
176
- :param tuple raw: Path(s) to the raw dataset(s). Should point to the 'bliss dataset' folder(s).
177
+ :param tuple raw: Path(s) to the input dataset(s), raw or processed. Should point to the dataset folder(s).
177
178
  If processing relies on multiple datasets, all corresponding folders must be provided.
178
179
  :param str store_filename: xml file with metadata to be stored.
179
180
  """
@@ -413,6 +414,15 @@ class IcatClientInterface:
413
414
  """
414
415
  raise NotImplementedError
415
416
 
417
+ def get_investigation_types(self) -> List[InvestigationType]:
418
+ """
419
+ Returns a list of investigation types. Does not require authentication.
420
+ API Reference: https://icatplus.esrf.fr/api-docs/#/Catalogue/get_catalogue_investigationtypes
421
+
422
+ :returns: list of investigation types.
423
+ """
424
+ raise NotImplementedError
425
+
416
426
  def get_parcels_by(self, investigation_id: str) -> List[dict]:
417
427
  """
418
428
  Returns the list of parcels associated to an investigation.
@@ -448,12 +458,31 @@ class IcatClientInterface:
448
458
  """
449
459
  raise NotImplementedError
450
460
 
461
+ @deprecated_method
451
462
  def get_sample_files_information_by(self, sample_id: str) -> dict:
452
- """
463
+ """DEPRECATED: This method is deprecated and may be removed in future versions.
464
+ Use `get_sample_information_list_by` instead.
465
+
453
466
  Returns the sample file information for a given sample.
454
467
 
455
468
  :param sample_id: Sample identifier.
456
- :returns: Sample file information as dict.
469
+ :returns: Sample file information as dict, or an empty dict when no match is found.
470
+ """
471
+ raise NotImplementedError
472
+
473
+ def get_sample_information_list_by(
474
+ self,
475
+ sample_id: Optional[str] = None,
476
+ investigation_id: Optional[str] = None,
477
+ ) -> List[dict]:
478
+ """
479
+ Returns the list of sample file information for a given sample and/or
480
+ investigation.
481
+
482
+ :param sample_id: Sample identifier.
483
+ :param investigation_id: Investigation identifier.
484
+ :returns: List of sample file information.
485
+ :raises ValueError: Neither `sample_id` nor `investigation_id` is provided.
457
486
  """
458
487
  raise NotImplementedError
459
488
 
@@ -23,6 +23,7 @@ from .types import ArchiveStatusType
23
23
  from .types import ArchiveStatusType as StatusType # noqa F401 deprecated
24
24
  from .types import Dataset
25
25
  from .types import DatasetId
26
+ from .types import InvestigationType
26
27
  from .types import InvestigationUser
27
28
  from .types import SampleMetadata
28
29
  from .update_metadata import IcatUpdateMetadataClient
@@ -666,7 +667,8 @@ class IcatClient(IcatClientInterface):
666
667
  :param dataset: The dataset name.
667
668
  :param path: The path to the dataset on disk.
668
669
  :param metadata: A dictionary of metadata to be attached to the dataset.
669
- :param raw: shorthand for ``metadata = {'input_datasets': ...}``.
670
+ :param raw: Path(s) to the input dataset(s), raw or processed. Shorthand for
671
+ ``metadata = {'input_datasets': ...}``.
670
672
  :param store_filename: Save metadata in this file instead of sending it to ICAT.
671
673
  :param strict: Strict metadata validation.
672
674
 
@@ -687,7 +689,7 @@ class IcatClient(IcatClientInterface):
687
689
  else:
688
690
  metadata["input_datasets"] = [raw]
689
691
  if not metadata.get("input_datasets"):
690
- raise ValueError("Provide 'raw' dataset directories")
692
+ raise ValueError("Provide 'raw' input dataset directories")
691
693
  self.store_dataset(
692
694
  beamline=beamline,
693
695
  proposal=proposal,
@@ -890,6 +892,9 @@ class IcatClient(IcatClientInterface):
890
892
  parameters=parameters,
891
893
  )
892
894
 
895
+ def get_investigation_types(self) -> List[InvestigationType]:
896
+ return self._icatplus_restricted_client.get_investigation_types()
897
+
893
898
  def get_samples_by(
894
899
  self,
895
900
  investigation_id: Optional[str] = None,
@@ -929,6 +934,15 @@ class IcatClient(IcatClientInterface):
929
934
  sample_id
930
935
  )
931
936
 
937
+ def get_sample_information_list_by(
938
+ self,
939
+ sample_id: Optional[str] = None,
940
+ investigation_id: Optional[str] = None,
941
+ ) -> List[dict]:
942
+ return self._icatplus_restricted_client.get_sample_information_list_by(
943
+ sample_id, investigation_id
944
+ )
945
+
932
946
  def download_file_by(
933
947
  self,
934
948
  sample_id: str,
@@ -26,7 +26,7 @@ class IcatMessagingClient:
26
26
  MONITOR_SCHEME = "http"
27
27
  DEFAULT_MONITOR_PORT = 8778
28
28
  MONITOR_USER = "user"
29
- MONITOR_PWD = "user"
29
+ MONITOR_PWD = "user" # noqa: S105 -- default placeholder credential, not a secret
30
30
 
31
31
  def __init__(
32
32
  self,
@@ -112,7 +112,7 @@ class IcatMessagingClient:
112
112
  @property
113
113
  def _consumer_count(self):
114
114
  url = self.__consumer_count_url.format(host=self._host)
115
- response = requests.get(url, headers=self.__jolokia_headers)
115
+ response = requests.get(url, headers=self.__jolokia_headers) # noqa: S113 -- trusted internal ActiveMQ broker
116
116
  if not response.ok:
117
117
  raise RuntimeError(
118
118
  response, "Failed to retrieve the ActiveMQ consumer count"
@@ -91,6 +91,9 @@ class IcatNullClient(IcatClientInterface):
91
91
  def get_sample_files_information_by(self, *args, **kwargs) -> None:
92
92
  pass
93
93
 
94
+ def get_sample_information_list_by(self, *args, **kwargs) -> None:
95
+ pass
96
+
94
97
  def download_file_by(self, *args, **kwargs) -> None:
95
98
  pass
96
99
 
@@ -73,3 +73,13 @@ class InvestigationUser(TypedDict):
73
73
  orcid: Optional[str]
74
74
  affiliation: Optional[str]
75
75
  role: str
76
+
77
+
78
+ class InvestigationType(TypedDict):
79
+ """
80
+ Investigation type information
81
+ """
82
+
83
+ id: str
84
+ name: str
85
+ description: Optional[str]
@@ -314,7 +314,6 @@ def load_group(in_node: ElementTree.Element, **node_attributes) -> IcatFieldGrou
314
314
  nodes[icat_item.name] = icat_item
315
315
 
316
316
  for group_name, element in groups:
317
-
318
317
  technique_names = _GROUP_NAME_TO_TECHNIQUES.get(group_name, None)
319
318
  if technique_names:
320
319
  metadata_generator = technique.get_technique_metadata(*technique_names)
@@ -367,7 +366,7 @@ def icat_fields_source(url: Optional[str] = None) -> Union[str, StringIO]:
367
366
  if url.startswith("file://") or url.startswith("xml://"):
368
367
  return re.sub(r"^[a-z]+://", "", url)
369
368
  try:
370
- response = requests.get(url)
369
+ response = requests.get(url) # noqa: S113 -- trusted ESRF-hosted definitions URL
371
370
  response.raise_for_status()
372
371
  except Exception:
373
372
  working_url = "https://gitlab.esrf.fr/icat/hdf5-master-config/-/raw/master/src/icat_esrf_definitions/hdf5_cfg.xml"
@@ -385,7 +384,7 @@ def load_icat_fields(url: Optional[str] = None) -> IcatFieldGroup:
385
384
  "file://", "xml://" or anything the `requests` library can handle.
386
385
  :returns: a mapping object representing the tree relationship of ICAT fields
387
386
  """
388
- tree = ElementTree.parse(icat_fields_source(url=url))
387
+ tree = ElementTree.parse(icat_fields_source(url=url)) # noqa: S314 -- trusted ESRF-hosted definitions file
389
388
  in_node = tree.getroot()
390
389
  return load_group(
391
390
  in_node, name="", parent=IcatNodeId(), NX_class="NXentry", techniques=set()
@@ -5,7 +5,6 @@ from esrf_ontologies import technique
5
5
 
6
6
  from ..errors import IcatMetadataFutureWarning
7
7
  from ..metadata.definitions import load_icat_fields
8
- from .fixtures.icat import * # noqa F401
9
8
 
10
9
  pytest.register_assert_rewrite("pyicat_plus.tests.utils.compare")
11
10
 
@@ -377,7 +377,7 @@ class _Investigations:
377
377
  investigation = {
378
378
  "id": len(self._investigations),
379
379
  "startDate": startdate.astimezone().isoformat(),
380
- "unique": random.uniform(0, 1),
380
+ "unique": random.uniform(0, 1), # noqa: S311 -- test data, not crypto
381
381
  }
382
382
  if end_offset is not None:
383
383
  enddate = self._date + end_offset
@@ -27,7 +27,11 @@ def test_login(icatplus_restricted_client):
27
27
  def test_login_with_username_plugin(icatplus_restricted_client):
28
28
  client, messages = icatplus_restricted_client
29
29
 
30
- result = client.login(password="correct", username="john", plugin="db")
30
+ result = client.login(
31
+ password="correct", # noqa: S106 -- test server sentinel value
32
+ username="john",
33
+ plugin="db",
34
+ )
31
35
 
32
36
  assert result["sessionId"] == client.session_id
33
37
  assert messages.empty()
@@ -230,13 +234,41 @@ def test_get_sample_files_information_by(icatplus_restricted_client):
230
234
 
231
235
  sample_id = generate.icat_id()
232
236
 
237
+ with pytest.warns(DeprecationWarning, match="get_sample_files_information_by"):
238
+ with pytest.raises(RuntimeError, match="Login is required."):
239
+ _ = client.get_sample_files_information_by(sample_id)
240
+
241
+ assert client.login("correct")
242
+
243
+ with pytest.warns(DeprecationWarning, match="get_sample_files_information_by"):
244
+ sample_files = client.get_sample_files_information_by(sample_id)
245
+
246
+ assert sample_files == {}
247
+ assert messages.empty()
248
+
249
+
250
+ def test_get_sample_information_list_by(icatplus_restricted_client):
251
+ client, messages = icatplus_restricted_client
252
+
253
+ sample_id = generate.icat_id()
254
+ investigation_id = generate.icat_id()
255
+
233
256
  with pytest.raises(RuntimeError, match="Login is required."):
234
- _ = client.get_sample_files_information_by(sample_id)
257
+ _ = client.get_sample_information_list_by(sample_id=sample_id)
235
258
 
236
259
  assert client.login("correct")
237
260
 
238
- sample_files = client.get_sample_files_information_by(sample_id)
261
+ with pytest.raises(
262
+ ValueError, match="Either 'sample_id' or 'investigation_id' must be provided."
263
+ ):
264
+ client.get_sample_information_list_by()
265
+
266
+ sample_files = client.get_sample_information_list_by(sample_id=sample_id)
267
+ assert sample_files == []
239
268
 
269
+ sample_files = client.get_sample_information_list_by(
270
+ investigation_id=investigation_id
271
+ )
240
272
  assert sample_files == []
241
273
  assert messages.empty()
242
274
 
@@ -292,6 +324,16 @@ def test_get_investigation_users(icatplus_restricted_client):
292
324
  assert messages.empty()
293
325
 
294
326
 
327
+ def test_get_investigation_types(icatplus_restricted_client):
328
+ client, messages = icatplus_restricted_client
329
+
330
+ investigation_types = client.get_investigation_types()
331
+
332
+ assert isinstance(investigation_types, list)
333
+ assert investigation_types
334
+ assert messages.empty()
335
+
336
+
295
337
  def _release_date_from_start_date(start_date_str: str) -> datetime.date:
296
338
  start = datetime.datetime.fromisoformat(start_date_str[:19])
297
339
  return start.date().replace(year=start.year + 3)
@@ -83,7 +83,7 @@ def _set_expected_value(expected: dict, value: str, *keys) -> None:
83
83
 
84
84
  if not isinstance(param_list, list):
85
85
  raise TypeError(
86
- f"Expected list at '{'.'.join(keys[:i+1])}', got {type(param_list).__name__}"
86
+ f"Expected list at '{'.'.join(keys[: i + 1])}', got {type(param_list).__name__}"
87
87
  )
88
88
 
89
89
  # Do not overwrite existing parameter
@@ -127,7 +127,7 @@ def _set_expected_to_actual_value(actual: dict, expected: dict, *keys: str) -> N
127
127
 
128
128
  if not isinstance(value, list):
129
129
  raise TypeError(
130
- f"Expected list at '{'.'.join(keys[:i+1])}', "
130
+ f"Expected list at '{'.'.join(keys[: i + 1])}', "
131
131
  f"got {type(value).__name__}"
132
132
  )
133
133
 
@@ -0,0 +1,5 @@
1
+ import random
2
+
3
+
4
+ def icat_id() -> int:
5
+ return random.randint(1000000000, 9999999999) # noqa: S311 -- test data, not crypto