aiondtech 2.0.0__py3-none-any.whl
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.
- aiondtech/__init__.py +79 -0
- aiondtech/client.py +603 -0
- aiondtech/models.py +428 -0
- aiondtech/py.typed +0 -0
- aiondtech-2.0.0.dist-info/METADATA +285 -0
- aiondtech-2.0.0.dist-info/RECORD +9 -0
- aiondtech-2.0.0.dist-info/WHEEL +5 -0
- aiondtech-2.0.0.dist-info/licenses/LICENSE +21 -0
- aiondtech-2.0.0.dist-info/top_level.txt +1 -0
aiondtech/__init__.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AiondTech Resume Analyser API - Python SDK v2.0
|
|
3
|
+
|
|
4
|
+
A Python client library for the AiondTech Resume Analyser API.
|
|
5
|
+
Provides resume parsing, analysis, and job matching capabilities.
|
|
6
|
+
|
|
7
|
+
Quick Start:
|
|
8
|
+
from aiondtech import ResumeAnalyser
|
|
9
|
+
|
|
10
|
+
client = ResumeAnalyser(api_key="your-api-key")
|
|
11
|
+
|
|
12
|
+
# Upload a resume
|
|
13
|
+
result = client.resumes.upload("resume.pdf")
|
|
14
|
+
print(f"Resume ID: {result.resume_id}")
|
|
15
|
+
|
|
16
|
+
# Upload and analyze
|
|
17
|
+
analysis = client.resumes.upload_and_analyze("resume.pdf")
|
|
18
|
+
print(f"Name: {analysis.full_name}")
|
|
19
|
+
print(f"Skills: {analysis.skills}")
|
|
20
|
+
|
|
21
|
+
# Create job and compare
|
|
22
|
+
job = client.jobs.create("Developer", "Python required...")
|
|
23
|
+
comparison = client.matching.compare(
|
|
24
|
+
resume_id=result.resume_id,
|
|
25
|
+
job_id=job.job_id
|
|
26
|
+
)
|
|
27
|
+
print(f"Match Score: {comparison.comparison_score}%")
|
|
28
|
+
|
|
29
|
+
Environment Variables:
|
|
30
|
+
AIONDTECH_API_KEY: Your API key
|
|
31
|
+
AIONDTECH_BASE_URL: Custom API URL (default: https://api.dev.aiondtech.com)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
__version__ = "2.0.0"
|
|
35
|
+
__author__ = "AiondTech"
|
|
36
|
+
|
|
37
|
+
from .client import ResumeAnalyser, Client
|
|
38
|
+
from .models import (
|
|
39
|
+
# Response models
|
|
40
|
+
ResumeUploadResult,
|
|
41
|
+
ResumeAnalysisResult,
|
|
42
|
+
ResumeComparisonResult,
|
|
43
|
+
JobResult,
|
|
44
|
+
ParsedResumeResult,
|
|
45
|
+
ResumeListResult,
|
|
46
|
+
JobListResult,
|
|
47
|
+
CreditBalance,
|
|
48
|
+
# Exceptions
|
|
49
|
+
APIError,
|
|
50
|
+
AuthenticationError,
|
|
51
|
+
RateLimitError,
|
|
52
|
+
ValidationError,
|
|
53
|
+
NotFoundError,
|
|
54
|
+
InsufficientCreditsError,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
# Client
|
|
59
|
+
"ResumeAnalyser",
|
|
60
|
+
"Client",
|
|
61
|
+
# Response models
|
|
62
|
+
"ResumeUploadResult",
|
|
63
|
+
"ResumeAnalysisResult",
|
|
64
|
+
"ResumeComparisonResult",
|
|
65
|
+
"JobResult",
|
|
66
|
+
"ParsedResumeResult",
|
|
67
|
+
"ResumeListResult",
|
|
68
|
+
"JobListResult",
|
|
69
|
+
"CreditBalance",
|
|
70
|
+
# Exceptions
|
|
71
|
+
"APIError",
|
|
72
|
+
"AuthenticationError",
|
|
73
|
+
"RateLimitError",
|
|
74
|
+
"ValidationError",
|
|
75
|
+
"NotFoundError",
|
|
76
|
+
"InsufficientCreditsError",
|
|
77
|
+
# Version
|
|
78
|
+
"__version__",
|
|
79
|
+
]
|
aiondtech/client.py
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AiondTech Resume Analyser API Client - Updated Version
|
|
3
|
+
|
|
4
|
+
This is the main entry point for interacting with the API.
|
|
5
|
+
Supports all external endpoints with credit tracking.
|
|
6
|
+
|
|
7
|
+
Endpoints:
|
|
8
|
+
- resumes.upload() - Upload resume, get ID
|
|
9
|
+
- resumes.upload_and_analyze() - Upload + parse
|
|
10
|
+
- resumes.upload_analyze_compare() - Upload + parse + compare to job
|
|
11
|
+
- resumes.analyze() - Parse existing resume by ID
|
|
12
|
+
- resumes.list() - List all uploaded resumes
|
|
13
|
+
- jobs.create() - Create job posting
|
|
14
|
+
- jobs.list() - List all jobs
|
|
15
|
+
- matching.compare() - Compare resume to job
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import mimetypes
|
|
20
|
+
from typing import Optional, Dict, Any, List, Union
|
|
21
|
+
from urllib.parse import urljoin
|
|
22
|
+
|
|
23
|
+
import requests
|
|
24
|
+
|
|
25
|
+
from .models import (
|
|
26
|
+
ResumeUploadResult,
|
|
27
|
+
ResumeAnalysisResult,
|
|
28
|
+
ResumeComparisonResult,
|
|
29
|
+
JobResult,
|
|
30
|
+
ResumeListResult,
|
|
31
|
+
JobListResult,
|
|
32
|
+
ParsedResumeResult,
|
|
33
|
+
APIError,
|
|
34
|
+
AuthenticationError,
|
|
35
|
+
RateLimitError,
|
|
36
|
+
ValidationError,
|
|
37
|
+
NotFoundError,
|
|
38
|
+
InsufficientCreditsError,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Default API base URLs
|
|
43
|
+
DEFAULT_BASE_URL = "https://api.dev.aiondtech.com"
|
|
44
|
+
PRODUCTION_BASE_URL = "https://api.aiondtech.com"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class HTTPClient:
|
|
48
|
+
"""Low-level HTTP client for API requests."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
api_key: str,
|
|
53
|
+
base_url: str,
|
|
54
|
+
timeout: int = 120,
|
|
55
|
+
max_retries: int = 3
|
|
56
|
+
):
|
|
57
|
+
self.api_key = api_key
|
|
58
|
+
self.base_url = base_url.rstrip("/")
|
|
59
|
+
self.timeout = timeout
|
|
60
|
+
self.max_retries = max_retries
|
|
61
|
+
|
|
62
|
+
self.session = requests.Session()
|
|
63
|
+
self.session.headers.update({
|
|
64
|
+
"Accept": "application/json",
|
|
65
|
+
"User-Agent": "aiondtech-python/2.0.0",
|
|
66
|
+
"X-API-Key": api_key,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
def request(
|
|
70
|
+
self,
|
|
71
|
+
method: str,
|
|
72
|
+
endpoint: str,
|
|
73
|
+
data: Optional[Dict] = None,
|
|
74
|
+
json_data: Optional[Dict] = None,
|
|
75
|
+
files: Optional[Dict] = None,
|
|
76
|
+
params: Optional[Dict] = None,
|
|
77
|
+
) -> Any:
|
|
78
|
+
"""Make an HTTP request to the API."""
|
|
79
|
+
url = f"{self.base_url}{endpoint}"
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
response = self.session.request(
|
|
83
|
+
method=method,
|
|
84
|
+
url=url,
|
|
85
|
+
data=data,
|
|
86
|
+
json=json_data,
|
|
87
|
+
files=files,
|
|
88
|
+
params=params,
|
|
89
|
+
timeout=self.timeout,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return self._handle_response(response)
|
|
93
|
+
|
|
94
|
+
except requests.exceptions.Timeout:
|
|
95
|
+
raise APIError("Request timed out", status_code=408)
|
|
96
|
+
except requests.exceptions.ConnectionError:
|
|
97
|
+
raise APIError("Failed to connect to API server")
|
|
98
|
+
except requests.exceptions.RequestException as e:
|
|
99
|
+
raise APIError(f"Request failed: {str(e)}")
|
|
100
|
+
|
|
101
|
+
def _handle_response(self, response: requests.Response) -> Any:
|
|
102
|
+
"""Handle API response and raise appropriate exceptions."""
|
|
103
|
+
|
|
104
|
+
# Try to parse JSON response
|
|
105
|
+
try:
|
|
106
|
+
data = response.json()
|
|
107
|
+
except ValueError:
|
|
108
|
+
data = {"detail": response.text}
|
|
109
|
+
|
|
110
|
+
# Handle error responses
|
|
111
|
+
if response.status_code == 401:
|
|
112
|
+
raise AuthenticationError(
|
|
113
|
+
data.get("detail", "Invalid API key"),
|
|
114
|
+
status_code=401,
|
|
115
|
+
response=data
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if response.status_code == 403:
|
|
119
|
+
detail = data.get("detail", "")
|
|
120
|
+
if "credit" in detail.lower():
|
|
121
|
+
raise InsufficientCreditsError(
|
|
122
|
+
detail or "Insufficient API credits",
|
|
123
|
+
status_code=403,
|
|
124
|
+
response=data
|
|
125
|
+
)
|
|
126
|
+
raise AuthenticationError(
|
|
127
|
+
detail or "Invalid or inactive API key",
|
|
128
|
+
status_code=403,
|
|
129
|
+
response=data
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if response.status_code == 404:
|
|
133
|
+
raise NotFoundError(
|
|
134
|
+
data.get("detail", "Resource not found"),
|
|
135
|
+
status_code=404,
|
|
136
|
+
response=data
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if response.status_code == 422:
|
|
140
|
+
raise ValidationError(
|
|
141
|
+
data.get("detail", "Validation error"),
|
|
142
|
+
status_code=422,
|
|
143
|
+
response=data
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if response.status_code == 429:
|
|
147
|
+
retry_after = response.headers.get("Retry-After")
|
|
148
|
+
raise RateLimitError(
|
|
149
|
+
data.get("detail", "Rate limit exceeded"),
|
|
150
|
+
retry_after=int(retry_after) if retry_after else None,
|
|
151
|
+
status_code=429,
|
|
152
|
+
response=data
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if response.status_code >= 500:
|
|
156
|
+
raise APIError(
|
|
157
|
+
data.get("detail", "Server error"),
|
|
158
|
+
status_code=response.status_code,
|
|
159
|
+
response=data
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if response.status_code >= 400:
|
|
163
|
+
raise APIError(
|
|
164
|
+
data.get("detail", f"Request failed with status {response.status_code}"),
|
|
165
|
+
status_code=response.status_code,
|
|
166
|
+
response=data
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return data
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class Resumes:
|
|
173
|
+
"""
|
|
174
|
+
Resume operations.
|
|
175
|
+
|
|
176
|
+
Usage:
|
|
177
|
+
client.resumes.upload("resume.pdf")
|
|
178
|
+
client.resumes.upload_and_analyze("resume.pdf")
|
|
179
|
+
client.resumes.upload_analyze_compare("resume.pdf", job_id=123)
|
|
180
|
+
client.resumes.analyze(resume_id=123)
|
|
181
|
+
client.resumes.list()
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
def __init__(self, client: HTTPClient):
|
|
185
|
+
self._client = client
|
|
186
|
+
|
|
187
|
+
def upload(self, file_path: str) -> ResumeUploadResult:
|
|
188
|
+
"""
|
|
189
|
+
Upload a resume file (PDF only).
|
|
190
|
+
|
|
191
|
+
Credits: 1 API credit
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
file_path: Path to the resume file (PDF)
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
ResumeUploadResult with resume_id
|
|
198
|
+
|
|
199
|
+
Example:
|
|
200
|
+
result = client.resumes.upload("path/to/resume.pdf")
|
|
201
|
+
print(f"Uploaded resume ID: {result.resume_id}")
|
|
202
|
+
"""
|
|
203
|
+
self._validate_file(file_path)
|
|
204
|
+
|
|
205
|
+
mime_type = mimetypes.guess_type(file_path)[0] or "application/pdf"
|
|
206
|
+
filename = os.path.basename(file_path)
|
|
207
|
+
|
|
208
|
+
with open(file_path, "rb") as f:
|
|
209
|
+
files = {"file": (filename, f, mime_type)}
|
|
210
|
+
data = self._client.request("POST", "/external/upload-resume", files=files)
|
|
211
|
+
|
|
212
|
+
return ResumeUploadResult.from_response(data)
|
|
213
|
+
|
|
214
|
+
def upload_and_analyze(self, file_path: str) -> ResumeAnalysisResult:
|
|
215
|
+
"""
|
|
216
|
+
Upload a resume and parse/extract structured data.
|
|
217
|
+
|
|
218
|
+
Credits: 3 API credits (includes AI parsing)
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
file_path: Path to the resume file (PDF)
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
ResumeAnalysisResult with resume_id and parsed_data
|
|
225
|
+
|
|
226
|
+
Example:
|
|
227
|
+
result = client.resumes.upload_and_analyze("resume.pdf")
|
|
228
|
+
print(f"Name: {result.parsed_data.get('full_name')}")
|
|
229
|
+
print(f"Skills: {result.parsed_data.get('skills')}")
|
|
230
|
+
"""
|
|
231
|
+
self._validate_file(file_path)
|
|
232
|
+
|
|
233
|
+
mime_type = mimetypes.guess_type(file_path)[0] or "application/pdf"
|
|
234
|
+
filename = os.path.basename(file_path)
|
|
235
|
+
|
|
236
|
+
with open(file_path, "rb") as f:
|
|
237
|
+
files = {"file": (filename, f, mime_type)}
|
|
238
|
+
data = self._client.request(
|
|
239
|
+
"POST",
|
|
240
|
+
"/external/upload-resume-analyze",
|
|
241
|
+
files=files
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
return ResumeAnalysisResult.from_response(data)
|
|
245
|
+
|
|
246
|
+
def upload_analyze_compare(
|
|
247
|
+
self,
|
|
248
|
+
file_path: str,
|
|
249
|
+
job_id: int
|
|
250
|
+
) -> ResumeComparisonResult:
|
|
251
|
+
"""
|
|
252
|
+
Upload, parse, and compare resume against a job posting.
|
|
253
|
+
|
|
254
|
+
Credits: 5 API credits (includes AI parsing + comparison)
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
file_path: Path to the resume file (PDF)
|
|
258
|
+
job_id: ID of the job posting to compare against
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
ResumeComparisonResult with resume_id, parsed_data, score, and reason
|
|
262
|
+
|
|
263
|
+
Example:
|
|
264
|
+
result = client.resumes.upload_analyze_compare("resume.pdf", job_id=42)
|
|
265
|
+
print(f"Match Score: {result.comparison_score}%")
|
|
266
|
+
print(f"Reason: {result.comparison_reason}")
|
|
267
|
+
"""
|
|
268
|
+
self._validate_file(file_path)
|
|
269
|
+
|
|
270
|
+
mime_type = mimetypes.guess_type(file_path)[0] or "application/pdf"
|
|
271
|
+
filename = os.path.basename(file_path)
|
|
272
|
+
|
|
273
|
+
with open(file_path, "rb") as f:
|
|
274
|
+
files = {"file": (filename, f, mime_type)}
|
|
275
|
+
form_data = {"job_id": str(job_id)}
|
|
276
|
+
data = self._client.request(
|
|
277
|
+
"POST",
|
|
278
|
+
"/external/upload-resume-analyze-compare",
|
|
279
|
+
files=files,
|
|
280
|
+
data=form_data
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
return ResumeComparisonResult.from_response(data)
|
|
284
|
+
|
|
285
|
+
def analyze(self, resume_id: int) -> ParsedResumeResult:
|
|
286
|
+
"""
|
|
287
|
+
Parse and extract structured data from an existing resume.
|
|
288
|
+
|
|
289
|
+
Credits: 2 API credits (AI parsing only)
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
resume_id: ID of the uploaded resume
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
ParsedResumeResult with extracted fields
|
|
296
|
+
|
|
297
|
+
Example:
|
|
298
|
+
parsed = client.resumes.analyze(resume_id=123)
|
|
299
|
+
print(f"Name: {parsed.full_name}")
|
|
300
|
+
print(f"Skills: {parsed.skills}")
|
|
301
|
+
"""
|
|
302
|
+
data = self._client.request(
|
|
303
|
+
"POST",
|
|
304
|
+
"/external/analyze-resume-by-id",
|
|
305
|
+
params={"resume_id": resume_id}
|
|
306
|
+
)
|
|
307
|
+
return ParsedResumeResult.from_response(data)
|
|
308
|
+
|
|
309
|
+
def list(
|
|
310
|
+
self,
|
|
311
|
+
page: int = 1,
|
|
312
|
+
limit: int = 50
|
|
313
|
+
) -> ResumeListResult:
|
|
314
|
+
"""
|
|
315
|
+
List all uploaded resumes for this API key.
|
|
316
|
+
|
|
317
|
+
Credits: 0 (free)
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
page: Page number (default: 1)
|
|
321
|
+
limit: Items per page (default: 50, max: 100)
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
ResumeListResult with list of resumes and pagination
|
|
325
|
+
|
|
326
|
+
Example:
|
|
327
|
+
result = client.resumes.list()
|
|
328
|
+
for resume in result.resumes:
|
|
329
|
+
print(f"ID: {resume['id']}, Name: {resume.get('full_name')}")
|
|
330
|
+
"""
|
|
331
|
+
data = self._client.request(
|
|
332
|
+
"GET",
|
|
333
|
+
"/external/list-resumes",
|
|
334
|
+
params={"page": page, "limit": min(limit, 100)}
|
|
335
|
+
)
|
|
336
|
+
return ResumeListResult.from_response(data)
|
|
337
|
+
|
|
338
|
+
def _validate_file(self, file_path: str) -> None:
|
|
339
|
+
"""Validate file exists and is PDF."""
|
|
340
|
+
if not os.path.exists(file_path):
|
|
341
|
+
raise ValidationError(f"File not found: {file_path}")
|
|
342
|
+
|
|
343
|
+
ext = os.path.splitext(file_path)[1].lower()
|
|
344
|
+
if ext != ".pdf":
|
|
345
|
+
raise ValidationError(
|
|
346
|
+
f"Invalid file type: {ext}. Only PDF files are supported."
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class Jobs:
|
|
351
|
+
"""
|
|
352
|
+
Job posting operations.
|
|
353
|
+
|
|
354
|
+
Usage:
|
|
355
|
+
client.jobs.create("Python Developer", "Job description...")
|
|
356
|
+
client.jobs.list()
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
def __init__(self, client: HTTPClient):
|
|
360
|
+
self._client = client
|
|
361
|
+
|
|
362
|
+
def create(self, title: str, description: str) -> JobResult:
|
|
363
|
+
"""
|
|
364
|
+
Create a new job posting.
|
|
365
|
+
|
|
366
|
+
Credits: 1 API credit
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
title: Job title
|
|
370
|
+
description: Full job description
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
JobResult with job_id
|
|
374
|
+
|
|
375
|
+
Example:
|
|
376
|
+
job = client.jobs.create(
|
|
377
|
+
title="Senior Python Developer",
|
|
378
|
+
description="We are looking for..."
|
|
379
|
+
)
|
|
380
|
+
print(f"Created job ID: {job.job_id}")
|
|
381
|
+
"""
|
|
382
|
+
form_data = {
|
|
383
|
+
"title": title,
|
|
384
|
+
"description": description
|
|
385
|
+
}
|
|
386
|
+
data = self._client.request("POST", "/external/create-job", data=form_data)
|
|
387
|
+
return JobResult.from_response(data)
|
|
388
|
+
|
|
389
|
+
def list(
|
|
390
|
+
self,
|
|
391
|
+
page: int = 1,
|
|
392
|
+
limit: int = 50
|
|
393
|
+
) -> JobListResult:
|
|
394
|
+
"""
|
|
395
|
+
List all job postings for this API key.
|
|
396
|
+
|
|
397
|
+
Credits: 0 (free)
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
page: Page number (default: 1)
|
|
401
|
+
limit: Items per page (default: 50, max: 100)
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
JobListResult with list of jobs and pagination
|
|
405
|
+
|
|
406
|
+
Example:
|
|
407
|
+
result = client.jobs.list()
|
|
408
|
+
for job in result.jobs:
|
|
409
|
+
print(f"ID: {job['id']}, Title: {job['title']}")
|
|
410
|
+
"""
|
|
411
|
+
data = self._client.request(
|
|
412
|
+
"GET",
|
|
413
|
+
"/external/list-jobs",
|
|
414
|
+
params={"page": page, "limit": min(limit, 100)}
|
|
415
|
+
)
|
|
416
|
+
return JobListResult.from_response(data)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class Matching:
|
|
420
|
+
"""
|
|
421
|
+
Resume-job matching/comparison operations.
|
|
422
|
+
|
|
423
|
+
Usage:
|
|
424
|
+
client.matching.compare(resume_id=123, job_id=456)
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
def __init__(self, client: HTTPClient):
|
|
428
|
+
self._client = client
|
|
429
|
+
|
|
430
|
+
def compare(
|
|
431
|
+
self,
|
|
432
|
+
resume_id: int,
|
|
433
|
+
job_id: int
|
|
434
|
+
) -> ResumeComparisonResult:
|
|
435
|
+
"""
|
|
436
|
+
Compare a resume against a job posting.
|
|
437
|
+
|
|
438
|
+
Credits: 3 API credits (AI comparison)
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
resume_id: ID of the uploaded resume
|
|
442
|
+
job_id: ID of the job posting
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
ResumeComparisonResult with score and reasoning
|
|
446
|
+
|
|
447
|
+
Example:
|
|
448
|
+
result = client.matching.compare(resume_id=123, job_id=456)
|
|
449
|
+
print(f"Match Score: {result.comparison_score}%")
|
|
450
|
+
print(f"Reasoning: {result.comparison_reason}")
|
|
451
|
+
"""
|
|
452
|
+
payload = {
|
|
453
|
+
"resume_id": resume_id,
|
|
454
|
+
"job_id": job_id
|
|
455
|
+
}
|
|
456
|
+
data = self._client.request(
|
|
457
|
+
"POST",
|
|
458
|
+
"/external/compare-resumes",
|
|
459
|
+
json_data=payload
|
|
460
|
+
)
|
|
461
|
+
return ResumeComparisonResult.from_response(data)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class Credits:
|
|
465
|
+
"""
|
|
466
|
+
Credit balance and usage operations.
|
|
467
|
+
|
|
468
|
+
Usage:
|
|
469
|
+
client.credits.balance()
|
|
470
|
+
client.credits.usage()
|
|
471
|
+
"""
|
|
472
|
+
|
|
473
|
+
def __init__(self, client: HTTPClient):
|
|
474
|
+
self._client = client
|
|
475
|
+
|
|
476
|
+
def balance(self) -> Dict[str, Any]:
|
|
477
|
+
"""
|
|
478
|
+
Get current credit balance.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
Dictionary with credit balance info
|
|
482
|
+
|
|
483
|
+
Example:
|
|
484
|
+
balance = client.credits.balance()
|
|
485
|
+
print(f"Remaining: {balance['remaining']}")
|
|
486
|
+
print(f"Used this month: {balance['used_mtd']}")
|
|
487
|
+
"""
|
|
488
|
+
data = self._client.request("GET", "/external/credits/balance")
|
|
489
|
+
return data
|
|
490
|
+
|
|
491
|
+
def usage(
|
|
492
|
+
self,
|
|
493
|
+
start_date: Optional[str] = None,
|
|
494
|
+
end_date: Optional[str] = None
|
|
495
|
+
) -> Dict[str, Any]:
|
|
496
|
+
"""
|
|
497
|
+
Get detailed usage history.
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
start_date: Start date (YYYY-MM-DD)
|
|
501
|
+
end_date: End date (YYYY-MM-DD)
|
|
502
|
+
|
|
503
|
+
Returns:
|
|
504
|
+
Dictionary with usage details
|
|
505
|
+
|
|
506
|
+
Example:
|
|
507
|
+
usage = client.credits.usage()
|
|
508
|
+
for entry in usage['history']:
|
|
509
|
+
print(f"{entry['endpoint']}: {entry['credits']} credits")
|
|
510
|
+
"""
|
|
511
|
+
params = {}
|
|
512
|
+
if start_date:
|
|
513
|
+
params["start_date"] = start_date
|
|
514
|
+
if end_date:
|
|
515
|
+
params["end_date"] = end_date
|
|
516
|
+
|
|
517
|
+
data = self._client.request("GET", "/external/credits/usage", params=params)
|
|
518
|
+
return data
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
class ResumeAnalyser:
|
|
522
|
+
"""
|
|
523
|
+
AiondTech Resume Analyser API Client.
|
|
524
|
+
|
|
525
|
+
This is the main entry point for interacting with the API.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
api_key: Your API key. Can also be set via AIONDTECH_API_KEY env var.
|
|
529
|
+
base_url: API base URL. Defaults to https://api.dev.aiondtech.com
|
|
530
|
+
timeout: Request timeout in seconds. Default 120.
|
|
531
|
+
production: If True, use production URL (https://api.aiondtech.com)
|
|
532
|
+
|
|
533
|
+
Example:
|
|
534
|
+
from aiondtech import ResumeAnalyser
|
|
535
|
+
|
|
536
|
+
# Initialize client
|
|
537
|
+
client = ResumeAnalyser(api_key="your-api-key")
|
|
538
|
+
|
|
539
|
+
# Or use environment variable
|
|
540
|
+
# export AIONDTECH_API_KEY="your-api-key"
|
|
541
|
+
client = ResumeAnalyser()
|
|
542
|
+
|
|
543
|
+
# Upload resume
|
|
544
|
+
result = client.resumes.upload("resume.pdf")
|
|
545
|
+
|
|
546
|
+
# Upload and analyze
|
|
547
|
+
analysis = client.resumes.upload_and_analyze("resume.pdf")
|
|
548
|
+
|
|
549
|
+
# Create job posting
|
|
550
|
+
job = client.jobs.create("Title", "Description")
|
|
551
|
+
|
|
552
|
+
# Compare resume to job
|
|
553
|
+
comparison = client.matching.compare(
|
|
554
|
+
resume_id=result.resume_id,
|
|
555
|
+
job_id=job.job_id
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
# Check credits
|
|
559
|
+
balance = client.credits.balance()
|
|
560
|
+
"""
|
|
561
|
+
|
|
562
|
+
def __init__(
|
|
563
|
+
self,
|
|
564
|
+
api_key: Optional[str] = None,
|
|
565
|
+
base_url: Optional[str] = None,
|
|
566
|
+
timeout: int = 120,
|
|
567
|
+
production: bool = False,
|
|
568
|
+
):
|
|
569
|
+
# Get API key from argument or environment
|
|
570
|
+
self.api_key = api_key or os.getenv("AIONDTECH_API_KEY")
|
|
571
|
+
if not self.api_key:
|
|
572
|
+
raise AuthenticationError(
|
|
573
|
+
"API key is required. Pass api_key argument or set AIONDTECH_API_KEY environment variable."
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
# Get base URL
|
|
577
|
+
if base_url:
|
|
578
|
+
self.base_url = base_url
|
|
579
|
+
elif production:
|
|
580
|
+
self.base_url = PRODUCTION_BASE_URL
|
|
581
|
+
else:
|
|
582
|
+
self.base_url = os.getenv("AIONDTECH_BASE_URL") or DEFAULT_BASE_URL
|
|
583
|
+
|
|
584
|
+
# Initialize HTTP client
|
|
585
|
+
self._client = HTTPClient(
|
|
586
|
+
api_key=self.api_key,
|
|
587
|
+
base_url=self.base_url,
|
|
588
|
+
timeout=timeout,
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
# Initialize resource classes
|
|
592
|
+
self.resumes = Resumes(self._client)
|
|
593
|
+
self.jobs = Jobs(self._client)
|
|
594
|
+
self.matching = Matching(self._client)
|
|
595
|
+
self.credits = Credits(self._client)
|
|
596
|
+
|
|
597
|
+
def __repr__(self):
|
|
598
|
+
masked_key = f"{self.api_key[:8]}...{self.api_key[-4:]}" if len(self.api_key) > 12 else "***"
|
|
599
|
+
return f"<ResumeAnalyser base_url='{self.base_url}' api_key='{masked_key}'>"
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
# Alias for convenience
|
|
603
|
+
Client = ResumeAnalyser
|