DIRACCommon 9.0.0a66__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.
- diraccommon-9.0.0a66/.gitignore +93 -0
- diraccommon-9.0.0a66/PKG-INFO +69 -0
- diraccommon-9.0.0a66/README.md +47 -0
- diraccommon-9.0.0a66/pyproject.toml +133 -0
- diraccommon-9.0.0a66/src/DIRACCommon/Utils/DErrno.py +327 -0
- diraccommon-9.0.0a66/src/DIRACCommon/Utils/ReturnValues.py +255 -0
- diraccommon-9.0.0a66/src/DIRACCommon/Utils/__init__.py +3 -0
- diraccommon-9.0.0a66/src/DIRACCommon/__init__.py +21 -0
- diraccommon-9.0.0a66/tests/Utils/test_DErrno.py +50 -0
- diraccommon-9.0.0a66/tests/Utils/test_ReturnValues.py +73 -0
- diraccommon-9.0.0a66/tests/__init__.py +1 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
*.py[cod]
|
|
3
|
+
|
|
4
|
+
# C extensions
|
|
5
|
+
*.so
|
|
6
|
+
var
|
|
7
|
+
sdist
|
|
8
|
+
lib
|
|
9
|
+
lib64
|
|
10
|
+
|
|
11
|
+
# Packages
|
|
12
|
+
*.egg
|
|
13
|
+
*.egg-info
|
|
14
|
+
dist
|
|
15
|
+
build
|
|
16
|
+
eggs
|
|
17
|
+
parts
|
|
18
|
+
bin
|
|
19
|
+
develop-eggs
|
|
20
|
+
.installed.cfg
|
|
21
|
+
|
|
22
|
+
# Translations
|
|
23
|
+
*.mo
|
|
24
|
+
|
|
25
|
+
# Mr Developer
|
|
26
|
+
.mr.developer.cfg
|
|
27
|
+
|
|
28
|
+
# Installer logs
|
|
29
|
+
pip-log.txt
|
|
30
|
+
|
|
31
|
+
# Unit test / coverage reports
|
|
32
|
+
.coverage
|
|
33
|
+
.tox
|
|
34
|
+
|
|
35
|
+
# Eclipse
|
|
36
|
+
.project
|
|
37
|
+
.pydevproject
|
|
38
|
+
.pyproject
|
|
39
|
+
.settings
|
|
40
|
+
.metadata
|
|
41
|
+
|
|
42
|
+
# Vim
|
|
43
|
+
.*.sw[a-z]
|
|
44
|
+
*.un~
|
|
45
|
+
Session.vim
|
|
46
|
+
|
|
47
|
+
# Emacs
|
|
48
|
+
*~
|
|
49
|
+
|
|
50
|
+
# Intellij
|
|
51
|
+
.idea/
|
|
52
|
+
DIRAC.iml
|
|
53
|
+
|
|
54
|
+
# MaxOSX files
|
|
55
|
+
.DS_Store
|
|
56
|
+
|
|
57
|
+
# test stuff
|
|
58
|
+
.pytest_cache
|
|
59
|
+
.cache
|
|
60
|
+
cache.db
|
|
61
|
+
__pycache__
|
|
62
|
+
pytests.xml
|
|
63
|
+
nosetests.xml
|
|
64
|
+
coverage.xml
|
|
65
|
+
Local_*
|
|
66
|
+
.hypothesis
|
|
67
|
+
virtualmachine/.vagrant/
|
|
68
|
+
|
|
69
|
+
integration_test_results/
|
|
70
|
+
tests/CI/CLIENTCONFIG
|
|
71
|
+
tests/CI/SERVERCONFIG
|
|
72
|
+
src/etc/
|
|
73
|
+
|
|
74
|
+
pilot.cfg
|
|
75
|
+
DIRAC_containers
|
|
76
|
+
docs/source/dirac.cfg
|
|
77
|
+
# VSCode
|
|
78
|
+
.vscode
|
|
79
|
+
.env
|
|
80
|
+
|
|
81
|
+
# docs
|
|
82
|
+
# this is auto generated
|
|
83
|
+
docs/source/CodeDocumentation/
|
|
84
|
+
docs/source/AdministratorGuide/Configuration/ExampleConfig.rst
|
|
85
|
+
docs/source/AdministratorGuide/CommandReference
|
|
86
|
+
docs/source/UserGuide/CommandReference
|
|
87
|
+
docs/_build
|
|
88
|
+
docs/source/_build
|
|
89
|
+
|
|
90
|
+
# pixi environments
|
|
91
|
+
.pixi
|
|
92
|
+
*.egg-info
|
|
93
|
+
pixi.lock
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: DIRACCommon
|
|
3
|
+
Version: 9.0.0a66
|
|
4
|
+
Summary: Stateless utilities extracted from DIRAC for use by DiracX and other projects
|
|
5
|
+
Project-URL: Homepage, https://github.com/DIRACGrid/DIRAC
|
|
6
|
+
Project-URL: Documentation, https://dirac.readthedocs.io/
|
|
7
|
+
Project-URL: Source Code, https://github.com/DIRACGrid/DIRAC
|
|
8
|
+
Author-email: DIRAC Collaboration <dirac-dev@cern.ch>
|
|
9
|
+
License: GPL-3.0-only
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering
|
|
15
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Requires-Dist: typing-extensions>=4.0.0
|
|
18
|
+
Provides-Extra: testing
|
|
19
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'testing'
|
|
20
|
+
Requires-Dist: pytest>=7.0.0; extra == 'testing'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# DIRACCommon
|
|
24
|
+
|
|
25
|
+
Stateless utilities extracted from DIRAC for use by DiracX and other projects without triggering DIRAC's global state initialization.
|
|
26
|
+
|
|
27
|
+
## Purpose
|
|
28
|
+
|
|
29
|
+
This package solves the circular dependency issue where DiracX needs DIRAC utilities but importing DIRAC triggers global state initialization. DIRACCommon contains only stateless utilities that can be safely imported without side effects.
|
|
30
|
+
|
|
31
|
+
## Contents
|
|
32
|
+
|
|
33
|
+
- `DIRACCommon.Utils.ReturnValues`: DIRAC's S_OK/S_ERROR return value system
|
|
34
|
+
- `DIRACCommon.Utils.DErrno`: DIRAC error codes and utilities
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install DIRACCommon
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from DIRACCommon.Utils.ReturnValues import S_OK, S_ERROR
|
|
46
|
+
|
|
47
|
+
def my_function():
|
|
48
|
+
if success:
|
|
49
|
+
return S_OK("Operation successful")
|
|
50
|
+
else:
|
|
51
|
+
return S_ERROR("Operation failed")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
This package is part of the DIRAC project and shares its version number. When DIRAC is released, DIRACCommon is also released with the same version.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pixi install
|
|
60
|
+
pixi run pytest
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Guidelines for Adding Code
|
|
64
|
+
|
|
65
|
+
Code added to DIRACCommon must:
|
|
66
|
+
- Be completely stateless
|
|
67
|
+
- Not import or use any of DIRAC's global objects (`gConfig`, `gLogger`, `gMonitor`, `Operations`)
|
|
68
|
+
- Not establish database connections
|
|
69
|
+
- Not have side effects on import
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# DIRACCommon
|
|
2
|
+
|
|
3
|
+
Stateless utilities extracted from DIRAC for use by DiracX and other projects without triggering DIRAC's global state initialization.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This package solves the circular dependency issue where DiracX needs DIRAC utilities but importing DIRAC triggers global state initialization. DIRACCommon contains only stateless utilities that can be safely imported without side effects.
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
- `DIRACCommon.Utils.ReturnValues`: DIRAC's S_OK/S_ERROR return value system
|
|
12
|
+
- `DIRACCommon.Utils.DErrno`: DIRAC error codes and utilities
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install DIRACCommon
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from DIRACCommon.Utils.ReturnValues import S_OK, S_ERROR
|
|
24
|
+
|
|
25
|
+
def my_function():
|
|
26
|
+
if success:
|
|
27
|
+
return S_OK("Operation successful")
|
|
28
|
+
else:
|
|
29
|
+
return S_ERROR("Operation failed")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Development
|
|
33
|
+
|
|
34
|
+
This package is part of the DIRAC project and shares its version number. When DIRAC is released, DIRACCommon is also released with the same version.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pixi install
|
|
38
|
+
pixi run pytest
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Guidelines for Adding Code
|
|
42
|
+
|
|
43
|
+
Code added to DIRACCommon must:
|
|
44
|
+
- Be completely stateless
|
|
45
|
+
- Not import or use any of DIRAC's global objects (`gConfig`, `gLogger`, `gMonitor`, `Operations`)
|
|
46
|
+
- Not establish database connections
|
|
47
|
+
- Not have side effects on import
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "DIRACCommon"
|
|
7
|
+
description = "Stateless utilities extracted from DIRAC for use by DiracX and other projects"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
license = {text = "GPL-3.0-only"}
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "DIRAC Collaboration", email = "dirac-dev@cern.ch"},
|
|
13
|
+
]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Scientific/Engineering",
|
|
20
|
+
"Topic :: System :: Distributed Computing",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"typing-extensions>=4.0.0",
|
|
24
|
+
]
|
|
25
|
+
dynamic = ["version"]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
testing = [
|
|
29
|
+
"pytest>=7.0.0",
|
|
30
|
+
"pytest-cov>=4.0.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/DIRACGrid/DIRAC"
|
|
35
|
+
Documentation = "https://dirac.readthedocs.io/"
|
|
36
|
+
"Source Code" = "https://github.com/DIRACGrid/DIRAC"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.version]
|
|
39
|
+
source = "vcs"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.version.raw-options]
|
|
42
|
+
root = ".."
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.sdist]
|
|
45
|
+
include = [
|
|
46
|
+
"/src",
|
|
47
|
+
"/tests",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.wheel]
|
|
51
|
+
packages = ["src/DIRACCommon"]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
python_files = "test_*.py"
|
|
56
|
+
python_classes = "Test*"
|
|
57
|
+
python_functions = "test_*"
|
|
58
|
+
addopts = ["-v", "--cov=DIRACCommon", "--cov-report=term-missing"]
|
|
59
|
+
|
|
60
|
+
[tool.coverage.run]
|
|
61
|
+
source = ["src/DIRACCommon"]
|
|
62
|
+
omit = ["*/tests/*"]
|
|
63
|
+
|
|
64
|
+
[tool.coverage.report]
|
|
65
|
+
exclude_lines = [
|
|
66
|
+
"pragma: no cover",
|
|
67
|
+
"def __repr__",
|
|
68
|
+
"raise AssertionError",
|
|
69
|
+
"raise NotImplementedError",
|
|
70
|
+
"if __name__ == .__main__.:",
|
|
71
|
+
"if TYPE_CHECKING:",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[tool.mypy]
|
|
75
|
+
python_version = "3.11"
|
|
76
|
+
files = ["src/DIRACCommon"]
|
|
77
|
+
strict = true
|
|
78
|
+
warn_return_any = true
|
|
79
|
+
warn_unused_configs = true
|
|
80
|
+
disallow_untyped_defs = true
|
|
81
|
+
disallow_incomplete_defs = true
|
|
82
|
+
check_untyped_defs = true
|
|
83
|
+
no_implicit_optional = true
|
|
84
|
+
warn_redundant_casts = true
|
|
85
|
+
warn_unused_ignores = true
|
|
86
|
+
warn_no_return = true
|
|
87
|
+
warn_unreachable = true
|
|
88
|
+
strict_equality = true
|
|
89
|
+
|
|
90
|
+
[tool.ruff]
|
|
91
|
+
line-length = 120
|
|
92
|
+
target-version = "py311"
|
|
93
|
+
select = [
|
|
94
|
+
"E", # pycodestyle errors
|
|
95
|
+
"F", # pyflakes
|
|
96
|
+
"B", # flake8-bugbear
|
|
97
|
+
"I", # isort
|
|
98
|
+
"PLE", # pylint errors
|
|
99
|
+
"UP", # pyupgrade
|
|
100
|
+
]
|
|
101
|
+
ignore = [
|
|
102
|
+
"B905", # zip without explicit strict parameter
|
|
103
|
+
"B008", # do not perform function calls in argument defaults
|
|
104
|
+
"B006", # do not use mutable data structures for argument defaults
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
|
108
|
+
# This ensures DIRACCommon never imports from DIRAC
|
|
109
|
+
"DIRAC" = {msg = "DIRACCommon must not import from DIRAC to avoid global state initialization"}
|
|
110
|
+
|
|
111
|
+
[tool.black]
|
|
112
|
+
line-length = 120
|
|
113
|
+
target-version = ['py311']
|
|
114
|
+
|
|
115
|
+
[tool.isort]
|
|
116
|
+
profile = "black"
|
|
117
|
+
line_length = 120
|
|
118
|
+
|
|
119
|
+
[tool.pixi.workspace]
|
|
120
|
+
channels = ["conda-forge"]
|
|
121
|
+
platforms = ["linux-64", "linux-aarch64", "osx-arm64"]
|
|
122
|
+
|
|
123
|
+
[tool.pixi.pypi-dependencies]
|
|
124
|
+
DIRACCommon = { path = ".", editable = true }
|
|
125
|
+
|
|
126
|
+
[tool.pixi.feature.testing.tasks.pytest]
|
|
127
|
+
cmd = "pytest"
|
|
128
|
+
|
|
129
|
+
[tool.pixi.environments]
|
|
130
|
+
default = { solve-group = "default" }
|
|
131
|
+
testing = { features = ["testing"], solve-group = "default" }
|
|
132
|
+
|
|
133
|
+
[tool.pixi.tasks]
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
""" :mod: DErrno
|
|
2
|
+
|
|
3
|
+
==========================
|
|
4
|
+
|
|
5
|
+
.. module: DErrno
|
|
6
|
+
|
|
7
|
+
:synopsis: Error list and utilities for handling errors in DIRAC
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
This module contains list of errors that can be encountered in DIRAC.
|
|
11
|
+
It complements the errno module of python.
|
|
12
|
+
|
|
13
|
+
It also contains utilities to manipulate these errors.
|
|
14
|
+
|
|
15
|
+
This is a stateless version extracted to DIRACCommon to avoid circular dependencies.
|
|
16
|
+
The extension loading functionality has been removed.
|
|
17
|
+
"""
|
|
18
|
+
import os
|
|
19
|
+
|
|
20
|
+
# To avoid conflict, the error numbers should be greater than 1000
|
|
21
|
+
# We decided to group the by range of 100 per system
|
|
22
|
+
|
|
23
|
+
# 1000: Generic
|
|
24
|
+
# 1100: Core
|
|
25
|
+
# 1200: Framework
|
|
26
|
+
# 1300: Interfaces
|
|
27
|
+
# 1400: Config
|
|
28
|
+
# 1500: WMS + Workflow
|
|
29
|
+
# 1600: DMS + StorageManagement
|
|
30
|
+
# 1700: RMS
|
|
31
|
+
# 1800: Accounting + Monitoring
|
|
32
|
+
# 1900: TS + Production
|
|
33
|
+
# 2000: Resources + RSS
|
|
34
|
+
|
|
35
|
+
# ## Generic (10XX)
|
|
36
|
+
# Python related: 0X
|
|
37
|
+
ETYPE = 1000
|
|
38
|
+
EIMPERR = 1001
|
|
39
|
+
ENOMETH = 1002
|
|
40
|
+
ECONF = 1003
|
|
41
|
+
EVALUE = 1004
|
|
42
|
+
EEEXCEPTION = 1005
|
|
43
|
+
# Files manipulation: 1X
|
|
44
|
+
ECTMPF = 1010
|
|
45
|
+
EOF = 1011
|
|
46
|
+
ERF = 1012
|
|
47
|
+
EWF = 1013
|
|
48
|
+
ESPF = 1014
|
|
49
|
+
|
|
50
|
+
# ## Core (11XX)
|
|
51
|
+
# Certificates and Proxy: 0X
|
|
52
|
+
EX509 = 1100
|
|
53
|
+
EPROXYFIND = 1101
|
|
54
|
+
EPROXYREAD = 1102
|
|
55
|
+
ECERTFIND = 1103
|
|
56
|
+
ECERTREAD = 1104
|
|
57
|
+
ENOCERT = 1105
|
|
58
|
+
ENOCHAIN = 1106
|
|
59
|
+
ENOPKEY = 1107
|
|
60
|
+
ENOGROUP = 1108
|
|
61
|
+
# DISET: 1X
|
|
62
|
+
EDISET = 1110
|
|
63
|
+
ENOAUTH = 1111
|
|
64
|
+
# 3rd party security: 2X
|
|
65
|
+
E3RDPARTY = 1120
|
|
66
|
+
EVOMS = 1121
|
|
67
|
+
# Databases : 3X
|
|
68
|
+
EDB = 1130
|
|
69
|
+
EMYSQL = 1131
|
|
70
|
+
ESQLA = 1132
|
|
71
|
+
# Message Queues: 4X
|
|
72
|
+
EMQUKN = 1140
|
|
73
|
+
EMQNOM = 1141
|
|
74
|
+
EMQCONN = 1142
|
|
75
|
+
# OpenSearch
|
|
76
|
+
EELNOFOUND = 1146
|
|
77
|
+
# Tokens
|
|
78
|
+
EATOKENFIND = 1150
|
|
79
|
+
EATOKENREAD = 1151
|
|
80
|
+
ETOKENTYPE = 1152
|
|
81
|
+
|
|
82
|
+
# config
|
|
83
|
+
ESECTION = 1400
|
|
84
|
+
|
|
85
|
+
# processes
|
|
86
|
+
EEZOMBIE = 1147
|
|
87
|
+
EENOPID = 1148
|
|
88
|
+
|
|
89
|
+
# ## WMS/Workflow
|
|
90
|
+
EWMSUKN = 1500
|
|
91
|
+
EWMSJDL = 1501
|
|
92
|
+
EWMSRESC = 1502
|
|
93
|
+
EWMSSUBM = 1503
|
|
94
|
+
EWMSJMAN = 1504
|
|
95
|
+
EWMSSTATUS = 1505
|
|
96
|
+
EWMSNOMATCH = 1510
|
|
97
|
+
EWMSPLTVER = 1511
|
|
98
|
+
EWMSNOPILOT = 1550
|
|
99
|
+
|
|
100
|
+
# ## DMS/StorageManagement (16XX)
|
|
101
|
+
EFILESIZE = 1601
|
|
102
|
+
EGFAL = 1602
|
|
103
|
+
EBADCKS = 1603
|
|
104
|
+
EFCERR = 1604
|
|
105
|
+
|
|
106
|
+
# ## RMS (17XX)
|
|
107
|
+
ERMSUKN = 1700
|
|
108
|
+
|
|
109
|
+
# ## TS (19XX)
|
|
110
|
+
ETSUKN = 1900
|
|
111
|
+
ETSDATA = 1901
|
|
112
|
+
|
|
113
|
+
# ## Resources and RSS (20XX)
|
|
114
|
+
ERESGEN = 2000
|
|
115
|
+
ERESUNA = 2001
|
|
116
|
+
ERESUNK = 2002
|
|
117
|
+
|
|
118
|
+
# This translates the integer number into the name of the variable
|
|
119
|
+
dErrorCode = {
|
|
120
|
+
# ## Generic (10XX)
|
|
121
|
+
# 100X: Python related
|
|
122
|
+
1000: "ETYPE",
|
|
123
|
+
1001: "EIMPERR",
|
|
124
|
+
1002: "ENOMETH",
|
|
125
|
+
1003: "ECONF",
|
|
126
|
+
1004: "EVALUE",
|
|
127
|
+
1005: "EEEXCEPTION",
|
|
128
|
+
# 101X: Files manipulation
|
|
129
|
+
1010: "ECTMPF",
|
|
130
|
+
1011: "EOF",
|
|
131
|
+
1012: "ERF",
|
|
132
|
+
1013: "EWF",
|
|
133
|
+
1014: "ESPF",
|
|
134
|
+
# ## Core
|
|
135
|
+
# 110X: Certificates and Proxy
|
|
136
|
+
1100: "EX509",
|
|
137
|
+
1101: "EPROXYFIND",
|
|
138
|
+
1102: "EPROXYREAD",
|
|
139
|
+
1103: "ECERTFIND",
|
|
140
|
+
1104: "ECERTREAD",
|
|
141
|
+
1105: "ENOCERT",
|
|
142
|
+
1106: "ENOCHAIN",
|
|
143
|
+
1107: "ENOPKEY",
|
|
144
|
+
1108: "ENOGROUP",
|
|
145
|
+
# 111X: DISET
|
|
146
|
+
1110: "EDISET",
|
|
147
|
+
1111: "ENOAUTH",
|
|
148
|
+
# 112X: 3rd party security
|
|
149
|
+
1120: "E3RDPARTY",
|
|
150
|
+
1121: "EVOMS",
|
|
151
|
+
# 113X: Databases
|
|
152
|
+
1130: "EDB",
|
|
153
|
+
1131: "EMYSQL",
|
|
154
|
+
1132: "ESQLA",
|
|
155
|
+
# 114X: Message Queues
|
|
156
|
+
1140: "EMQUKN",
|
|
157
|
+
1141: "EMQNOM",
|
|
158
|
+
1142: "EMQCONN",
|
|
159
|
+
# OpenSearch
|
|
160
|
+
1146: "EELNOFOUND",
|
|
161
|
+
# 115X: Tokens
|
|
162
|
+
1150: "EATOKENFIND",
|
|
163
|
+
1151: "EATOKENREAD",
|
|
164
|
+
1152: "ETOKENTYPE",
|
|
165
|
+
# Config
|
|
166
|
+
1400: "ESECTION",
|
|
167
|
+
# Processes
|
|
168
|
+
1147: "EEZOMBIE",
|
|
169
|
+
1148: "EENOPID",
|
|
170
|
+
# WMS/Workflow
|
|
171
|
+
1500: "EWMSUKN",
|
|
172
|
+
1501: "EWMSJDL",
|
|
173
|
+
1502: "EWMSRESC",
|
|
174
|
+
1503: "EWMSSUBM",
|
|
175
|
+
1504: "EWMSJMAN",
|
|
176
|
+
1505: "EWMSSTATUS",
|
|
177
|
+
1510: "EWMSNOMATCH",
|
|
178
|
+
1511: "EWMSPLTVER",
|
|
179
|
+
1550: "EWMSNOPILOT",
|
|
180
|
+
# DMS/StorageManagement
|
|
181
|
+
1601: "EFILESIZE",
|
|
182
|
+
1602: "EGFAL",
|
|
183
|
+
1603: "EBADCKS",
|
|
184
|
+
1604: "EFCERR",
|
|
185
|
+
# RMS
|
|
186
|
+
1700: "ERMSUKN",
|
|
187
|
+
# Resources and RSS
|
|
188
|
+
2000: "ERESGEN",
|
|
189
|
+
2001: "ERESUNA",
|
|
190
|
+
2002: "ERESUNK",
|
|
191
|
+
# TS
|
|
192
|
+
1900: "ETSUKN",
|
|
193
|
+
1901: "ETSDATA",
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
dStrError = { # Generic (10XX)
|
|
198
|
+
# 100X: Python related
|
|
199
|
+
ETYPE: "Object Type Error",
|
|
200
|
+
EIMPERR: "Failed to import library",
|
|
201
|
+
ENOMETH: "No such method or function",
|
|
202
|
+
ECONF: "Configuration error",
|
|
203
|
+
EVALUE: "Wrong value passed",
|
|
204
|
+
EEEXCEPTION: "runtime general exception",
|
|
205
|
+
# 101X: Files manipulation
|
|
206
|
+
ECTMPF: "Failed to create temporary file",
|
|
207
|
+
EOF: "Cannot open file",
|
|
208
|
+
ERF: "Cannot read from file",
|
|
209
|
+
EWF: "Cannot write to file",
|
|
210
|
+
ESPF: "Cannot set permissions to file",
|
|
211
|
+
# ## Core
|
|
212
|
+
# 110X: Certificates and Proxy
|
|
213
|
+
EX509: "Generic Error with X509",
|
|
214
|
+
EPROXYFIND: "Can't find proxy",
|
|
215
|
+
EPROXYREAD: "Can't read proxy",
|
|
216
|
+
ECERTFIND: "Can't find certificate",
|
|
217
|
+
ECERTREAD: "Can't read certificate",
|
|
218
|
+
ENOCERT: "No certificate loaded",
|
|
219
|
+
ENOCHAIN: "No chain loaded",
|
|
220
|
+
ENOPKEY: "No private key loaded",
|
|
221
|
+
ENOGROUP: "No DIRAC group",
|
|
222
|
+
# 111X: DISET
|
|
223
|
+
EDISET: "DISET Error",
|
|
224
|
+
ENOAUTH: "Unauthorized query",
|
|
225
|
+
# 112X: 3rd party security
|
|
226
|
+
E3RDPARTY: "3rd party security service error",
|
|
227
|
+
EVOMS: "VOMS Error",
|
|
228
|
+
# 113X: Databases
|
|
229
|
+
EDB: "Database Error",
|
|
230
|
+
EMYSQL: "MySQL Error",
|
|
231
|
+
ESQLA: "SQLAlchemy Error",
|
|
232
|
+
# 114X: Message Queues
|
|
233
|
+
EMQUKN: "Unknown MQ Error",
|
|
234
|
+
EMQNOM: "No messages",
|
|
235
|
+
EMQCONN: "MQ connection failure",
|
|
236
|
+
# 114X OpenSearch
|
|
237
|
+
EELNOFOUND: "Index not found",
|
|
238
|
+
# 115X: Tokens
|
|
239
|
+
EATOKENFIND: "Can't find a bearer access token.",
|
|
240
|
+
EATOKENREAD: "Can't read a bearer access token.",
|
|
241
|
+
ETOKENTYPE: "Unsupported access token type.",
|
|
242
|
+
# Config
|
|
243
|
+
ESECTION: "Section is not found",
|
|
244
|
+
# processes
|
|
245
|
+
EEZOMBIE: "Zombie process",
|
|
246
|
+
EENOPID: "No PID of process",
|
|
247
|
+
# WMS/Workflow
|
|
248
|
+
EWMSUKN: "Unknown WMS error",
|
|
249
|
+
EWMSJDL: "Invalid job description",
|
|
250
|
+
EWMSRESC: "Job to reschedule",
|
|
251
|
+
EWMSSUBM: "Job submission error",
|
|
252
|
+
EWMSJMAN: "Job management error",
|
|
253
|
+
EWMSSTATUS: "Job status error",
|
|
254
|
+
EWMSNOPILOT: "No pilots found",
|
|
255
|
+
EWMSPLTVER: "Pilot version does not match",
|
|
256
|
+
EWMSNOMATCH: "No match found",
|
|
257
|
+
# DMS/StorageManagement
|
|
258
|
+
EFILESIZE: "Bad file size",
|
|
259
|
+
EGFAL: "Error with the gfal call",
|
|
260
|
+
EBADCKS: "Bad checksum",
|
|
261
|
+
EFCERR: "FileCatalog error",
|
|
262
|
+
# RMS
|
|
263
|
+
ERMSUKN: "Unknown RMS error",
|
|
264
|
+
# Resources and RSS
|
|
265
|
+
ERESGEN: "Unknown Resource Failure",
|
|
266
|
+
ERESUNA: "Resource not available",
|
|
267
|
+
ERESUNK: "Unknown Resource",
|
|
268
|
+
# TS
|
|
269
|
+
ETSUKN: "Unknown Transformation System Error",
|
|
270
|
+
ETSDATA: "Invalid Input Data definition",
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def strerror(code: int) -> str:
|
|
275
|
+
"""This method wraps up os.strerror, and behave the same way.
|
|
276
|
+
It completes it with the DIRAC specific errors.
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
if code == 0:
|
|
280
|
+
return "Undefined error"
|
|
281
|
+
|
|
282
|
+
errMsg = f"Unknown error {code}"
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
errMsg = dStrError[code]
|
|
286
|
+
except KeyError:
|
|
287
|
+
# It is not a DIRAC specific error, try the os one
|
|
288
|
+
try:
|
|
289
|
+
errMsg = os.strerror(code)
|
|
290
|
+
# On some system, os.strerror raises an exception with unknown code,
|
|
291
|
+
# on others, it returns a message...
|
|
292
|
+
except ValueError:
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
return errMsg
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def cmpError(inErr: str | int | dict, candidate: int) -> bool:
|
|
299
|
+
"""This function compares an error (in its old form (a string or dictionary) or in its int form
|
|
300
|
+
with a candidate error code.
|
|
301
|
+
|
|
302
|
+
:param inErr: a string, an integer, a S_ERROR dictionary
|
|
303
|
+
:type inErr: str or int or S_ERROR
|
|
304
|
+
:param int candidate: error code to compare with
|
|
305
|
+
|
|
306
|
+
:return: True or False
|
|
307
|
+
|
|
308
|
+
If an S_ERROR instance is passed, we compare the code with S_ERROR['Errno']
|
|
309
|
+
If it is a Integer, we do a direct comparison
|
|
310
|
+
If it is a String, we use strerror to check the error string
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
if isinstance(inErr, str): # old style
|
|
314
|
+
# Compare error message strings
|
|
315
|
+
errMsg = strerror(candidate)
|
|
316
|
+
return errMsg in inErr
|
|
317
|
+
elif isinstance(inErr, dict): # if the S_ERROR structure is given
|
|
318
|
+
# Check if Errno defined in the dict
|
|
319
|
+
errorNumber = inErr.get("Errno")
|
|
320
|
+
if errorNumber:
|
|
321
|
+
return errorNumber == candidate
|
|
322
|
+
errMsg = strerror(candidate)
|
|
323
|
+
return errMsg in inErr.get("Message", "")
|
|
324
|
+
elif isinstance(inErr, int):
|
|
325
|
+
return inErr == candidate
|
|
326
|
+
else:
|
|
327
|
+
raise TypeError(f"Unknown input error type {type(inErr)}")
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DIRAC return dictionary
|
|
3
|
+
|
|
4
|
+
Message values are converted to string
|
|
5
|
+
|
|
6
|
+
keys are converted to string
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import functools
|
|
11
|
+
import sys
|
|
12
|
+
import traceback
|
|
13
|
+
from types import TracebackType
|
|
14
|
+
from typing import Any, Callable, cast, Generic, Literal, overload, Type, TypeVar, Union
|
|
15
|
+
from typing_extensions import TypedDict, ParamSpec, NotRequired
|
|
16
|
+
|
|
17
|
+
from DIRACCommon.Utils.DErrno import strerror
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
P = ParamSpec("P")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DOKReturnType(TypedDict, Generic[T]):
|
|
25
|
+
"""used for typing the DIRAC return structure"""
|
|
26
|
+
|
|
27
|
+
OK: Literal[True]
|
|
28
|
+
Value: T
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DErrorReturnType(TypedDict):
|
|
32
|
+
"""used for typing the DIRAC return structure"""
|
|
33
|
+
|
|
34
|
+
OK: Literal[False]
|
|
35
|
+
Message: str
|
|
36
|
+
Errno: int
|
|
37
|
+
ExecInfo: NotRequired[tuple[type[BaseException], BaseException, TracebackType]]
|
|
38
|
+
CallStack: NotRequired[list[str]]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
DReturnType = Union[DOKReturnType[T], DErrorReturnType]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def S_ERROR(*args: Any, **kwargs: Any) -> DErrorReturnType:
|
|
45
|
+
"""return value on error condition
|
|
46
|
+
|
|
47
|
+
Arguments are either Errno and ErrorMessage or just ErrorMessage fro backward compatibility
|
|
48
|
+
|
|
49
|
+
:param int errno: Error number
|
|
50
|
+
:param string message: Error message
|
|
51
|
+
:param list callStack: Manually override the CallStack attribute better performance
|
|
52
|
+
"""
|
|
53
|
+
callStack = kwargs.pop("callStack", None)
|
|
54
|
+
|
|
55
|
+
result: DErrorReturnType = {"OK": False, "Errno": 0, "Message": ""}
|
|
56
|
+
|
|
57
|
+
message = ""
|
|
58
|
+
if args:
|
|
59
|
+
if isinstance(args[0], int):
|
|
60
|
+
result["Errno"] = args[0]
|
|
61
|
+
if len(args) > 1:
|
|
62
|
+
message = args[1]
|
|
63
|
+
else:
|
|
64
|
+
message = args[0]
|
|
65
|
+
|
|
66
|
+
if result["Errno"]:
|
|
67
|
+
message = f"{strerror(result['Errno'])} ( {result['Errno']} : {message})"
|
|
68
|
+
result["Message"] = message
|
|
69
|
+
|
|
70
|
+
if callStack is None:
|
|
71
|
+
try:
|
|
72
|
+
callStack = traceback.format_stack()
|
|
73
|
+
callStack.pop()
|
|
74
|
+
except Exception:
|
|
75
|
+
callStack = []
|
|
76
|
+
|
|
77
|
+
result["CallStack"] = callStack
|
|
78
|
+
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# mypy doesn't understand default parameter values with generics so use overloads (python/mypy#3737)
|
|
83
|
+
@overload
|
|
84
|
+
def S_OK() -> DOKReturnType[None]:
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@overload
|
|
89
|
+
def S_OK(value: T) -> DOKReturnType[T]:
|
|
90
|
+
...
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def S_OK(value=None): # type: ignore
|
|
94
|
+
"""return value on success
|
|
95
|
+
|
|
96
|
+
:param value: value of the 'Value'
|
|
97
|
+
:return: dictionary { 'OK' : True, 'Value' : value }
|
|
98
|
+
"""
|
|
99
|
+
return {"OK": True, "Value": value}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def isReturnStructure(unk: Any) -> bool:
|
|
103
|
+
"""Check if value is an `S_OK`/`S_ERROR` object"""
|
|
104
|
+
if not isinstance(unk, dict):
|
|
105
|
+
return False
|
|
106
|
+
if "OK" not in unk:
|
|
107
|
+
return False
|
|
108
|
+
if unk["OK"]:
|
|
109
|
+
return "Value" in unk
|
|
110
|
+
else:
|
|
111
|
+
return "Message" in unk
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def isSError(value: Any) -> bool:
|
|
115
|
+
"""Check if value is an `S_ERROR` object"""
|
|
116
|
+
if not isinstance(value, dict):
|
|
117
|
+
return False
|
|
118
|
+
if "OK" not in value:
|
|
119
|
+
return False
|
|
120
|
+
return "Message" in value
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def reprReturnErrorStructure(struct: DErrorReturnType, full: bool = False) -> str:
|
|
124
|
+
errorNumber = struct.get("Errno", 0)
|
|
125
|
+
message = struct.get("Message", "")
|
|
126
|
+
if errorNumber:
|
|
127
|
+
reprStr = f"{strerror(errorNumber)} ( {errorNumber} : {message})"
|
|
128
|
+
else:
|
|
129
|
+
reprStr = message
|
|
130
|
+
|
|
131
|
+
if full:
|
|
132
|
+
callStack = struct.get("CallStack")
|
|
133
|
+
if callStack:
|
|
134
|
+
reprStr += "\n" + "".join(callStack)
|
|
135
|
+
|
|
136
|
+
return reprStr
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def returnSingleResult(dictRes: DReturnType[Any]) -> DReturnType[Any]:
|
|
140
|
+
"""Transform the S_OK{Successful/Failed} dictionary convention into
|
|
141
|
+
an S_OK/S_ERROR return. To be used when a single returned entity
|
|
142
|
+
is expected from a generally bulk call.
|
|
143
|
+
|
|
144
|
+
:param dictRes: S_ERROR or S_OK( "Failed" : {}, "Successful" : {})
|
|
145
|
+
:returns: S_ERROR or S_OK(value)
|
|
146
|
+
|
|
147
|
+
The following rules are applied:
|
|
148
|
+
|
|
149
|
+
- if dictRes is an S_ERROR: returns it as is
|
|
150
|
+
- we start by looking at the Failed directory
|
|
151
|
+
- if there are several items in a dictionary, we return the first one
|
|
152
|
+
- if both dictionaries are empty, we return S_ERROR
|
|
153
|
+
- For an item in Failed, we return S_ERROR
|
|
154
|
+
- Far an item in Successful we return S_OK
|
|
155
|
+
|
|
156
|
+
Behavior examples (would be perfect unit test :-) )::
|
|
157
|
+
|
|
158
|
+
{'Message': 'Kaput', 'OK': False} -> {'Message': 'Kaput', 'OK': False}
|
|
159
|
+
{'OK': True, 'Value': {'Successful': {}, 'Failed': {'a': 1}}} -> {'Message': '1', 'OK': False}
|
|
160
|
+
{'OK': True, 'Value': {'Successful': {'b': 2}, 'Failed': {}}} -> {'OK': True, 'Value': 2}
|
|
161
|
+
{'OK': True, 'Value': {'Successful': {'b': 2}, 'Failed': {'a': 1}}} -> {'Message': '1', 'OK': False}
|
|
162
|
+
{'OK': True, 'Value': {'Successful': {'b': 2}, 'Failed': {'a': 1, 'c': 3}}} -> {'Message': '1', 'OK': False}
|
|
163
|
+
{'OK': True, 'Value': {'Successful': {'b': 2, 'd': 4}, 'Failed': {}}} -> {'OK': True, 'Value': 2}
|
|
164
|
+
{'OK': True, 'Value': {'Successful': {}, 'Failed': {}}} ->
|
|
165
|
+
{'Message': 'returnSingleResult: Failed and Successful dictionaries are empty', 'OK': False}
|
|
166
|
+
"""
|
|
167
|
+
# if S_ERROR was returned, we return it as well
|
|
168
|
+
if not dictRes["OK"]:
|
|
169
|
+
return dictRes
|
|
170
|
+
# if there is a Failed, we return the first one in an S_ERROR
|
|
171
|
+
if "Failed" in dictRes["Value"] and len(dictRes["Value"]["Failed"]):
|
|
172
|
+
errorMessage = list(dictRes["Value"]["Failed"].values())[0]
|
|
173
|
+
if isinstance(errorMessage, dict):
|
|
174
|
+
if isReturnStructure(errorMessage):
|
|
175
|
+
return cast(DErrorReturnType, errorMessage)
|
|
176
|
+
else:
|
|
177
|
+
return S_ERROR(str(errorMessage))
|
|
178
|
+
return S_ERROR(errorMessage)
|
|
179
|
+
# if there is a Successful, we return the first one in an S_OK
|
|
180
|
+
elif "Successful" in dictRes["Value"] and len(dictRes["Value"]["Successful"]):
|
|
181
|
+
return S_OK(list(dictRes["Value"]["Successful"].values())[0])
|
|
182
|
+
else:
|
|
183
|
+
return S_ERROR("returnSingleResult: Failed and Successful dictionaries are empty")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class SErrorException(Exception):
|
|
187
|
+
"""Exception class for use with `convertToReturnValue`"""
|
|
188
|
+
|
|
189
|
+
def __init__(self, result: DErrorReturnType | str, errCode: int = 0):
|
|
190
|
+
"""Create a new exception return value
|
|
191
|
+
|
|
192
|
+
If `result` is a `S_ERROR` return it directly else convert it to an
|
|
193
|
+
appropriate value using `S_ERROR(errCode, result)`.
|
|
194
|
+
|
|
195
|
+
:param result: The error to propagate
|
|
196
|
+
:param errCode: the error code to propagate
|
|
197
|
+
"""
|
|
198
|
+
if not isSError(result):
|
|
199
|
+
result = S_ERROR(errCode, result)
|
|
200
|
+
self.result = cast(DErrorReturnType, result)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def returnValueOrRaise(result: DReturnType[T], *, errorCode: int = 0) -> T:
|
|
204
|
+
"""Unwrap an S_OK/S_ERROR response into a value or Exception
|
|
205
|
+
|
|
206
|
+
This method assists with using exceptions in DIRAC code by raising
|
|
207
|
+
:exc:`SErrorException` if `result` is an error. This can then by propagated
|
|
208
|
+
automatically as an `S_ERROR` by wrapping public facing functions with
|
|
209
|
+
`@convertToReturnValue`.
|
|
210
|
+
|
|
211
|
+
:param result: Result of a DIRAC function which returns `S_OK`/`S_ERROR`
|
|
212
|
+
:returns: The value associated with the `S_OK` object
|
|
213
|
+
:raises: If `result["OK"]` is falsey the original exception is re-raised.
|
|
214
|
+
If no exception is known an :exc:`SErrorException` is raised.
|
|
215
|
+
"""
|
|
216
|
+
if not result["OK"]:
|
|
217
|
+
if "ExecInfo" in result:
|
|
218
|
+
raise result["ExecInfo"][0]
|
|
219
|
+
else:
|
|
220
|
+
raise SErrorException(result, errorCode)
|
|
221
|
+
return result["Value"]
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def convertToReturnValue(func: Callable[P, T]) -> Callable[P, DReturnType[T]]:
|
|
225
|
+
"""Decorate a function to convert return values to `S_OK`/`S_ERROR`
|
|
226
|
+
|
|
227
|
+
If `func` returns, wrap the return value in `S_OK`.
|
|
228
|
+
If `func` raises :exc:`SErrorException`, return the associated `S_ERROR`
|
|
229
|
+
If `func` raises any other exception type, convert it to an `S_ERROR` object
|
|
230
|
+
|
|
231
|
+
:param result: The bare result of a function call
|
|
232
|
+
:returns: `S_OK`/`S_ERROR`
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
@functools.wraps(func)
|
|
236
|
+
def wrapped(*args: P.args, **kwargs: P.kwargs) -> DReturnType[T]:
|
|
237
|
+
try:
|
|
238
|
+
value = func(*args, **kwargs)
|
|
239
|
+
except SErrorException as e:
|
|
240
|
+
return e.result
|
|
241
|
+
except Exception as e:
|
|
242
|
+
retval = S_ERROR(f"{repr(e)}: {e}")
|
|
243
|
+
# Replace CallStack with the one from the exception
|
|
244
|
+
# Use cast as mypy doesn't understand that sys.exc_info can't return None in an exception block
|
|
245
|
+
retval["ExecInfo"] = cast(tuple[type[BaseException], BaseException, TracebackType], sys.exc_info())
|
|
246
|
+
exc_type, exc_value, exc_tb = retval["ExecInfo"]
|
|
247
|
+
retval["CallStack"] = traceback.format_tb(exc_tb)
|
|
248
|
+
return retval
|
|
249
|
+
else:
|
|
250
|
+
return S_OK(value)
|
|
251
|
+
|
|
252
|
+
# functools will copy the annotations. Since we change the return type
|
|
253
|
+
# we have to update it
|
|
254
|
+
wrapped.__annotations__["return"] = DReturnType
|
|
255
|
+
return wrapped
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DIRACCommon - Stateless utilities for DIRAC
|
|
3
|
+
|
|
4
|
+
This package contains stateless utilities extracted from DIRAC that can be used
|
|
5
|
+
by DiracX and other projects without triggering DIRAC's global state initialization.
|
|
6
|
+
|
|
7
|
+
The utilities here should not depend on:
|
|
8
|
+
- gConfig (Configuration system)
|
|
9
|
+
- gLogger (Global logging)
|
|
10
|
+
- gMonitor (Monitoring)
|
|
11
|
+
- Database connections
|
|
12
|
+
- Any other global state
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import importlib.metadata
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
__version__ = importlib.metadata.version(__name__)
|
|
19
|
+
except importlib.metadata.PackageNotFoundError:
|
|
20
|
+
# package is not installed
|
|
21
|
+
__version__ = "Unknown"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Tests for DErrno module"""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from DIRACCommon.Utils import DErrno
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_strerror():
|
|
8
|
+
"""Test strerror function"""
|
|
9
|
+
# Test DIRAC specific errors
|
|
10
|
+
assert DErrno.strerror(DErrno.ETYPE) == "Object Type Error"
|
|
11
|
+
assert DErrno.strerror(DErrno.EIMPERR) == "Failed to import library"
|
|
12
|
+
assert DErrno.strerror(DErrno.EOF) == "Cannot open file"
|
|
13
|
+
|
|
14
|
+
# Test unknown error
|
|
15
|
+
assert "Unknown error" in DErrno.strerror(999999)
|
|
16
|
+
|
|
17
|
+
# Test zero error
|
|
18
|
+
assert DErrno.strerror(0) == "Undefined error"
|
|
19
|
+
|
|
20
|
+
# Test OS errors (should fall back to os.strerror)
|
|
21
|
+
# Error code 2 is usually "No such file or directory" on Unix
|
|
22
|
+
import errno
|
|
23
|
+
|
|
24
|
+
assert DErrno.strerror(errno.ENOENT) == "No such file or directory"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_cmpError():
|
|
28
|
+
"""Test cmpError function"""
|
|
29
|
+
# Test with integer
|
|
30
|
+
assert DErrno.cmpError(DErrno.ETYPE, DErrno.ETYPE) is True
|
|
31
|
+
assert DErrno.cmpError(DErrno.ETYPE, DErrno.EIMPERR) is False
|
|
32
|
+
|
|
33
|
+
# Test with string (old style)
|
|
34
|
+
assert DErrno.cmpError("Object Type Error", DErrno.ETYPE) is True
|
|
35
|
+
assert DErrno.cmpError("Some error with Object Type Error in it", DErrno.ETYPE) is True
|
|
36
|
+
assert DErrno.cmpError("Different error", DErrno.ETYPE) is False
|
|
37
|
+
|
|
38
|
+
# Test with S_ERROR dictionary
|
|
39
|
+
error_dict = {"OK": False, "Message": "Object Type Error", "Errno": DErrno.ETYPE}
|
|
40
|
+
assert DErrno.cmpError(error_dict, DErrno.ETYPE) is True
|
|
41
|
+
|
|
42
|
+
error_dict_no_errno = {"OK": False, "Message": "Object Type Error"}
|
|
43
|
+
assert DErrno.cmpError(error_dict_no_errno, DErrno.ETYPE) is True
|
|
44
|
+
|
|
45
|
+
error_dict_wrong = {"OK": False, "Message": "Different error", "Errno": DErrno.EIMPERR}
|
|
46
|
+
assert DErrno.cmpError(error_dict_wrong, DErrno.ETYPE) is False
|
|
47
|
+
|
|
48
|
+
# Test with invalid type
|
|
49
|
+
with pytest.raises(TypeError):
|
|
50
|
+
DErrno.cmpError([], DErrno.ETYPE)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from DIRACCommon.Utils.ReturnValues import S_OK, S_ERROR, SErrorException, convertToReturnValue, returnValueOrRaise
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_Ok():
|
|
7
|
+
retVal = S_OK("Hello world")
|
|
8
|
+
assert retVal["OK"] is True
|
|
9
|
+
assert retVal["Value"] == "Hello world"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_Error():
|
|
13
|
+
retVal = S_ERROR("This is bad")
|
|
14
|
+
assert retVal["OK"] is False
|
|
15
|
+
assert retVal["Message"] == "This is bad"
|
|
16
|
+
callStack = "".join(retVal["CallStack"])
|
|
17
|
+
assert "test_ReturnValues" in callStack
|
|
18
|
+
assert "test_Error" in callStack
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_ErrorWithCustomTraceback():
|
|
22
|
+
retVal = S_ERROR("This is bad", callStack=["My callstack"])
|
|
23
|
+
assert retVal["OK"] is False
|
|
24
|
+
assert retVal["Message"] == "This is bad"
|
|
25
|
+
assert retVal["CallStack"] == ["My callstack"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CustomException(Exception):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@convertToReturnValue
|
|
33
|
+
def _happyFunction():
|
|
34
|
+
return {"12345": "Success"}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@convertToReturnValue
|
|
38
|
+
def _sadFunction():
|
|
39
|
+
raise CustomException("I am sad")
|
|
40
|
+
return {}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@convertToReturnValue
|
|
44
|
+
def _verySadFunction():
|
|
45
|
+
raise SErrorException("I am very sad")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@convertToReturnValue
|
|
49
|
+
def _sadButPreciseFunction():
|
|
50
|
+
raise SErrorException("I am sad, yet precise", errCode=123)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_convertToReturnValue():
|
|
54
|
+
retVal = _happyFunction()
|
|
55
|
+
assert retVal["OK"] is True
|
|
56
|
+
assert retVal["Value"] == {"12345": "Success"}
|
|
57
|
+
# Make sure exceptions are captured correctly
|
|
58
|
+
retVal = _sadFunction()
|
|
59
|
+
assert retVal["OK"] is False
|
|
60
|
+
assert "CustomException" in retVal["Message"]
|
|
61
|
+
# Make sure the exception is re-raised
|
|
62
|
+
with pytest.raises(CustomException):
|
|
63
|
+
returnValueOrRaise(_sadFunction())
|
|
64
|
+
|
|
65
|
+
retVal = _verySadFunction()
|
|
66
|
+
assert retVal["OK"] is False
|
|
67
|
+
assert retVal["Errno"] == 0
|
|
68
|
+
assert retVal["Message"] == "I am very sad"
|
|
69
|
+
|
|
70
|
+
retVal = _sadButPreciseFunction()
|
|
71
|
+
assert retVal["OK"] is False
|
|
72
|
+
assert retVal["Errno"] == 123
|
|
73
|
+
assert "I am sad, yet precise" in retVal["Message"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""DIRACCommon test suite"""
|