pyardent 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ProsD03
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyardent
3
+ Version: 1.0.0
4
+ Summary: Python client for the Ardent Insight API (Elite Dangerous trade/system data)
5
+ Author: ProsD
6
+ Author-email: ProsD <me@prosd.dev>
7
+ License-Expression: MIT
8
+ License-File: LICENSE.md
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Typing :: Typed
14
+ Requires-Dist: httpx>=0.28.1
15
+ Requires-Dist: pydantic==2.13.5
16
+ Requires-Python: >=3.12
17
+ Project-URL: Issues, https://github.com/ProsD03/pyardent/issues
18
+ Project-URL: Repository, https://github.com/ProsD03/pyardent
19
+ Description-Content-Type: text/markdown
20
+
21
+ # pyardent
22
+
23
+ [![PyPI version](https://badge.fury.io/py/pyardent.svg)](https://badge.fury.io/py/pyardent)
24
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.md)
25
+ [![Python](https://img.shields.io/badge/python-3.12%2B-blue.svg)](pyproject.toml)
26
+ [![Type Check](https://github.com/ProsD03/pyardent/actions/workflows/type_check.yml/badge.svg)](https://github.com/ProsD03/pyardent/actions/workflows/type_check.yml)
27
+ [![Tests badge](https://raw.githubusercontent.com/ProsD03/pyardent/tests-badge-data/tests-badge.svg)](https://github.com/ProsD03/pyardent/actions/workflows/tests.yml)
28
+ [![Coverage badge](https://raw.githubusercontent.com/ProsD03/pyardent/python-coverage-comment-action-data/badge.svg)](https://github.com/ProsD03/pyardent/tree/python-coverage-comment-action-data)
29
+
30
+ A Python client for the [Ardent Insight](https://ardent-insight.com) API: trade and system data for Elite Dangerous.
31
+
32
+ Built on `httpx` and `pydantic`, fully type-hinted (ships a `py.typed` marker), with zero runtime dependencies beyond those two.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install pyardent
38
+ ```
39
+
40
+ Requires Python 3.12+.
41
+
42
+ ## Quickstart
43
+
44
+ ```python
45
+ from pyardent import ArdentClient
46
+
47
+ client = ArdentClient()
48
+
49
+ gold = client.commodity.get_by_name("gold")
50
+ print(gold)
51
+ #> commodity_name='gold' rare=False rare_station_id=None rare_max_count=None min_buy_price=3800 ...
52
+
53
+
54
+ sol = client.system.get_by_name("Sol")
55
+ stations = sol.get_stations()
56
+ lincoln = next(s for s in stations if s.station_name == "Abraham Lincoln")
57
+ print(lincoln)
58
+ #> system_address=10477373803 station_id=128016896 station_name='Abraham Lincoln' ...
59
+ ```
60
+
61
+ ## Why pyardent
62
+
63
+ Models aren't flat DTOs: they carry a reference back to the client, so you can traverse straight from one resource to another without manually threading IDs through further calls:
64
+
65
+ ```python
66
+ gold = client.commodity.get_by_name("gold")
67
+
68
+ # Where can I sell gold for at least 50,000 credits, near Sol?
69
+ sol = client.system.get_by_name("Sol")
70
+ buyers = gold.get_nearby_importers(sol, min_price=50_000, max_distance=50)
71
+
72
+ for order in buyers:
73
+ station = order.get_station() # one more hop, still through the same client
74
+ print(station.station_name, order.sell_price)
75
+ ```
76
+
77
+ This graph-traversal pattern is consistent across the library: `System.get_stations()`, `Station.get_full_details()`, `Commodity.get_exporters()`, `CommodityMarket.get_station()`, and more.
78
+
79
+ ## Error handling
80
+
81
+ Failed requests raise a typed exception instead of a bare `httpx.HTTPStatusError`:
82
+
83
+ ```python
84
+ from pyardent import ArdentClient, SystemNotFoundError
85
+
86
+ client = ArdentClient()
87
+
88
+ try:
89
+ client.system.get_by_name("Not A Real System")
90
+ except SystemNotFoundError:
91
+ ...
92
+ ```
93
+
94
+ All exceptions inherit from `PyArdentError`; more specific subclasses (`CommodityNotFoundError`, `ServiceNotFoundError`, `ResourceNotFoundError`) are raised where the API's error response is specific enough to tell them apart.
95
+
96
+ ## Features
97
+
98
+ - Full pydantic models for every major resource: `System`, `Station`, `Commodity`, `CommodityMarket`
99
+ - Graph-traversal methods to move between related resources without re-fetching by hand
100
+ - A typed exception hierarchy instead of raw HTTP errors
101
+ - Custom `base_url` support, for pointing at a self-hosted or staging Ardent deployment
102
+ - Fully typed public API (`py.typed` included, so your type checker sees real types, not `Any`)
103
+
104
+ ## Development
105
+
106
+ This project uses [`uv`](https://docs.astral.sh/uv/) for dependency management.
107
+
108
+ ```bash
109
+ uv sync # install dependencies
110
+ uv run pytest # run tests
111
+ uv run pytest --cov=src/pyardent --cov-report=term-missing # run tests with coverage
112
+ uv run mypy src # type check
113
+ ```
114
+
115
+ Contributions are welcome. Please make sure the commands above are clean before opening a pull request.
116
+
117
+ ## AI Usage Disclosure
118
+
119
+ AI assistance (Claude Code) was used for parts of this project: documentation, the test suite, and general bug fixing.
120
+
121
+ The research into Ardent Insight's documented and undocumented API surface, the overall structure of the library, and most of the core implementation were written by hand.
122
+
123
+ ## License
124
+
125
+ MIT. See [LICENSE.md](LICENSE.md) for details.
@@ -0,0 +1,105 @@
1
+ # pyardent
2
+
3
+ [![PyPI version](https://badge.fury.io/py/pyardent.svg)](https://badge.fury.io/py/pyardent)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.md)
5
+ [![Python](https://img.shields.io/badge/python-3.12%2B-blue.svg)](pyproject.toml)
6
+ [![Type Check](https://github.com/ProsD03/pyardent/actions/workflows/type_check.yml/badge.svg)](https://github.com/ProsD03/pyardent/actions/workflows/type_check.yml)
7
+ [![Tests badge](https://raw.githubusercontent.com/ProsD03/pyardent/tests-badge-data/tests-badge.svg)](https://github.com/ProsD03/pyardent/actions/workflows/tests.yml)
8
+ [![Coverage badge](https://raw.githubusercontent.com/ProsD03/pyardent/python-coverage-comment-action-data/badge.svg)](https://github.com/ProsD03/pyardent/tree/python-coverage-comment-action-data)
9
+
10
+ A Python client for the [Ardent Insight](https://ardent-insight.com) API: trade and system data for Elite Dangerous.
11
+
12
+ Built on `httpx` and `pydantic`, fully type-hinted (ships a `py.typed` marker), with zero runtime dependencies beyond those two.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install pyardent
18
+ ```
19
+
20
+ Requires Python 3.12+.
21
+
22
+ ## Quickstart
23
+
24
+ ```python
25
+ from pyardent import ArdentClient
26
+
27
+ client = ArdentClient()
28
+
29
+ gold = client.commodity.get_by_name("gold")
30
+ print(gold)
31
+ #> commodity_name='gold' rare=False rare_station_id=None rare_max_count=None min_buy_price=3800 ...
32
+
33
+
34
+ sol = client.system.get_by_name("Sol")
35
+ stations = sol.get_stations()
36
+ lincoln = next(s for s in stations if s.station_name == "Abraham Lincoln")
37
+ print(lincoln)
38
+ #> system_address=10477373803 station_id=128016896 station_name='Abraham Lincoln' ...
39
+ ```
40
+
41
+ ## Why pyardent
42
+
43
+ Models aren't flat DTOs: they carry a reference back to the client, so you can traverse straight from one resource to another without manually threading IDs through further calls:
44
+
45
+ ```python
46
+ gold = client.commodity.get_by_name("gold")
47
+
48
+ # Where can I sell gold for at least 50,000 credits, near Sol?
49
+ sol = client.system.get_by_name("Sol")
50
+ buyers = gold.get_nearby_importers(sol, min_price=50_000, max_distance=50)
51
+
52
+ for order in buyers:
53
+ station = order.get_station() # one more hop, still through the same client
54
+ print(station.station_name, order.sell_price)
55
+ ```
56
+
57
+ This graph-traversal pattern is consistent across the library: `System.get_stations()`, `Station.get_full_details()`, `Commodity.get_exporters()`, `CommodityMarket.get_station()`, and more.
58
+
59
+ ## Error handling
60
+
61
+ Failed requests raise a typed exception instead of a bare `httpx.HTTPStatusError`:
62
+
63
+ ```python
64
+ from pyardent import ArdentClient, SystemNotFoundError
65
+
66
+ client = ArdentClient()
67
+
68
+ try:
69
+ client.system.get_by_name("Not A Real System")
70
+ except SystemNotFoundError:
71
+ ...
72
+ ```
73
+
74
+ All exceptions inherit from `PyArdentError`; more specific subclasses (`CommodityNotFoundError`, `ServiceNotFoundError`, `ResourceNotFoundError`) are raised where the API's error response is specific enough to tell them apart.
75
+
76
+ ## Features
77
+
78
+ - Full pydantic models for every major resource: `System`, `Station`, `Commodity`, `CommodityMarket`
79
+ - Graph-traversal methods to move between related resources without re-fetching by hand
80
+ - A typed exception hierarchy instead of raw HTTP errors
81
+ - Custom `base_url` support, for pointing at a self-hosted or staging Ardent deployment
82
+ - Fully typed public API (`py.typed` included, so your type checker sees real types, not `Any`)
83
+
84
+ ## Development
85
+
86
+ This project uses [`uv`](https://docs.astral.sh/uv/) for dependency management.
87
+
88
+ ```bash
89
+ uv sync # install dependencies
90
+ uv run pytest # run tests
91
+ uv run pytest --cov=src/pyardent --cov-report=term-missing # run tests with coverage
92
+ uv run mypy src # type check
93
+ ```
94
+
95
+ Contributions are welcome. Please make sure the commands above are clean before opening a pull request.
96
+
97
+ ## AI Usage Disclosure
98
+
99
+ AI assistance (Claude Code) was used for parts of this project: documentation, the test suite, and general bug fixing.
100
+
101
+ The research into Ardent Insight's documented and undocumented API surface, the overall structure of the library, and most of the core implementation were written by hand.
102
+
103
+ ## License
104
+
105
+ MIT. See [LICENSE.md](LICENSE.md) for details.
@@ -0,0 +1,42 @@
1
+ [project]
2
+ name = "pyardent"
3
+ version = "1.0.0"
4
+ description = "Python client for the Ardent Insight API (Elite Dangerous trade/system data)"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE.md"]
8
+ authors = [
9
+ { name = "ProsD", email = "me@prosd.dev" }
10
+ ]
11
+ requires-python = ">=3.12"
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Intended Audience :: Developers",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Typing :: Typed",
18
+ ]
19
+ dependencies = [
20
+ "httpx>=0.28.1",
21
+ "pydantic==2.13.5",
22
+ ]
23
+
24
+ [project.urls]
25
+ Repository = "https://github.com/ProsD03/pyardent"
26
+ Issues = "https://github.com/ProsD03/pyardent/issues"
27
+
28
+ [build-system]
29
+ requires = ["uv_build>=0.8.12,<0.9.0"]
30
+ build-backend = "uv_build"
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "mypy>=2.1.0",
35
+ "pytest>=9.0.3",
36
+ "pytest-asyncio>=1.4.0",
37
+ "pytest-cov>=7.1.0",
38
+ "respx>=0.23.1",
39
+ ]
40
+
41
+ [tool.coverage.run]
42
+ relative_files = true
@@ -0,0 +1,10 @@
1
+ import logging
2
+
3
+ from .client import ArdentClient
4
+ from .exceptions import PyArdentError, ResourceNotFoundError, CommodityNotFoundError, SystemNotFoundError, ServiceNotFoundError
5
+ from .types import StationServices, LandingPad
6
+ from .models import System, Station, Commodity, CommodityMarket
7
+
8
+ logging.getLogger("pyardent").addHandler(logging.NullHandler())
9
+
10
+ __all__ = ['ArdentClient', 'ResourceNotFoundError', 'CommodityNotFoundError', 'SystemNotFoundError', 'ServiceNotFoundError', 'PyArdentError', "StationServices", "LandingPad", "System", "Station", "Commodity", "CommodityMarket"]
@@ -0,0 +1,95 @@
1
+ """Top-level client for the Ardent Insight API.
2
+
3
+ Wires up an `httpx.Client` shared by all `*Module` instances and installs the
4
+ response hook that translates HTTP error responses into `PyArdentError`
5
+ subclasses.
6
+ """
7
+
8
+ import logging
9
+
10
+ import httpx
11
+ from .modules import MetaModule, CommodityModule, SystemModule
12
+ from .exceptions import PyArdentError, ResourceNotFoundError, CommodityNotFoundError, SystemNotFoundError, \
13
+ ServiceNotFoundError
14
+ from .modules.station import StationModule
15
+
16
+ logger = logging.getLogger("pyardent.client")
17
+
18
+
19
+ def _handle_response_errors(response: httpx.Response):
20
+ """Translate failed HTTP responses into `PyArdentError` subclasses.
21
+
22
+ Registered as an `httpx.Client` "response" event hook, so it runs on
23
+ every request made through `ArdentClient`. Successful responses pass
24
+ through unchanged.
25
+
26
+ Args:
27
+ response: The `httpx.Response` returned by the underlying request.
28
+
29
+ Raises:
30
+ CommodityNotFoundError: On a 404 whose API error message mentions a commodity.
31
+ SystemNotFoundError: On a 404 whose API error message mentions a system.
32
+ ServiceNotFoundError: On a 404 whose API error message mentions a service.
33
+ ResourceNotFoundError: On a 404 with no recognizable error message.
34
+ PyArdentError: On any other HTTP error status, a 404 with an
35
+ unrecognized message, or a network-level error.
36
+ """
37
+ response.read()
38
+ try:
39
+ response.raise_for_status()
40
+ except httpx.HTTPStatusError as e:
41
+
42
+ logger.error(f"HTTP error {e.response.status_code} for URL: {response.url}")
43
+
44
+ if e.response.status_code == 404:
45
+ error_payload = e.response.json()
46
+ api_msg = error_payload.get("message")
47
+ if api_msg:
48
+ if "commodity" in api_msg.lower():
49
+ raise CommodityNotFoundError(str(response.url)) from e
50
+ elif "system" in api_msg.lower():
51
+ raise SystemNotFoundError(str(response.url)) from e
52
+ elif "service" in api_msg.lower():
53
+ raise ServiceNotFoundError(str(response.url)) from e
54
+ else:
55
+ raise PyArdentError(api_msg) from e
56
+ raise ResourceNotFoundError(str(response.url)) from e
57
+ else:
58
+ raise PyArdentError(f"HTTP error {e.response.status_code} for URL: {response.url}") from e
59
+
60
+
61
+ class ArdentClient:
62
+ """Entry point for the Ardent Insight API client.
63
+
64
+ Holds the shared `httpx.Client` and exposes one module per API resource
65
+ area (`meta`, `commodity`, `system`, `station`). Methods on those modules
66
+ return rich pydantic models (e.g. `System`, `Station`, `Commodity`,
67
+ `CommodityMarket`) that carry a reference back to this client so they can
68
+ make further API calls themselves (e.g. `system.get_stations()`).
69
+ """
70
+
71
+ DEFAULT_BASE_URL = "https://api.ardent-insight.com/v2"
72
+ _client: httpx.Client
73
+
74
+ meta: MetaModule
75
+ commodity: CommodityModule
76
+ system: SystemModule
77
+ station: StationModule
78
+
79
+ def __init__(self, base_url: str | None = None):
80
+ """Create a client and its underlying HTTP session.
81
+
82
+ Args:
83
+ base_url: Override for the API base URL. Defaults to
84
+ `ArdentClient.DEFAULT_BASE_URL` when omitted.
85
+ """
86
+ self._base_url = base_url or self.DEFAULT_BASE_URL
87
+
88
+ self._client = httpx.Client(base_url=self._base_url, event_hooks={"response": [_handle_response_errors, ]})
89
+
90
+ self.meta = MetaModule(self._client)
91
+ self.commodity = CommodityModule(self._client)
92
+ self.system = SystemModule(self._client)
93
+ self.station = StationModule(self._client)
94
+
95
+ logger.info(f"Initialized ArdentClient pointing to {self._base_url}")
@@ -0,0 +1,56 @@
1
+ """Exception hierarchy raised by pyardent.
2
+
3
+ All errors raised by the client are `PyArdentError` or one of its subclasses
4
+ below, translated from HTTP responses by `client._handle_response_errors`.
5
+ """
6
+
7
+
8
+ class PyArdentError(Exception):
9
+ """Base class for all errors raised by this library.
10
+
11
+ Args:
12
+ message: Human-readable description of the failure.
13
+ """
14
+
15
+ def __init__(self, message: str):
16
+ super().__init__(message)
17
+
18
+ class ResourceNotFoundError(PyArdentError):
19
+ """Raised on a 404 whose API error message didn't match a more specific case.
20
+
21
+ Args:
22
+ url: The request URL that returned the 404.
23
+ """
24
+
25
+ def __init__(self, url: str):
26
+ super().__init__(f"Resource not found at endpoint: {url}")
27
+
28
+ class CommodityNotFoundError(PyArdentError):
29
+ """Raised when the requested commodity does not exist.
30
+
31
+ Args:
32
+ url: The request URL that returned the 404.
33
+ """
34
+
35
+ def __init__(self, url: str):
36
+ super().__init__(f"Commodity does not exist. Endpoint: {url}")
37
+
38
+ class SystemNotFoundError(PyArdentError):
39
+ """Raised when the requested system does not exist.
40
+
41
+ Args:
42
+ url: The request URL that returned the 404.
43
+ """
44
+
45
+ def __init__(self, url: str):
46
+ super().__init__(f"System does not exist. Endpoint: {url}")
47
+
48
+ class ServiceNotFoundError(PyArdentError):
49
+ """Raised when the requested station service type is unknown to the API.
50
+
51
+ Args:
52
+ url: The request URL that returned the 404.
53
+ """
54
+
55
+ def __init__(self, url: str):
56
+ super().__init__(f"Service does not exist. Endpoint: {url}")
@@ -0,0 +1,7 @@
1
+ from .meta import APIVersion, APIStats, APIEconomies, APIStations
2
+ from .commodity import Commodity
3
+ from .system import System
4
+ from .station import Station
5
+ from .market import CommodityMarket
6
+
7
+ __all__ = ["APIVersion", "APIStats", "APIEconomies", "APIStations", "Commodity", "System", "Station", "CommodityMarket"]