hrxlib 0.1.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.
- hrxlib-0.1.0/LICENSE +21 -0
- hrxlib-0.1.0/PKG-INFO +64 -0
- hrxlib-0.1.0/README.md +38 -0
- hrxlib-0.1.0/hrx/__init__.py +71 -0
- hrxlib-0.1.0/hrx/__init__.pyi +39 -0
- hrxlib-0.1.0/hrx/api.py +171 -0
- hrxlib-0.1.0/hrx/api.pyi +17 -0
- hrxlib-0.1.0/hrx/errors.py +33 -0
- hrxlib-0.1.0/hrx/errors.pyi +11 -0
- hrxlib-0.1.0/hrx/models.py +222 -0
- hrxlib-0.1.0/hrx/models.pyi +200 -0
- hrxlib-0.1.0/hrx/py.typed +0 -0
- hrxlib-0.1.0/hrxlib.egg-info/PKG-INFO +64 -0
- hrxlib-0.1.0/hrxlib.egg-info/SOURCES.txt +18 -0
- hrxlib-0.1.0/hrxlib.egg-info/dependency_links.txt +1 -0
- hrxlib-0.1.0/hrxlib.egg-info/requires.txt +5 -0
- hrxlib-0.1.0/hrxlib.egg-info/top_level.txt +1 -0
- hrxlib-0.1.0/pyproject.toml +47 -0
- hrxlib-0.1.0/setup.cfg +4 -0
- hrxlib-0.1.0/tests/test_hrx_api.py +108 -0
hrxlib-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HRX Project
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, 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,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
hrxlib-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hrxlib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: HRX SDK for Python — types and helpers for HRX models
|
|
5
|
+
Author-email: HRX Project <rverschuur@audit-io.fr>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://example.com/hrx
|
|
8
|
+
Project-URL: Source, https://example.com/hrx/repo
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: pydantic<3,>=2
|
|
22
|
+
Requires-Dist: jsonschema<5,>=4
|
|
23
|
+
Provides-Extra: test
|
|
24
|
+
Requires-Dist: pytest<9,>=8; extra == "test"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# HRX Python SDK (hrxlib)
|
|
28
|
+
|
|
29
|
+
Paquet Python fournissant les modeles Pydantic et helpers pour le format HRX.
|
|
30
|
+
|
|
31
|
+
Installation (locally):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install build
|
|
35
|
+
cd src/hrx/sdk/python
|
|
36
|
+
python -m build
|
|
37
|
+
pip install dist/hrxlib-0.1.0-py3-none-any.whl
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Usage:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
|
|
45
|
+
from hrx import HRX
|
|
46
|
+
|
|
47
|
+
doc = HRX.from_path(Path("candidate.hrx"))
|
|
48
|
+
candidate = doc.candidate
|
|
49
|
+
payload_dict = doc.to_dict()
|
|
50
|
+
payload_json = doc.to_json(indent=2)
|
|
51
|
+
|
|
52
|
+
# Optional: strict JSON schema validation
|
|
53
|
+
doc.validate_against_schema(Path("../../models/hrx-schema-v1.json"))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Erreurs SDK:
|
|
57
|
+
|
|
58
|
+
- `HrxFileError`: probleme de lecture/ecriture fichier
|
|
59
|
+
- `HrxParseError`: JSON invalide
|
|
60
|
+
- `HrxValidationError`: payload non conforme aux modeles Pydantic
|
|
61
|
+
- `HrxSchemaError`: payload non conforme au schema JSON
|
|
62
|
+
- `HrxVersionError`: version HRX non supportee
|
|
63
|
+
|
|
64
|
+
Licence: MIT
|
hrxlib-0.1.0/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# HRX Python SDK (hrxlib)
|
|
2
|
+
|
|
3
|
+
Paquet Python fournissant les modeles Pydantic et helpers pour le format HRX.
|
|
4
|
+
|
|
5
|
+
Installation (locally):
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install build
|
|
9
|
+
cd src/hrx/sdk/python
|
|
10
|
+
python -m build
|
|
11
|
+
pip install dist/hrxlib-0.1.0-py3-none-any.whl
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from hrx import HRX
|
|
20
|
+
|
|
21
|
+
doc = HRX.from_path(Path("candidate.hrx"))
|
|
22
|
+
candidate = doc.candidate
|
|
23
|
+
payload_dict = doc.to_dict()
|
|
24
|
+
payload_json = doc.to_json(indent=2)
|
|
25
|
+
|
|
26
|
+
# Optional: strict JSON schema validation
|
|
27
|
+
doc.validate_against_schema(Path("../../models/hrx-schema-v1.json"))
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Erreurs SDK:
|
|
31
|
+
|
|
32
|
+
- `HrxFileError`: probleme de lecture/ecriture fichier
|
|
33
|
+
- `HrxParseError`: JSON invalide
|
|
34
|
+
- `HrxValidationError`: payload non conforme aux modeles Pydantic
|
|
35
|
+
- `HrxSchemaError`: payload non conforme au schema JSON
|
|
36
|
+
- `HrxVersionError`: version HRX non supportee
|
|
37
|
+
|
|
38
|
+
Licence: MIT
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""HRX Python SDK public API."""
|
|
2
|
+
|
|
3
|
+
from .api import HRX
|
|
4
|
+
from .errors import (
|
|
5
|
+
HrxError,
|
|
6
|
+
HrxFileError,
|
|
7
|
+
HrxParseError,
|
|
8
|
+
HrxSchemaError,
|
|
9
|
+
HrxValidationError,
|
|
10
|
+
HrxVersionError,
|
|
11
|
+
)
|
|
12
|
+
from .models import (
|
|
13
|
+
Award,
|
|
14
|
+
Bibliography,
|
|
15
|
+
BibliographyType,
|
|
16
|
+
Candidate,
|
|
17
|
+
Civility,
|
|
18
|
+
Contact,
|
|
19
|
+
ContactType,
|
|
20
|
+
ContractType,
|
|
21
|
+
Credentials,
|
|
22
|
+
Education,
|
|
23
|
+
Experience,
|
|
24
|
+
HrxMetadata,
|
|
25
|
+
Identity,
|
|
26
|
+
Level,
|
|
27
|
+
Location,
|
|
28
|
+
Mobility,
|
|
29
|
+
Preferences,
|
|
30
|
+
Project,
|
|
31
|
+
Reference,
|
|
32
|
+
RemoteType,
|
|
33
|
+
SkillDomain,
|
|
34
|
+
SkillItem,
|
|
35
|
+
Skills,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"HRX",
|
|
40
|
+
"HrxError",
|
|
41
|
+
"HrxFileError",
|
|
42
|
+
"HrxParseError",
|
|
43
|
+
"HrxSchemaError",
|
|
44
|
+
"HrxValidationError",
|
|
45
|
+
"HrxVersionError",
|
|
46
|
+
"Candidate",
|
|
47
|
+
"HrxMetadata",
|
|
48
|
+
"Identity",
|
|
49
|
+
"Contact",
|
|
50
|
+
"Location",
|
|
51
|
+
"Skills",
|
|
52
|
+
"SkillDomain",
|
|
53
|
+
"SkillItem",
|
|
54
|
+
"Experience",
|
|
55
|
+
"Project",
|
|
56
|
+
"Education",
|
|
57
|
+
"Credentials",
|
|
58
|
+
"Award",
|
|
59
|
+
"Reference",
|
|
60
|
+
"Bibliography",
|
|
61
|
+
"Preferences",
|
|
62
|
+
"Civility",
|
|
63
|
+
"ContactType",
|
|
64
|
+
"Mobility",
|
|
65
|
+
"Level",
|
|
66
|
+
"ContractType",
|
|
67
|
+
"RemoteType",
|
|
68
|
+
"BibliographyType",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Stubs du package HRX: réexporte l'API publique du SDK."""
|
|
2
|
+
from .api import HRX # type: ignore
|
|
3
|
+
from .errors import (
|
|
4
|
+
HrxError, # type: ignore
|
|
5
|
+
HrxFileError, # type: ignore
|
|
6
|
+
HrxParseError, # type: ignore
|
|
7
|
+
HrxSchemaError, # type: ignore
|
|
8
|
+
HrxValidationError, # type: ignore
|
|
9
|
+
HrxVersionError, # type: ignore
|
|
10
|
+
)
|
|
11
|
+
from .models import (
|
|
12
|
+
Award, # type: ignore
|
|
13
|
+
Bibliography, # type: ignore
|
|
14
|
+
BibliographyType, # type: ignore
|
|
15
|
+
Candidate, # type: ignore
|
|
16
|
+
Civility, # type: ignore
|
|
17
|
+
Contact, # type: ignore
|
|
18
|
+
ContactType, # type: ignore
|
|
19
|
+
ContractType, # type: ignore
|
|
20
|
+
Credentials, # type: ignore
|
|
21
|
+
Education, # type: ignore
|
|
22
|
+
Experience, # type: ignore
|
|
23
|
+
HrxMetadata, # type: ignore
|
|
24
|
+
Identity, # type: ignore
|
|
25
|
+
Level, # type: ignore
|
|
26
|
+
Location, # type: ignore
|
|
27
|
+
Mobility, # type: ignore
|
|
28
|
+
Preferences, # type: ignore
|
|
29
|
+
Project, # type: ignore
|
|
30
|
+
Reference, # type: ignore
|
|
31
|
+
RemoteType, # type: ignore
|
|
32
|
+
SkillDomain, # type: ignore
|
|
33
|
+
SkillItem, # type: ignore
|
|
34
|
+
Skills, # type: ignore
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__: list[str]
|
|
38
|
+
|
|
39
|
+
__version__: str
|
hrxlib-0.1.0/hrx/api.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""High-level API for reading and writing HRX documents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, cast
|
|
8
|
+
|
|
9
|
+
from pydantic import ValidationError
|
|
10
|
+
|
|
11
|
+
from .errors import HrxFileError, HrxParseError, HrxSchemaError, HrxValidationError, HrxVersionError
|
|
12
|
+
from .models import Candidate
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HRX:
|
|
16
|
+
"""Load, validate and serialize an HRX document."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, source: Path | str | dict[str, Any] | Candidate) -> None:
|
|
19
|
+
self.candidate = self._load_candidate(source)
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_path(cls, path: Path | str) -> "HRX":
|
|
23
|
+
"""
|
|
24
|
+
Load an HRX document from a file.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
path: Path to the HRX file.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
HRX document.
|
|
31
|
+
"""
|
|
32
|
+
if isinstance(path, str):
|
|
33
|
+
path = Path(path)
|
|
34
|
+
return cls(path)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_json(cls, payload: str) -> "HRX":
|
|
38
|
+
"""
|
|
39
|
+
Load an HRX document from a JSON string.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
payload: JSON string representing the HRX document.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
HRX document.
|
|
46
|
+
"""
|
|
47
|
+
return cls(payload)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_dict(cls, payload: dict[str, Any]) -> "HRX":
|
|
51
|
+
"""
|
|
52
|
+
Load an HRX document from a dictionary.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
payload: Dictionary representing the HRX document.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
HRX document.
|
|
59
|
+
"""
|
|
60
|
+
return cls(payload)
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _load_candidate(source: Path | str | dict[str, Any] | Candidate) -> Candidate:
|
|
64
|
+
if isinstance(source, Candidate):
|
|
65
|
+
candidate = source
|
|
66
|
+
elif isinstance(source, Path):
|
|
67
|
+
candidate = HRX._load_from_path(source)
|
|
68
|
+
elif isinstance(source, dict):
|
|
69
|
+
candidate = HRX._load_from_dict(source)
|
|
70
|
+
else:
|
|
71
|
+
source_path = Path(source)
|
|
72
|
+
if source_path.exists():
|
|
73
|
+
candidate = HRX._load_from_path(source_path)
|
|
74
|
+
else:
|
|
75
|
+
candidate = HRX._load_from_json(source)
|
|
76
|
+
|
|
77
|
+
version = candidate.hrx.version
|
|
78
|
+
if version != "1.0":
|
|
79
|
+
raise HrxVersionError(
|
|
80
|
+
f"Unsupported HRX version '{version}'. Only version '1.0' is currently supported.",
|
|
81
|
+
)
|
|
82
|
+
return candidate
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def _load_from_path(path: Path) -> Candidate:
|
|
86
|
+
try:
|
|
87
|
+
payload = path.read_text(encoding="utf-8")
|
|
88
|
+
except OSError as exc:
|
|
89
|
+
raise HrxFileError(f"Cannot read HRX file: {path}") from exc
|
|
90
|
+
return HRX._load_from_json(payload)
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def _load_from_json(payload: str) -> Candidate:
|
|
94
|
+
try:
|
|
95
|
+
parsed = json.loads(payload)
|
|
96
|
+
except json.JSONDecodeError as exc:
|
|
97
|
+
raise HrxParseError(f"Invalid HRX JSON payload: {exc}") from exc
|
|
98
|
+
if not isinstance(parsed, dict):
|
|
99
|
+
raise HrxParseError("HRX payload must be a JSON object.")
|
|
100
|
+
return HRX._load_from_dict(cast(dict[str, Any], parsed))
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _load_from_dict(payload: dict[str, Any]) -> Candidate:
|
|
104
|
+
try:
|
|
105
|
+
return Candidate.model_validate(payload)
|
|
106
|
+
except ValidationError as exc:
|
|
107
|
+
raise HrxValidationError(
|
|
108
|
+
"HRX payload does not match the expected model.",
|
|
109
|
+
details=exc.errors()
|
|
110
|
+
) from exc
|
|
111
|
+
|
|
112
|
+
def to_dict(self) -> dict[str, Any]:
|
|
113
|
+
"""
|
|
114
|
+
Serialize the HRX document to a dictionary.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
None
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Dictionary representing the HRX document.
|
|
121
|
+
"""
|
|
122
|
+
return self.candidate.model_dump(mode="json", by_alias=True, exclude_none=True)
|
|
123
|
+
|
|
124
|
+
def to_json(self, *, indent: int = 2) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Serialize the HRX document to a JSON string.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
indent: Number of spaces for indentation in the JSON string.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
JSON string representing the HRX document.
|
|
133
|
+
"""
|
|
134
|
+
return self.candidate.model_dump_json(by_alias=True, exclude_none=True, indent=indent)
|
|
135
|
+
|
|
136
|
+
def validate_against_schema(self, schema: dict[str, Any] | str | Path) -> None:
|
|
137
|
+
"""
|
|
138
|
+
Validate the HRX document against a JSON schema.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
schema: JSON schema to validate against.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
None
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
from jsonschema import validate as jsonschema_validate #pylint: disable=import-outside-toplevel
|
|
148
|
+
from jsonschema.exceptions import ValidationError as JsonSchemaValidationError #pylint: disable=import-outside-toplevel
|
|
149
|
+
except ImportError as exc:
|
|
150
|
+
raise HrxSchemaError(
|
|
151
|
+
"jsonschema package is required to validate against a JSON schema."
|
|
152
|
+
) from exc
|
|
153
|
+
|
|
154
|
+
if isinstance(schema, dict):
|
|
155
|
+
schema_data = schema
|
|
156
|
+
else:
|
|
157
|
+
schema_path = Path(schema)
|
|
158
|
+
try:
|
|
159
|
+
schema_data = json.loads(schema_path.read_text(encoding="utf-8"))
|
|
160
|
+
except OSError as exc:
|
|
161
|
+
raise HrxSchemaError(f"Cannot read schema file: {schema_path}") from exc
|
|
162
|
+
except json.JSONDecodeError as exc:
|
|
163
|
+
raise HrxSchemaError(f"Invalid schema JSON: {exc}") from exc
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
jsonschema_validate(instance=self.to_dict(), schema=schema_data)
|
|
167
|
+
except JsonSchemaValidationError as exc:
|
|
168
|
+
raise HrxSchemaError(
|
|
169
|
+
"HRX payload does not match the provided JSON schema.",
|
|
170
|
+
details=exc.message
|
|
171
|
+
) from exc
|
hrxlib-0.1.0/hrx/api.pyi
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from .models import Candidate
|
|
5
|
+
|
|
6
|
+
class HRX:
|
|
7
|
+
candidate: Candidate
|
|
8
|
+
def __init__(self, source: Path | str | dict[str, Any] | Candidate) -> None: ...
|
|
9
|
+
@classmethod
|
|
10
|
+
def from_path(cls, path: Path | str) -> HRX: ...
|
|
11
|
+
@classmethod
|
|
12
|
+
def from_json(cls, payload: str) -> HRX: ...
|
|
13
|
+
@classmethod
|
|
14
|
+
def from_dict(cls, payload: dict[str, Any]) -> HRX: ...
|
|
15
|
+
def to_dict(self) -> dict[str, Any]: ...
|
|
16
|
+
def to_json(self, *, indent: int = 2) -> str: ...
|
|
17
|
+
def validate_against_schema(self, schema: dict[str, Any] | str | Path) -> None: ...
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Custom exceptions for the HRX Python SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HrxError(Exception):
|
|
9
|
+
"""Base exception for all SDK errors."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message: str, *, details: Any | None = None) -> None:
|
|
12
|
+
super().__init__(message)
|
|
13
|
+
self.details = details
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HrxFileError(HrxError):
|
|
17
|
+
"""Raised when a file cannot be read or written."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class HrxParseError(HrxError):
|
|
21
|
+
"""Raised when HRX payload is not valid JSON."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HrxValidationError(HrxError):
|
|
25
|
+
"""Raised when payload does not match the Pydantic HRX model."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class HrxSchemaError(HrxError):
|
|
29
|
+
"""Raised when payload does not match a JSON schema."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class HrxVersionError(HrxError):
|
|
33
|
+
"""Raised when document version is not supported."""
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
class HrxError(Exception):
|
|
4
|
+
details: Any | None
|
|
5
|
+
def __init__(self, message: str, *, details: Any | None = None) -> None: ...
|
|
6
|
+
|
|
7
|
+
class HrxFileError(HrxError): ...
|
|
8
|
+
class HrxParseError(HrxError): ...
|
|
9
|
+
class HrxValidationError(HrxError): ...
|
|
10
|
+
class HrxSchemaError(HrxError): ...
|
|
11
|
+
class HrxVersionError(HrxError): ...
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Pydantic models aligned with HRX v1 JSON schema."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import date
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field, HttpUrl, StringConstraints
|
|
9
|
+
from typing_extensions import Annotated
|
|
10
|
+
|
|
11
|
+
YearMonth = Annotated[str, StringConstraints(pattern=r"^\d{4}-\d{2}$")]
|
|
12
|
+
CountryCode = Annotated[str, StringConstraints(pattern=r"^[A-Z]{2}$")]
|
|
13
|
+
LanguageCode = Annotated[str, StringConstraints(pattern=r"^[a-z]{2}$")]
|
|
14
|
+
VersionCode = Annotated[str, StringConstraints(pattern=r"^\d+\.\d+$")]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StrictModel(BaseModel):
|
|
18
|
+
"""Shared model config: no unknown keys and alias support."""
|
|
19
|
+
|
|
20
|
+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Civility(str, Enum):
|
|
24
|
+
"""Common civility titles."""
|
|
25
|
+
MR = "Mr."
|
|
26
|
+
MRS = "Mrs."
|
|
27
|
+
DR = "Dr."
|
|
28
|
+
PROF = "Prof."
|
|
29
|
+
MX = "Mx."
|
|
30
|
+
ME = "Me"
|
|
31
|
+
OTHER = "other"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ContactType(str, Enum):
|
|
35
|
+
"""Types of contact methods."""
|
|
36
|
+
EMAIL = "email"
|
|
37
|
+
PHONE = "phone"
|
|
38
|
+
LINKEDIN = "linkedin"
|
|
39
|
+
GITHUB = "github"
|
|
40
|
+
X = "x"
|
|
41
|
+
FACEBOOK = "facebook"
|
|
42
|
+
INSTAGRAM = "instagram"
|
|
43
|
+
WEBSITE = "website"
|
|
44
|
+
OTHER = "other"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Mobility(str, Enum):
|
|
48
|
+
"""Types of mobility."""
|
|
49
|
+
LOCAL = "local"
|
|
50
|
+
REGIONAL = "regional"
|
|
51
|
+
NATIONAL = "national"
|
|
52
|
+
INTERNATIONAL = "international"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Level(str, Enum):
|
|
56
|
+
"""Proficiency levels."""
|
|
57
|
+
BEGINNER = "beginner"
|
|
58
|
+
INTERMEDIATE = "intermediate"
|
|
59
|
+
ADVANCED = "advanced"
|
|
60
|
+
EXPERT = "expert"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ContractType(str, Enum):
|
|
64
|
+
"""Types of contract."""
|
|
65
|
+
PERMANENT = "permanent"
|
|
66
|
+
FIXED_TERM = "fixed-term"
|
|
67
|
+
FREELANCE = "freelance"
|
|
68
|
+
MISSION = "mission"
|
|
69
|
+
INTERNSHIP = "internship"
|
|
70
|
+
APPRENTICESHIP = "apprenticeship"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class RemoteType(str, Enum):
|
|
74
|
+
"""Types of remote work."""
|
|
75
|
+
NO = "no"
|
|
76
|
+
PARTIAL = "partial"
|
|
77
|
+
FULL = "full"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class BibliographyType(str, Enum):
|
|
81
|
+
"""Types of bibliography entries."""
|
|
82
|
+
ARTICLE = "article"
|
|
83
|
+
BOOK = "book"
|
|
84
|
+
CONFERENCE = "conference"
|
|
85
|
+
REPORT = "report"
|
|
86
|
+
OTHER = "other"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class HrxMetadata(StrictModel):
|
|
90
|
+
"""HRX metadata."""
|
|
91
|
+
version: VersionCode
|
|
92
|
+
schema_uri: HttpUrl = Field(alias="schema")
|
|
93
|
+
issuer: str | None = None
|
|
94
|
+
date: date
|
|
95
|
+
lang: LanguageCode | None = None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Contact(StrictModel):
|
|
99
|
+
"""Contact information."""
|
|
100
|
+
type: ContactType
|
|
101
|
+
value: str
|
|
102
|
+
primary: bool | None = None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Location(StrictModel):
|
|
106
|
+
"""Location information."""
|
|
107
|
+
city: str | None = None
|
|
108
|
+
postal_code: str | None = None
|
|
109
|
+
country: CountryCode | None = None
|
|
110
|
+
mobility: Mobility | None = None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Identity(StrictModel):
|
|
114
|
+
"""Identity information."""
|
|
115
|
+
civility: Civility | None = None
|
|
116
|
+
last_name: str
|
|
117
|
+
first_name: str
|
|
118
|
+
contacts: list[Contact] | None = None
|
|
119
|
+
location: Location | None = None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class SkillItem(StrictModel):
|
|
123
|
+
"""Skill item."""
|
|
124
|
+
label: str
|
|
125
|
+
level: Level | None = None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class SkillDomain(StrictModel):
|
|
129
|
+
"""Skill domain."""
|
|
130
|
+
domain: str
|
|
131
|
+
items: list[SkillItem]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class Skills(StrictModel):
|
|
135
|
+
"""Skills information."""
|
|
136
|
+
hard: list[SkillDomain] | None = None
|
|
137
|
+
soft: list[SkillDomain] | None = None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class Period(StrictModel):
|
|
141
|
+
"""Period information."""
|
|
142
|
+
start: YearMonth
|
|
143
|
+
end: YearMonth | None = None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class Project(StrictModel):
|
|
147
|
+
"""Project information."""
|
|
148
|
+
title: str
|
|
149
|
+
description: str | None = None
|
|
150
|
+
role: str | None = None
|
|
151
|
+
period: Period | None = None
|
|
152
|
+
url: HttpUrl | None = None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class Experience(StrictModel):
|
|
156
|
+
"""Experience information."""
|
|
157
|
+
position: str
|
|
158
|
+
organisation: str
|
|
159
|
+
sector: str | None = None
|
|
160
|
+
period: Period
|
|
161
|
+
description: str | None = None
|
|
162
|
+
achievements: list[str] | None = None
|
|
163
|
+
projects: list[Project] | None = None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class Education(StrictModel):
|
|
167
|
+
"""Education information."""
|
|
168
|
+
title: str
|
|
169
|
+
institution: str
|
|
170
|
+
year: int = Field(ge=1900, le=2100)
|
|
171
|
+
certification: bool | None = None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class Award(StrictModel):
|
|
175
|
+
"""Award information."""
|
|
176
|
+
title: str
|
|
177
|
+
issuer: str | None = None
|
|
178
|
+
year: int | None = None
|
|
179
|
+
description: str | None = None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class Reference(StrictModel):
|
|
183
|
+
"""Reference information."""
|
|
184
|
+
name: str
|
|
185
|
+
position: str | None = None
|
|
186
|
+
organisation: str | None = None
|
|
187
|
+
contact: str | None = None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class Bibliography(StrictModel):
|
|
191
|
+
"""Bibliography information."""
|
|
192
|
+
title: str
|
|
193
|
+
authors: list[str] | None = None
|
|
194
|
+
year: int | None = None
|
|
195
|
+
type: BibliographyType | None = None
|
|
196
|
+
url: HttpUrl | None = None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class Credentials(StrictModel):
|
|
200
|
+
"""Credentials information."""
|
|
201
|
+
awards: list[Award] | None = None
|
|
202
|
+
references: list[Reference] | None = None
|
|
203
|
+
bibliography: list[Bibliography] | None = None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class Preferences(StrictModel):
|
|
207
|
+
"""Preferences information."""
|
|
208
|
+
availability: date | None = None
|
|
209
|
+
contracts: list[ContractType] | None = None
|
|
210
|
+
remote: RemoteType | None = None
|
|
211
|
+
salary_min: int | None = None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class Candidate(StrictModel):
|
|
215
|
+
"""Candidate information."""
|
|
216
|
+
hrx: HrxMetadata = Field(alias="$hrx")
|
|
217
|
+
identity: Identity
|
|
218
|
+
skills: Skills | None = None
|
|
219
|
+
experiences: list[Experience] | None = None
|
|
220
|
+
education: list[Education] | None = None
|
|
221
|
+
credentials: Credentials | None = None
|
|
222
|
+
preferences: Preferences | None = None
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Stubs des modèles Pydantic utilisés par le SDK HRX.
|
|
2
|
+
|
|
3
|
+
Ce fichier décrit l'API de type des modèles définis dans `models.py`.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from datetime import date
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import List, Optional, ClassVar
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, HttpUrl, ConfigDict
|
|
13
|
+
|
|
14
|
+
# Types simples (les contraintes sont appliquées au runtime dans `models.py`).
|
|
15
|
+
YearMonth = str
|
|
16
|
+
CountryCode = str
|
|
17
|
+
LanguageCode = str
|
|
18
|
+
VersionCode = str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StrictModel(BaseModel): # pylint: disable=C0115
|
|
22
|
+
model_config: ClassVar[ConfigDict]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Civility(str, Enum): # pylint: disable=C0115
|
|
26
|
+
MR: str
|
|
27
|
+
MRS: str
|
|
28
|
+
DR: str
|
|
29
|
+
PROF: str
|
|
30
|
+
MX: str
|
|
31
|
+
ME: str
|
|
32
|
+
OTHER: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ContactType(str, Enum): # pylint: disable=C0115
|
|
36
|
+
EMAIL: str
|
|
37
|
+
PHONE: str
|
|
38
|
+
LINKEDIN: str
|
|
39
|
+
GITHUB: str
|
|
40
|
+
X: str
|
|
41
|
+
FACEBOOK: str
|
|
42
|
+
INSTAGRAM: str
|
|
43
|
+
WEBSITE: str
|
|
44
|
+
OTHER: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Mobility(str, Enum): # pylint: disable=C0115
|
|
48
|
+
LOCAL: str
|
|
49
|
+
REGIONAL: str
|
|
50
|
+
NATIONAL: str
|
|
51
|
+
INTERNATIONAL: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Level(str, Enum): # pylint: disable=C0115
|
|
55
|
+
BEGINNER: str
|
|
56
|
+
INTERMEDIATE: str
|
|
57
|
+
ADVANCED: str
|
|
58
|
+
EXPERT: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ContractType(str, Enum): # pylint: disable=C0115
|
|
62
|
+
PERMANENT: str
|
|
63
|
+
FIXED_TERM: str
|
|
64
|
+
FREELANCE: str
|
|
65
|
+
MISSION: str
|
|
66
|
+
INTERNSHIP: str
|
|
67
|
+
APPRENTICESHIP: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class RemoteType(str, Enum): # pylint: disable=C0115
|
|
71
|
+
NO: str
|
|
72
|
+
PARTIAL: str
|
|
73
|
+
FULL: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class BibliographyType(str, Enum): # pylint: disable=C0115
|
|
77
|
+
ARTICLE: str
|
|
78
|
+
BOOK: str
|
|
79
|
+
CONFERENCE: str
|
|
80
|
+
REPORT: str
|
|
81
|
+
OTHER: str
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class HrxMetadata(StrictModel): # pylint: disable=C0115
|
|
85
|
+
version: VersionCode
|
|
86
|
+
schema_uri: HttpUrl
|
|
87
|
+
issuer: Optional[str]
|
|
88
|
+
date: date
|
|
89
|
+
lang: Optional[LanguageCode]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Contact(StrictModel): # pylint: disable=C0115
|
|
93
|
+
type: ContactType
|
|
94
|
+
value: str
|
|
95
|
+
primary: Optional[bool]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Location(StrictModel): # pylint: disable=C0115
|
|
99
|
+
city: Optional[str]
|
|
100
|
+
postal_code: Optional[str]
|
|
101
|
+
country: Optional[CountryCode]
|
|
102
|
+
mobility: Optional[Mobility]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Identity(StrictModel): # pylint: disable=C0115
|
|
106
|
+
civility: Optional[Civility]
|
|
107
|
+
last_name: str
|
|
108
|
+
first_name: str
|
|
109
|
+
contacts: Optional[List[Contact]]
|
|
110
|
+
location: Optional[Location]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class SkillItem(StrictModel): # pylint: disable=C0115
|
|
114
|
+
label: str
|
|
115
|
+
level: Optional[Level]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class SkillDomain(StrictModel): # pylint: disable=C0115
|
|
119
|
+
domain: str
|
|
120
|
+
items: List[SkillItem]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class Skills(StrictModel): # pylint: disable=C0115
|
|
124
|
+
hard: Optional[List[SkillDomain]]
|
|
125
|
+
soft: Optional[List[SkillDomain]]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class Period(StrictModel): # pylint: disable=C0115
|
|
129
|
+
start: YearMonth
|
|
130
|
+
end: Optional[YearMonth]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class Project(StrictModel): # pylint: disable=C0115
|
|
134
|
+
title: str
|
|
135
|
+
description: Optional[str]
|
|
136
|
+
role: Optional[str]
|
|
137
|
+
period: Optional[Period]
|
|
138
|
+
url: Optional[HttpUrl]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Experience(StrictModel): # pylint: disable=C0115
|
|
142
|
+
position: str
|
|
143
|
+
organisation: str
|
|
144
|
+
sector: Optional[str]
|
|
145
|
+
period: Period
|
|
146
|
+
description: Optional[str]
|
|
147
|
+
achievements: Optional[List[str]]
|
|
148
|
+
projects: Optional[List[Project]]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class Education(StrictModel): # pylint: disable=C0115
|
|
152
|
+
title: str
|
|
153
|
+
institution: str
|
|
154
|
+
year: int
|
|
155
|
+
certification: Optional[bool]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class Award(StrictModel): # pylint: disable=C0115
|
|
159
|
+
title: str
|
|
160
|
+
issuer: Optional[str]
|
|
161
|
+
year: Optional[int]
|
|
162
|
+
description: Optional[str]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class Reference(StrictModel): # pylint: disable=C0115
|
|
166
|
+
name: str
|
|
167
|
+
position: Optional[str]
|
|
168
|
+
organisation: Optional[str]
|
|
169
|
+
contact: Optional[str]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class Bibliography(StrictModel): # pylint: disable=C0115
|
|
173
|
+
title: str
|
|
174
|
+
authors: Optional[List[str]]
|
|
175
|
+
year: Optional[int]
|
|
176
|
+
type: Optional[BibliographyType]
|
|
177
|
+
url: Optional[HttpUrl]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class Credentials(StrictModel): # pylint: disable=C0115
|
|
181
|
+
awards: Optional[List[Award]]
|
|
182
|
+
references: Optional[List[Reference]]
|
|
183
|
+
bibliography: Optional[List[Bibliography]]
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class Preferences(StrictModel): # pylint: disable=C0115
|
|
187
|
+
availability: Optional[date]
|
|
188
|
+
contracts: Optional[List[ContractType]]
|
|
189
|
+
remote: Optional[RemoteType]
|
|
190
|
+
salary_min: Optional[int]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class Candidate(StrictModel): # pylint: disable=C0115
|
|
194
|
+
hrx: HrxMetadata
|
|
195
|
+
identity: Identity
|
|
196
|
+
skills: Optional[Skills]
|
|
197
|
+
experiences: Optional[List[Experience]]
|
|
198
|
+
education: Optional[List[Education]]
|
|
199
|
+
credentials: Optional[Credentials]
|
|
200
|
+
preferences: Optional[Preferences]
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hrxlib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: HRX SDK for Python — types and helpers for HRX models
|
|
5
|
+
Author-email: HRX Project <rverschuur@audit-io.fr>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://example.com/hrx
|
|
8
|
+
Project-URL: Source, https://example.com/hrx/repo
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: pydantic<3,>=2
|
|
22
|
+
Requires-Dist: jsonschema<5,>=4
|
|
23
|
+
Provides-Extra: test
|
|
24
|
+
Requires-Dist: pytest<9,>=8; extra == "test"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# HRX Python SDK (hrxlib)
|
|
28
|
+
|
|
29
|
+
Paquet Python fournissant les modeles Pydantic et helpers pour le format HRX.
|
|
30
|
+
|
|
31
|
+
Installation (locally):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install build
|
|
35
|
+
cd src/hrx/sdk/python
|
|
36
|
+
python -m build
|
|
37
|
+
pip install dist/hrxlib-0.1.0-py3-none-any.whl
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Usage:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
|
|
45
|
+
from hrx import HRX
|
|
46
|
+
|
|
47
|
+
doc = HRX.from_path(Path("candidate.hrx"))
|
|
48
|
+
candidate = doc.candidate
|
|
49
|
+
payload_dict = doc.to_dict()
|
|
50
|
+
payload_json = doc.to_json(indent=2)
|
|
51
|
+
|
|
52
|
+
# Optional: strict JSON schema validation
|
|
53
|
+
doc.validate_against_schema(Path("../../models/hrx-schema-v1.json"))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Erreurs SDK:
|
|
57
|
+
|
|
58
|
+
- `HrxFileError`: probleme de lecture/ecriture fichier
|
|
59
|
+
- `HrxParseError`: JSON invalide
|
|
60
|
+
- `HrxValidationError`: payload non conforme aux modeles Pydantic
|
|
61
|
+
- `HrxSchemaError`: payload non conforme au schema JSON
|
|
62
|
+
- `HrxVersionError`: version HRX non supportee
|
|
63
|
+
|
|
64
|
+
Licence: MIT
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
hrx/__init__.py
|
|
5
|
+
hrx/__init__.pyi
|
|
6
|
+
hrx/api.py
|
|
7
|
+
hrx/api.pyi
|
|
8
|
+
hrx/errors.py
|
|
9
|
+
hrx/errors.pyi
|
|
10
|
+
hrx/models.py
|
|
11
|
+
hrx/models.pyi
|
|
12
|
+
hrx/py.typed
|
|
13
|
+
hrxlib.egg-info/PKG-INFO
|
|
14
|
+
hrxlib.egg-info/SOURCES.txt
|
|
15
|
+
hrxlib.egg-info/dependency_links.txt
|
|
16
|
+
hrxlib.egg-info/requires.txt
|
|
17
|
+
hrxlib.egg-info/top_level.txt
|
|
18
|
+
tests/test_hrx_api.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hrx
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hrxlib"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "HRX SDK for Python — types and helpers for HRX models"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{name = "HRX Project", email = "rverschuur@audit-io.fr"}]
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"pydantic>=2,<3",
|
|
16
|
+
"jsonschema>=4,<5",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[tool.setuptools]
|
|
31
|
+
include-package-data = true
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.package-data]
|
|
34
|
+
hrx = ["*.pyi", "py.typed"]
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["."]
|
|
38
|
+
include = ["hrx*"]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
"Homepage" = "https://example.com/hrx"
|
|
42
|
+
"Source" = "https://example.com/hrx/repo"
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
test = [
|
|
46
|
+
"pytest>=8,<9",
|
|
47
|
+
]
|
hrxlib-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for the HRX Python SDK.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from hrx import HRX
|
|
13
|
+
from hrx.errors import (
|
|
14
|
+
HrxFileError,
|
|
15
|
+
HrxParseError,
|
|
16
|
+
HrxSchemaError,
|
|
17
|
+
HrxValidationError,
|
|
18
|
+
HrxVersionError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_load_from_path_success(hrx_sample_file: Path) -> None:
|
|
23
|
+
"""Test loading an HRX document from a file."""
|
|
24
|
+
doc = HRX.from_path(hrx_sample_file)
|
|
25
|
+
|
|
26
|
+
assert doc.candidate.identity.first_name == "Claire"
|
|
27
|
+
assert doc.candidate.hrx.version == "1.0"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_round_trip_dict_and_json(sample_payload: dict[str, Any]) -> None:
|
|
31
|
+
"""Test round-trip serialization to dict and JSON."""
|
|
32
|
+
doc = HRX.from_dict(sample_payload)
|
|
33
|
+
|
|
34
|
+
as_dict = doc.to_dict()
|
|
35
|
+
as_json = doc.to_json()
|
|
36
|
+
parsed_back = json.loads(as_json)
|
|
37
|
+
|
|
38
|
+
assert as_dict["$hrx"]["version"] == "1.0"
|
|
39
|
+
assert parsed_back["identity"]["last_name"] == "Martin"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_invalid_json_raises_parse_error() -> None:
|
|
43
|
+
"""Test parsing invalid JSON."""
|
|
44
|
+
with pytest.raises(HrxParseError):
|
|
45
|
+
HRX.from_json("{not-valid-json}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_non_existing_file_raises_file_error() -> None:
|
|
49
|
+
"""Test loading from a non-existing file."""
|
|
50
|
+
with pytest.raises(HrxFileError):
|
|
51
|
+
HRX.from_path(Path("does-not-exist.hrx"))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_missing_required_field_raises_validation_error(
|
|
55
|
+
sample_payload: dict[str, Any]
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Test loading a document with missing required fields."""
|
|
58
|
+
payload = dict(sample_payload)
|
|
59
|
+
payload.pop("identity", None)
|
|
60
|
+
|
|
61
|
+
with pytest.raises(HrxValidationError):
|
|
62
|
+
HRX.from_dict(payload)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_unsupported_version_raises_version_error(
|
|
66
|
+
sample_payload: dict[str, Any]
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Test loading a document with an unsupported HRX version."""
|
|
69
|
+
payload = dict(sample_payload)
|
|
70
|
+
hrx_meta = dict(sample_payload["$hrx"])
|
|
71
|
+
hrx_meta["version"] = "2.0"
|
|
72
|
+
payload["$hrx"] = hrx_meta
|
|
73
|
+
|
|
74
|
+
with pytest.raises(HrxVersionError):
|
|
75
|
+
HRX.from_dict(payload)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_schema_validation_success(
|
|
79
|
+
hrx_sample_file: Path, sample_schema_path: Path
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Test successful schema validation."""
|
|
82
|
+
doc = HRX.from_path(hrx_sample_file)
|
|
83
|
+
|
|
84
|
+
doc.validate_against_schema(sample_schema_path)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_schema_validation_failure_raises_schema_error(
|
|
88
|
+
sample_payload: dict[str, Any]
|
|
89
|
+
) -> None:
|
|
90
|
+
"""Test schema validation failure."""
|
|
91
|
+
invalid_doc = HRX.from_dict(sample_payload)
|
|
92
|
+
strict_schema = {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"required": ["identity"],
|
|
95
|
+
"properties": {
|
|
96
|
+
"identity": {
|
|
97
|
+
"type": "object",
|
|
98
|
+
"required": ["last_name", "first_name"],
|
|
99
|
+
"properties": {
|
|
100
|
+
"last_name": {"type": "string", "const": "SHOULD_FAIL"},
|
|
101
|
+
"first_name": {"type": "string"},
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
with pytest.raises(HrxSchemaError):
|
|
108
|
+
invalid_doc.validate_against_schema(strict_schema)
|