diff --git a/.github/res/badges/code-style.svg b/.github/res/badges/code-style.svg
new file mode 100644
index 0000000..0f81b57
--- /dev/null
+++ b/.github/res/badges/code-style.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/.github/res/badges/python-version.svg b/.github/res/badges/python-version.svg
new file mode 100644
index 0000000..1e7d84c
--- /dev/null
+++ b/.github/res/badges/python-version.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/.github/workflows/lint-test.yaml b/.github/workflows/lint-test.yaml
index 9b27d5b..8d11c36 100644
--- a/.github/workflows/lint-test.yaml
+++ b/.github/workflows/lint-test.yaml
@@ -1,5 +1,5 @@
-name: lint-and-test
-run-name: Lint and Test
+name: ci-regressions
+run-name: Run CI Regressions
on:
pull_request:
branches:
@@ -9,25 +9,36 @@ on:
- main
jobs:
- lint:
- name: Build Lint and Test Code
+ regressions:
+ name: CI Regression Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- - name: Install Dependencies
- run: |
- sudo apt-get update
- sudo apt-get upgrade -y
- sudo apt-get install -y poetry
- poetry install
+ - name: Setup Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.12'
+ cache: 'pip'
- - name: Build Lint and Test
- run: |
- make ci-build-lint-test
+ - name: Grab Tox and Poetry
+ run: pip install tox poetry
+
+ - name: Lint and Test
+ run: tox
env:
- MARKETSTACK_TOKEN_TEST: "${{ secrets.MarketstackFreeTestToken }}"
+ MARKETSTACK_TOKEN_TEST: "${{ secrets.MARKETSTACK_TOKEN_TEST }}"
+
+ - name: Run a Build
+ run: poetry build
+
+ - name: Create a build-artifacts tarball
+ run: |
+ tar -czf \
+ build-artifacts.tar.gz \
+ test-artifacts-* \
+ dist
- name: Upload Artifacts
uses: actions/upload-artifact@v3
diff --git a/.gitignore b/.gitignore
index 2cccbc3..ad8c18f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -160,4 +160,5 @@ cython_debug/
#.idea/
# Build Artifact ignores
-build-artifacts
+build-artifacts*/
+build-artifacts.tar.gz
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..eba8aeb
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Marko Vejnovic
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 72e1c0a..0000000
--- a/Makefile
+++ /dev/null
@@ -1,28 +0,0 @@
-PYTHON = poetry run python
-
-# Directories or files which can be linted
-LINTABLE_NODES = aiomarketstack tests
-
-.PHONY: lint autolint test build
-
-lint:
- $(PYTHON) -m mypy aiomarketstack tests
- $(PYTHON) -m ruff check .
-
-autolint:
- $(PYTHON) -m ruff check --fix .
-
-test:
- $(PYTHON) -m pytest \
- --cov=aiomarketstack \
- --cov-report=html \
- --cov-report=term
-
-build:
- mkdir -p build-artifacts
- poetry build
- rm -rf build-artifacts/dist
- mv dist build-artifacts/dist
-
-ci-build-lint-test: build lint test
- tar -czf build-artifacts.tar.gz build-artifacts
diff --git a/README.md b/README.md
index 2df646b..4283cde 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,16 @@
# aiomarketstack
-`aiomarketstack` is a thin-and-light, Python-only `asyncio`-based
-[marketsack](https://marketstack.com/) client.
+![Python 3.8-3.11](.github/res/badges/python-version.svg)
+![Code Style](.github/res/badges/code-style.svg)
-## Getting Started
+_`aiomarketstack` is an unofficial Python-only `asyncio`-based
+[Marketstack](https://marketstack.com/) client._
+
+This library aims to be extremely observable and, therefore, comes with
+`structlog` (which I adore ❤️) support out of the box. I am also currently
+working on `OpenTelemetry` metrics as well.
+
+## Installing
To get started, grab yourself a copy of `aiomarketstack`:
@@ -11,7 +18,132 @@ To get started, grab yourself a copy of `aiomarketstack`:
pip install aiomarketstack
```
+## Getting Started
+
+The easiest way to use client is via the `HttpMarketstackClient`. The following
+example illustrates the most powerful utilities in `aiomarketstack`.
+
+```python
+from __future__ import annotations
+
+import asyncio
+
+from datetime import date
+import matplotlib.pyplot as plt
+
+from aiomarketstack import HttpMarketstackClient, MarketstackPlan
+from aiomarketstack.exceptions import ResponseError
+from aiomarketstack.types import Eod
+
+
+async def main():
+ date_range = (date(2023, 1, 1), date(2023, 1, 31))
+
+ async with HttpMarketstackClient(
+ "your-token-here",
+ MarketstackPlan.FREE
+ ) as client:
+ try:
+ eod_values: tuple[Eod, ...] = (
+ await client.get_eod_range(("AMZN", ), date_range)
+ )
+ except ResponseError as resp_err:
+ print(f"Uh-oh, a response error ocurred: {resp_err}")
+ raise
+
+ # Note that there will be missing data (if the market was closed).
+ plt_xaxis = [eod["date"] for eod in eod_values]
+ plt_yaxes = {
+ key: [eod[key] for eod in eod_values]
+ for key in {"open", "high", "low", "close"}
+ }
+
+ for label, data in plt_yaxes.items():
+ plt.plot(plt_xaxis, data, label=label.capitalize())
+ plt.legend()
+ plt.grid()
+ plt.title("End-of-day prices for AMZN for January 2023")
+ plt.xlabel("Date in January")
+ plt.ylabel("Price in $")
+ plt.show()
+
+
+asyncio.run(main())
+```
+
+Run this example to get a pretty output:
+
+![Matplotlib Example Output](docs/res/example_matplotlib_plot.png)
+
+## Documentation
+
+In progress!
+
+### Why not [marketstack-python](https://github.com/mreiche/marketstack-python)?
+
+The main reason this library exists is because I found the great
+[marketstack-python](https://github.com/mreiche/marketstack-python) to be
+unwieldy for my needs.
+
+The three main advantages of `aiomarketstack` are:
+
+* OpenTelemetry Metrics (Currently In Progress)
+* [structlog](https://www.structlog.org/en/stable/)-style Logging.
+* Much more flexible API
+
+The main disadvantage of this library, compared with
+[marketstack-python](https://github.com/mreiche/marketstack-python) is that it
+does not and **will not** cache your queries. This is not and will not be the
+goal of this library.
+
+## Development
+
+To manually build `aiomarketstack` for development, we use `poetry`. Get
+started with:
+
+```bash
+git clone https://github.com/markovejnovic/aiomarketstack.git
+cd aiomarketstack
+poetry install && poetry build
+```
+
+### Unit Tests
+
+You can test your code against test suite via `tox`.
+
+```bash
+MARKETSTACK_TOKEN_TEST= tox
+```
+
+> [!CAUTION]
+> This will consume about 30 API requests.
+
+## Contributing
+In progress! Feel free to open a issue if you spot something!
+## License
-## Dependencies
+```
+MIT License
+
+Copyright (c) 2023 Marko Vejnovic
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
diff --git a/aiomarketstack/__init__.py b/aiomarketstack/__init__.py
index a1baf15..58227be 100644
--- a/aiomarketstack/__init__.py
+++ b/aiomarketstack/__init__.py
@@ -21,7 +21,7 @@
from __future__ import annotations
import warnings
-from datetime import date, datetime
+from datetime import date, datetime, timezone
from enum import IntEnum
from typing import (
TYPE_CHECKING,
@@ -29,13 +29,15 @@
Callable,
Collection,
Literal,
- Self,
)
import aiohttp
import structlog
from aiohttp import ClientResponse, ClientSession
+if TYPE_CHECKING:
+ from typing_extensions import Self
+
from aiomarketstack.exceptions import (
ForbiddenError,
FunctionAccessRestrictedError,
@@ -306,9 +308,16 @@ def _deserialize_eod(raw_eod: RawEod) -> Eod:
"dividend": raw_eod["dividend"],
"symbol": raw_eod["symbol"],
"exchange": raw_eod["exchange"],
- "date": datetime.fromisoformat(raw_eod["date"]).date(),
+ "date": _MarketstackClient._parse_marketstack_date(raw_eod["date"]),
}
+ @staticmethod
+ def _parse_marketstack_date(date_str: str) -> date:
+ # Necessary because python < 3.11's fromisoformat didn't support IS8601.
+ marketstack_date_format = "%Y-%m-%dT%H:%M:%S%z"
+ return datetime.strptime(date_str, marketstack_date_format) \
+ .replace(tzinfo=timezone.utc).date()
+
class HttpMarketstackClient(_MarketstackClient):
"""The main marketstack client.
diff --git a/aiomarketstack/exceptions.py b/aiomarketstack/exceptions.py
index 77fadde..fa53ab2 100644
--- a/aiomarketstack/exceptions.py
+++ b/aiomarketstack/exceptions.py
@@ -1,8 +1,8 @@
"""Marketstack Error Response Errors."""
-from typing import Self
from aiohttp.client import ClientResponse
+from typing_extensions import Self
class ResponseError(Exception):
diff --git a/aiomarketstack/types.py b/aiomarketstack/types.py
index 0240681..c8157ff 100644
--- a/aiomarketstack/types.py
+++ b/aiomarketstack/types.py
@@ -2,11 +2,13 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, NotRequired, Required, Sequence, TypedDict
+from typing import TYPE_CHECKING, Sequence, TypedDict
if TYPE_CHECKING:
from datetime import date
+ from typing_extensions import NotRequired, Required
+
class Eod(TypedDict):
"""Represents an end-of-day marketstack return type."""
diff --git a/docs/examples/matplotlib_plot.py b/docs/examples/matplotlib_plot.py
new file mode 100644
index 0000000..9ac0e80
--- /dev/null
+++ b/docs/examples/matplotlib_plot.py
@@ -0,0 +1,45 @@
+from __future__ import annotations
+
+import asyncio
+
+from datetime import date
+import matplotlib.pyplot as plt
+
+from aiomarketstack import HttpMarketstackClient, MarketstackPlan
+from aiomarketstack.exceptions import ResponseError
+from aiomarketstack.types import Eod
+
+
+async def main():
+ date_range = (date(2023, 1, 1), date(2023, 1, 31))
+
+ async with HttpMarketstackClient(
+ "your-token-here",
+ MarketstackPlan.FREE
+ ) as client:
+ try:
+ eod_values: tuple[Eod, ...] = (
+ await client.get_eod_range(("AMZN", ), date_range)
+ )
+ except ResponseError as resp_err:
+ print(f"Uh-oh, a response error ocurred: {resp_err}")
+ raise
+
+ # Note that there will be missing data (if the market was closed).
+ plt_xaxis = [eod["date"] for eod in eod_values]
+ plt_yaxes = {
+ key: [eod[key] for eod in eod_values]
+ for key in {"open", "high", "low", "close"}
+ }
+
+ for label, data in plt_yaxes.items():
+ plt.plot(plt_xaxis, data, label=label)
+ plt.legend()
+ plt.grid()
+ plt.title("End-of-day prices for AMZN for January 2023")
+ plt.xlabel("Date in January")
+ plt.ylabel("Price in $")
+ plt.show()
+
+
+asyncio.run(main())
diff --git a/docs/res/example_matplotlib_plot.png b/docs/res/example_matplotlib_plot.png
new file mode 100644
index 0000000..61071b5
Binary files /dev/null and b/docs/res/example_matplotlib_plot.png differ
diff --git a/matplotlib_plot.py b/matplotlib_plot.py
new file mode 100644
index 0000000..e3b1049
--- /dev/null
+++ b/matplotlib_plot.py
@@ -0,0 +1,45 @@
+from __future__ import annotations
+
+import asyncio
+
+from datetime import date
+import matplotlib.pyplot as plt
+
+from aiomarketstack import HttpMarketstackClient, MarketstackPlan
+from aiomarketstack.exceptions import ResponseError
+from aiomarketstack.types import Eod
+
+
+async def main():
+ date_range = (date(2023, 1, 1), date(2023, 1, 31))
+
+ async with HttpMarketstackClient(
+ "3dd3d00e96ae31eddb921276a1650ba9",
+ MarketstackPlan.FREE
+ ) as client:
+ try:
+ eod_values: tuple[Eod, ...] = (
+ await client.get_eod_range(("AMZN", ), date_range)
+ )
+ except ResponseError as resp_err:
+ print(f"Uh-oh, a response error ocurred: {resp_err}")
+ raise
+
+ # Note that there will be missing data (if the market was closed).
+ plt_xaxis = [eod["date"] for eod in eod_values]
+ plt_yaxes = {
+ key: [eod[key] for eod in eod_values]
+ for key in {"open", "high", "low", "close"}
+ }
+
+ for label, data in plt_yaxes.items():
+ plt.plot(plt_xaxis, data, label=label)
+ plt.legend()
+ plt.grid()
+ plt.title("End-of-day prices for AMZN for January 2023")
+ plt.xlabel("Date in January")
+ plt.ylabel("Price in $")
+ plt.show()
+
+
+asyncio.run(main())
diff --git a/poetry.lock b/poetry.lock
index 5aee000..cc8ce4d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -466,47 +466,39 @@ files = [
[[package]]
name = "numpy"
-version = "1.26.2"
+version = "1.24.4"
description = "Fundamental package for array computing in Python"
optional = false
-python-versions = ">=3.9"
+python-versions = ">=3.8"
files = [
- {file = "numpy-1.26.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f"},
- {file = "numpy-1.26.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440"},
- {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75"},
- {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00"},
- {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe"},
- {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523"},
- {file = "numpy-1.26.2-cp310-cp310-win32.whl", hash = "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9"},
- {file = "numpy-1.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919"},
- {file = "numpy-1.26.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841"},
- {file = "numpy-1.26.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1"},
- {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a"},
- {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b"},
- {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7"},
- {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8"},
- {file = "numpy-1.26.2-cp311-cp311-win32.whl", hash = "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186"},
- {file = "numpy-1.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d"},
- {file = "numpy-1.26.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0"},
- {file = "numpy-1.26.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75"},
- {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7"},
- {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6"},
- {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6"},
- {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec"},
- {file = "numpy-1.26.2-cp312-cp312-win32.whl", hash = "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167"},
- {file = "numpy-1.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e"},
- {file = "numpy-1.26.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef"},
- {file = "numpy-1.26.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2"},
- {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3"},
- {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818"},
- {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210"},
- {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36"},
- {file = "numpy-1.26.2-cp39-cp39-win32.whl", hash = "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80"},
- {file = "numpy-1.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060"},
- {file = "numpy-1.26.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79"},
- {file = "numpy-1.26.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d"},
- {file = "numpy-1.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841"},
- {file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"},
+ {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"},
+ {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"},
+ {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"},
+ {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"},
+ {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"},
+ {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"},
+ {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"},
+ {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"},
+ {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"},
+ {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"},
+ {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"},
+ {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"},
+ {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"},
+ {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"},
+ {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"},
+ {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"},
+ {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"},
+ {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"},
+ {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"},
+ {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"},
+ {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"},
+ {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"},
+ {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"},
+ {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"},
+ {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"},
+ {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"},
+ {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"},
+ {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"},
]
[[package]]
@@ -763,5 +755,5 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
-python-versions = "^3.9"
-content-hash = "4c8c4e960bb38cd71626bccdc1807f7a5d367bb82a80a3bf0985b273265fdecf"
+python-versions = "^3.8"
+content-hash = "f5701842e4394c82e7fc96fb993b29a3a4d6c65ff378520e5fd328bc3278fdae"
diff --git a/pyproject.toml b/pyproject.toml
index 780cc40..d7de9bb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,17 +7,18 @@ readme = "README.md"
packages = [{ include = "aiomarketstack" }]
[tool.poetry.dependencies]
-aiohttp = "^3.7.3"
-python = "^3.9"
-structlog = "^23.1.0"
+aiohttp = {version = "^3.9.1", python = "^3.8"}
+python = "^3.8"
+structlog = {version = "^23.2.0", python = "^3.8"}
+typing-extensions = {version = "^4.8.0", python = "^3.8"}
[tool.poetry.group.test.dependencies]
-mypy = "^1.7.1"
-numpy = "^1.26.2"
-pytest = "^7.4.3"
-pytest-asyncio = "^0.23.1"
-pytest-cov = "^4.1.0"
-ruff = "^0.1.6"
+mypy = {version = "^1.7.1", python = "^3.8"}
+numpy = {version = "^1.24", python = "^3.8"}
+pytest = {version = "^7.4.3", python = "^3.8"}
+pytest-asyncio = {version = "^0.23.1", python = "^3.8"}
+pytest-cov = {version = "^4.1.0", python = "^3.8"}
+ruff = {version = "^0.1.6", python = "^3.8"}
[build-system]
requires = ["poetry-core"]
diff --git a/tests/__init__.py b/tests/__init__.py
index afed29a..a873eba 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,9 +1,7 @@
"""aiomarketstack's testing package."""
-import unittest
-
from . import test_aiomarketstack
-ALL_TESTS = [
- unittest.defaultTestLoader.loadTestsFromModule(test_aiomarketstack),
+__all__ = [
+ "test_aiomarketstack",
]
diff --git a/tests/__main__.py b/tests/__main__.py
deleted file mode 100644
index 2682385..0000000
--- a/tests/__main__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""Run the aiomarketstack test suite."""
-import unittest
-
-from . import ALL_TESTS
-
-unittest.TextTestRunner().run(*ALL_TESTS)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..e98bdd8
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,21 @@
+[tox]
+isolated_build = True
+envlist = py38,py39,py310,py311
+
+[testenv]
+deps =
+ mypy
+ numpy
+ pytest
+ pytest-asyncio
+ pytest-cov
+ ruff
+commands =
+ pytest \
+ --cov=aiomarketstack \
+ --cov-report=html:test-artifacts-{envname} \
+ --cov-report=term tests/
+ mypy aiomarketstack tests
+ ruff check .
+passenv =
+ MARKETSTACK_TOKEN_TEST