# Common Usage Patterns

This document shows common soildb usage patterns without extensive explanation.

## Basic Queries

```{python}
#| eval: false
import soildb

# Point query (synchronous)
response = soildb.get_mapunit_by_point.sync(-93.6, 42.0)
df = response.to_pandas()

# Area query (synchronous)
response = soildb.get_mapunit_by_areasymbol.sync("IA015")
df = response.to_pandas()

# Or async for high-performance applications
async with soildb.SDAClient() as client:
    response = await soildb.get_mapunit_by_point(-93.6, 42.0, client=client)
    df = response.to_pandas()
```

## Spatial Queries

```{python}
#| eval: false
# Bounding box
bbox = {"xmin": -93.65, "ymin": 42.0, "xmax": -93.6, "ymax": 42.05}
response = await soildb.spatial_query(bbox, "mupolygon", spatial_relation="intersects")
df = response.to_pandas()

# Point with geometry
response = await soildb.spatial_query(
    "POINT(-93.6 42.0)", 
    "mupolygon", 
    spatial_relation="intersects",
    return_type="spatial"
)
gdf = response.to_geodataframe()  # Requires GeoPandas
```

## Bulk Data

```{python}
#| eval: false
# Get many mukeys (synchronous)
mukeys = soildb.get_mukey_by_areasymbol.sync(["IA015", "IA109"])

# Fetch map unit data (synchronous)
response = soildb.fetch_by_keys.sync(mukeys, "mapunit")
df = response.to_pandas()

# Fetch with specific columns (synchronous)
response = soildb.fetch_by_keys.sync(
    mukeys, 
    "component",
    key_column="mukey",
    columns=["cokey", "compname", "comppct_r"]
)
df = response.to_pandas()

# Or async for concurrent processing
async with soildb.SDAClient() as client:
    mukeys = await soildb.get_mukey_by_areasymbol(["IA015", "IA109"], client=client)
    response = await soildb.fetch_by_keys(mukeys, "component", key_column="mukey", client=client)
    df = response.to_pandas()
```

## Custom Queries

```{python}
#| eval: false
import soildb

# Build SQL
query = (soildb.Query()
    .select("mukey", "muname")
    .from_("mapunit")
    .where("mukind = 'Consociation'")
    .limit(100))

# Execute
async with soildb.SDAClient() as client:
    response = await client.execute(query)
    df = response.to_pandas()
```

## Error Handling

```{python}
#| eval: false
try:
    # Synchronous usage
    response = soildb.get_mapunit_by_point.sync(-93.6, 42.0)
except soildb.SDAConnectionError:
    print("SDA service unavailable")
except soildb.SDAQueryError as e:
    print(f"Query error: {e}")

# Or async usage
try:
    async with soildb.SDAClient() as client:
        response = await soildb.get_mapunit_by_point(-93.6, 42.0, client=client)
except soildb.SDAConnectionError:
    print("SDA service unavailable")
except soildb.SDAQueryError as e:
    print(f"Query error: {e}")
```

## Structured Data Objects

```{python}
#| eval: false
# Fetch structured objects with default columns (synchronous)
map_unit = soildb.fetch_mapunit_struct_by_point.sync(38.5, -121.5)
pedon = soildb.fetch_pedon_struct_by_id.sync("S1999NY061001")

# Access nested data
for component in map_unit.components:
    print(f"Component: {component.component_name}")
    for horizon in component.aggregate_horizons:
        print(f"  Horizon: {horizon.horizon_name}")

# Access pedon horizons
for horizon in pedon.horizons:
    print(f"Horizon: {horizon.horizon_name}, Depth: {horizon.top_depth}-{horizon.bottom_depth}")

# Or async
async with soildb.SDAClient() as client:
    map_unit = await soildb.fetch_mapunit_struct_by_point(38.5, -121.5, client=client)
    pedon = await soildb.fetch_pedon_struct_by_id("S1999NY061001", client=client)
```

## Flexible Column Support

```{python}
#| eval: false
# Fetch with additional custom columns
map_unit = await soildb.fetch_mapunit_struct_by_point(
    38.5, -121.5,
    component_columns=["cokey", "compname", "comppct_r", "majcompflag", "localphase", "drainagecl"],
    horizon_columns=["chkey", "hzname", "hzdept_r", "hzdepb_r", "claytotal_r", "om_r", "ph1to1h2o_r"]
)

# Access extra fields
component = map_unit.components[0]
if "localphase" in component.extra_fields:
    print(f"Local phase: {component.extra_fields['localphase']}")

horizon = component.aggregate_horizons[0]
if "om_r" in horizon.extra_fields:
    print(f"Organic matter: {horizon.extra_fields['om_r']}")

# Pedon with custom horizon columns
pedon = await soildb.fetch_pedon_struct_by_id(
    "S1999NY061001",
    horizon_columns=[
        "layer_key", "layer_sequence", "hzn_desgn", "hzn_top", "hzn_bot",
        "sand_total", "silt_total", "clay_total", "ph_h2o", "organic_carbon",
        "cec7_r", "ecec_r", "sar_r"  # Custom columns
    ]
)

# Access custom horizon data
horizon = pedon.horizons[0]
if "cec7_r" in horizon.extra_fields:
    print(f"CEC: {horizon.extra_fields['cec7_r']}")
```

## DataFrame Export

```{python}
#| eval: false
# Pandas (default)
df = response.to_pandas()

# Polars (if installed)
df = response.to_polars()

# GeoPandas (only for spatial data)
# gdf = response.to_geodataframe()
```

## Synchronous Usage

While soildb is designed to be async-first for high-performance applications, we provide a convenient way to call async functions from a synchronous context. All public async functions have an equivalent `.sync` version that you can call directly.

### Before:

```{python}
#| eval: false
import asyncio
from soildb import SDAClient, get_sacatalog

async with SDAClient() as client:
    result = await get_sacatalog(client=client)
```

### After:

```{python}
#| eval: false
from soildb import get_sacatalog

result = get_sacatalog.sync()  # Client created automatically!
```

For convenience, the `.sync` versions automatically create and manage an `SDAClient` instance when no `client` parameter is provided. This makes synchronous usage even simpler, though with some performance overhead for client creation.

If you're making multiple sync calls, consider creating a client once and reusing it:

```{python}
#| eval: false
from soildb import SDAClient, get_sacatalog, get_mapunit_by_point

client = SDAClient()
result1 = get_sacatalog.sync(client=client)
result2 = get_mapunit_by_point.sync(-93.6, 42.0, client=client)
client.close()
```

### Important Limitation

The `.sync` methods are intended for use in synchronous code only. They will raise a `SyncUsageError` if called from within an existing asyncio event loop. In such cases, use the async version instead.