rxresume-python 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.
- rxresume_python-0.1.0/LICENSE +21 -0
- rxresume_python-0.1.0/PKG-INFO +155 -0
- rxresume_python-0.1.0/README.md +131 -0
- rxresume_python-0.1.0/pyproject.toml +46 -0
- rxresume_python-0.1.0/reactive_resume/__init__.py +26 -0
- rxresume_python-0.1.0/reactive_resume/api/__init__.py +11 -0
- rxresume_python-0.1.0/reactive_resume/api/auth.py +66 -0
- rxresume_python-0.1.0/reactive_resume/api/resumes.py +169 -0
- rxresume_python-0.1.0/reactive_resume/core/__init__.py +17 -0
- rxresume_python-0.1.0/reactive_resume/core/async_client.py +151 -0
- rxresume_python-0.1.0/reactive_resume/core/client.py +151 -0
- rxresume_python-0.1.0/reactive_resume/core/exceptions.py +45 -0
- rxresume_python-0.1.0/reactive_resume/models/__init__.py +49 -0
- rxresume_python-0.1.0/reactive_resume/models/resume.py +206 -0
- rxresume_python-0.1.0/reactive_resume/models/user.py +24 -0
- rxresume_python-0.1.0/rxresume_python.egg-info/PKG-INFO +155 -0
- rxresume_python-0.1.0/rxresume_python.egg-info/SOURCES.txt +21 -0
- rxresume_python-0.1.0/rxresume_python.egg-info/dependency_links.txt +1 -0
- rxresume_python-0.1.0/rxresume_python.egg-info/requires.txt +11 -0
- rxresume_python-0.1.0/rxresume_python.egg-info/top_level.txt +1 -0
- rxresume_python-0.1.0/setup.cfg +4 -0
- rxresume_python-0.1.0/tests/test_client_async.py +107 -0
- rxresume_python-0.1.0/tests/test_client_sync.py +113 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ata Can Yaymacı
|
|
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.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rxresume-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial Python API client (SDK) for Reactive Resume v4
|
|
5
|
+
Author: Ata Can Yaymacı
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: httpx>=0.24.0
|
|
14
|
+
Requires-Dist: pydantic[email]>=2.0.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.20.0; extra == "dev"
|
|
18
|
+
Requires-Dist: respx>=0.20.0; extra == "dev"
|
|
19
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
20
|
+
Requires-Dist: pre-commit>=3.0.0; extra == "dev"
|
|
21
|
+
Requires-Dist: bandit>=1.7.0; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# Reactive Resume Python SDK (`rxresume-python`)
|
|
26
|
+
|
|
27
|
+
[](https://www.python.org/downloads/)
|
|
28
|
+
[](https://docs.pydantic.dev/)
|
|
29
|
+
[](https://www.python-httpx.org/)
|
|
30
|
+
|
|
31
|
+
An unofficial modern, type-safe Python API Client (SDK) for **Reactive Resume v4**.
|
|
32
|
+
|
|
33
|
+
It supports both synchronous (`httpx.Client`) and asynchronous (`httpx.AsyncClient`) clients, ensures fully typed request/response schemas using **Pydantic v2**, and maps API errors to clean, descriptive Python exceptions.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Dual client modes**: Support for both sync and async APIs.
|
|
40
|
+
- **Type safety**: Fully typed models for Resumes, Sections, Basics, and Users using Pydantic V2.
|
|
41
|
+
- **Robust error handling**: Raw API status errors are automatically parsed into specific exceptions (`AuthenticationError`, `NotFoundError`, etc.).
|
|
42
|
+
- **Developer Experience (DX)**: Code-completion ready with clear typing and docstrings.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
Install the package via `pip` or your favorite package manager:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install rxresume-python
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Quick Start
|
|
57
|
+
|
|
58
|
+
### 1. Asynchronous Client (FastAPI / Asynchronous Code)
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import asyncio
|
|
62
|
+
from reactive_resume import AsyncRxResumeClient
|
|
63
|
+
from reactive_resume.models import ResumeImportData, Basics
|
|
64
|
+
|
|
65
|
+
async def main():
|
|
66
|
+
# Initialize the async client
|
|
67
|
+
async with AsyncRxResumeClient(base_url="https://rxresu.me", api_key="your_api_key") as client:
|
|
68
|
+
# Create a new resume
|
|
69
|
+
import_data = ResumeImportData(
|
|
70
|
+
title="Ata Can Yaymacı - Backend Engineer",
|
|
71
|
+
basics=Basics(
|
|
72
|
+
name="Ata Can Yaymacı",
|
|
73
|
+
headline="Backend Engineer",
|
|
74
|
+
email="ata@example.com",
|
|
75
|
+
phone="+905555555555",
|
|
76
|
+
website="https://example.com"
|
|
77
|
+
),
|
|
78
|
+
sections={}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
# Import/Create a new resume
|
|
83
|
+
new_resume = await client.resumes.import_resume(import_data)
|
|
84
|
+
print(f"Created resume: {new_resume.name} (ID: {new_resume.id})")
|
|
85
|
+
|
|
86
|
+
# Fetch the generated PDF URL
|
|
87
|
+
pdf_url = await client.resumes.get_pdf_url(new_resume.id)
|
|
88
|
+
print(f"PDF URL: {pdf_url}")
|
|
89
|
+
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(f"An error occurred: {e}")
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
asyncio.run(main())
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 2. Synchronous Client
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from reactive_resume import RxResumeClient
|
|
101
|
+
from reactive_resume.models import ResumeImportData
|
|
102
|
+
|
|
103
|
+
with RxResumeClient(base_url="https://rxresu.me", api_key="your_api_key") as client:
|
|
104
|
+
resumes = client.resumes.list()
|
|
105
|
+
for resume in resumes:
|
|
106
|
+
print(f"Resume: {resume.name} (Slug: {resume.slug})")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Error Handling
|
|
112
|
+
|
|
113
|
+
All client API calls map HTTP errors to specific exception classes:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from reactive_resume import RxResumeClient, AuthenticationError, NotFoundError
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
with RxResumeClient(base_url="https://rxresu.me", api_key="wrong_key") as client:
|
|
120
|
+
client.resumes.list()
|
|
121
|
+
except AuthenticationError as e:
|
|
122
|
+
print(f"Auth error (Status {e.status_code}): {e}")
|
|
123
|
+
except NotFoundError as e:
|
|
124
|
+
print(f"Not found: {e}")
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"Generic error: {e}")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Development & Testing
|
|
132
|
+
|
|
133
|
+
1. Clone the repository:
|
|
134
|
+
```bash
|
|
135
|
+
git clone https://github.com/your-username/reactive-resume-api-client-py.git
|
|
136
|
+
cd reactive-resume-api-client-py
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
2. Install dependencies:
|
|
140
|
+
```bash
|
|
141
|
+
python3 -m venv .venv
|
|
142
|
+
source .venv/bin/activate
|
|
143
|
+
pip install -e ".[dev]"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
3. Run the tests:
|
|
147
|
+
```bash
|
|
148
|
+
pytest
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Reactive Resume Python SDK (`rxresume-python`)
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/downloads/)
|
|
4
|
+
[](https://docs.pydantic.dev/)
|
|
5
|
+
[](https://www.python-httpx.org/)
|
|
6
|
+
|
|
7
|
+
An unofficial modern, type-safe Python API Client (SDK) for **Reactive Resume v4**.
|
|
8
|
+
|
|
9
|
+
It supports both synchronous (`httpx.Client`) and asynchronous (`httpx.AsyncClient`) clients, ensures fully typed request/response schemas using **Pydantic v2**, and maps API errors to clean, descriptive Python exceptions.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Dual client modes**: Support for both sync and async APIs.
|
|
16
|
+
- **Type safety**: Fully typed models for Resumes, Sections, Basics, and Users using Pydantic V2.
|
|
17
|
+
- **Robust error handling**: Raw API status errors are automatically parsed into specific exceptions (`AuthenticationError`, `NotFoundError`, etc.).
|
|
18
|
+
- **Developer Experience (DX)**: Code-completion ready with clear typing and docstrings.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Install the package via `pip` or your favorite package manager:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install rxresume-python
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### 1. Asynchronous Client (FastAPI / Asynchronous Code)
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import asyncio
|
|
38
|
+
from reactive_resume import AsyncRxResumeClient
|
|
39
|
+
from reactive_resume.models import ResumeImportData, Basics
|
|
40
|
+
|
|
41
|
+
async def main():
|
|
42
|
+
# Initialize the async client
|
|
43
|
+
async with AsyncRxResumeClient(base_url="https://rxresu.me", api_key="your_api_key") as client:
|
|
44
|
+
# Create a new resume
|
|
45
|
+
import_data = ResumeImportData(
|
|
46
|
+
title="Ata Can Yaymacı - Backend Engineer",
|
|
47
|
+
basics=Basics(
|
|
48
|
+
name="Ata Can Yaymacı",
|
|
49
|
+
headline="Backend Engineer",
|
|
50
|
+
email="ata@example.com",
|
|
51
|
+
phone="+905555555555",
|
|
52
|
+
website="https://example.com"
|
|
53
|
+
),
|
|
54
|
+
sections={}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
# Import/Create a new resume
|
|
59
|
+
new_resume = await client.resumes.import_resume(import_data)
|
|
60
|
+
print(f"Created resume: {new_resume.name} (ID: {new_resume.id})")
|
|
61
|
+
|
|
62
|
+
# Fetch the generated PDF URL
|
|
63
|
+
pdf_url = await client.resumes.get_pdf_url(new_resume.id)
|
|
64
|
+
print(f"PDF URL: {pdf_url}")
|
|
65
|
+
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"An error occurred: {e}")
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
asyncio.run(main())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Synchronous Client
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from reactive_resume import RxResumeClient
|
|
77
|
+
from reactive_resume.models import ResumeImportData
|
|
78
|
+
|
|
79
|
+
with RxResumeClient(base_url="https://rxresu.me", api_key="your_api_key") as client:
|
|
80
|
+
resumes = client.resumes.list()
|
|
81
|
+
for resume in resumes:
|
|
82
|
+
print(f"Resume: {resume.name} (Slug: {resume.slug})")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Error Handling
|
|
88
|
+
|
|
89
|
+
All client API calls map HTTP errors to specific exception classes:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from reactive_resume import RxResumeClient, AuthenticationError, NotFoundError
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
with RxResumeClient(base_url="https://rxresu.me", api_key="wrong_key") as client:
|
|
96
|
+
client.resumes.list()
|
|
97
|
+
except AuthenticationError as e:
|
|
98
|
+
print(f"Auth error (Status {e.status_code}): {e}")
|
|
99
|
+
except NotFoundError as e:
|
|
100
|
+
print(f"Not found: {e}")
|
|
101
|
+
except Exception as e:
|
|
102
|
+
print(f"Generic error: {e}")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Development & Testing
|
|
108
|
+
|
|
109
|
+
1. Clone the repository:
|
|
110
|
+
```bash
|
|
111
|
+
git clone https://github.com/your-username/reactive-resume-api-client-py.git
|
|
112
|
+
cd reactive-resume-api-client-py
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
2. Install dependencies:
|
|
116
|
+
```bash
|
|
117
|
+
python3 -m venv .venv
|
|
118
|
+
source .venv/bin/activate
|
|
119
|
+
pip install -e ".[dev]"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
3. Run the tests:
|
|
123
|
+
```bash
|
|
124
|
+
pytest
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rxresume-python"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Unofficial Python API client (SDK) for Reactive Resume v4"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Ata Can Yaymacı"}
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"httpx>=0.24.0",
|
|
22
|
+
"pydantic[email]>=2.0.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
"pytest>=7.0.0",
|
|
28
|
+
"pytest-asyncio>=0.20.0",
|
|
29
|
+
"respx>=0.20.0",
|
|
30
|
+
"ruff>=0.1.0",
|
|
31
|
+
"pre-commit>=3.0.0",
|
|
32
|
+
"bandit>=1.7.0",
|
|
33
|
+
"pytest-cov>=4.0.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["."]
|
|
38
|
+
include = ["reactive_resume*"]
|
|
39
|
+
|
|
40
|
+
[tool.ruff]
|
|
41
|
+
line-length = 100
|
|
42
|
+
target-version = "py310"
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
asyncio_mode = "auto"
|
|
46
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Reactive Resume API Python SDK.
|
|
2
|
+
|
|
3
|
+
An unofficial client for programmatically interacting with Reactive Resume v4 API.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .core.client import RxResumeClient
|
|
7
|
+
from .core.async_client import AsyncRxResumeClient
|
|
8
|
+
from .core.exceptions import (
|
|
9
|
+
ReactiveResumeError,
|
|
10
|
+
ReactiveResumeAPIError,
|
|
11
|
+
AuthenticationError,
|
|
12
|
+
NotFoundError,
|
|
13
|
+
ValidationError,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"RxResumeClient",
|
|
20
|
+
"AsyncRxResumeClient",
|
|
21
|
+
"ReactiveResumeError",
|
|
22
|
+
"ReactiveResumeAPIError",
|
|
23
|
+
"AuthenticationError",
|
|
24
|
+
"NotFoundError",
|
|
25
|
+
"ValidationError",
|
|
26
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Authentication endpoints implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import Tuple
|
|
4
|
+
from ..models.user import User
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AuthAPI:
|
|
8
|
+
"""Synchronous Authentication operations."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, client) -> None:
|
|
11
|
+
self._client = client
|
|
12
|
+
|
|
13
|
+
def login(self, email: str, password: str) -> Tuple[str, User]:
|
|
14
|
+
"""Log in with email and password.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
email: User's email.
|
|
18
|
+
password: User's password.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
A tuple of (access_token, User object).
|
|
22
|
+
"""
|
|
23
|
+
payload = {"identifier": email, "password": password}
|
|
24
|
+
response = self._client._request("POST", "/api/auth/login", json=payload)
|
|
25
|
+
|
|
26
|
+
# Typically returns token in cookies or response body
|
|
27
|
+
# Let's extract token and user
|
|
28
|
+
token = response.get("token") or response.get("accessToken", "")
|
|
29
|
+
user_data = response.get("user") or response
|
|
30
|
+
user = User.model_validate(user_data)
|
|
31
|
+
return token, user
|
|
32
|
+
|
|
33
|
+
def me(self) -> User:
|
|
34
|
+
"""Get the current authenticated user profile."""
|
|
35
|
+
response = self._client._request("GET", "/api/user/me")
|
|
36
|
+
return User.model_validate(response)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AsyncAuthAPI:
|
|
40
|
+
"""Asynchronous Authentication operations."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, client) -> None:
|
|
43
|
+
self._client = client
|
|
44
|
+
|
|
45
|
+
async def login(self, email: str, password: str) -> Tuple[str, User]:
|
|
46
|
+
"""Log in asynchronously with email and password.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
email: User's email.
|
|
50
|
+
password: User's password.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
A tuple of (access_token, User object).
|
|
54
|
+
"""
|
|
55
|
+
payload = {"identifier": email, "password": password}
|
|
56
|
+
response = await self._client._request("POST", "/api/auth/login", json=payload)
|
|
57
|
+
|
|
58
|
+
token = response.get("token") or response.get("accessToken", "")
|
|
59
|
+
user_data = response.get("user") or response
|
|
60
|
+
user = User.model_validate(user_data)
|
|
61
|
+
return token, user
|
|
62
|
+
|
|
63
|
+
async def me(self) -> User:
|
|
64
|
+
"""Get the current authenticated user profile asynchronously."""
|
|
65
|
+
response = await self._client._request("GET", "/api/user/me")
|
|
66
|
+
return User.model_validate(response)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Resumes endpoints implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Any
|
|
4
|
+
from ..models.resume import Resume, ResumeImportData
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ResumesAPI:
|
|
8
|
+
"""Synchronous Resume operations."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, client) -> None:
|
|
11
|
+
self._client = client
|
|
12
|
+
|
|
13
|
+
def list(self) -> List[Resume]:
|
|
14
|
+
"""List all resumes for the authenticated user.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
A list of Resume objects.
|
|
18
|
+
"""
|
|
19
|
+
response = self._client._request("GET", "/api/openapi/resumes")
|
|
20
|
+
# The response is usually a list of resume objects
|
|
21
|
+
if isinstance(response, dict) and "data" in response:
|
|
22
|
+
# Handle potential pagination wrapper
|
|
23
|
+
items = response["data"]
|
|
24
|
+
else:
|
|
25
|
+
items = response
|
|
26
|
+
return [Resume.model_validate(item) for item in items]
|
|
27
|
+
|
|
28
|
+
def get(self, resume_id: str) -> Resume:
|
|
29
|
+
"""Retrieve a specific resume by ID.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
resume_id: Unique identifier for the resume.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
The Resume object.
|
|
36
|
+
"""
|
|
37
|
+
response = self._client._request("GET", f"/api/openapi/resume/{resume_id}")
|
|
38
|
+
return Resume.model_validate(response)
|
|
39
|
+
|
|
40
|
+
def create(self, data: ResumeImportData) -> Resume:
|
|
41
|
+
"""Create or import a new resume.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
data: ResumeImportData object containing title, basics, and sections.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
The created Resume object.
|
|
48
|
+
"""
|
|
49
|
+
# Convert Pydantic model to dict, matching what API expects
|
|
50
|
+
payload = data.model_dump(by_alias=True, exclude_none=True)
|
|
51
|
+
response = self._client._request("POST", "/api/openapi/resumes", json=payload)
|
|
52
|
+
return Resume.model_validate(response)
|
|
53
|
+
|
|
54
|
+
def import_resume(self, data: ResumeImportData) -> Resume:
|
|
55
|
+
"""Alias for create, importing a resume.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
data: ResumeImportData object containing title, basics, and sections.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
The imported Resume object.
|
|
62
|
+
"""
|
|
63
|
+
return self.create(data)
|
|
64
|
+
|
|
65
|
+
def update(self, resume_id: str, data: Dict[str, Any]) -> Resume:
|
|
66
|
+
"""Update a resume (PATCH).
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
resume_id: Unique identifier for the resume.
|
|
70
|
+
data: Dictionary of properties to update (e.g. JSON Patch or key-value updates).
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The updated Resume object.
|
|
74
|
+
"""
|
|
75
|
+
# Reactive Resume v4 supports PATCH with JSON Patch or direct body updates
|
|
76
|
+
response = self._client._request("PATCH", f"/api/openapi/resume/{resume_id}", json=data)
|
|
77
|
+
return Resume.model_validate(response)
|
|
78
|
+
|
|
79
|
+
def delete(self, resume_id: str) -> None:
|
|
80
|
+
"""Delete a resume by ID.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
resume_id: Unique identifier for the resume.
|
|
84
|
+
"""
|
|
85
|
+
self._client._request("DELETE", f"/api/openapi/resume/{resume_id}")
|
|
86
|
+
|
|
87
|
+
def get_pdf_url(self, resume_id: str) -> str:
|
|
88
|
+
"""Get the URL to download the PDF for a specific resume.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
resume_id: Unique identifier for the resume.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
The absolute PDF download URL.
|
|
95
|
+
"""
|
|
96
|
+
# The print/pdf download URL format in Reactive Resume V4 API
|
|
97
|
+
# Typically of form: {base_url}/api/openapi/resume/{resume_id}/pdf
|
|
98
|
+
return f"{self._client.base_url}/api/openapi/resume/{resume_id}/pdf"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class AsyncResumesAPI:
|
|
102
|
+
"""Asynchronous Resume operations."""
|
|
103
|
+
|
|
104
|
+
def __init__(self, client) -> None:
|
|
105
|
+
self._client = client
|
|
106
|
+
|
|
107
|
+
async def list(self) -> List[Resume]:
|
|
108
|
+
"""List all resumes asynchronously.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
A list of Resume objects.
|
|
112
|
+
"""
|
|
113
|
+
response = await self._client._request("GET", "/api/openapi/resumes")
|
|
114
|
+
if isinstance(response, dict) and "data" in response:
|
|
115
|
+
items = response["data"]
|
|
116
|
+
else:
|
|
117
|
+
items = response
|
|
118
|
+
return [Resume.model_validate(item) for item in items]
|
|
119
|
+
|
|
120
|
+
async def get(self, resume_id: str) -> Resume:
|
|
121
|
+
"""Retrieve a specific resume asynchronously.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
resume_id: Unique identifier for the resume.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
The Resume object.
|
|
128
|
+
"""
|
|
129
|
+
response = await self._client._request("GET", f"/api/openapi/resume/{resume_id}")
|
|
130
|
+
return Resume.model_validate(response)
|
|
131
|
+
|
|
132
|
+
async def create(self, data: ResumeImportData) -> Resume:
|
|
133
|
+
"""Create or import a new resume asynchronously.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
data: ResumeImportData object.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
The created Resume object.
|
|
140
|
+
"""
|
|
141
|
+
payload = data.model_dump(by_alias=True, exclude_none=True)
|
|
142
|
+
response = await self._client._request("POST", "/api/openapi/resumes", json=payload)
|
|
143
|
+
return Resume.model_validate(response)
|
|
144
|
+
|
|
145
|
+
async def import_resume(self, data: ResumeImportData) -> Resume:
|
|
146
|
+
"""Alias for create, importing a resume asynchronously."""
|
|
147
|
+
return await self.create(data)
|
|
148
|
+
|
|
149
|
+
async def update(self, resume_id: str, data: Dict[str, Any]) -> Resume:
|
|
150
|
+
"""Update a resume asynchronously."""
|
|
151
|
+
response = await self._client._request(
|
|
152
|
+
"PATCH", f"/api/openapi/resume/{resume_id}", json=data
|
|
153
|
+
)
|
|
154
|
+
return Resume.model_validate(response)
|
|
155
|
+
|
|
156
|
+
async def delete(self, resume_id: str) -> None:
|
|
157
|
+
"""Delete a resume asynchronously."""
|
|
158
|
+
await self._client._request("DELETE", f"/api/openapi/resume/{resume_id}")
|
|
159
|
+
|
|
160
|
+
async def get_pdf_url(self, resume_id: str) -> str:
|
|
161
|
+
"""Get the URL to download the PDF asynchronously.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
resume_id: Unique identifier for the resume.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
The absolute PDF download URL.
|
|
168
|
+
"""
|
|
169
|
+
return f"{self._client.base_url}/api/openapi/resume/{resume_id}/pdf"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Core functionality and HTTP client structures."""
|
|
2
|
+
|
|
3
|
+
from .exceptions import (
|
|
4
|
+
ReactiveResumeError,
|
|
5
|
+
ReactiveResumeAPIError,
|
|
6
|
+
AuthenticationError,
|
|
7
|
+
NotFoundError,
|
|
8
|
+
ValidationError,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ReactiveResumeError",
|
|
13
|
+
"ReactiveResumeAPIError",
|
|
14
|
+
"AuthenticationError",
|
|
15
|
+
"NotFoundError",
|
|
16
|
+
"ValidationError",
|
|
17
|
+
]
|