Metadata-Version: 2.4
Name: apploi-partner-api
Version: 0.1.7
Summary: Official Python client for Apploi Partner API with high-level abstractions
Home-page: https://github.com/apploitech/partner-api
Author: Apploi
Author-email: engineering@apploi.com
Project-URL: Bug Tracker, https://github.com/apploitech/partner-api/issues
Project-URL: Documentation, https://integrate.apploi.com/
Project-URL: Source Code, https://github.com/apploitech/partner-api/tree/main/adapters/python
Keywords: apploi,api,client,sdk,partner-api,hr,recruitment,applicant-tracking
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Typing :: Typed
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24.0
Requires-Dist: python-dateutil>=2.8.0
Requires-Dist: pydantic>=1.10.0
Provides-Extra: dev
Requires-Dist: pytest>=6.2.5; extra == "dev"
Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
Requires-Dist: mypy>=1.0.0; extra == "dev"
Requires-Dist: types-python-dateutil; extra == "dev"
Requires-Dist: black>=23.0.0; extra == "dev"
Requires-Dist: isort>=5.12.0; extra == "dev"
Requires-Dist: flake8>=6.0.0; extra == "dev"
Provides-Extra: test
Requires-Dist: pytest>=6.2.5; extra == "test"
Requires-Dist: pytest-mock>=3.10.0; extra == "test"
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: project-url
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# Python Adapter for Apploi Partner API

A clean, type-safe Python adapter that wraps the Fern-generated SDK with better abstractions, validation, and error handling.

## Installation

```bash
# Install dependencies
pip install httpx python-dateutil

# Or install all requirements
pip install -r requirements.txt
```

## Quick Start

```python
from apploi_partner_api import ApploiClient
from datetime import date

# Initialize client
client = ApploiClient(api_key="your-api-key")

# Get job applications with native Python types
job_applications = client.get_job_applications(
    job_id=12345,
    updated_after=date(2024, 1, 1),
    limit=50
)

# Work with strongly-typed results
for application in job_applications:
    print(f"{application.name} - {application.status.value}")
    print(f"  Email: {application.email}")
    print(f"  Applied: {application.applied_at}")
```

> **💡 See `example_usage.py` for comprehensive usage examples including pagination, error handling, and comparisons with the raw SDK.**

## Features

- ✅ **Native Python Types**: Use `int`, `date`, `datetime` instead of strings
- ✅ **Type Safety**: Dataclasses with proper type hints
- ✅ **Validation**: Parameters validated before API calls
- ✅ **Error Handling**: Custom exception hierarchy
- ✅ **IDE Support**: Full autocomplete and type checking

## API Reference

### Client Initialization

```python
from apploi_partner_api import ApploiClient

client = ApploiClient(
    api_key="your-api-key",        # Required
    base_url=None,                  # Optional, defaults to production
    timeout=60                      # Optional, request timeout in seconds
)
```

### get_job_applications()

Retrieve job applications with filtering and pagination.

```python
job_applications = client.get_job_applications(
    state="applied",                          # Filter by status
    query="Smith",                            # Search query filters by name, phone number, or email
    limit=50,                                 # Max results (1-1000)
    team_id=123,                              # Team ID (int or str)
    updated_before=datetime.now(),            # Before date
    offset=0,                                 # Pagination offset
    job_id=456,                               # Job ID (int or str)
    updated_after=date(2024, 1, 1)            # After date
)
```

**Parameters:**
- All parameters are optional
- IDs accept `int` or `str`
- Dates accept `date`, `datetime`, or ISO string
- Limit must be 1-1000
- Offset must be ≥ 0

**Returns:** `JobApplicationsList` with:
- `items`: List of `JobApplication` objects
- `total`: Total count available
- `limit`: Page size
- `offset`: Current offset
- `has_more`: Boolean for more pages

## Domain Models

### JobApplication

```python
@dataclass
class JobApplication:
    # Identifiers
    id: int                              # Application ID
    candidate_id: int                    # Candidate ID
    job_id: int                          # Job ID
    
    # Required fields
    status: ApplicationStatus            # Application status
    job: Job                             # Job details (nested)
    candidate: Candidate                 # Candidate details (nested)
    email: str                           # Applicant email
    applicant_state: str                 # Current state
    applicant_state_id: int              # State ID
    
    # DateTime/State Management
    date_current_state_assigned: Optional[datetime]
    previous_applicant_state: Optional[str]
    previous_applicant_state_id: Optional[int]
    date_previous_state_assigned: Optional[datetime]
    applicant_updated: Optional[datetime]
    date_created: Optional[datetime]
    application_source: Optional[str]
    
    # Optional fields
    first_name: Optional[str]
    last_name: Optional[str]
    phone: Optional[str]
    applied_at: Optional[datetime]
    updated_at: Optional[datetime]
    team_id: Optional[int]

    @property
    def name(self) -> str:
        \"\"\"Full name (first + last)\"\"\"
```

### Job

```python
@dataclass  
class Job:
    id: Optional[int]
    name: Optional[str]               # Job title
    archived: Optional[bool]          # Is archived
    archived_date: Optional[datetime]
    address: Optional[str]            # Job location
    source: Optional[str]             # Job source
    brand_name: Optional[str]         # Company brand
    job_code: Optional[str]           # Internal job code
    creator_id: Optional[int]         # Creator ID
    team_id: Optional[int]            # Team ID
    team_name: Optional[str]          # Team name
    role_type: Optional[str]          # Role type
    longitude: Optional[float]        # Geo coordinates
    latitude: Optional[float]
```

### Candidate

```python
@dataclass
class Candidate:
    id: int                           # Candidate ID
    first_name: Optional[str]
    last_name: Optional[str]
    email: Optional[str]
    home_phone_number: Optional[str]
    mobile_phone_number: Optional[str]
    city: Optional[str]
    state: Optional[str]
    country: Optional[str]
    address: Optional[str]
    zip_code: Optional[str]

    @property
    def name(self) -> str:
        \"\"\"Full name (first + last)\"\"\"

    @property 
    def phone(self) -> Optional[str]:
        \"\"\"Primary phone (mobile or home)\"\"\"
```

### ApplicationStatus

```python
class ApplicationStatus(Enum):
    APPLIED = "applied"
    SCREENING = "screening"
    INTERVIEWING = "interviewing"
    REFERENCE_CHECK = "reference_check"
    OFFER_EXTENDED = "offer_extended"
    HIRED = "hired"
    REJECTED = "rejected"
    WITHDRAWN = "withdrawn"
    ON_HOLD = "on_hold"
    UNKNOWN = "unknown"  # Fallback for unrecognized values
```

**Note:** The enum supports legacy values `"interview"` and `"offer"` for backwards compatibility, automatically mapping them to `INTERVIEWING` and `OFFER_EXTENDED` respectively.

## Error Handling

```python
from apploi_partner_api import (
    ApploiValidationError,
    ApploiAuthenticationError,
    ApploiAPIError
)

try:
    job_applications = client.get_job_applications(limit=50)

except ApploiValidationError as e:
    # Invalid parameters (e.g., limit > 1000)
    print(f"Validation error: {e}")

except ApploiAuthenticationError as e:
    # 401/403 errors
    print(f"Auth failed: {e}")

except ApploiAPIError as e:
    # Other API errors
    print(f"API error: {e}")
    if e.status_code == 429:
        # Handle rate limiting
        time.sleep(60)
```

## Usage Examples

### Basic Usage

```python
# Get all job applications
all_job_applications = client.get_job_applications()
print(f"Found {len(all_job_applications)} job applications")
```

### Filtering

```python
# Filter with native types
filtered_job_applications = client.get_job_applications(
    job_id=12345,                         # Integer
    team_id=789,                          # Integer
    updated_after=date(2024, 1, 1),       # Date object
    state="applied",                      # String
    limit=50                              # Integer
)
```

### Pagination

```python
# Paginate through all results
all_results = []
offset = 0
page_size = 50

while True:
    page = client.get_job_applications(
        limit=page_size,
        offset=offset
    )

    all_results.extend(page.items)

    if not page.has_more:
        break

    offset += page_size

print(f"Total fetched: {len(all_results)}")
```

### Working with Results

```python
job_applications = client.get_job_applications(limit=1)

if job_applications:
    application = job_applications[0]

    # Strongly typed fields
    print(f"ID: {application.id}")
    print(f"Name: {application.candidate.name}")
    print(f"Status: {application.status.value}")

    # Date handling
    if application.applied_at:
        days_ago = (datetime.now() - application.applied_at).days
        print(f"Applied {days_ago} days ago")

    # Access raw data if needed
    raw = application.raw_data
```

## Testing

```bash
# Run all tests
pytest tests/

# Run specific test file  
pytest tests/test_client.py -v

# Run with coverage
pytest tests/ --cov=apploi_partner_api

# Integration tests (requires credentials)
export APPLOI_API_KEY=\"your-key\"
export APPLOI_AUTHORIZATION=\"your-auth\"
pytest tests/integration/ -m integration
```

## Project Structure

```
python/
├── __init__.py              # Package initialization
├── client.py                # Main ApploiClient class
├── exceptions.py            # Custom exceptions
├── models/
│   ├── __init__.py
│   ├── job_application.py  # JobApplication, JobApplicationsList
│   └── common.py           # Shared models
├── validators/
│   ├── __init__.py
│   ├── dates.py            # Date validation/parsing
│   └── parameters.py       # Parameter validation
├── example_usage.py        # Usage examples
├── requirements.txt        # Dependencies
└── README.md              # This file
```

## Requirements

- Python 3.7+
- httpx (for Fern SDK)
- python-dateutil (optional, for date parsing)

## Differences from Raw SDK

### Without Adapter (Fern SDK)
```python
# Everything must be strings
sdk.applicants.get_applicants(
    job_id="12345",
    limit="50",
    updated_after="2024-01-01"
)  # Returns: Dict[str, Any]
```

### With Adapter
```python
# Native Python types
client.get_job_applications(
    job_id=12345,
    limit=50,
    updated_after=date(2024, 1, 1)
)  # Returns: ApplicantsList (typed)
```

## Architecture

See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed design documentation.

## Contributing

1. Follow existing patterns
2. Add tests for new features
3. Update type hints
4. Run tests before submitting

## License

[Your License Here]
