minedrac 0.0.1__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.
- minedrac-0.0.1/LICENSE.md +20 -0
- minedrac-0.0.1/PKG-INFO +27 -0
- minedrac-0.0.1/README.md +56 -0
- minedrac-0.0.1/pyproject.toml +73 -0
- minedrac-0.0.1/setup.cfg +4 -0
- minedrac-0.0.1/src/authentication/cli.py +30 -0
- minedrac-0.0.1/src/data/__init__.py +0 -0
- minedrac-0.0.1/src/data/dataset.py +64 -0
- minedrac-0.0.1/src/data/instrument.py +39 -0
- minedrac-0.0.1/src/data/investigation.py +65 -0
- minedrac-0.0.1/src/data/parcel.py +59 -0
- minedrac-0.0.1/src/data/sample.py +37 -0
- minedrac-0.0.1/src/data/session.py +53 -0
- minedrac-0.0.1/src/invoicing/__init__.py +0 -0
- minedrac-0.0.1/src/invoicing/cli.py +148 -0
- minedrac-0.0.1/src/invoicing/duration.py +152 -0
- minedrac-0.0.1/src/logger_utils/__init__.py +0 -0
- minedrac-0.0.1/src/logger_utils/csv_formatter.py +19 -0
- minedrac-0.0.1/src/logger_utils/time_formatter.py +10 -0
- minedrac-0.0.1/src/minedrac/__init__.py +0 -0
- minedrac-0.0.1/src/minedrac.egg-info/PKG-INFO +27 -0
- minedrac-0.0.1/src/minedrac.egg-info/SOURCES.txt +42 -0
- minedrac-0.0.1/src/minedrac.egg-info/dependency_links.txt +1 -0
- minedrac-0.0.1/src/minedrac.egg-info/entry_points.txt +2 -0
- minedrac-0.0.1/src/minedrac.egg-info/requires.txt +24 -0
- minedrac-0.0.1/src/minedrac.egg-info/top_level.txt +10 -0
- minedrac-0.0.1/src/performance/__init__.py +0 -0
- minedrac-0.0.1/src/performance/cli.py +88 -0
- minedrac-0.0.1/src/stats/__init__.py +0 -0
- minedrac-0.0.1/src/stats/cli.py +14 -0
- minedrac-0.0.1/src/stats/datasets.py +11 -0
- minedrac-0.0.1/src/stats/investigation_parameters.py +29 -0
- minedrac-0.0.1/src/stats/samples.py +12 -0
- minedrac-0.0.1/src/stats/technique/cli.py +107 -0
- minedrac-0.0.1/src/stats/technique/technique.py +148 -0
- minedrac-0.0.1/src/stats/volume/cli.py +192 -0
- minedrac-0.0.1/src/stats/volume/volume.py +144 -0
- minedrac-0.0.1/src/tests/invoicing/test_calculate_duration.py +175 -0
- minedrac-0.0.1/src/tests/statistics/test_calculate_volume.py +77 -0
- minedrac-0.0.1/src/tests/statistics/test_technique.py +76 -0
- minedrac-0.0.1/src/tests_integration/test_technique.py +43 -0
- minedrac-0.0.1/src/utils/__init__.py +0 -0
- minedrac-0.0.1/src/utils/timing.py +15 -0
- minedrac-0.0.1/src/utils/units.py +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
**Copyright (c) 2021 European Synchrotron Radiation Facility**
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
minedrac-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: minedrac
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Requires-Python: >=3.11
|
|
5
|
+
License-File: LICENSE.md
|
|
6
|
+
Requires-Dist: icat-plus-client
|
|
7
|
+
Requires-Dist: typer
|
|
8
|
+
Requires-Dist: python-dotenv
|
|
9
|
+
Requires-Dist: pandas
|
|
10
|
+
Provides-Extra: test
|
|
11
|
+
Requires-Dist: pytest; extra == "test"
|
|
12
|
+
Requires-Dist: pytest-spec; extra == "test"
|
|
13
|
+
Requires-Dist: pytest-dotenv; extra == "test"
|
|
14
|
+
Provides-Extra: docs
|
|
15
|
+
Requires-Dist: sphinx<9,>=7; extra == "docs"
|
|
16
|
+
Requires-Dist: myst-parser<5,>=3; extra == "docs"
|
|
17
|
+
Requires-Dist: sphinx-click>=6; extra == "docs"
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: typer>=0.20; extra == "dev"
|
|
20
|
+
Requires-Dist: pandas>=2.3; extra == "dev"
|
|
21
|
+
Requires-Dist: black>=25; extra == "dev"
|
|
22
|
+
Requires-Dist: flake8>=4; extra == "dev"
|
|
23
|
+
Requires-Dist: ruff; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-spec; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-dotenv; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
minedrac-0.0.1/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# MineDRAC
|
|
2
|
+
|
|
3
|
+
## Configuration
|
|
4
|
+
|
|
5
|
+
### Logging
|
|
6
|
+
|
|
7
|
+
You can manage the logging level with an env variable:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
export LOG_LEVEL=DEBUG
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### ICAT+
|
|
14
|
+
|
|
15
|
+
MineDRAC is configured to use the official ESRF icat+ server by default: https://icatplus.esrf.fr
|
|
16
|
+
|
|
17
|
+
It can be configured to use any other server via a configuration `.env` file or export the variable `icat_plus`
|
|
18
|
+
Example:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
export icat_plus_server="http://localhost:8000"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
You can find out which server is pointing to by using the command:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
> minedrac config icat_plus
|
|
28
|
+
http://localhost:8083
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Commands
|
|
33
|
+
|
|
34
|
+
#### login
|
|
35
|
+
|
|
36
|
+
This command allows to get the token of a DB user (this will not work for user accounts). For getting your token you can access it via the data portal.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
> minedrac login authentication --username reader --password ****
|
|
40
|
+
c99d6e5d-096b-45f1-bde3-c4322fe15251
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### Retrieve
|
|
45
|
+
|
|
46
|
+
This command family allows to retrieve data from ICAT via ICAT+.
|
|
47
|
+
|
|
48
|
+
##### Dataset
|
|
49
|
+
|
|
50
|
+
This method will return the json serialization of the datasets with the filters applied.
|
|
51
|
+
|
|
52
|
+
You can list the filters by typing:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
minedrac retrieve dataset --help
|
|
56
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "minedrac"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"icat-plus-client",
|
|
11
|
+
"typer",
|
|
12
|
+
"python-dotenv",
|
|
13
|
+
"pandas"
|
|
14
|
+
]
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
test = [
|
|
17
|
+
"pytest",
|
|
18
|
+
"pytest-spec",
|
|
19
|
+
"pytest-dotenv"
|
|
20
|
+
]
|
|
21
|
+
docs = [
|
|
22
|
+
"sphinx>=7,<9",
|
|
23
|
+
"myst-parser>=3,<5",
|
|
24
|
+
"sphinx-click>=6",
|
|
25
|
+
]
|
|
26
|
+
dev = [
|
|
27
|
+
"typer >=0.20",
|
|
28
|
+
"pandas >=2.3",
|
|
29
|
+
"black >=25",
|
|
30
|
+
"flake8 >=4",
|
|
31
|
+
"ruff",
|
|
32
|
+
"pytest",
|
|
33
|
+
"pytest-spec",
|
|
34
|
+
"pytest-dotenv"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
[tool.setuptools]
|
|
39
|
+
package-dir = {"" = "src"}
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
|
|
44
|
+
[tool.black]
|
|
45
|
+
line-length = 110
|
|
46
|
+
target-version = ["py39", "py310", "py311"] # adjust to your versions
|
|
47
|
+
include = '\.pyi?$'
|
|
48
|
+
exclude = '''
|
|
49
|
+
/(
|
|
50
|
+
\.git
|
|
51
|
+
| \.mypy_cache
|
|
52
|
+
| \.pytest_cache
|
|
53
|
+
| build
|
|
54
|
+
| dist
|
|
55
|
+
)/
|
|
56
|
+
'''
|
|
57
|
+
|
|
58
|
+
[tool.ruff]
|
|
59
|
+
line-length = 110
|
|
60
|
+
target-version = "py311"
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint]
|
|
63
|
+
select = [
|
|
64
|
+
"E", # errors
|
|
65
|
+
"F", # pyflakes
|
|
66
|
+
"I", # import sorting
|
|
67
|
+
"B", # flake8-bugbear
|
|
68
|
+
"UP", # pyupgrade
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
[project.scripts]
|
|
73
|
+
minedrac = "cli:main"
|
minedrac-0.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from data import session
|
|
4
|
+
|
|
5
|
+
authentication_app = typer.Typer(help="Authentication commands")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@authentication_app.command("authenticate", help="Gets the ICAT's token based on username and password")
|
|
9
|
+
def authenticate_cli(
|
|
10
|
+
authenticator: str = typer.Option(
|
|
11
|
+
"db",
|
|
12
|
+
"-a",
|
|
13
|
+
"--authenticator",
|
|
14
|
+
help="Plugin authentication. it can be db or esrf. Default is db",
|
|
15
|
+
),
|
|
16
|
+
username: str = typer.Option(..., "-u", "--username", help="ICAT username"),
|
|
17
|
+
password: str = typer.Option(..., "-p", "--password", help="ICAT password"),
|
|
18
|
+
):
|
|
19
|
+
"""Retrieve investigations by session_id."""
|
|
20
|
+
session_response = session.get_session(authenticator, username, password)
|
|
21
|
+
typer.echo(session_response)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@authentication_app.command("info", help="Gets the ICAT's session information based on a valid ICAT's token")
|
|
25
|
+
def get_session_info(
|
|
26
|
+
token: str = typer.Option(..., "-t", "--token", help="ICAT token"),
|
|
27
|
+
):
|
|
28
|
+
"""Retrieve investigations by session_id."""
|
|
29
|
+
session_response = session.get_info(token)
|
|
30
|
+
typer.echo(session_response)
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import icat_plus_client
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
from icat_plus_client.models.dataset import Dataset
|
|
6
|
+
|
|
7
|
+
from config import configuration
|
|
8
|
+
|
|
9
|
+
# Load .env if needed
|
|
10
|
+
load_dotenv()
|
|
11
|
+
|
|
12
|
+
# Setup logging
|
|
13
|
+
logging.basicConfig(level=logging.INFO)
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_datasets(
|
|
18
|
+
session_id: str,
|
|
19
|
+
investigation_ids: str | None = None,
|
|
20
|
+
instrument_name: str | None = None,
|
|
21
|
+
dataset_ids: str | None = None,
|
|
22
|
+
dataset_type: str | None = None,
|
|
23
|
+
nested: str | None = None,
|
|
24
|
+
sample_id: str | None = None,
|
|
25
|
+
limit: int | None = None,
|
|
26
|
+
skip: int | None = None,
|
|
27
|
+
search: str | None = None,
|
|
28
|
+
sort_by: str | None = None,
|
|
29
|
+
sort_order: str | None = None,
|
|
30
|
+
) -> list[Dataset]:
|
|
31
|
+
with icat_plus_client.ApiClient(configuration) as api_client:
|
|
32
|
+
api_instance = icat_plus_client.CatalogueApi(api_client)
|
|
33
|
+
try:
|
|
34
|
+
datasets: list[Dataset] = api_instance.catalogue_session_id_dataset_get(
|
|
35
|
+
session_id,
|
|
36
|
+
investigation_ids=investigation_ids,
|
|
37
|
+
dataset_ids=dataset_ids,
|
|
38
|
+
sample_id=sample_id,
|
|
39
|
+
instrument_name=instrument_name,
|
|
40
|
+
limit=limit,
|
|
41
|
+
skip=skip,
|
|
42
|
+
search=search,
|
|
43
|
+
dataset_type=dataset_type,
|
|
44
|
+
nested=nested,
|
|
45
|
+
sort_by=sort_by,
|
|
46
|
+
sort_order=sort_order,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if datasets:
|
|
50
|
+
logger.debug("Fetched %d datasets successfully.", len(datasets))
|
|
51
|
+
return datasets
|
|
52
|
+
else:
|
|
53
|
+
logger.debug("No datasets found for the given parameters.")
|
|
54
|
+
return []
|
|
55
|
+
|
|
56
|
+
except icat_plus_client.rest.ApiException as api_exc:
|
|
57
|
+
logger.error(
|
|
58
|
+
"API Exception when CatalogueApi->catalogue_session_id_dataset_get: %s",
|
|
59
|
+
api_exc,
|
|
60
|
+
)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.exception("Unexpected error while fetching datasets: %s", e)
|
|
63
|
+
|
|
64
|
+
return []
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import icat_plus_client
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
from icat_plus_client.models.instrument import Instrument
|
|
6
|
+
|
|
7
|
+
from config import configuration
|
|
8
|
+
|
|
9
|
+
# Load .env if needed
|
|
10
|
+
load_dotenv()
|
|
11
|
+
|
|
12
|
+
# Setup logging
|
|
13
|
+
logging.basicConfig(level=logging.INFO)
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_instruments() -> list[Instrument]:
|
|
18
|
+
"""Fetch all instruments from ICAT Catalogue."""
|
|
19
|
+
with icat_plus_client.ApiClient(configuration) as api_client:
|
|
20
|
+
api_instance = icat_plus_client.CatalogueApi(api_client)
|
|
21
|
+
try:
|
|
22
|
+
instruments: list[Instrument] = api_instance.catalogue_instruments_get()
|
|
23
|
+
|
|
24
|
+
if instruments:
|
|
25
|
+
logger.debug("Fetched %d instruments successfully.", len(instruments))
|
|
26
|
+
return instruments
|
|
27
|
+
else:
|
|
28
|
+
logger.warning("No instruments found in the catalogue.")
|
|
29
|
+
return []
|
|
30
|
+
|
|
31
|
+
except icat_plus_client.rest.ApiException as api_exc:
|
|
32
|
+
logger.error(
|
|
33
|
+
"API Exception when calling CatalogueApi->catalogue_instruments_get: %s",
|
|
34
|
+
api_exc,
|
|
35
|
+
)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
logger.exception("Unexpected error while fetching instruments: %s", e)
|
|
38
|
+
|
|
39
|
+
return []
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import icat_plus_client
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
from icat_plus_client.models.investigation import Investigation
|
|
6
|
+
|
|
7
|
+
from config import configuration
|
|
8
|
+
|
|
9
|
+
# Load .env if needed
|
|
10
|
+
load_dotenv()
|
|
11
|
+
|
|
12
|
+
# Setup logging
|
|
13
|
+
logging.basicConfig(level=logging.INFO)
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_investigation_by_id(
|
|
18
|
+
session_id: str,
|
|
19
|
+
investigation_id: str | None = None,
|
|
20
|
+
instrument_name: str | None = None,
|
|
21
|
+
investigation_name: str | None = None,
|
|
22
|
+
filter: str | None = None,
|
|
23
|
+
start_date: str | None = None,
|
|
24
|
+
end_date: str | None = None,
|
|
25
|
+
limit: int | None = None,
|
|
26
|
+
skip: int | None = None,
|
|
27
|
+
search: str | None = None,
|
|
28
|
+
sort_by: str | None = None,
|
|
29
|
+
sort_order: str | None = None,
|
|
30
|
+
with_has_access: bool | None = None,
|
|
31
|
+
) -> list[Investigation]:
|
|
32
|
+
"""Fetch investigations by session ID with optional filters."""
|
|
33
|
+
|
|
34
|
+
with icat_plus_client.ApiClient(configuration) as api_client:
|
|
35
|
+
api_instance = icat_plus_client.CatalogueApi(api_client)
|
|
36
|
+
try:
|
|
37
|
+
investigations: list[Investigation] = api_instance.catalogue_session_id_investigation_get(
|
|
38
|
+
session_id=session_id,
|
|
39
|
+
instrument_name=instrument_name,
|
|
40
|
+
investigation_name=investigation_name,
|
|
41
|
+
ids=investigation_id,
|
|
42
|
+
filter=filter,
|
|
43
|
+
start_date=start_date,
|
|
44
|
+
end_date=end_date,
|
|
45
|
+
limit=limit,
|
|
46
|
+
skip=skip,
|
|
47
|
+
search=search,
|
|
48
|
+
sort_by=sort_by,
|
|
49
|
+
sort_order=sort_order,
|
|
50
|
+
with_has_access=with_has_access,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if investigations:
|
|
54
|
+
logger.debug("Fetched %d investigations successfully.", len(investigations))
|
|
55
|
+
return investigations
|
|
56
|
+
else:
|
|
57
|
+
logger.debug("No investigations found for the given parameters.")
|
|
58
|
+
return []
|
|
59
|
+
|
|
60
|
+
except icat_plus_client.rest.ApiException as api_exc:
|
|
61
|
+
logger.error(f"API Exception CatalogueApi->catalogue_session_id_investigation_get: {api_exc}")
|
|
62
|
+
except Exception as e:
|
|
63
|
+
logger.exception("Unexpected error while fetching investigations: %s", e)
|
|
64
|
+
|
|
65
|
+
return []
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import icat_plus_client
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
from icat_plus_client.models.item import Item
|
|
6
|
+
from icat_plus_client.models.parcel import Parcel
|
|
7
|
+
|
|
8
|
+
from config import configuration
|
|
9
|
+
|
|
10
|
+
# Load .env if needed
|
|
11
|
+
load_dotenv()
|
|
12
|
+
|
|
13
|
+
# Setup logging
|
|
14
|
+
logging.basicConfig(level=logging.INFO)
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_parcels_by(
|
|
19
|
+
token: str,
|
|
20
|
+
investigation_id: str | None = None,
|
|
21
|
+
) -> list[Parcel]:
|
|
22
|
+
with icat_plus_client.ApiClient(configuration) as api_client:
|
|
23
|
+
# Create an instance of the API class
|
|
24
|
+
api_instance = icat_plus_client.TrackingApi(api_client)
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
# Lists shipments associated to an investigation
|
|
28
|
+
shipments = api_instance.tracking_session_id_shipment_get(
|
|
29
|
+
investigation_id=investigation_id, session_id=token
|
|
30
|
+
)
|
|
31
|
+
if len(shipments) == 1:
|
|
32
|
+
parcels = api_instance.tracking_session_id_parcel_get(
|
|
33
|
+
shipments[0].id, token, "", investigation_id=investigation_id
|
|
34
|
+
)
|
|
35
|
+
logger.debug("Fetched %d parcels successfully.", len(parcels))
|
|
36
|
+
if parcels:
|
|
37
|
+
return parcels
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.error(f"Exception when TrackingApi->tracking_session_id_parcel_get: {e}")
|
|
40
|
+
return []
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_declared_samples_by(
|
|
44
|
+
token: str,
|
|
45
|
+
investigation_id: str | None = None,
|
|
46
|
+
) -> list[Item]:
|
|
47
|
+
try:
|
|
48
|
+
parcels = get_parcels_by(token, investigation_id)
|
|
49
|
+
samples = []
|
|
50
|
+
for parcel in parcels:
|
|
51
|
+
for item in parcel.content:
|
|
52
|
+
if item.type == "CONTAINER":
|
|
53
|
+
samples.extend(item.content)
|
|
54
|
+
else:
|
|
55
|
+
samples.extend(item)
|
|
56
|
+
return samples
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.error(f"Exception when get_declared_samples_by: {e}")
|
|
59
|
+
return []
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import icat_plus_client
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
from icat_plus_client.models.sample import Sample
|
|
6
|
+
|
|
7
|
+
from config import configuration
|
|
8
|
+
|
|
9
|
+
# Load .env if needed
|
|
10
|
+
load_dotenv()
|
|
11
|
+
|
|
12
|
+
# Setup logging
|
|
13
|
+
logging.basicConfig(level=logging.INFO)
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_samples_by(
|
|
18
|
+
token: str,
|
|
19
|
+
investigation_id: str | None = None,
|
|
20
|
+
) -> list[Sample]:
|
|
21
|
+
with icat_plus_client.ApiClient(configuration) as api_client:
|
|
22
|
+
# Create an instance of the API class
|
|
23
|
+
api_instance = icat_plus_client.CatalogueApi(api_client)
|
|
24
|
+
investigation_id = investigation_id
|
|
25
|
+
try:
|
|
26
|
+
# Gets samples
|
|
27
|
+
samples = api_instance.catalogue_session_id_samples_get(
|
|
28
|
+
token, investigation_id=str(investigation_id)
|
|
29
|
+
)
|
|
30
|
+
if samples:
|
|
31
|
+
logger.debug("Fetched %d samples successfully.", len(samples))
|
|
32
|
+
return samples
|
|
33
|
+
else:
|
|
34
|
+
logger.debug("No samples found for the given parameters.")
|
|
35
|
+
return []
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f"Exception when CatalogueApi->catalogue_session_id_samples_get: {e}")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import icat_plus_client
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
from icat_plus_client.models.credentials import Credentials
|
|
6
|
+
from icat_plus_client.models.session import Session
|
|
7
|
+
|
|
8
|
+
from config import configuration
|
|
9
|
+
|
|
10
|
+
# Load .env if needed
|
|
11
|
+
load_dotenv()
|
|
12
|
+
|
|
13
|
+
# Setup logging
|
|
14
|
+
logging.basicConfig(level=logging.INFO)
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_session(authenticator: str, username: str, password: str):
|
|
19
|
+
"""Get the sessionId"""
|
|
20
|
+
with icat_plus_client.ApiClient(configuration) as api_client:
|
|
21
|
+
api_instance = icat_plus_client.SessionApi(api_client)
|
|
22
|
+
try:
|
|
23
|
+
cred = Credentials(
|
|
24
|
+
username=username,
|
|
25
|
+
password=password,
|
|
26
|
+
plugin=authenticator,
|
|
27
|
+
)
|
|
28
|
+
api_response = api_instance.session_post(cred)
|
|
29
|
+
return api_response.session_id
|
|
30
|
+
except icat_plus_client.rest.ApiException as api_exc:
|
|
31
|
+
logger.error(
|
|
32
|
+
"API Exception when calling SessionApi->session_post: %s",
|
|
33
|
+
api_exc,
|
|
34
|
+
)
|
|
35
|
+
except Exception as e:
|
|
36
|
+
logger.exception("Unexpected error while logging in: %s", e)
|
|
37
|
+
|
|
38
|
+
return []
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_info(token: str) -> Session:
|
|
42
|
+
"""Get the sessionId"""
|
|
43
|
+
with icat_plus_client.ApiClient(configuration) as api_client:
|
|
44
|
+
# Create an instance of the API class
|
|
45
|
+
api_instance = icat_plus_client.SessionApi(api_client)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
# Gets information about the session in ICAT
|
|
49
|
+
api_response: Session = api_instance.session_session_id_get(token)
|
|
50
|
+
return api_response
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(f"Exception when calling SessionApi->session_session_id_get: {e}\n")
|
|
File without changes
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import sys
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from data import dataset, investigation
|
|
8
|
+
from data.parcel import get_declared_samples_by, get_parcels_by
|
|
9
|
+
from invoicing import duration
|
|
10
|
+
from stats.volume.volume import get_volumes_by_investigation
|
|
11
|
+
|
|
12
|
+
invoicing_app = typer.Typer(help="Invoicing commands")
|
|
13
|
+
report_invoicing_app = typer.Typer(help="report commands")
|
|
14
|
+
invoicing_app.add_typer(report_invoicing_app, name="report")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@report_invoicing_app.command("report", help="Generates a report for invoicong for each investigation")
|
|
18
|
+
def invoicing_report(
|
|
19
|
+
token: str = typer.Option(..., "-t", "--token", help="ICAT session ID"),
|
|
20
|
+
investigation_ids: str | None = typer.Option(
|
|
21
|
+
None,
|
|
22
|
+
"--investigation-ids",
|
|
23
|
+
"-i",
|
|
24
|
+
help="Comma separated list of investigation IDs (optional)",
|
|
25
|
+
),
|
|
26
|
+
instrument_name: str | None = typer.Option(None, help="Filter by instrument name"),
|
|
27
|
+
investigation_name: str | None = typer.Option(None, help="Filter by instrument name"),
|
|
28
|
+
start_date: str = typer.Option(
|
|
29
|
+
None,
|
|
30
|
+
"-s",
|
|
31
|
+
"--start-date",
|
|
32
|
+
help="Start date range. Format is YYYY-MM-DD. Example: 2019-01-01",
|
|
33
|
+
),
|
|
34
|
+
end_date: str = typer.Option(
|
|
35
|
+
None,
|
|
36
|
+
"-e",
|
|
37
|
+
"--end-date",
|
|
38
|
+
help="End date range. Format is YYYY-MM-DD. Example: 2019-01-01",
|
|
39
|
+
),
|
|
40
|
+
unit: str = typer.Option(
|
|
41
|
+
"GB",
|
|
42
|
+
"-u",
|
|
43
|
+
"--unit",
|
|
44
|
+
help="It can be MB, GB or TB (otherwise will return in GB)",
|
|
45
|
+
),
|
|
46
|
+
merge_gap_minutes: int = typer.Option(
|
|
47
|
+
20,
|
|
48
|
+
"-g",
|
|
49
|
+
"--gap",
|
|
50
|
+
help="Gap in minutes to merge datasets. Less than 'gap' minutes the datasets time will be merged",
|
|
51
|
+
),
|
|
52
|
+
output: str = typer.Option(
|
|
53
|
+
None,
|
|
54
|
+
"-o",
|
|
55
|
+
"--output",
|
|
56
|
+
help="CSV output file path. Example: myfile.csv",
|
|
57
|
+
),
|
|
58
|
+
log: str = typer.Option(
|
|
59
|
+
None,
|
|
60
|
+
"-l",
|
|
61
|
+
"--log",
|
|
62
|
+
help="CSV log output file path. Example: myfile_log.csv",
|
|
63
|
+
),
|
|
64
|
+
ignore_longer_than: int = typer.Option(
|
|
65
|
+
None,
|
|
66
|
+
"-ig",
|
|
67
|
+
"--ignore",
|
|
68
|
+
help="The time in hours that will be taken into account to omit a dataset as considered an error.",
|
|
69
|
+
),
|
|
70
|
+
):
|
|
71
|
+
investigations = investigation.get_investigation_by_id(
|
|
72
|
+
session_id=token,
|
|
73
|
+
investigation_id=investigation_ids,
|
|
74
|
+
start_date=start_date,
|
|
75
|
+
end_date=end_date,
|
|
76
|
+
instrument_name=instrument_name,
|
|
77
|
+
investigation_name=investigation_name,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
fields = [
|
|
81
|
+
"proposal",
|
|
82
|
+
"investigation_id",
|
|
83
|
+
"beamline",
|
|
84
|
+
"start_date",
|
|
85
|
+
"end_date",
|
|
86
|
+
"booked_time",
|
|
87
|
+
"samples_collected",
|
|
88
|
+
"samples_declared",
|
|
89
|
+
"parcels",
|
|
90
|
+
"volume",
|
|
91
|
+
"raw_volume",
|
|
92
|
+
"processed_volume",
|
|
93
|
+
"effective_time",
|
|
94
|
+
"total_time",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
writers = [csv.DictWriter(sys.stdout, fieldnames=fields, delimiter="\t")]
|
|
98
|
+
# If file provided, also write to the file
|
|
99
|
+
if output:
|
|
100
|
+
f = open(output, "w", newline="")
|
|
101
|
+
writers.append(csv.DictWriter(f, fieldnames=fields, delimiter="\t"))
|
|
102
|
+
else:
|
|
103
|
+
f = None
|
|
104
|
+
|
|
105
|
+
# Write headers on all writers
|
|
106
|
+
for w in writers:
|
|
107
|
+
w.writeheader()
|
|
108
|
+
|
|
109
|
+
for inv in investigations:
|
|
110
|
+
(total, raw, processed) = get_volumes_by_investigation([inv], unit)
|
|
111
|
+
samples_declared = get_declared_samples_by(token, str(inv.id))
|
|
112
|
+
parcels = get_parcels_by(token, str(inv.id))
|
|
113
|
+
start = datetime.fromisoformat(inv.start_date)
|
|
114
|
+
end = datetime.fromisoformat(inv.end_date) if inv.end_date is not None else start
|
|
115
|
+
row = {
|
|
116
|
+
"proposal": inv.name,
|
|
117
|
+
"investigation_id": inv.id,
|
|
118
|
+
"beamline": inv.instrument.name,
|
|
119
|
+
"start_date": start.strftime("%Y-%m-%d %H:%M:%S"),
|
|
120
|
+
"end_date": end.strftime("%Y-%m-%d %H:%M:%S"),
|
|
121
|
+
"booked_time": end - start,
|
|
122
|
+
"volume": f"{total} {unit}",
|
|
123
|
+
"raw_volume": f"{raw} {unit}",
|
|
124
|
+
"processed_volume": f"{processed} {unit}",
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
"""Calculate total dataset duration for a session."""
|
|
128
|
+
datasets = dataset.get_datasets(
|
|
129
|
+
session_id=token,
|
|
130
|
+
investigation_ids=str(inv.id),
|
|
131
|
+
dataset_type="acquisition",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
samples_collected = sorted({d.sample_name for d in datasets if getattr(d, "sample_name", None)})
|
|
135
|
+
row["samples_collected"] = len(samples_collected)
|
|
136
|
+
row["samples_declared"] = len(samples_declared)
|
|
137
|
+
row["parcels"] = len(parcels)
|
|
138
|
+
row["total_time"] = duration.sum_dataset_durations(
|
|
139
|
+
datasets, merge_gap_minutes, log, ignore_longer_than
|
|
140
|
+
)
|
|
141
|
+
row["effective_time"] = duration.sum_dataset_durations(datasets, 0, None, ignore_longer_than)
|
|
142
|
+
|
|
143
|
+
for w in writers:
|
|
144
|
+
w.writerow(row)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
click_invoicing_app = typer.main.get_command(invoicing_app)
|
|
148
|
+
report_click_invoicing_app = typer.main.get_command(report_invoicing_app)
|