Metadata-Version: 2.4
Name: geocodio-library-python
Version: 1.0.0
Summary: A Python client for the Geocodio API
Project-URL: Homepage, https://www.geocod.io
Project-URL: Documentation, https://www.geocod.io/docs/?python
Project-URL: Repository, https://github.com/geocodio/geocodio-library-python
Project-URL: Issues, https://github.com/geocodio/geocodio-library-python/issues
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Requires-Dist: httpx>=0.24.0
Provides-Extra: dev
Requires-Dist: black>=23.0.0; extra == 'dev'
Requires-Dist: flake8>=6.0.0; extra == 'dev'
Requires-Dist: isort>=5.12.0; extra == 'dev'
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest-httpx>=0.27.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.10.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: python-dotenv>=1.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# geocodio

The official Python client for the Geocodio API.

Features
--------

- Forward geocoding of single addresses or in batches (up to 10,000 lookups).
- Reverse geocoding of coordinates (single or batch).
- Append additional data fields (e.g. congressional districts, timezone, census data).
- Distance calculations (single origin to multiple destinations, distance matrices).
- Async distance matrix jobs for large calculations.
- Automatic parsing of address components.
- Simple exception handling for authentication, data, and server errors.

Installation
------------

Install via pip:

    pip install geocodio-library-python

Usage
-----

> Don't have an API key yet? Sign up at [https://dash.geocod.io](https://dash.geocod.io) to get an API key. The first 2,500 lookups per day are free.

### Geocoding

```python
from geocodio import Geocodio

# Initialize the client with your API key
client = Geocodio("YOUR_API_KEY")
# client = Geocodio("YOUR_API_KEY", hostname="api-hipaa.geocod.io")  # optionally overwrite the API hostname

# Single forward geocode
response = client.geocode("1600 Pennsylvania Ave, Washington, DC")
print(response.results[0].formatted_address)

# Batch forward geocode
addresses = [
    "1600 Pennsylvania Ave, Washington, DC",
    "1 Infinite Loop, Cupertino, CA"
]
batch_response = client.geocode(addresses)
for result in batch_response.results:
    print(result.formatted_address)

# Single reverse geocode
rev = client.reverse("38.9002898,-76.9990361")
print(rev.results[0].formatted_address)

# Reverse with tuple coordinates
rev = client.reverse((38.9002898, -76.9990361))
```

> Note: You can read more about accuracy scores, accuracy types, input formats and more at https://www.geocod.io/docs/

### Batch geocoding

To batch geocode, simply pass a list of addresses or coordinates instead of a single string:

```python
response = client.geocode([
    "1109 N Highland St, Arlington VA",
    "525 University Ave, Toronto, ON, Canada",
    "4410 S Highway 17 92, Casselberry FL",
    "15000 NE 24th Street, Redmond WA",
    "17015 Walnut Grove Drive, Morgan Hill CA"
])

response = client.reverse([
    "35.9746000,-77.9658000",
    "32.8793700,-96.6303900",
    "33.8337100,-117.8362320",
    "35.4171240,-80.6784760"
])

# Optionally supply a custom key that will be returned along with results
response = client.geocode({
    "MyId1": "1109 N Highland St, Arlington VA",
    "MyId2": "525 University Ave, Toronto, ON, Canada",
    "MyId3": "4410 S Highway 17 92, Casselberry FL",
    "MyId4": "15000 NE 24th Street, Redmond WA",
    "MyId5": "17015 Walnut Grove Drive, Morgan Hill CA"
})
```

### Field appends

Geocodio allows you to append additional data points such as congressional districts, census codes, timezone, ACS survey results and [much more](https://www.geocod.io/docs/#fields).

To request additional fields, simply supply them as a list:

```python
response = client.geocode(
    [
        "1109 N Highland St, Arlington VA",
        "525 University Ave, Toronto, ON, Canada"
    ],
    fields=["cd", "timezone"]
)

response = client.reverse("38.9002898,-76.9990361", fields=["census2010"])
```

### Address components

For forward geocoding requests it is possible to supply [individual address components](https://www.geocod.io/docs/#single-address) instead of a full address string:

```python
response = client.geocode({
    "street": "1109 N Highland St",
    "city": "Arlington",
    "state": "VA",
    "postal_code": "22201"
})

response = client.geocode([
    {
        "street": "1109 N Highland St",
        "city": "Arlington",
        "state": "VA"
    },
    {
        "street": "525 University Ave",
        "city": "Toronto",
        "state": "ON",
        "country": "Canada"
    }
])
```

### Limit results

Optionally limit the number of maximum geocoding results:

```python
# Only get the first result
response = client.geocode("1109 N Highland St, Arlington, VA", limit=1)

# Return up to 5 geocoding results
response = client.reverse("38.9002898,-76.9990361", fields=["timezone"], limit=5)
```

### Distance calculations

Calculate distances from a single origin to multiple destinations, or compute full distance matrices.

#### Coordinate format with custom IDs

You can add custom identifiers to coordinates using the `lat,lng,id` format. The ID will be returned in the response, making it easy to match results back to your data:

```python
from geocodio import Coordinate

# String format with ID
"37.7749,-122.4194,warehouse_1"

# Tuple format with ID
(37.7749, -122.4194, "warehouse_1")

# Using the Coordinate class
Coordinate(37.7749, -122.4194, "warehouse_1")

# The ID is returned in the response:
# DistanceDestination(
#     query="37.7749,-122.4194,warehouse_1",
#     location=(37.7749, -122.4194),
#     id="warehouse_1",
#     distance_miles=3.2,
#     distance_km=5.1
# )
```

#### Distance mode and units

The SDK provides constants for type-safe distance configuration:

```python
from geocodio import (
    DISTANCE_MODE_STRAIGHTLINE,  # Default - great-circle (as the crow flies)
    DISTANCE_MODE_DRIVING,       # Road network routing with duration
    DISTANCE_MODE_HAVERSINE,     # Alias for Straightline (backward compat)
    DISTANCE_UNITS_MILES,        # Default
    DISTANCE_UNITS_KM,
    DISTANCE_ORDER_BY_DISTANCE,  # Default
    DISTANCE_ORDER_BY_DURATION,
    DISTANCE_SORT_ASC,           # Default
    DISTANCE_SORT_DESC,
)
```

> **Note:** The default mode is `straightline` (great-circle distance). Use `DISTANCE_MODE_DRIVING` if you need road network routing with duration estimates.

#### Add distance to geocoding requests

You can add distance calculations to existing geocode or reverse geocode requests. Each geocoded result will include distance data to each destination.

```python
from geocodio import (
    Geocodio,
    DISTANCE_MODE_DRIVING,
    DISTANCE_UNITS_MILES,
    DISTANCE_ORDER_BY_DISTANCE,
    DISTANCE_SORT_ASC,
)

client = Geocodio("YOUR_API_KEY")

# Geocode an address and calculate distances to store locations
response = client.geocode(
    "1600 Pennsylvania Ave NW, Washington DC",
    destinations=[
        "38.9072,-77.0369,store_dc",
        "39.2904,-76.6122,store_baltimore",
        "39.9526,-75.1652,store_philly"
    ],
    distance_mode=DISTANCE_MODE_DRIVING,
    distance_units=DISTANCE_UNITS_MILES
)

# Reverse geocode with distances
response = client.reverse(
    "38.8977,-77.0365",
    destinations=["38.9072,-77.0369,capitol", "38.8895,-77.0353,monument"],
    distance_mode=DISTANCE_MODE_STRAIGHTLINE
)

# With filtering - find nearest 3 stores within 50 miles
response = client.geocode(
    "1600 Pennsylvania Ave NW, Washington DC",
    destinations=[
        "38.9072,-77.0369,store_1",
        "39.2904,-76.6122,store_2",
        "39.9526,-75.1652,store_3",
        "40.7128,-74.0060,store_4"
    ],
    distance_mode=DISTANCE_MODE_DRIVING,
    distance_max_results=3,
    distance_max_distance=50.0,
    distance_order_by=DISTANCE_ORDER_BY_DISTANCE,
    distance_sort_order=DISTANCE_SORT_ASC
)
```

#### Single origin to multiple destinations

```python
from geocodio import (
    Geocodio,
    Coordinate,
    DISTANCE_MODE_DRIVING,
    DISTANCE_UNITS_KM,
    DISTANCE_ORDER_BY_DISTANCE,
    DISTANCE_SORT_ASC,
)

client = Geocodio("YOUR_API_KEY")

# Calculate distances from one origin to multiple destinations
response = client.distance(
    origin="37.7749,-122.4194,headquarters",  # Origin with ID
    destinations=[
        "37.7849,-122.4094,customer_a",
        "37.7949,-122.3994,customer_b",
        "37.8049,-122.4294,customer_c"
    ]
)

print(response.origin.id)  # "headquarters"
for dest in response.destinations:
    print(f"{dest.id}: {dest.distance_miles} miles")

# Use driving mode for road network routing (includes duration)
response = client.distance(
    origin="37.7749,-122.4194",
    destinations=["37.7849,-122.4094"],
    mode=DISTANCE_MODE_DRIVING
)
print(response.destinations[0].duration_seconds)  # e.g., 180

# With all filtering and sorting options
response = client.distance(
    origin="37.7749,-122.4194,warehouse",
    destinations=[
        "37.7849,-122.4094,store_1",
        "37.7949,-122.3994,store_2",
        "37.8049,-122.4294,store_3"
    ],
    mode=DISTANCE_MODE_DRIVING,
    units=DISTANCE_UNITS_KM,
    max_results=2,
    max_distance=10.0,
    order_by=DISTANCE_ORDER_BY_DISTANCE,
    sort_order=DISTANCE_SORT_ASC
)

# Using Coordinate class
origin = Coordinate(37.7749, -122.4194, "warehouse")
destinations = [
    Coordinate(37.7849, -122.4094, "store_1"),
    Coordinate(37.7949, -122.3994, "store_2")
]
response = client.distance(origin=origin, destinations=destinations)

# Tuple format for coordinates (with or without ID)
response = client.distance(
    origin=(37.7749, -122.4194),                    # Without ID
    destinations=[(37.7849, -122.4094, "dest_1")]   # With ID as third element
)
```

#### Distance matrix (multiple origins × destinations)

```python
from geocodio import Geocodio, Coordinate, DISTANCE_MODE_DRIVING, DISTANCE_UNITS_KM

client = Geocodio("YOUR_API_KEY")

# Calculate full distance matrix with custom IDs
response = client.distance_matrix(
    origins=[
        "37.7749,-122.4194,warehouse_sf",
        "37.8049,-122.4294,warehouse_oak"
    ],
    destinations=[
        "37.7849,-122.4094,customer_1",
        "37.7949,-122.3994,customer_2"
    ]
)

for result in response.results:
    print(f"From {result.origin.id}:")
    for dest in result.destinations:
        print(f"  To {dest.id}: {dest.distance_miles} miles")

# With driving mode and kilometers
response = client.distance_matrix(
    origins=["37.7749,-122.4194"],
    destinations=["37.7849,-122.4094"],
    mode=DISTANCE_MODE_DRIVING,
    units=DISTANCE_UNITS_KM
)

# Using Coordinate objects
origins = [
    Coordinate(37.7749, -122.4194, "warehouse_sf"),
    Coordinate(37.8049, -122.4294, "warehouse_oak")
]
destinations = [
    Coordinate(37.7849, -122.4094, "customer_1"),
    Coordinate(37.7949, -122.3994, "customer_2")
]
response = client.distance_matrix(origins=origins, destinations=destinations)
```

#### Nearest mode (find closest destinations)

```python
# Find up to 2 nearest destinations from each origin
response = client.distance_matrix(
    origins=["37.7749,-122.4194"],
    destinations=["37.7849,-122.4094", "37.7949,-122.3994", "37.8049,-122.4294"],
    max_results=2
)

# Filter by maximum distance (in miles or km depending on units)
response = client.distance_matrix(
    origins=["37.7749,-122.4194"],
    destinations=[...],
    max_distance=2.0
)

# Filter by minimum and maximum distance
response = client.distance_matrix(
    origins=["37.7749,-122.4194"],
    destinations=[...],
    min_distance=1.0,
    max_distance=10.0
)

# Filter by duration (seconds, driving mode only)
response = client.distance_matrix(
    origins=["37.7749,-122.4194"],
    destinations=[...],
    mode=DISTANCE_MODE_DRIVING,
    max_duration=300,  # 5 minutes
    min_duration=60    # 1 minute minimum
)

# Sort by duration descending
response = client.distance_matrix(
    origins=["37.7749,-122.4194"],
    destinations=[...],
    mode=DISTANCE_MODE_DRIVING,
    max_results=5,
    order_by=DISTANCE_ORDER_BY_DURATION,
    sort_order=DISTANCE_SORT_DESC
)
```

#### Async distance matrix jobs

For large distance matrix calculations, use async jobs that process in the background.

```python
from geocodio import Geocodio, DISTANCE_MODE_DRIVING, DISTANCE_UNITS_MILES

client = Geocodio("YOUR_API_KEY")

# Create a new distance matrix job
job = client.create_distance_matrix_job(
    name="My Distance Calculation",
    origins=["37.7749,-122.4194", "37.8049,-122.4294"],
    destinations=["37.7849,-122.4094", "37.7949,-122.3994"],
    mode=DISTANCE_MODE_DRIVING,
    units=DISTANCE_UNITS_MILES,
    callback_url="https://example.com/webhook"  # Optional
)

print(job.id)            # Job identifier
print(job.status)        # "ENQUEUED"
print(job.total_calculations)  # 4

# Or use list IDs from previously uploaded lists
job = client.create_distance_matrix_job(
    name="Distance from List",
    origins=12345,       # List ID
    destinations=67890,  # List ID
    mode=DISTANCE_MODE_STRAIGHTLINE
)

# Check job status
status = client.distance_matrix_job_status(job.id)
print(status.status)     # "ENQUEUED", "PROCESSING", "COMPLETED", or "FAILED"
print(status.progress)   # 0-100

# List all jobs (paginated)
jobs = client.distance_matrix_jobs()
jobs = client.distance_matrix_jobs(page=2)  # Page 2

# Get results when complete (same format as distance_matrix response)
results = client.get_distance_matrix_job_results(job.id)
for result in results.results:
    print(f"From {result.origin.id}:")
    for dest in result.destinations:
        print(f"  To {dest.id}: {dest.distance_miles} miles")

# Or download to a file for very large results
client.download_distance_matrix_job(job.id, "results.json")

# Delete a job
client.delete_distance_matrix_job(job.id)
```

### List API

The List API allows you to manage lists of addresses or coordinates for batch processing.

```python
from geocodio import Geocodio

client = Geocodio("YOUR_API_KEY")

# Get all lists
lists = client.get_lists()
print(f"Found {len(lists.data)} lists")

# Create a new list from a file
with open("addresses.csv", "rb") as f:
    new_list = client.create_list(
        file=f,
        filename="addresses.csv",
        direction="forward"
    )
print(f"Created list: {new_list.id}")

# Get a specific list
list_details = client.get_list(new_list.id)
print(f"List status: {list_details.status}")

# Download a completed list
if list_details.status and list_details.status.get("state") == "COMPLETED":
    file_content = client.download(new_list.id, "downloaded_results.csv")
    print("List downloaded successfully")

# Delete a list
client.delete_list(new_list.id)
```

Error Handling
--------------

```python
from geocodio import Geocodio
from geocodio.exceptions import AuthenticationError, InvalidRequestError

try:
    client = Geocodio("INVALID_API_KEY")
    response = client.geocode("1600 Pennsylvania Ave, Washington, DC")
except AuthenticationError as e:
    print(f"Authentication failed: {e}")

try:
    client = Geocodio("YOUR_API_KEY")
    response = client.geocode("")  # Empty address
except InvalidRequestError as e:
    print(f"Invalid request: {e}")
```

Geocodio Enterprise
-------------------

To use this library with Geocodio Enterprise, pass `api.enterprise.geocod.io` as the `hostname` parameter when initializing the client:

```python
from geocodio import Geocodio

# Initialize client for Geocodio Enterprise
client = Geocodio(
    "YOUR_API_KEY",
    hostname="api.enterprise.geocod.io"
)

# All methods work the same as with the standard API
response = client.geocode("1600 Pennsylvania Ave, Washington, DC")
print(response.results[0].formatted_address)
```

Testing
-------

```bash
$ pip install -e ".[dev]"
$ pytest
```

Documentation
-------------

Full documentation is available at <https://www.geocod.io/docs/?python>.

Changelog
---------

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Security
--------

If you discover any security related issues, please email security@geocod.io instead of using the issue tracker.

License
-------

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

Contributing
------------

Contributions are welcome! Please open issues and pull requests on GitHub.

Issues: <https://github.com/geocodio/geocodio-library-python/issues>
