Skip to content

Main Client

The pybdl.client.BDL class is the main entry point for the library. It provides two interfaces for accessing BDL data:

  1. Access Layer (default, recommended): Returns pandas DataFrames - use bdl.levels, bdl.data, etc.
  2. API Layer: Returns raw dictionaries - use bdl.api.levels, bdl.api.data, etc.

For most users, the access layer is recommended as it provides DataFrames that are immediately ready for data analysis.

Access Layer (Default Interface)

The access layer is the primary interface and returns pandas DataFrames:

from pybdl import BDL, BDLConfig

bdl = BDL(BDLConfig(api_key="your-api-key"))

# Access layer - returns DataFrames
levels_df = bdl.levels.list_levels()
variables_df = bdl.variables.list_variables()
data_df = bdl.data.get_data_by_variable(variable_id="3643", years=[2021])

# Data is ready for pandas operations
print(levels_df.head())
print(data_df.dtypes)
print(data_df.columns)

Key features of the access layer:

  • Automatic DataFrame conversion: All responses are pandas DataFrames
  • Column normalization: camelCase → snake_case (e.g., variableIdvariable_id)
  • Data type inference: Proper types (integers, floats, booleans)
  • Nested data flattening: Complex structures are normalized into tabular format

See access layer for comprehensive documentation on the access layer.

API Layer (Low-Level Interface)

The API layer provides direct access to raw API responses as dictionaries:

from pybdl import BDL

bdl = BDL()

# API layer - returns raw dictionaries
levels_data = bdl.api.levels.list_levels()
data_dict = bdl.api.data.get_data_by_variable(variable_id="3643", years=[2021])

# Raw API response structure
print(type(levels_data))  # list
print(type(data_dict))    # list or dict

Use the API layer when you need:

  • Raw API response structure
  • Custom response processing
  • Direct access to API metadata
  • Integration with non-pandas workflows

See api clients for details about the API layer.

Examples

Basic Usage with Access Layer

from pybdl import BDL, BDLConfig

# Initialize client
bdl = BDL(BDLConfig(api_key="your-api-key"))

# Get administrative levels
levels = bdl.levels.list_levels()
print(f"Found {len(levels)} administrative levels")

# Get variables
variables = bdl.variables.search_variables(name="population")
print(f"Found {len(variables)} population variables")

# Get data
data = bdl.data.get_data_by_variable("3643", years=[2021], unit_level=2)
print(f"Retrieved {len(data)} data points")
print(data.head())

Using Both Interfaces

from pybdl import BDL

bdl = BDL()

# Access layer for DataFrame analysis
df = bdl.data.get_data_by_variable("3643", years=[2021])
avg_value = df['val'].mean()

# API layer for raw metadata
metadata = bdl.api.data.get_data_by_variable(
    "3643", years=[2021], return_metadata=True
)
if isinstance(metadata, tuple):
    data, meta = metadata
    print(f"Total pages: {meta.get('totalPages', 'unknown')}")

Context Manager and Session Lifecycle

BDL can be used as a context manager to ensure HTTP sessions and underlying resources are properly closed when you are done. This is especially important in long-running processes and scripts that should not leave open connections behind.

from pybdl import BDL, BDLConfig

# Context manager — sessions are closed automatically on exit
with BDL(BDLConfig(api_key="your-api-key")) as bdl:
    levels = bdl.levels.list_levels()
    data = bdl.data.get_data_by_variable("3643", years=[2021])

When not using the context manager, call bdl.close() explicitly when finished:

bdl = BDL()
try:
    data = bdl.data.get_data_by_variable("3643", years=[2021])
finally:
    bdl.close()

The async interface supports async with:

import asyncio
from pybdl import BDL

async def main():
    async with BDL() as bdl:
        data = await bdl.data.aget_data_by_variable("3643", years=[2021])
    return data

asyncio.run(main())

Async Usage

Both interfaces support async operations:

import asyncio
from pybdl import BDL

async def main():
    bdl = BDL()

    # Async access layer
    levels_df = await bdl.levels.alist_levels()
    variables_df = await bdl.variables.alist_variables()

    # Async API layer
    levels_data = await bdl.api.levels.alist_levels()

    return levels_df, variables_df, levels_data

asyncio.run(main())

API reference

client

APINamespace dataclass

APINamespace(
    aggregates,
    attributes,
    data,
    levels,
    measures,
    subjects,
    units,
    variables,
    version,
    years,
)

Typed namespace for low-level API clients.

aggregates instance-attribute

aggregates

attributes instance-attribute

attributes

data instance-attribute

data

levels instance-attribute

levels

measures instance-attribute

measures

subjects instance-attribute

subjects

units instance-attribute

units

variables instance-attribute

variables

version instance-attribute

version

years instance-attribute

years

BDL

BDL(config=None)

Main interface for interacting with the Local Data Bank (BDL) API.

This class provides a unified entry point to all BDL API endpoints, including aggregates, attributes, data, levels, measures, subjects, units, variables, version, and years.

The access layer (default interface) returns pandas DataFrames with proper column labels and data types. The API layer (via .api) returns raw dictionaries for advanced use cases.

Initialize the BDL client and all API endpoint namespaces.

Parameters:

Name Type Description Default
config BDLConfig | dict | None

BDLConfig instance or dict. If not provided, configuration is loaded from environment variables and defaults.

None

Raises:

Type Description
TypeError

If config is not a dict, BDLConfig, or None.

Note

Configuration can be set through: 1. Direct parameter passing to BDLConfig 2. Environment variables (BDL_API_KEY, BDL_LANGUAGE, etc.) 3. Default values

Source code in pybdl/client.py
def __init__(self, config: BDLConfig | dict | None = None):
    """
    Initialize the BDL client and all API endpoint namespaces.

    Args:
        config: BDLConfig instance or dict. If not provided, configuration is loaded from
            environment variables and defaults.

    Raises:
        TypeError: If config is not a dict, BDLConfig, or None.

    Note:
        Configuration can be set through:
        1. Direct parameter passing to BDLConfig
        2. Environment variables (BDL_API_KEY, BDL_LANGUAGE, etc.)
        3. Default values
    """
    if config is None:
        config_obj = BDLConfig()
    elif isinstance(config, dict):
        config_obj = BDLConfig(**config)
    elif isinstance(config, BDLConfig):
        config_obj = config
    else:
        raise TypeError(f"config must be a dict, BDLConfig, or None, got {type(config)}")
    self.config = config_obj

    self.api = APINamespace(
        aggregates=api.AggregatesAPI(self.config),
        attributes=api.AttributesAPI(self.config),
        data=api.DataAPI(self.config),
        levels=api.LevelsAPI(self.config),
        measures=api.MeasuresAPI(self.config),
        subjects=api.SubjectsAPI(self.config),
        units=api.UnitsAPI(self.config),
        variables=api.VariablesAPI(self.config),
        version=api.VersionAPI(self.config),
        years=api.YearsAPI(self.config),
    )

    # Initialize access layer (default interface, returns DataFrames)
    self.aggregates = access.AggregatesAccess(self.api.aggregates)
    self.attributes = access.AttributesAccess(self.api.attributes)
    self.data = access.DataAccess(self.api.data)
    self.levels = access.LevelsAccess(self.api.levels)
    self.measures = access.MeasuresAccess(self.api.measures)
    self.subjects = access.SubjectsAccess(self.api.subjects)
    self.units = access.UnitsAccess(self.api.units)
    self.variables = access.VariablesAccess(self.api.variables)
    self.years = access.YearsAccess(self.api.years)

config instance-attribute

config = config_obj

api instance-attribute

api = APINamespace(
    aggregates=AggregatesAPI(config),
    attributes=AttributesAPI(config),
    data=DataAPI(config),
    levels=LevelsAPI(config),
    measures=MeasuresAPI(config),
    subjects=SubjectsAPI(config),
    units=UnitsAPI(config),
    variables=VariablesAPI(config),
    version=VersionAPI(config),
    years=YearsAPI(config),
)

aggregates instance-attribute

aggregates = AggregatesAccess(aggregates)

attributes instance-attribute

attributes = AttributesAccess(attributes)

data instance-attribute

data = DataAccess(data)

levels instance-attribute

levels = LevelsAccess(levels)

measures instance-attribute

measures = MeasuresAccess(measures)

subjects instance-attribute

subjects = SubjectsAccess(subjects)

units instance-attribute

units = UnitsAccess(units)

variables instance-attribute

variables = VariablesAccess(variables)

years instance-attribute

years = YearsAccess(years)

close

close()

Close all synchronous HTTP resources owned by the client.

Source code in pybdl/client.py
def close(self) -> None:
    """Close all synchronous HTTP resources owned by the client."""
    for client in vars(self.api).values():
        close = getattr(client, "close", None)
        if callable(close):
            close()

aclose async

aclose()

Close all synchronous and asynchronous HTTP resources owned by the client.

Source code in pybdl/client.py
async def aclose(self) -> None:
    """Close all synchronous and asynchronous HTTP resources owned by the client."""
    for client in vars(self.api).values():
        aclose = getattr(client, "aclose", None)
        if callable(aclose):
            await aclose()
        else:
            close = getattr(client, "close", None)
            if callable(close):
                close()

__enter__

__enter__()
Source code in pybdl/client.py
def __enter__(self) -> "BDL":
    return self

__exit__

__exit__(exc_type, exc_val, exc_tb)
Source code in pybdl/client.py
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Literal[False]:
    self.close()
    return False

__aenter__ async

__aenter__()
Source code in pybdl/client.py
async def __aenter__(self) -> "BDL":
    return self

__aexit__ async

__aexit__(exc_type, exc_val, exc_tb)
Source code in pybdl/client.py
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Literal[False]:
    await self.aclose()
    return False

Seealso

- [Access layer](access_layer.md) — comprehensive access layer documentation
- [API clients](api_clients.md) — API layer details
- [Examples](examples.ipynb) — real-world usage
- [Configuration](config.md) — configuration options