Skip to main content

Quantec

A Python package for working with Quantec EasyData API. Fetch economic time series data with support for caching and advanced filtering.

⚠️ Early Development Notice: This package is in early development and may undergo breaking changes without backwards compatibility until version 1.0 is reached.

The Quantec EasyData API is available to EasyData subscribers. To subscribe and get API access, visit quantec.co.za/easydata for more information.

Features

  • 🎯 Multiple Data Access: Time series codes, selections, and grid/pivot data
  • 📊 Format Support: CSV for time series, CSV/Parquet for grid data
  • 📈 Multiple Frequencies: Monthly (M), Quarterly (Q), and Annual (A) frequencies
  • 🔍 Advanced Filtering: Dimension-based filtering for grid data
  • ⚡ Performance Caching: Optional caching for time series and grid data
  • 🛡️ Error Handling: Comprehensive network and API error handling
  • 🔧 Flexible Configuration: Environment variables and parameter setup
  • 🔐 Header Authentication: API keys are sent via the Authorization header only

Installation

pip install quantec

Quick Start

from quantec.easydata.client import Client

# Initialize client
client = Client()

# Get time series data (CSV format only)
data = client.get_data(time_series_codes="NMS-EC_BUS,NMS-GA_BUS")
print(data.head())

Configuration

Environment Variables

Mac/Linux (bash/zsh):

export EASYDATA_API_KEY="your-api-key-here"
export EASYDATA_API_URL="https://www.easydata.co.za/api/v3/"

On Windows (PowerShell):

$env:EASYDATA_API_KEY = "your-api-key-here"
$env:EASYDATA_API_URL = "https://www.easydata.co.za/api/v3/"

Authentication

Quantec uses header-only API authentication. Provide your API key via api_key or EASYDATA_API_KEY; the client sends it as an Authorization: Token ... header. API keys are never added to query parameters.

Applications that authenticate with an OAuth 2.1 access token instead of a DRF API key can switch the scheme per client instance with auth_scheme ("Token", the default, or "Bearer"):

client = Client(api_key=access_token, auth_scheme="Bearer")
# Sends: Authorization: Bearer <access_token>

The scheme is never read from an environment variable. Requests to external or pre-signed download URLs (for example object storage at the end of an async grid download) never carry the Authorization header, whichever scheme is configured.

The client also sends a default User-Agent of quantec-python/<version>. Apps can identify themselves by passing a product prefix:

client = Client(user_agent="easydata-cli/0.3.0")
# Sends: User-Agent: easydata-cli/0.3.0 quantec-python/<version>

Client Options

from quantec.easydata.client import Client

# Basic client (uses environment variables)
client = Client()
# All parameters and defaults
client = Client(
    api_key=None,  # default: uses EASYDATA_API_KEY env var
    api_url=None,  # default: EASYDATA_API_URL or "https://www.easydata.co.za/api/v3"
    use_cache=True,
    cache_dir="cache",
    user_agent=None,  # optional product identifier, e.g. "easydata-cli/0.3.0"
    headers=None,     # optional additional default request headers
    auth_scheme="Token",  # "Token" (DRF API key) or "Bearer" (OAuth 2.1 access token)
)

Time Series Data

Main Methods

  • get_data: Fetch time series or selection data.

    • Parameters:
      • time_series_codes: Optional[str] = None - Comma-separated time series codes
      • selection_pk: Optional[int] = None - Selection primary key (takes precedence over codes)
      • freq: str = "A" - Data frequency ("M", "Q", "A")
      • start_year: str = "" - Start year (yyyy)
      • end_year: str = "" - End year (yyyy)
      • analysis: bool = False - Include analysis (selection_pk only)
      • resp_format: str = "dataframe" - Response format ("dataframe", "csv", "json")
      • is_tidy: bool = True - Return tidy data
    • Returns: DataFrame when resp_format="dataframe", CSV string when "csv", dict when "json".
  • get_selections: Fetch user selections.

    • Parameters:
      • status: Optional[str] = None - Status flags: U=Unsaved, P=Private, S=Shared, O=Open, W=Owner (e.g., "PSW" for Private and Shared selections owned by user)
      • show: Optional[str] = None - Selection visibility filter passed through to the API
      • filter: Optional[str] = None - Selection state filter passed through to the API
    • Returns: DataFrame of selections.

Direct Access with Codes

# Single time series
data = client.get_data(time_series_codes="NMS-EC_BUS")

# Multiple time series
data = client.get_data(time_series_codes="NMS-EC_BUS,NMS-GA_BUS")

# With date filtering and frequency
data = client.get_data(
    time_series_codes="NMS-EC_BUS,NMS-GA_BUS",
    freq="Q",           # Quarterly data
    start_year="2020",  # Year format only
    end_year="2023"
)

# Different return formats
df_data = client.get_data(time_series_codes="NMS-EC_BUS", resp_format="dataframe")  # Default
csv_data = client.get_data(time_series_codes="NMS-EC_BUS", resp_format="csv")       # Raw CSV string
json_data = client.get_data(time_series_codes="NMS-EC_BUS", resp_format="json")     # JSON dict

Discovery-Based Access

# Find available selections
selections = client.get_selections(status="PSO")  # Private, Shared, Open
# Or get only private and shared selections owned by user
selections = client.get_selections(status="PSW")  # Private, Shared, Owner

# Use selection for data retrieval (returns DataFrame)
if len(selections) > 0:
    selection_pk = selections.iloc[0]['pk']
    data = client.get_data(selection_pk=selection_pk)

Grid/Pivot Data

Main Methods

  • get_recipes: Fetch available recipes.

    • Parameters: none.
    • Returns: DataFrame of recipes.
  • get_grid_data: Fetch grid/pivot data by recipe.

    • Parameters:
      • recipe_pk: int - Recipe primary key identifier
      • is_expanded: bool = False - Return expanded data format
      • is_melted: bool = True - Return melted data format
      • resp_format: str = "dataframe" - Response format ("dataframe", "csv", "parquet")
      • selectdimensionnodes: dict | list[dict] | None = None - Dimension filtering
      • has_tscodes: bool = False - Include time series codes
      • has_dncodes: bool = False - Include dimension node codes
      • is_sparse: bool = True - Return sparse data format
      • freq: Optional[str] = None - Data frequency ("M", "Q", "A")
    • Returns: DataFrame when resp_format="dataframe", CSV string when "csv", bytes when "parquet".

Basic Grid Data Access

# Get available recipes
recipes = client.get_recipes()

# Basic grid data retrieval
if len(recipes) > 0:
    recipe_id = recipes.iloc[0]['id']
    grid_data = client.get_grid_data(recipe_pk=recipe_id)
    
# Grid data with frequency
if len(recipes) > 0:
    recipe_id = recipes.iloc[0]['id']
    grid_data = client.get_grid_data(
        recipe_pk=recipe_id,
        freq="Q"  # Quarterly data
    )

Grid Data with Filtering

# Single dimension filter by levels only
filters = {"dimension": "d3", "levels": [2]}
grid_data = client.get_grid_data(recipe_pk=1066, selectdimensionnodes=filters)

# Single dimension filter by codes only
filters = {"dimension": "d3", "codes": ["TRD01-R_FI"]}
grid_data = client.get_grid_data(recipe_pk=1066, selectdimensionnodes=filters)

# Single dimension filter combining codes and levels
filters = {"dimension": "d3", "levels": [1], "codes": ["TRD01-R_FI"]}
grid_data = client.get_grid_data(recipe_pk=1066, selectdimensionnodes=filters)

# Multiple dimension filters
filters = [
    {"dimension": "d1", "codes": ["CODE1", "CODE2"]},
    {"dimension": "d3", "levels": [2]},
    {"dimension": "d2", "codes": ["PARENT_CODE"], "children": True}
]
grid_data = client.get_grid_data(recipe_pk=1066, selectdimensionnodes=filters)

# Single code with children
filters = {"dimension": "d3", "codes": ["TRD01-R_FI"], "children": True}
grid_data = client.get_grid_data(recipe_pk=1066, selectdimensionnodes=filters)

# Single code with children including self
filters = {"dimension": "d3", "codes": ["TRD01-R_FI"], "children_include_self": True}
grid_data = client.get_grid_data(recipe_pk=1066, selectdimensionnodes=filters)

Supported Filter Combinations

These are the valid filter combinations for each dimension:

  1. Codes only: {"dimension": "d1", "codes": ["CODE1", "CODE2", ...]}
  2. Levels only: {"dimension": "d1", "levels": [1, 2, ...]}
  3. Codes and levels: {"dimension": "d1", "codes": ["CODE1"], "levels": [1, 2]}
  4. Single code with children: {"dimension": "d1", "codes": ["PARENT_CODE"], "children": True}
  5. Single code with children including self: {"dimension": "d1", "codes": ["PARENT_CODE"], "children_include_self": True}

Important constraints:

  • children and children_include_self require exactly one code
  • children and children_include_self cannot be used together
  • children and children_include_self cannot be combined with levels
  • Valid dimensions: "d1", "d2", "d3", "d4", "d5", "d6", "d7"

Format Options (CSV/Parquet only)

# DataFrame format (default)
df_data = client.get_grid_data(recipe_pk=1066)

# CSV format 
csv_data = client.get_grid_data(recipe_pk=1066, resp_format="csv")

# Parquet format (recommended for large datasets)
parquet_data = client.get_grid_data(recipe_pk=1066, resp_format="parquet")

Caching

Caching is enabled by default to improve performance. The client automatically caches time series and grid data responses.

# Caching is enabled by default
client = Client()  # use_cache=True by default

# Time series caching
ts1 = client.get_data(time_series_codes="NMS-EC_BUS")     # fetch + cache
ts2 = client.get_data(time_series_codes="NMS-EC_BUS")     # loaded from cache

# Grid data caching
grid1 = client.get_grid_data(recipe_pk=1066)               # fetch + cache
grid2 = client.get_grid_data(recipe_pk=1066)               # loaded from cache

# Clear cache
client.cache.clear()  # Remove all cached files

# Disable caching if needed
no_cache_client = Client(use_cache=False)

Error Handling

import requests
from quantec.easydata.client import Client

client = Client()

try:
    data = client.get_data(time_series_codes="INVALID_CODE")
except requests.HTTPError as e:
    print(f"API Error: {e}")
except ValueError as e:
    print(f"Parameter Error: {e}")

Complete Example

from quantec.easydata.client import Client

# Initialize client with caching
client = Client(use_cache=True, cache_dir="./cache")

# 1. Get time series data
ts_data = client.get_data(
    time_series_codes="NMS-EC_BUS,NMS-GA_BUS",
    freq="Q",
    start_year="2020"
)

# 2. Get selections for discovery
selections = client.get_selections(status="PSO")  # Private, Shared, Open
# selections.head()

selection_pk = int(selections.loc[selections.title == "CPI overview", "pk"].iloc[0])

selection_data = client.get_data(
    selection_pk=selection_pk,
    freq="M"
)

# 3. Get available recipes and grid data for `TRD01` data set
recipes = client.get_recipes()
# recipes.head()

recipe_pk = int(recipes.loc[recipes.dataset_code == "TRD01", "id"].iloc[0])

grid_data = client.get_grid_data(
    recipe_pk=recipes.iloc[0]['id'],
)

Important Notes

  • Time series data: Supports DataFrame (default), CSV and JSON formats
  • Grid data: Supports DataFrame (default), CSV and Parquet formats
  • Date parameters: Use year format only (e.g., "2020", not "2020-01-01")
  • Caching: Available for time series and grid data by default
  • Dimension filtering: Must provide at least one of: codes, levels, children, or children_include_self. May provide a list of filters.
  • Authentication: API keys are sent in the Authorization header only, never as query parameters.

License

MIT License - see the LICENSE file for details.

Support

For support, contact Quantec

Metadata

Release files for quantec 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for quantec 0.6.0
File Size Uploaded Provenance Actions
quantec-0.6.0.tar.gz 92.5 kB With provenance Details for quantec-0.6.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for quantec 0.6.0
File Interpreter ABI Platform Actions
quantec-0.6.0-py3-none-any.whl Python 3 none any Details for quantec-0.6.0-py3-none-any.whl

Total release size: 110.4 kB

Release files / quantec-0.6.0.tar.gz

Download URL quantec-0.6.0.tar.gz
Size 92.5 kB
Tags Source
SHA-256 checksum
How to use checksums
e9d28cfa17e02bb928569f9fe3c7f5dc864151e09e58a885e86c23286ddb34dc
BLAKE2b-256 checksum
How to use checksums
e8498d40f2977566093ea401cb4057a3e1cc609f1f4b09ffb9e658164d70492b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release files / quantec-0.6.0-py3-none-any.whl

Download URL quantec-0.6.0-py3-none-any.whl
Size 17.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d5f293da9238656aca40a783cb8eff99c9f560c3571d800144b7c0d227e61048
BLAKE2b-256 checksum
How to use checksums
d2ee31bc4e83d6e7dbeb5bb0931c93df4614a5a5c1e6ab59da15f3866163a4b8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Security for quantec 0.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for quantec-0.6.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page