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.
@@ -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
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
28
+ [![Pydantic v2](https://img.shields.io/badge/pydantic-v2-red.svg)](https://docs.pydantic.dev/)
29
+ [![HTTPX](https://img.shields.io/badge/http--client-httpx-brightgreen.svg)](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
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
4
+ [![Pydantic v2](https://img.shields.io/badge/pydantic-v2-red.svg)](https://docs.pydantic.dev/)
5
+ [![HTTPX](https://img.shields.io/badge/http--client-httpx-brightgreen.svg)](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,11 @@
1
+ """API endpoints and service groups."""
2
+
3
+ from .auth import AuthAPI, AsyncAuthAPI
4
+ from .resumes import ResumesAPI, AsyncResumesAPI
5
+
6
+ __all__ = [
7
+ "AuthAPI",
8
+ "AsyncAuthAPI",
9
+ "ResumesAPI",
10
+ "AsyncResumesAPI",
11
+ ]
@@ -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
+ ]