rxresume-python 0.1.0__tar.gz → 0.3.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.
Files changed (33) hide show
  1. {rxresume_python-0.1.0/rxresume_python.egg-info → rxresume_python-0.3.0}/PKG-INFO +37 -2
  2. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/README.md +31 -1
  3. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/pyproject.toml +21 -2
  4. rxresume_python-0.3.0/reactive_resume/api/__init__.py +26 -0
  5. rxresume_python-0.3.0/reactive_resume/api/agent.py +37 -0
  6. rxresume_python-0.3.0/reactive_resume/api/ai_providers.py +31 -0
  7. rxresume_python-0.3.0/reactive_resume/api/applications.py +78 -0
  8. rxresume_python-0.3.0/reactive_resume/api/statistics.py +27 -0
  9. rxresume_python-0.3.0/reactive_resume/api/storage.py +53 -0
  10. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/reactive_resume/core/async_client.py +10 -0
  11. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/reactive_resume/core/client.py +10 -0
  12. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/reactive_resume/models/__init__.py +11 -0
  13. rxresume_python-0.3.0/reactive_resume/models/agent.py +15 -0
  14. rxresume_python-0.3.0/reactive_resume/models/application.py +33 -0
  15. rxresume_python-0.3.0/reactive_resume/models/statistics.py +14 -0
  16. rxresume_python-0.3.0/reactive_resume/models/storage.py +14 -0
  17. {rxresume_python-0.1.0 → rxresume_python-0.3.0/rxresume_python.egg-info}/PKG-INFO +37 -2
  18. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/rxresume_python.egg-info/SOURCES.txt +10 -3
  19. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/rxresume_python.egg-info/requires.txt +6 -0
  20. rxresume_python-0.1.0/reactive_resume/api/__init__.py +0 -11
  21. rxresume_python-0.1.0/tests/test_client_async.py +0 -107
  22. rxresume_python-0.1.0/tests/test_client_sync.py +0 -113
  23. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/LICENSE +0 -0
  24. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/reactive_resume/__init__.py +0 -0
  25. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/reactive_resume/api/auth.py +0 -0
  26. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/reactive_resume/api/resumes.py +0 -0
  27. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/reactive_resume/core/__init__.py +0 -0
  28. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/reactive_resume/core/exceptions.py +0 -0
  29. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/reactive_resume/models/resume.py +0 -0
  30. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/reactive_resume/models/user.py +0 -0
  31. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/rxresume_python.egg-info/dependency_links.txt +0 -0
  32. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/rxresume_python.egg-info/top_level.txt +0 -0
  33. {rxresume_python-0.1.0 → rxresume_python-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rxresume-python
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: Unofficial Python API client (SDK) for Reactive Resume v4
5
5
  Author: Ata Can Yaymacı
6
6
  License: MIT
@@ -20,6 +20,11 @@ Requires-Dist: ruff>=0.1.0; extra == "dev"
20
20
  Requires-Dist: pre-commit>=3.0.0; extra == "dev"
21
21
  Requires-Dist: bandit>=1.7.0; extra == "dev"
22
22
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
23
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
24
+ Provides-Extra: docs
25
+ Requires-Dist: mkdocs>=1.5.0; extra == "docs"
26
+ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
27
+ Requires-Dist: mkdocstrings[python]>=0.20.0; extra == "docs"
23
28
  Dynamic: license-file
24
29
 
25
30
  # Reactive Resume Python SDK (`rxresume-python`)
@@ -37,10 +42,12 @@ It supports both synchronous (`httpx.Client`) and asynchronous (`httpx.AsyncClie
37
42
  ## Features
38
43
 
39
44
  - **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.
45
+ - **Full API coverage**: Integrated modules for Resume management, Auth, Job Tracker (Applications), AI Agent prompts, AI Providers configurations, Statistics, and Storage uploads.
46
+ - **Type safety**: Fully typed models for all entities using Pydantic V2.
41
47
  - **Robust error handling**: Raw API status errors are automatically parsed into specific exceptions (`AuthenticationError`, `NotFoundError`, etc.).
42
48
  - **Developer Experience (DX)**: Code-completion ready with clear typing and docstrings.
43
49
 
50
+
44
51
  ---
45
52
 
46
53
  ## Installation
@@ -106,8 +113,36 @@ with RxResumeClient(base_url="https://rxresu.me", api_key="your_api_key") as cli
106
113
  print(f"Resume: {resume.name} (Slug: {resume.slug})")
107
114
  ```
108
115
 
116
+ ### 3. Advanced Features (AI Agent, Storage, Statistics, Applications)
117
+
118
+ ```python
119
+ with RxResumeClient(base_url="https://rxresu.me", api_key="your_api_key") as client:
120
+ # 1. Ask the AI agent to optimize a resume summary
121
+ ai_response = client.agent.chat("resume_id_here", "Suggest a professional summary for a software developer.")
122
+ print(f"AI Suggestion: {ai_response.response}")
123
+
124
+ # 2. Upload a profile image to storage
125
+ with open("avatar.png", "rb") as f:
126
+ file_metadata = client.storage.upload_file(f.read(), "avatar.png")
127
+ print(f"Uploaded Image URL: {file_metadata.url}")
128
+
129
+ # 3. Retrieve resume metrics
130
+ stats = client.statistics.get("resume_id_here")
131
+ print(f"Views: {stats.views}, Downloads: {stats.downloads}")
132
+
133
+ # 4. Log a new job application
134
+ from reactive_resume.models import ApplicationCreate
135
+ app = client.applications.create(ApplicationCreate(
136
+ company="Google",
137
+ position="Senior Backend Engineer",
138
+ stage="Interviewing"
139
+ ))
140
+ print(f"Logged Application ID: {app.id}")
141
+ ```
142
+
109
143
  ---
110
144
 
145
+
111
146
  ## Error Handling
112
147
 
113
148
  All client API calls map HTTP errors to specific exception classes:
@@ -13,10 +13,12 @@ It supports both synchronous (`httpx.Client`) and asynchronous (`httpx.AsyncClie
13
13
  ## Features
14
14
 
15
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.
16
+ - **Full API coverage**: Integrated modules for Resume management, Auth, Job Tracker (Applications), AI Agent prompts, AI Providers configurations, Statistics, and Storage uploads.
17
+ - **Type safety**: Fully typed models for all entities using Pydantic V2.
17
18
  - **Robust error handling**: Raw API status errors are automatically parsed into specific exceptions (`AuthenticationError`, `NotFoundError`, etc.).
18
19
  - **Developer Experience (DX)**: Code-completion ready with clear typing and docstrings.
19
20
 
21
+
20
22
  ---
21
23
 
22
24
  ## Installation
@@ -82,8 +84,36 @@ with RxResumeClient(base_url="https://rxresu.me", api_key="your_api_key") as cli
82
84
  print(f"Resume: {resume.name} (Slug: {resume.slug})")
83
85
  ```
84
86
 
87
+ ### 3. Advanced Features (AI Agent, Storage, Statistics, Applications)
88
+
89
+ ```python
90
+ with RxResumeClient(base_url="https://rxresu.me", api_key="your_api_key") as client:
91
+ # 1. Ask the AI agent to optimize a resume summary
92
+ ai_response = client.agent.chat("resume_id_here", "Suggest a professional summary for a software developer.")
93
+ print(f"AI Suggestion: {ai_response.response}")
94
+
95
+ # 2. Upload a profile image to storage
96
+ with open("avatar.png", "rb") as f:
97
+ file_metadata = client.storage.upload_file(f.read(), "avatar.png")
98
+ print(f"Uploaded Image URL: {file_metadata.url}")
99
+
100
+ # 3. Retrieve resume metrics
101
+ stats = client.statistics.get("resume_id_here")
102
+ print(f"Views: {stats.views}, Downloads: {stats.downloads}")
103
+
104
+ # 4. Log a new job application
105
+ from reactive_resume.models import ApplicationCreate
106
+ app = client.applications.create(ApplicationCreate(
107
+ company="Google",
108
+ position="Senior Backend Engineer",
109
+ stage="Interviewing"
110
+ ))
111
+ print(f"Logged Application ID: {app.id}")
112
+ ```
113
+
85
114
  ---
86
115
 
116
+
87
117
  ## Error Handling
88
118
 
89
119
  All client API calls map HTTP errors to specific exception classes:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rxresume-python"
7
- version = "0.1.0"
7
+ version = "0.3.0"
8
8
  description = "Unofficial Python API client (SDK) for Reactive Resume v4"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -21,9 +21,9 @@ dependencies = [
21
21
  "httpx>=0.24.0",
22
22
  "pydantic[email]>=2.0.0",
23
23
  ]
24
-
25
24
  [project.optional-dependencies]
26
25
  dev = [
26
+
27
27
  "pytest>=7.0.0",
28
28
  "pytest-asyncio>=0.20.0",
29
29
  "respx>=0.20.0",
@@ -31,8 +31,15 @@ dev = [
31
31
  "pre-commit>=3.0.0",
32
32
  "bandit>=1.7.0",
33
33
  "pytest-cov>=4.0.0",
34
+ "mypy>=1.0.0",
35
+ ]
36
+ docs = [
37
+ "mkdocs>=1.5.0",
38
+ "mkdocs-material>=9.0.0",
39
+ "mkdocstrings[python]>=0.20.0",
34
40
  ]
35
41
 
42
+
36
43
  [tool.setuptools.packages.find]
37
44
  where = ["."]
38
45
  include = ["reactive_resume*"]
@@ -44,3 +51,15 @@ target-version = "py310"
44
51
  [tool.pytest.ini_options]
45
52
  asyncio_mode = "auto"
46
53
  testpaths = ["tests"]
54
+
55
+ [tool.mypy]
56
+ python_version = "3.10"
57
+ warn_unused_configs = true
58
+ warn_redundant_casts = true
59
+ warn_unused_ignores = true
60
+ strict_equality = true
61
+ extra_checks = true
62
+ check_untyped_defs = true
63
+ disallow_untyped_decorators = true
64
+ disallow_any_generics = true
65
+ plugins = ["pydantic.mypy"]
@@ -0,0 +1,26 @@
1
+ """API endpoints and service groups."""
2
+
3
+ from .auth import AuthAPI, AsyncAuthAPI
4
+ from .resumes import ResumesAPI, AsyncResumesAPI
5
+ from .applications import ApplicationsAPI, AsyncApplicationsAPI
6
+ from .statistics import StatisticsAPI, AsyncStatisticsAPI
7
+ from .storage import StorageAPI, AsyncStorageAPI
8
+ from .agent import AgentAPI, AsyncAgentAPI
9
+ from .ai_providers import AiProvidersAPI, AsyncAiProvidersAPI
10
+
11
+ __all__ = [
12
+ "AuthAPI",
13
+ "AsyncAuthAPI",
14
+ "ResumesAPI",
15
+ "AsyncResumesAPI",
16
+ "ApplicationsAPI",
17
+ "AsyncApplicationsAPI",
18
+ "StatisticsAPI",
19
+ "AsyncStatisticsAPI",
20
+ "StorageAPI",
21
+ "AsyncStorageAPI",
22
+ "AgentAPI",
23
+ "AsyncAgentAPI",
24
+ "AiProvidersAPI",
25
+ "AsyncAiProvidersAPI",
26
+ ]
@@ -0,0 +1,37 @@
1
+ """Agent API endpoints implementation."""
2
+
3
+ from ..models.agent import AgentRequest, AgentResponse
4
+
5
+
6
+ class AgentAPI:
7
+ """Synchronous AI Agent operations."""
8
+
9
+ def __init__(self, client) -> None:
10
+ self._client = client
11
+
12
+ def chat(self, resume_id: str, prompt: str) -> AgentResponse:
13
+ """Send a prompt to the AI Agent relative to a specific resume context."""
14
+ payload = AgentRequest(prompt=prompt).model_dump()
15
+ response = self._client._request(
16
+ "POST",
17
+ f"/api/openapi/agent/{resume_id}/chat",
18
+ json=payload,
19
+ )
20
+ return AgentResponse.model_validate(response)
21
+
22
+
23
+ class AsyncAgentAPI:
24
+ """Asynchronous AI Agent operations."""
25
+
26
+ def __init__(self, client) -> None:
27
+ self._client = client
28
+
29
+ async def chat(self, resume_id: str, prompt: str) -> AgentResponse:
30
+ """Send a prompt to the AI Agent asynchronously relative to a specific resume context."""
31
+ payload = AgentRequest(prompt=prompt).model_dump()
32
+ response = await self._client._request(
33
+ "POST",
34
+ f"/api/openapi/agent/{resume_id}/chat",
35
+ json=payload,
36
+ )
37
+ return AgentResponse.model_validate(response)
@@ -0,0 +1,31 @@
1
+ """AI Providers API endpoints implementation."""
2
+
3
+ from typing import List, Dict, Any
4
+
5
+
6
+ class AiProvidersAPI:
7
+ """Synchronous AI Providers operations."""
8
+
9
+ def __init__(self, client) -> None:
10
+ self._client = client
11
+
12
+ def list(self) -> List[Dict[str, Any]]:
13
+ """List all configured AI Providers."""
14
+ response = self._client._request("GET", "/api/openapi/ai-providers")
15
+ if isinstance(response, list):
16
+ return response
17
+ return [response]
18
+
19
+
20
+ class AsyncAiProvidersAPI:
21
+ """Asynchronous AI Providers operations."""
22
+
23
+ def __init__(self, client) -> None:
24
+ self._client = client
25
+
26
+ async def list(self) -> List[Dict[str, Any]]:
27
+ """List all configured AI Providers asynchronously."""
28
+ response = await self._client._request("GET", "/api/openapi/ai-providers")
29
+ if isinstance(response, list):
30
+ return response
31
+ return [response]
@@ -0,0 +1,78 @@
1
+ """Applications API endpoints implementation."""
2
+
3
+ from typing import List, Dict, Any
4
+ from ..models.application import Application, ApplicationCreate
5
+
6
+
7
+ class ApplicationsAPI:
8
+ """Synchronous Applications operations."""
9
+
10
+ def __init__(self, client) -> None:
11
+ self._client = client
12
+
13
+ def list(self) -> List[Application]:
14
+ """List all job applications."""
15
+ response = self._client._request("GET", "/api/openapi/applications")
16
+ if isinstance(response, dict) and "data" in response:
17
+ items = response["data"]
18
+ else:
19
+ items = response
20
+ return [Application.model_validate(item) for item in items]
21
+
22
+ def get(self, app_id: str) -> Application:
23
+ """Retrieve a specific job application by ID."""
24
+ response = self._client._request("GET", f"/api/openapi/applications/{app_id}")
25
+ return Application.model_validate(response)
26
+
27
+ def create(self, data: ApplicationCreate) -> Application:
28
+ """Log/create a new job application."""
29
+ payload = data.model_dump(by_alias=True, exclude_none=True)
30
+ response = self._client._request("POST", "/api/openapi/applications", json=payload)
31
+ return Application.model_validate(response)
32
+
33
+ def update(self, app_id: str, data: Dict[str, Any]) -> Application:
34
+ """Update an existing job application."""
35
+ response = self._client._request("PATCH", f"/api/openapi/applications/{app_id}", json=data)
36
+ return Application.model_validate(response)
37
+
38
+ def delete(self, app_id: str) -> None:
39
+ """Delete a job application by ID."""
40
+ self._client._request("DELETE", f"/api/openapi/applications/{app_id}")
41
+
42
+
43
+ class AsyncApplicationsAPI:
44
+ """Asynchronous Applications operations."""
45
+
46
+ def __init__(self, client) -> None:
47
+ self._client = client
48
+
49
+ async def list(self) -> List[Application]:
50
+ """List all job applications asynchronously."""
51
+ response = await self._client._request("GET", "/api/openapi/applications")
52
+ if isinstance(response, dict) and "data" in response:
53
+ items = response["data"]
54
+ else:
55
+ items = response
56
+ return [Application.model_validate(item) for item in items]
57
+
58
+ async def get(self, app_id: str) -> Application:
59
+ """Retrieve a specific job application by ID asynchronously."""
60
+ response = await self._client._request("GET", f"/api/openapi/applications/{app_id}")
61
+ return Application.model_validate(response)
62
+
63
+ async def create(self, data: ApplicationCreate) -> Application:
64
+ """Log/create a new job application asynchronously."""
65
+ payload = data.model_dump(by_alias=True, exclude_none=True)
66
+ response = await self._client._request("POST", "/api/openapi/applications", json=payload)
67
+ return Application.model_validate(response)
68
+
69
+ async def update(self, app_id: str, data: Dict[str, Any]) -> Application:
70
+ """Update an existing job application asynchronously."""
71
+ response = await self._client._request(
72
+ "PATCH", f"/api/openapi/applications/{app_id}", json=data
73
+ )
74
+ return Application.model_validate(response)
75
+
76
+ async def delete(self, app_id: str) -> None:
77
+ """Delete a job application by ID asynchronously."""
78
+ await self._client._request("DELETE", f"/api/openapi/applications/{app_id}")
@@ -0,0 +1,27 @@
1
+ """Statistics API endpoints implementation."""
2
+
3
+ from ..models.statistics import ResumeStats
4
+
5
+
6
+ class StatisticsAPI:
7
+ """Synchronous Statistics operations."""
8
+
9
+ def __init__(self, client) -> None:
10
+ self._client = client
11
+
12
+ def get(self, resume_id: str) -> ResumeStats:
13
+ """Retrieve interaction statistics for a specific resume."""
14
+ response = self._client._request("GET", f"/api/openapi/statistics/{resume_id}")
15
+ return ResumeStats.model_validate(response)
16
+
17
+
18
+ class AsyncStatisticsAPI:
19
+ """Asynchronous Statistics operations."""
20
+
21
+ def __init__(self, client) -> None:
22
+ self._client = client
23
+
24
+ async def get(self, resume_id: str) -> ResumeStats:
25
+ """Retrieve interaction statistics for a specific resume asynchronously."""
26
+ response = await self._client._request("GET", f"/api/openapi/statistics/{resume_id}")
27
+ return ResumeStats.model_validate(response)
@@ -0,0 +1,53 @@
1
+ """Storage API endpoints implementation."""
2
+
3
+ from ..models.storage import StorageFile
4
+
5
+
6
+ class StorageAPI:
7
+ """Synchronous Storage operations."""
8
+
9
+ def __init__(self, client) -> None:
10
+ self._client = client
11
+
12
+ def upload_file(self, file_content: bytes, filename: str) -> StorageFile:
13
+ """Upload a file to storage.
14
+
15
+ Args:
16
+ file_content: Raw bytes of the file.
17
+ filename: Target name for the uploaded file.
18
+
19
+ Returns:
20
+ Metadata details of the uploaded file.
21
+ """
22
+ files = {"file": (filename, file_content)}
23
+ response = self._client._request(
24
+ "POST",
25
+ "/api/openapi/storage/upload",
26
+ files=files,
27
+ )
28
+ return StorageFile.model_validate(response)
29
+
30
+
31
+ class AsyncStorageAPI:
32
+ """Asynchronous Storage operations."""
33
+
34
+ def __init__(self, client) -> None:
35
+ self._client = client
36
+
37
+ async def upload_file(self, file_content: bytes, filename: str) -> StorageFile:
38
+ """Upload a file to storage asynchronously.
39
+
40
+ Args:
41
+ file_content: Raw bytes of the file.
42
+ filename: Target name for the uploaded file.
43
+
44
+ Returns:
45
+ Metadata details of the uploaded file.
46
+ """
47
+ files = {"file": (filename, file_content)}
48
+ response = await self._client._request(
49
+ "POST",
50
+ "/api/openapi/storage/upload",
51
+ files=files,
52
+ )
53
+ return StorageFile.model_validate(response)
@@ -11,6 +11,11 @@ from .exceptions import (
11
11
  )
12
12
  from ..api.auth import AsyncAuthAPI
13
13
  from ..api.resumes import AsyncResumesAPI
14
+ from ..api.applications import AsyncApplicationsAPI
15
+ from ..api.statistics import AsyncStatisticsAPI
16
+ from ..api.storage import AsyncStorageAPI
17
+ from ..api.agent import AsyncAgentAPI
18
+ from ..api.ai_providers import AsyncAiProvidersAPI
14
19
 
15
20
 
16
21
  class AsyncRxResumeClient:
@@ -57,6 +62,11 @@ class AsyncRxResumeClient:
57
62
  # Initialize API sections
58
63
  self.auth = AsyncAuthAPI(self)
59
64
  self.resumes = AsyncResumesAPI(self)
65
+ self.applications = AsyncApplicationsAPI(self)
66
+ self.statistics = AsyncStatisticsAPI(self)
67
+ self.storage = AsyncStorageAPI(self)
68
+ self.agent = AsyncAgentAPI(self)
69
+ self.ai_providers = AsyncAiProvidersAPI(self)
60
70
 
61
71
  def set_token(self, token: str) -> None:
62
72
  """Update client headers with a new Bearer token."""
@@ -11,6 +11,11 @@ from .exceptions import (
11
11
  )
12
12
  from ..api.auth import AuthAPI
13
13
  from ..api.resumes import ResumesAPI
14
+ from ..api.applications import ApplicationsAPI
15
+ from ..api.statistics import StatisticsAPI
16
+ from ..api.storage import StorageAPI
17
+ from ..api.agent import AgentAPI
18
+ from ..api.ai_providers import AiProvidersAPI
14
19
 
15
20
 
16
21
  class RxResumeClient:
@@ -57,6 +62,11 @@ class RxResumeClient:
57
62
  # Initialize API sections
58
63
  self.auth = AuthAPI(self)
59
64
  self.resumes = ResumesAPI(self)
65
+ self.applications = ApplicationsAPI(self)
66
+ self.statistics = StatisticsAPI(self)
67
+ self.storage = StorageAPI(self)
68
+ self.agent = AgentAPI(self)
69
+ self.ai_providers = AiProvidersAPI(self)
60
70
 
61
71
  def set_token(self, token: str) -> None:
62
72
  """Update client headers with a new Bearer token."""
@@ -24,6 +24,11 @@ from .resume import (
24
24
  ResumeImportData,
25
25
  )
26
26
 
27
+ from .application import Application, ApplicationCreate
28
+ from .statistics import ResumeStats
29
+ from .storage import StorageFile
30
+ from .agent import AgentRequest, AgentResponse
31
+
27
32
  __all__ = [
28
33
  "User",
29
34
  "URLModel",
@@ -46,4 +51,10 @@ __all__ = [
46
51
  "ResumeData",
47
52
  "Resume",
48
53
  "ResumeImportData",
54
+ "Application",
55
+ "ApplicationCreate",
56
+ "ResumeStats",
57
+ "StorageFile",
58
+ "AgentRequest",
59
+ "AgentResponse",
49
60
  ]
@@ -0,0 +1,15 @@
1
+ """Pydantic models representing AI Agent communication in Reactive Resume."""
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class AgentRequest(BaseModel):
7
+ """Payload to send to the AI Agent."""
8
+
9
+ prompt: str
10
+
11
+
12
+ class AgentResponse(BaseModel):
13
+ """Response returned by the AI Agent."""
14
+
15
+ response: str
@@ -0,0 +1,33 @@
1
+ """Pydantic models representing job applications in Reactive Resume."""
2
+
3
+ from pydantic import BaseModel, Field, ConfigDict
4
+ from datetime import datetime
5
+ from typing import Optional
6
+
7
+
8
+ class Application(BaseModel):
9
+ """Represents a job application in the tracker."""
10
+
11
+ model_config = ConfigDict(populate_by_name=True)
12
+
13
+ id: str
14
+ user_id: str = Field(..., alias="userId")
15
+ company: str
16
+ position: str
17
+ stage: str = "Applied" # Applied, Interviewing, Offered, Rejected
18
+ date: datetime
19
+ summary: str = ""
20
+ url: str = ""
21
+ created_at: datetime = Field(..., alias="createdAt")
22
+ updated_at: datetime = Field(..., alias="updatedAt")
23
+
24
+
25
+ class ApplicationCreate(BaseModel):
26
+ """Payload to log/create a new job application."""
27
+
28
+ company: str
29
+ position: str
30
+ stage: Optional[str] = "Applied"
31
+ date: Optional[datetime] = None
32
+ summary: Optional[str] = ""
33
+ url: Optional[str] = ""
@@ -0,0 +1,14 @@
1
+ """Pydantic models representing resume interaction statistics."""
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+ from typing import Dict, Any
5
+
6
+
7
+ class ResumeStats(BaseModel):
8
+ """Represents view and download statistics for a resume."""
9
+
10
+ model_config = ConfigDict(populate_by_name=True)
11
+
12
+ views: int = 0
13
+ downloads: int = 0
14
+ history: Dict[str, Any] = {}
@@ -0,0 +1,14 @@
1
+ """Pydantic models representing storage files in Reactive Resume."""
2
+
3
+ from pydantic import BaseModel, Field, ConfigDict
4
+
5
+
6
+ class StorageFile(BaseModel):
7
+ """Represents a file/blob uploaded to Reactive Resume storage."""
8
+
9
+ model_config = ConfigDict(populate_by_name=True)
10
+
11
+ filename: str
12
+ url: str
13
+ size: int
14
+ mime_type: str = Field(..., alias="mimeType")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rxresume-python
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: Unofficial Python API client (SDK) for Reactive Resume v4
5
5
  Author: Ata Can Yaymacı
6
6
  License: MIT
@@ -20,6 +20,11 @@ Requires-Dist: ruff>=0.1.0; extra == "dev"
20
20
  Requires-Dist: pre-commit>=3.0.0; extra == "dev"
21
21
  Requires-Dist: bandit>=1.7.0; extra == "dev"
22
22
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
23
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
24
+ Provides-Extra: docs
25
+ Requires-Dist: mkdocs>=1.5.0; extra == "docs"
26
+ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
27
+ Requires-Dist: mkdocstrings[python]>=0.20.0; extra == "docs"
23
28
  Dynamic: license-file
24
29
 
25
30
  # Reactive Resume Python SDK (`rxresume-python`)
@@ -37,10 +42,12 @@ It supports both synchronous (`httpx.Client`) and asynchronous (`httpx.AsyncClie
37
42
  ## Features
38
43
 
39
44
  - **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.
45
+ - **Full API coverage**: Integrated modules for Resume management, Auth, Job Tracker (Applications), AI Agent prompts, AI Providers configurations, Statistics, and Storage uploads.
46
+ - **Type safety**: Fully typed models for all entities using Pydantic V2.
41
47
  - **Robust error handling**: Raw API status errors are automatically parsed into specific exceptions (`AuthenticationError`, `NotFoundError`, etc.).
42
48
  - **Developer Experience (DX)**: Code-completion ready with clear typing and docstrings.
43
49
 
50
+
44
51
  ---
45
52
 
46
53
  ## Installation
@@ -106,8 +113,36 @@ with RxResumeClient(base_url="https://rxresu.me", api_key="your_api_key") as cli
106
113
  print(f"Resume: {resume.name} (Slug: {resume.slug})")
107
114
  ```
108
115
 
116
+ ### 3. Advanced Features (AI Agent, Storage, Statistics, Applications)
117
+
118
+ ```python
119
+ with RxResumeClient(base_url="https://rxresu.me", api_key="your_api_key") as client:
120
+ # 1. Ask the AI agent to optimize a resume summary
121
+ ai_response = client.agent.chat("resume_id_here", "Suggest a professional summary for a software developer.")
122
+ print(f"AI Suggestion: {ai_response.response}")
123
+
124
+ # 2. Upload a profile image to storage
125
+ with open("avatar.png", "rb") as f:
126
+ file_metadata = client.storage.upload_file(f.read(), "avatar.png")
127
+ print(f"Uploaded Image URL: {file_metadata.url}")
128
+
129
+ # 3. Retrieve resume metrics
130
+ stats = client.statistics.get("resume_id_here")
131
+ print(f"Views: {stats.views}, Downloads: {stats.downloads}")
132
+
133
+ # 4. Log a new job application
134
+ from reactive_resume.models import ApplicationCreate
135
+ app = client.applications.create(ApplicationCreate(
136
+ company="Google",
137
+ position="Senior Backend Engineer",
138
+ stage="Interviewing"
139
+ ))
140
+ print(f"Logged Application ID: {app.id}")
141
+ ```
142
+
109
143
  ---
110
144
 
145
+
111
146
  ## Error Handling
112
147
 
113
148
  All client API calls map HTTP errors to specific exception classes:
@@ -3,19 +3,26 @@ README.md
3
3
  pyproject.toml
4
4
  reactive_resume/__init__.py
5
5
  reactive_resume/api/__init__.py
6
+ reactive_resume/api/agent.py
7
+ reactive_resume/api/ai_providers.py
8
+ reactive_resume/api/applications.py
6
9
  reactive_resume/api/auth.py
7
10
  reactive_resume/api/resumes.py
11
+ reactive_resume/api/statistics.py
12
+ reactive_resume/api/storage.py
8
13
  reactive_resume/core/__init__.py
9
14
  reactive_resume/core/async_client.py
10
15
  reactive_resume/core/client.py
11
16
  reactive_resume/core/exceptions.py
12
17
  reactive_resume/models/__init__.py
18
+ reactive_resume/models/agent.py
19
+ reactive_resume/models/application.py
13
20
  reactive_resume/models/resume.py
21
+ reactive_resume/models/statistics.py
22
+ reactive_resume/models/storage.py
14
23
  reactive_resume/models/user.py
15
24
  rxresume_python.egg-info/PKG-INFO
16
25
  rxresume_python.egg-info/SOURCES.txt
17
26
  rxresume_python.egg-info/dependency_links.txt
18
27
  rxresume_python.egg-info/requires.txt
19
- rxresume_python.egg-info/top_level.txt
20
- tests/test_client_async.py
21
- tests/test_client_sync.py
28
+ rxresume_python.egg-info/top_level.txt
@@ -9,3 +9,9 @@ ruff>=0.1.0
9
9
  pre-commit>=3.0.0
10
10
  bandit>=1.7.0
11
11
  pytest-cov>=4.0.0
12
+ mypy>=1.0.0
13
+
14
+ [docs]
15
+ mkdocs>=1.5.0
16
+ mkdocs-material>=9.0.0
17
+ mkdocstrings[python]>=0.20.0
@@ -1,11 +0,0 @@
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
- ]
@@ -1,107 +0,0 @@
1
- import pytest
2
- import respx
3
- from httpx import Response
4
- from reactive_resume import AsyncRxResumeClient, AuthenticationError
5
- from reactive_resume.models import ResumeImportData, Resume
6
-
7
- BASE_URL = "https://rxresu.me"
8
- API_KEY = "test-api-key"
9
-
10
-
11
- @pytest.fixture
12
- async def async_client():
13
- async with AsyncRxResumeClient(base_url=BASE_URL, api_key=API_KEY) as c:
14
- yield c
15
-
16
-
17
- @pytest.mark.asyncio
18
- @respx.mock
19
- async def test_list_resumes_async(async_client):
20
- mock_resume = {
21
- "id": "test-resume-id",
22
- "name": "Test CV",
23
- "slug": "test-cv",
24
- "userId": "user-123",
25
- "visibility": "public",
26
- "locked": False,
27
- "data": {
28
- "basics": {
29
- "name": "John Doe",
30
- "headline": "Software Developer",
31
- "email": "john@example.com",
32
- "phone": "123456",
33
- "website": "https://john.me",
34
- "location": "Earth",
35
- "picture": "",
36
- "profiles": [],
37
- },
38
- "sections": {},
39
- },
40
- "createdAt": "2026-07-13T12:00:00Z",
41
- "updatedAt": "2026-07-13T12:00:00Z",
42
- }
43
-
44
- route = respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
45
- return_value=Response(200, json=[mock_resume])
46
- )
47
-
48
- resumes = await async_client.resumes.list()
49
- assert len(resumes) == 1
50
- assert isinstance(resumes[0], Resume)
51
- assert resumes[0].id == "test-resume-id"
52
- assert resumes[0].data.basics.name == "John Doe"
53
- assert route.called
54
-
55
-
56
- @pytest.mark.asyncio
57
- @respx.mock
58
- async def test_get_resume_async(async_client):
59
- mock_resume = {
60
- "id": "resume-1",
61
- "name": "Test CV 1",
62
- "slug": "test-cv-1",
63
- "userId": "user-123",
64
- "data": {"basics": {}, "sections": {}},
65
- "createdAt": "2026-07-13T12:00:00Z",
66
- "updatedAt": "2026-07-13T12:00:00Z",
67
- }
68
- route = respx.get(f"{BASE_URL}/api/openapi/resume/resume-1").mock(
69
- return_value=Response(200, json=mock_resume)
70
- )
71
-
72
- resume = await async_client.resumes.get("resume-1")
73
- assert resume.id == "resume-1"
74
- assert route.called
75
-
76
-
77
- @pytest.mark.asyncio
78
- @respx.mock
79
- async def test_create_resume_async(async_client):
80
- mock_resume = {
81
- "id": "new-resume",
82
- "name": "My New CV",
83
- "slug": "my-new-cv",
84
- "userId": "user-123",
85
- "data": {"basics": {}, "sections": {}},
86
- "createdAt": "2026-07-13T12:00:00Z",
87
- "updatedAt": "2026-07-13T12:00:00Z",
88
- }
89
- route = respx.post(f"{BASE_URL}/api/openapi/resumes").mock(
90
- return_value=Response(201, json=mock_resume)
91
- )
92
-
93
- import_data = ResumeImportData(title="My New CV")
94
- resume = await async_client.resumes.import_resume(import_data)
95
- assert resume.id == "new-resume"
96
- assert route.called
97
-
98
-
99
- @pytest.mark.asyncio
100
- @respx.mock
101
- async def test_authentication_error_async(async_client):
102
- respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
103
- return_value=Response(401, json={"message": "Invalid API Key"})
104
- )
105
-
106
- with pytest.raises(AuthenticationError):
107
- await async_client.resumes.list()
@@ -1,113 +0,0 @@
1
- import pytest
2
- import respx
3
- from httpx import Response
4
- from reactive_resume import RxResumeClient, AuthenticationError, NotFoundError
5
- from reactive_resume.models import ResumeImportData, Resume
6
-
7
- BASE_URL = "https://rxresu.me"
8
- API_KEY = "test-api-key"
9
-
10
-
11
- @pytest.fixture
12
- def client():
13
- with RxResumeClient(base_url=BASE_URL, api_key=API_KEY) as c:
14
- yield c
15
-
16
-
17
- @respx.mock
18
- def test_list_resumes(client):
19
- mock_resume = {
20
- "id": "test-resume-id",
21
- "name": "Test CV",
22
- "slug": "test-cv",
23
- "userId": "user-123",
24
- "visibility": "public",
25
- "locked": False,
26
- "data": {
27
- "basics": {
28
- "name": "John Doe",
29
- "headline": "Software Developer",
30
- "email": "john@example.com",
31
- "phone": "123456",
32
- "website": "https://john.me",
33
- "location": "Earth",
34
- "picture": "",
35
- "profiles": [],
36
- },
37
- "sections": {},
38
- },
39
- "createdAt": "2026-07-13T12:00:00Z",
40
- "updatedAt": "2026-07-13T12:00:00Z",
41
- }
42
-
43
- route = respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
44
- return_value=Response(200, json=[mock_resume])
45
- )
46
-
47
- resumes = client.resumes.list()
48
- assert len(resumes) == 1
49
- assert isinstance(resumes[0], Resume)
50
- assert resumes[0].id == "test-resume-id"
51
- assert resumes[0].data.basics.name == "John Doe"
52
- assert route.called
53
-
54
-
55
- @respx.mock
56
- def test_get_resume(client):
57
- mock_resume = {
58
- "id": "resume-1",
59
- "name": "Test CV 1",
60
- "slug": "test-cv-1",
61
- "userId": "user-123",
62
- "data": {"basics": {}, "sections": {}},
63
- "createdAt": "2026-07-13T12:00:00Z",
64
- "updatedAt": "2026-07-13T12:00:00Z",
65
- }
66
- route = respx.get(f"{BASE_URL}/api/openapi/resume/resume-1").mock(
67
- return_value=Response(200, json=mock_resume)
68
- )
69
-
70
- resume = client.resumes.get("resume-1")
71
- assert resume.id == "resume-1"
72
- assert route.called
73
-
74
-
75
- @respx.mock
76
- def test_create_resume(client):
77
- mock_resume = {
78
- "id": "new-resume",
79
- "name": "My New CV",
80
- "slug": "my-new-cv",
81
- "userId": "user-123",
82
- "data": {"basics": {}, "sections": {}},
83
- "createdAt": "2026-07-13T12:00:00Z",
84
- "updatedAt": "2026-07-13T12:00:00Z",
85
- }
86
- route = respx.post(f"{BASE_URL}/api/openapi/resumes").mock(
87
- return_value=Response(201, json=mock_resume)
88
- )
89
-
90
- import_data = ResumeImportData(title="My New CV")
91
- resume = client.resumes.import_resume(import_data)
92
- assert resume.id == "new-resume"
93
- assert route.called
94
-
95
-
96
- @respx.mock
97
- def test_authentication_error(client):
98
- respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
99
- return_value=Response(401, json={"message": "Invalid API Key"})
100
- )
101
-
102
- with pytest.raises(AuthenticationError):
103
- client.resumes.list()
104
-
105
-
106
- @respx.mock
107
- def test_not_found_error(client):
108
- respx.get(f"{BASE_URL}/api/openapi/resume/non-existent").mock(
109
- return_value=Response(404, json={"message": "Not found"})
110
- )
111
-
112
- with pytest.raises(NotFoundError):
113
- client.resumes.get("non-existent")
File without changes