diff --git a/.gitignore b/.gitignore index 68bc17f..52044fd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # C extensions *.so +test.py +.cache.sqlite + # Distribution / packaging .Python build/ diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..235633d --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,20 @@ +### Development + +Install dependencies + +```bash +pip3 install . +pip3 install ".[test]" +pip3 install pytest-xdist +pre-commit install +``` + +Run linter and tests +```bash +black . +flake8 +bandit -r openmeteo_requests/ +pylint openmeteo_requests/ +python3 -m pytest tests/ +pre-commit run --all-files +``` diff --git a/README.md b/README.md index f58dd2d..8268341 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,87 @@ -# python-requests -Open-Meteo Python Library using `requests` +# Open-Meteo Python API Client +An API client to get weather data from the Open-Meteo Weather API based on the Python library `requests`. +Instead of using JSON, the API client uses FlatBuffers to transfer data. Encoding data in FlatBuffers is more efficient for long time-series data. Data can be transferred to `numpy` or `pandas` using [Zero-Copy](https://en.wikipedia.org/wiki/Zero-copy) to analyze large amount of data quickly. +TODO: +- Document data structure +- Consider dedicated pandas library -### Development +## Basic Usage -```bash -pip3 install . -pip3 install ".[test]" -pip3 install pytest-xdist -pre-commit install +```python +# pip install openmeteo-requests -black . -flake8 -bandit -r openmeteo_requests/ -pylint openmeteo_requests/ -python3 -m pytest tests/ -pre-commit run --all-files +import openmeteo_requests + +om = openmeteo_requests.Client() +params = { + "latitude": 52.54, + "longitude": 13.41, + "hourly": ["temperature_2m", "precipitation"], + "current": ["temperature_2m"] +} + +results = om.weather_api("https://api.open-meteo.com/v1/forecast", params=params) +result = results[0] + +print(f"Coordinates {result.Latitude()}°E {result.Longitude()}°N {result.Elevation()} m asl") +print(f"Timezone {result.Timezone()} {result.TimezoneAbbreviation()} Offset={result.UtcOffsetSeconds()}s") + +print(f"Current temperature is {result.Current().Temperature2m().Value() °C}") + +# Accessing hourly forecasts as numpy arrays +hourly = result.Hourly() +temperature_2m = hourly.Temperature2m().ValuesAsNumpy() +precipitation = hourly.Temperature2m().ValuesAsNumpy() + +# Usage with Pandas Dataframes +import pandas as pd +date = pd.date_range( + start=pd.to_datetime(hourly.Time().Start(), unit="s"), + end=pd.to_datetime(hourly.Time().End(), unit="s"), + freq=pd.Timedelta(seconds=hourly.Time().Interval()), + inclusive="left" +) +df = pd.DataFrame( + data={ + "date": date, + "temperature_2m": hourly.Temperature2m().ValuesAsNumpy(), + "precipitation": hourly.Precipitation().ValuesAsNumpy() + } +) +print(df) +#date temperature_2m precipitation +#0 2023-08-01 00:00:00 16.945999 1.7 +#1 2023-08-01 01:00:00 16.996000 2.1 +#2 2023-08-01 02:00:00 16.996000 1.0 +#3 2023-08-01 03:00:00 16.846001 0.2 +``` + +## Caching Data + +If you are working with large amounts of data, caching data can make it easier to develop. You can pass a cached session from the library `requests-cache` to the Open-Meteo API client. + +The following example stores all data indefinitely (`expire_after=-1`) in a SQLite database called `.cache.sqlite`. For more options read the [requests-cache documentation](https://pypi.org/project/requests-cache/). + +Additionally, `retry-requests` to automatically retry failed API calls in case there has been any unexpected network or server error. + +```python +# pip install openmeteo-requests +# pip install requests-cache retry-requests + +import openmeteo_requests +import requests_cache +from retry_requests import retry + +# Setup the Open-Meteo API client with a cache and retry mechanism +cache_session = requests_cache.CachedSession('.cache', expire_after=-1) +retry_session = retry(cache_session, retries=5, backoff_factor=0.2) +om = openmeteo_requests.Client(session=retry_session) + +# Using the client object `om` will now cache all weather data ``` + +# License +MIT diff --git a/openmeteo_requests/__init__.py b/openmeteo_requests/__init__.py index e69de29..ded2887 100644 --- a/openmeteo_requests/__init__.py +++ b/openmeteo_requests/__init__.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from openmeteo_requests.Client import Client + +__all__ = ['Client'] diff --git a/tests/test_methods.py b/tests/test_methods.py index e9c068e..2b5b18e 100644 --- a/tests/test_methods.py +++ b/tests/test_methods.py @@ -3,11 +3,11 @@ import pytest -from openmeteo_requests.Client import Client +import openmeteo_requests def test_fetch_all(): - om = Client() + om = openmeteo_requests.Client() params = { "latitude": [52.54, 48.1, 48.4], "longitude": [13.41, 9.31, 8.5], @@ -21,15 +21,17 @@ def test_fetch_all(): results = om.weather_api("https://archive-api.open-meteo.com/v1/archive", params=params) assert len(results) == 3 - res = results[0] - assert res.Latitude() == pytest.approx(52.5) - assert res.Longitude() == pytest.approx(13.4) - res = results[1] - assert res.Latitude() == pytest.approx(48.1) - assert res.Longitude() == pytest.approx(9.3) - print("Coordinates ", res.Latitude(), res.Longitude(), res.Elevation()) - print(res.Timezone(), res.TimezoneAbbreviation()) - print("Generation time", res.GenerationtimeMs()) + result = results[0] + assert result.Latitude() == pytest.approx(52.5) + assert result.Longitude() == pytest.approx(13.4) + result = results[1] + assert result.Latitude() == pytest.approx(48.1) + assert result.Longitude() == pytest.approx(9.3) + result = results[0] + + print(f"Coordinates {result.Latitude()}°E {result.Longitude()}°N {result.Elevation()} m asl") + print(f"Timezone {result.Timezone()} {result.TimezoneAbbreviation()} {result.UtcOffsetSeconds()}") + print(f"Generation time {result.GenerationtimeMs()} ms") def test_int_client():