Metadata-Version: 2.4
Name: locara
Version: 0.1.2
Summary: Official Python SDK for the Locara Location Data API
Project-URL: Homepage, https://locara.online
Project-URL: Documentation, https://locara.online/docs
Project-URL: Repository, https://github.com/vijaybhatiya/locara-python-sdk
License: MIT
License-File: LICENSE
Keywords: api,cities,countries,geography,locara,location,states
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
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
Requires-Python: >=3.8
Requires-Dist: requests>=2.28.0
Provides-Extra: async
Requires-Dist: httpx>=0.24.0; extra == 'async'
Provides-Extra: dev
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pytest-mock; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: responses>=0.23; extra == 'dev'
Description-Content-Type: text/markdown

# Locara Python SDK

[![PyPI](https://img.shields.io/pypi/v/locara)](https://pypi.org/project/locara/)

[![PyPI version](https://img.shields.io/pypi/v/locara)](https://pypi.org/project/locara/)
[![Python versions](https://img.shields.io/pypi/pyversions/locara)](https://pypi.org/project/locara/)
[![License](https://img.shields.io/github/license/vijaybhatiya/locara-python-sdk)](https://github.com/vijaybhatiya/locara-python-sdk/blob/main/LICENSE)

Official Python SDK for the Locara Location Data API.

[Locara](https://locara.online) provides standard endpoints to query countries, states, and cities globally, and search them rapidly.

---

## Installation

Install using `pip`:

```bash
pip install locara
```

---

## Quickstart

Here are five typical usage patterns:

### 1. Initialize Client & Fetch Country Info
```python
from locara import LocaraClient

client = LocaraClient(api_key="your_api_key_here")

# Get a single country by its ISO2 code
country = client.countries.get("US")
print(f"Country: {country.name}, Phone Code: +{country.phonecode}")
```

### 2. List Countries with Search Query
```python
# List countries with pagination and filter search
countries_page = client.countries.list(limit=10, search="United")
for country in countries_page.data:
    print(f"{country.name} ({country.iso3})")
```

### 3. List States/Provinces under a Country
```python
# Retrieve states of India (IN)
states_page = client.countries.states("IN").list(limit=20)
for state in states_page.data:
    print(f"State Name: {state.name}, Code: {state.code}")
```

### 4. Fetch Cities under a State or Country
```python
# Option A: Get cities inside Maharashtra (MH) in India (IN)
cities_page = client.countries.states("IN").cities("MH").list(limit=100)
for city in cities_page.data:
    print(f"City: {city.name} ({city.latitude}, {city.longitude})")

# Option B: Get cities directly under a Country (IN)
country_cities = client.countries.cities("IN").list(limit=10)
```

### 5. Auto-Paginate with Generators
Instead of manually handling pages, use `.iter_all()` to yield results seamlessly.
```python
# Auto-fetches all pages under the hood using a Python generator
for state in client.countries.states("IN").iter_all(limit=50):
    print(state.name)
```

---

## API Reference

### Country Resources (`client.countries`)

| Endpoint | Method Signature | Return Type |
|---|---|---|
| `GET /countries` | `client.countries.list(limit=50, offset=0, search=None)` | `PaginatedResponse[Country]` |
| `GET /countries/{ciso}` | `client.countries.get(ciso)` | `Country` |
| `GET /countries/{ciso}/states` | `client.countries.list_states(ciso, limit=50, offset=0)` | `PaginatedResponse[State]` |
| `GET /countries/{ciso}/states/{siso}` | `client.countries.get_state(ciso, siso)` | `State` |
| `GET /countries/{ciso}/cities` | `client.countries.list_cities(ciso, limit=50, offset=0)` | `PaginatedResponse[City]` |
| `GET /countries/{ciso}/states` (sub-resource) | `client.countries.states(ciso).list(limit=50, offset=0)` | `PaginatedResponse[State]` |
| `GET /countries/{ciso}/states/{siso}` (sub-resource) | `client.countries.states(ciso).get(siso)` | `State` |
| `GET /countries/{ciso}/cities` (sub-resource) | `client.countries.cities(ciso).list(limit=50, offset=0)` | `PaginatedResponse[City]` |
| `GET /countries/{ciso}/states/{siso}/cities` (sub-resource) | `client.countries.states(ciso).cities(siso).list(limit=50, offset=0)` | `PaginatedResponse[City]` |

### State Resources (`client.states`)

| Endpoint | Method Signature | Return Type |
|---|---|---|
| `GET /countries/{ciso}/states` | `client.states.list(ciso, limit=50, offset=0)` | `PaginatedResponse[State]` |
| `GET /countries/{ciso}/states/{siso}` | `client.states.get(ciso, siso)` | `State` |
| `GET /countries/{ciso}/states/{siso}/cities` | `client.states.list_cities(ciso, siso, limit=50, offset=0)` | `PaginatedResponse[City]` |

### City Resources (`client.cities`)

| Endpoint | Method Signature | Return Type |
|---|---|---|
| `GET /countries/{ciso}/cities` | `client.cities.list(ciso, siso=None, limit=50, offset=0)` | `PaginatedResponse[City]` |

### Search (`client.search`)

| Endpoint | Method Signature | Return Type |
|---|---|---|
| `GET /search` | `client.search(q, type=None, limit=10, offset=0)` | `PaginatedResponse[SearchResult]` |

### Pagination Helpers (Generator Iterators)

Every list endpoint exposes `.iter_all()` returning a generator yielding individual objects. There is also a standalone `paginate` helper.

- **Standalone Helper**: `paginate(method, *args, **kwargs)` -> `Iterator[T]` (e.g. `paginate(client.countries.list, limit=10)`)
- **Country Resource Iterators**:
  - `client.countries.iter_all(limit=50, search=None)` -> `Iterator[Country]`
  - `client.countries.iter_all_states(ciso, limit=50)` -> `Iterator[State]`
  - `client.countries.iter_all_cities(ciso, limit=50)` -> `Iterator[City]`
  - `client.countries.states(ciso).iter_all(limit=50)` -> `Iterator[State]`
  - `client.countries.cities(ciso).iter_all(limit=50)` -> `Iterator[City]`
  - `client.countries.states(ciso).cities(siso).iter_all(limit=50)` -> `Iterator[City]`
- **State Resource Iterators**:
  - `client.states.iter_all(ciso, limit=50)` -> `Iterator[State]`
  - `client.states.iter_all_cities(ciso, siso, limit=50)` -> `Iterator[City]`
- **City Resource Iterators**:
  - `client.cities.iter_all(ciso, siso=None, limit=50)` -> `Iterator[City]`
- **Search Iterators**:
  - `client.search_iter(q, type=None, limit=10)` -> `Iterator[SearchResult]`

---

## Error Handling

All SDK exceptions inherit from `LocaraError`. Specific status codes map to custom exceptions:

- **401 Unauthorized**: Raises `AuthenticationError`
- **404 Not Found**: Raises `NotFoundError` (stores the requested `.path`)
- **429 Rate Limited**: Raises `RateLimitError` (stores the server-provided `.retry_after` delay in seconds, if present)
- **5xx Server Error**: Raises `LocaraServerError`

Example usage:

```python
from locara import LocaraClient, AuthenticationError, NotFoundError, RateLimitError, LocaraError

client = LocaraClient(api_key="your_api_key")

try:
    country = client.countries.get("XX")
except AuthenticationError as e:
    print("Authentication failed. Please verify your API Key.")
except NotFoundError as e:
    print(f"Path not found: {e.path}. Details: {e}")
except RateLimitError as e:
    print(f"Rate limited. Please wait {e.retry_after} seconds before retrying.")
except LocaraError as e:
    print(f"General Locara SDK error occurred: {e}")
```

For full documentation and API guidelines, check out [Locara Docs](https://locara.online/docs).
