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 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