pyardent 1.0.0__py3-none-any.whl

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.
pyardent/__init__.py ADDED
@@ -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"]
pyardent/client.py ADDED
@@ -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}")
pyardent/exceptions.py ADDED
@@ -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"]
@@ -0,0 +1,320 @@
1
+ """Models for the API's commodity resource (`/commodity/...`).
2
+
3
+ `Commodity` is a graph-traversal node, not a flat DTO: every `get_*` method
4
+ makes a further API call rooted at `self.commodity_name` and returns other
5
+ rich models (`Station`, `CommodityMarket`).
6
+ """
7
+
8
+ import logging
9
+ from datetime import datetime
10
+ from typing import TYPE_CHECKING
11
+
12
+ import httpx
13
+ from pydantic import BaseModel, ConfigDict, PrivateAttr, Field
14
+ from pydantic.alias_generators import to_camel
15
+
16
+ if TYPE_CHECKING:
17
+ from .station import Station
18
+ from .market import CommodityMarket
19
+ from .system import System
20
+
21
+ logger = logging.getLogger("pyardent.models.commodity")
22
+
23
+ class CommodityData(BaseModel):
24
+ """Plain fields of a commodity summary report, as returned by the API.
25
+
26
+ The `min`/`max`/`avg`/`total` price and stock/demand fields are
27
+ aggregates across every known market, excluding fleet carriers. Split
28
+ from `Commodity` so the rich subclass can carry a `_client` reference
29
+ without it leaking into the field-validation schema.
30
+ """
31
+
32
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
33
+
34
+ commodity_name: str
35
+ rare: bool = False
36
+ rare_station_id: int | None = Field(alias="rareMarketId", default=None)
37
+ rare_max_count: int | None = None
38
+ """For rare commodities, the cargo quantity threshold beyond which
39
+ selling more at once starts degrading the price."""
40
+ min_buy_price: int | None = None
41
+ max_buy_price: int | None = None
42
+ avg_buy_price: int | None = None
43
+ total_stock: int | None = None
44
+ min_sell_price: int | None = None
45
+ max_sell_price: int | None = None
46
+ avg_sell_price: int | None = None
47
+ total_demand: int | None = None
48
+ timestamp: datetime
49
+
50
+ class Commodity(CommodityData):
51
+ """A tradeable commodity, with methods to find where it's bought, sold, or made."""
52
+
53
+ _client: httpx.Client = PrivateAttr()
54
+
55
+ @classmethod
56
+ def from_json(cls, client: httpx.Client, payload: dict) -> "Commodity":
57
+ """Construct a `Commodity` from a JSON payload and attach the client.
58
+
59
+ This is the only supported way to build a `Commodity`: constructing
60
+ one via `model_validate` directly would leave `_client` unset,
61
+ breaking every `get_*` method on the instance.
62
+
63
+ Args:
64
+ client: The shared `httpx.Client` to attach for further requests.
65
+ payload: A single commodity object as returned by the API.
66
+
67
+ Returns:
68
+ The constructed, traversable `Commodity`.
69
+ """
70
+ instance = cls.model_validate(payload)
71
+ instance._client = client
72
+ return instance
73
+
74
+ def get_rare_station(self) -> "Station | None":
75
+ """Fetch the station where this rare commodity is sold, if it is one.
76
+
77
+ Returns:
78
+ The rare commodity's home station, or None if this commodity
79
+ isn't rare (or has no recorded home station).
80
+ """
81
+ if not self.rare or self.rare_station_id is None:
82
+ return None
83
+
84
+ from .station import Station
85
+ logger.debug(f"GET /market/{self.rare_station_id}")
86
+ response = self._client.get(f"/market/{self.rare_station_id}")
87
+ station = response.json()
88
+ return Station.from_json(self._client, station)
89
+
90
+ def get_importers(self, min_volume: int = 1, min_price: int = 1, fleet_carriers: bool | None = None, max_days_ago: int = 30) -> list["CommodityMarket"]:
91
+ """Fetch places importing this commodity — places you can sell to.
92
+
93
+ Args:
94
+ min_volume: Minimum demand to include. Demand of 0 means
95
+ infinite demand and is always included.
96
+ min_price: Minimum sell price to include.
97
+ fleet_carriers: If True, only fleet carriers; if False, exclude
98
+ them; if None (default), include all station types.
99
+ max_days_ago: Exclude trade data older than this many days.
100
+
101
+ Returns:
102
+ Up to 100 matching import orders, ordered by highest price
103
+ (the best place to sell first).
104
+
105
+ Raises:
106
+ ValueError: If `min_volume`, `min_price`, or `max_days_ago` is negative.
107
+ """
108
+ if min_volume < 0:
109
+ raise ValueError(f"min_volume cannot be negative. received: {min_volume}")
110
+ if min_price < 0:
111
+ raise ValueError(f"min_price cannot be negative. received: {min_price}")
112
+ if max_days_ago < 0:
113
+ raise ValueError(f"max_days_ago cannot be negative. received: {max_days_ago}")
114
+
115
+ from .market import CommodityMarket
116
+ logger.debug(f"GET /commodity/name/{self.commodity_name}/imports")
117
+
118
+ response = self._client.get(f"/commodity/name/{self.commodity_name}/imports", params={
119
+ "minVolume": min_volume,
120
+ "minPrice": min_price,
121
+ "fleetCarriers": fleet_carriers,
122
+ "maxDaysAgo": max_days_ago,
123
+ })
124
+ importers = response.json()
125
+ logger.debug(f"Parsing {len(importers)} importers")
126
+ return [CommodityMarket.from_json(self._client, importer) for importer in importers]
127
+
128
+ def get_exporters(self, min_volume: int = 1, max_price: int | None = None, fleet_carriers: bool | None = None, max_days_ago: int = 30) -> list["CommodityMarket"]:
129
+ """Fetch places exporting this commodity — places you can buy from.
130
+
131
+ Args:
132
+ min_volume: Minimum stock to include.
133
+ max_price: If given, exclude entries with a buy price above this.
134
+ fleet_carriers: If True, only fleet carriers; if False, exclude
135
+ them; if None (default), include all station types.
136
+ max_days_ago: Exclude trade data older than this many days.
137
+
138
+ Returns:
139
+ Up to 100 matching export orders, ordered by lowest price
140
+ (the best place to buy first).
141
+
142
+ Raises:
143
+ ValueError: If `min_volume` or `max_price` is negative, or if
144
+ `max_days_ago` is negative.
145
+ """
146
+ if min_volume < 0:
147
+ raise ValueError(f"min_volume cannot be negative. received: {min_volume}")
148
+ if max_price and max_price < 0:
149
+ raise ValueError(f"max_price cannot be negative. received: {max_price}")
150
+ if max_days_ago < 0:
151
+ raise ValueError(f"max_days_ago cannot be negative. received: {max_days_ago}")
152
+
153
+ from .market import CommodityMarket
154
+ logger.debug(f"GET /commodity/name/{self.commodity_name}/exports")
155
+ if max_price is None:
156
+ params = {
157
+ "minVolume": min_volume,
158
+ "fleetCarriers": fleet_carriers,
159
+ "maxDaysAgo": max_days_ago,
160
+ }
161
+ else:
162
+ params = {
163
+ "minVolume": min_volume,
164
+ "maxPrice": max_price,
165
+ "fleetCarriers": fleet_carriers,
166
+ "maxDaysAgo": max_days_ago,
167
+ }
168
+ response = self._client.get(f"/commodity/name/{self.commodity_name}/exports", params=params)
169
+ exporters = response.json()
170
+ logger.debug(f"Parsing {len(exporters)} exporters")
171
+ return [CommodityMarket.from_json(self._client, exporter) for exporter in exporters]
172
+
173
+ def get_system_market(self, system: "System", max_days_ago: int = 30) -> list["CommodityMarket"]:
174
+ """Fetch all buy/sell orders for this commodity across stations in a system.
175
+
176
+ Args:
177
+ system: The system to look up.
178
+ max_days_ago: Exclude trade data older than this many days.
179
+
180
+ Returns:
181
+ One `CommodityMarket` entry per station in `system` trading this
182
+ commodity (empty if none do).
183
+
184
+ Raises:
185
+ ValueError: If `max_days_ago` is negative.
186
+ """
187
+ if max_days_ago < 0:
188
+ raise ValueError(f"max_days_ago cannot be negative. received: {max_days_ago}")
189
+
190
+ from .market import CommodityMarket
191
+ logger.debug(f"GET /system/address/{system.system_address}/commodity/name/{self.commodity_name}")
192
+ response = self._client.get(f"/system/address/{system.system_address}/commodity/name/{self.commodity_name}", params={
193
+ "maxDaysAgo": max_days_ago,
194
+ })
195
+ commodities = response.json()
196
+ logger.debug(f"Parsing {len(commodities)} commodities")
197
+ return [
198
+ CommodityMarket.from_json(self._client, entry) for entry in commodities
199
+ ]
200
+
201
+ def get_nearby_importers(self, system: "System", min_volume: int = 1, min_price: int = 1, fleet_carriers: bool | None = None, max_distance: int = 100, max_days_ago: int = 30) -> list["CommodityMarket"]:
202
+ """Fetch nearby places importing this commodity — places you can sell to near a system.
203
+
204
+ Args:
205
+ system: The system to search around.
206
+ min_volume: Minimum demand to include. Demand of 0 means
207
+ infinite demand and is always included.
208
+ min_price: Minimum sell price to include.
209
+ fleet_carriers: If True, only fleet carriers; if False, exclude
210
+ them; if None (default), include all station types.
211
+ max_distance: Search radius in light-years from `system`. Must
212
+ be between 0 and 500 (the API's own cap).
213
+ max_days_ago: Exclude trade data older than this many days.
214
+
215
+ Returns:
216
+ Up to 1000 matching import orders, ordered by highest price
217
+ (the best place to sell first).
218
+
219
+ Raises:
220
+ ValueError: If `min_volume` or `min_price` is negative, if
221
+ `max_distance` is outside `[0, 500]`, or if `max_days_ago`
222
+ is negative.
223
+ """
224
+ if min_volume < 0:
225
+ raise ValueError(f"min_volume cannot be negative. received: {min_volume}")
226
+ if min_price < 0:
227
+ raise ValueError(f"min_price cannot be negative. received: {min_price}")
228
+ if max_distance < 0 or max_distance > 500:
229
+ raise ValueError(f"max_distance cannot be negative or greater than 500. received: {max_distance}")
230
+ if max_days_ago < 0:
231
+ raise ValueError(f"max_days_ago cannot be negative. received: {max_days_ago}")
232
+
233
+ from .market import CommodityMarket
234
+ logger.debug(f"GET /system/address/{system.system_address}/commodity/name/{self.commodity_name}/nearby/imports")
235
+ response = self._client.get(f"/system/address/{system.system_address}/commodity/name/{self.commodity_name}/nearby/imports",
236
+ params={
237
+ "minVolume": min_volume,
238
+ "minPrice": min_price,
239
+ "fleetCarriers": fleet_carriers,
240
+ "maxDistance": max_distance,
241
+ "maxDaysAgo": max_days_ago,
242
+ })
243
+ commodities = response.json()
244
+ logger.debug(f"Parsing {len(commodities)} commodities")
245
+ return [
246
+ CommodityMarket.from_json(self._client, entry) for entry in commodities
247
+ ]
248
+
249
+ def get_nearby_exporters(self, system: "System", min_volume: int = 1, max_price: int | None = None,
250
+ fleet_carriers: bool | None = None, max_distance: int = 100, max_days_ago: int = 30) -> \
251
+ list["CommodityMarket"]:
252
+ """Fetch nearby places exporting this commodity — places you can buy from near a system.
253
+
254
+ Args:
255
+ system: The system to search around.
256
+ min_volume: Minimum stock to include.
257
+ max_price: If given, exclude entries with a buy price above this.
258
+ fleet_carriers: If True, only fleet carriers; if False, exclude
259
+ them; if None (default), include all station types.
260
+ max_distance: Search radius in light-years from `system`. Must
261
+ be between 0 and 500 (the API's own cap).
262
+ max_days_ago: Exclude trade data older than this many days.
263
+
264
+ Returns:
265
+ Up to 1000 matching export orders, ordered by lowest price
266
+ (the best place to buy first).
267
+
268
+ Raises:
269
+ ValueError: If `min_volume` or `max_price` is negative, if
270
+ `max_distance` is outside `[0, 500]`, or if `max_days_ago`
271
+ is negative.
272
+ """
273
+ if min_volume < 0:
274
+ raise ValueError(f"min_volume cannot be negative. received: {min_volume}")
275
+ if max_price and max_price < 0:
276
+ raise ValueError(f"max_price cannot be negative. received: {max_price}")
277
+ if max_distance < 0 or max_distance > 500:
278
+ raise ValueError(f"max_distance cannot be negative or greater than 500. received: {max_distance}")
279
+ if max_days_ago < 0:
280
+ raise ValueError(f"max_days_ago cannot be negative. received: {max_days_ago}")
281
+
282
+ from .market import CommodityMarket
283
+ logger.debug(f"GET /system/address/{system.system_address}/commodity/name/{self.commodity_name}/nearby/exports")
284
+ if max_price is None:
285
+ params = {
286
+ "minVolume": min_volume,
287
+ "fleetCarriers": fleet_carriers,
288
+ "maxDaysAgo": max_days_ago,
289
+ }
290
+ else:
291
+ params = {
292
+ "minVolume": min_volume,
293
+ "maxPrice": max_price,
294
+ "fleetCarriers": fleet_carriers,
295
+ "maxDaysAgo": max_days_ago,
296
+ }
297
+
298
+ response = self._client.get(
299
+ f"/system/address/{system.system_address}/commodity/name/{self.commodity_name}/nearby/exports",
300
+ params=params)
301
+ commodities = response.json()
302
+ logger.debug(f"Parsing {len(commodities)} commodities")
303
+ return [
304
+ CommodityMarket.from_json(self._client, entry) for entry in commodities
305
+ ]
306
+
307
+ def get_station_market(self, station: "Station") -> "CommodityMarket":
308
+ """Fetch the buy/sell order for this commodity at one specific station.
309
+
310
+ Args:
311
+ station: The station to look up.
312
+
313
+ Returns:
314
+ The matching `CommodityMarket` entry.
315
+ """
316
+ from .market import CommodityMarket
317
+ logger.debug(f"GET /market/{station.station_id}/commodity/name/{self.commodity_name}")
318
+ response = self._client.get(f"/market/{station.station_id}/commodity/name/{self.commodity_name}")
319
+ commodity = response.json()
320
+ return CommodityMarket.from_json(self._client, commodity)
@@ -0,0 +1,82 @@
1
+ """Model for a single commodity buy/sell order at a market.
2
+
3
+ `CommodityMarket` is the common return type for nearly every trade-related
4
+ endpoint in this library (`System`/`Commodity`/`Station` import and export
5
+ lookups) since the API returns this same shape across all of them.
6
+ """
7
+
8
+ import logging
9
+ from datetime import datetime
10
+ from typing import TYPE_CHECKING
11
+
12
+ import httpx
13
+ from pydantic import BaseModel, PrivateAttr, ConfigDict, Field
14
+ from pydantic.alias_generators import to_camel
15
+
16
+ if TYPE_CHECKING:
17
+ from .station import Station
18
+
19
+ logger = logging.getLogger("pyardent.models.market")
20
+
21
+ class CommodityMarketData(BaseModel):
22
+ """Plain fields of a single commodity order at a market, as returned by the API.
23
+
24
+ `buy_price` is what you pay to buy from this station (i.e. it's
25
+ exporting); `sell_price` is what this station pays you to sell to it
26
+ (i.e. it's importing). `demand_bracket`/`stock_bracket` are the game's
27
+ coarse low/medium/high indicators (0-3); the API occasionally returns an
28
+ empty string instead of an int for these, hence the `int | str` type.
29
+
30
+ Split from `CommodityMarket` so the rich subclass can carry a `_client`
31
+ reference without it leaking into the field-validation schema.
32
+ """
33
+
34
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
35
+
36
+ commodity_name: str | None = None
37
+ buy_price: int | None = None
38
+ demand: int | None = None
39
+ demand_bracket: int | str | None = None
40
+ mean_price: int | None = None
41
+ sell_price: int | None = None
42
+ stock: int | None = None
43
+ stock_bracket: int | str | None = None
44
+ updated_at: datetime | None = None
45
+
46
+ station_id: int | None = Field(alias="marketId", default=None)
47
+
48
+ class CommodityMarket(CommodityMarketData):
49
+ """A single commodity's buy/sell order at one market, with a method to traverse to its station."""
50
+
51
+ _client: httpx.Client = PrivateAttr()
52
+
53
+ @classmethod
54
+ def from_json(cls, client: httpx.Client, payload: dict) -> "CommodityMarket":
55
+ """Construct a `CommodityMarket` from a JSON payload and attach the client.
56
+
57
+ This is the only supported way to build a `CommodityMarket`:
58
+ constructing one via `model_validate` directly would leave `_client`
59
+ unset, breaking `get_station`.
60
+
61
+ Args:
62
+ client: The shared `httpx.Client` to attach for further requests.
63
+ payload: A single commodity-order object as returned by the API.
64
+
65
+ Returns:
66
+ The constructed, traversable `CommodityMarket`.
67
+ """
68
+ instance = cls.model_validate(payload)
69
+ instance._client = client
70
+ return instance
71
+
72
+ def get_station(self) -> "Station":
73
+ """Fetch the station this order belongs to.
74
+
75
+ Returns:
76
+ The `Station` identified by `station_id`.
77
+ """
78
+ from .station import Station
79
+ logger.debug(f"GET /market/{self.station_id}")
80
+ response = self._client.get(f"/market/{self.station_id}")
81
+ station = response.json()
82
+ return Station.from_json(self._client, station)