python-eveonline 0.2.0__tar.gz → 0.2.2__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.
Files changed (27) hide show
  1. python_eveonline-0.2.2/PKG-INFO +144 -0
  2. python_eveonline-0.2.2/README.md +112 -0
  3. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/pyproject.toml +1 -1
  4. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/src/eveonline/__init__.py +10 -1
  5. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/src/eveonline/client.py +23 -9
  6. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/src/eveonline/const.py +4 -2
  7. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/src/eveonline/exceptions.py +14 -2
  8. python_eveonline-0.2.2/src/eveonline/models.py +305 -0
  9. python_eveonline-0.2.2/src/python_eveonline.egg-info/PKG-INFO +144 -0
  10. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/tests/test_client_authenticated.py +16 -1
  11. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/tests/test_client_public.py +66 -0
  12. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/tests/test_const.py +3 -3
  13. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/tests/test_exceptions.py +10 -0
  14. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/tests/test_models.py +37 -2
  15. python_eveonline-0.2.0/PKG-INFO +0 -71
  16. python_eveonline-0.2.0/README.md +0 -39
  17. python_eveonline-0.2.0/src/eveonline/models.py +0 -178
  18. python_eveonline-0.2.0/src/python_eveonline.egg-info/PKG-INFO +0 -71
  19. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/LICENSE +0 -0
  20. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/setup.cfg +0 -0
  21. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/src/eveonline/auth.py +0 -0
  22. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/src/eveonline/py.typed +0 -0
  23. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/src/python_eveonline.egg-info/SOURCES.txt +0 -0
  24. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/src/python_eveonline.egg-info/dependency_links.txt +0 -0
  25. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/src/python_eveonline.egg-info/requires.txt +0 -0
  26. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/src/python_eveonline.egg-info/top_level.txt +0 -0
  27. {python_eveonline-0.2.0 → python_eveonline-0.2.2}/tests/test_auth.py +0 -0
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-eveonline
3
+ Version: 0.2.2
4
+ Summary: Async Python client for the Eve Online ESI API
5
+ Author: Ronald van der Meer
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ronaldvdmeer/python-eveonline
8
+ Project-URL: Repository, https://github.com/ronaldvdmeer/python-eveonline
9
+ Project-URL: Issues, https://github.com/ronaldvdmeer/python-eveonline/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Games/Entertainment
17
+ Classifier: Typing :: Typed
18
+ Classifier: Framework :: AsyncIO
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: aiohttp>=3.9.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.0; extra == "dev"
25
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
26
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
27
+ Requires-Dist: aioresponses>=0.7; extra == "dev"
28
+ Requires-Dist: mypy>=1.8; extra == "dev"
29
+ Requires-Dist: pylint>=3.0; extra == "dev"
30
+ Requires-Dist: ruff>=0.3; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # python-eveonline
34
+
35
+ [![PyPI](https://img.shields.io/pypi/v/python-eveonline.svg)](https://pypi.org/project/python-eveonline/)
36
+ [![Python](https://img.shields.io/pypi/pyversions/python-eveonline.svg)](https://pypi.org/project/python-eveonline/)
37
+ [![License](https://img.shields.io/github/license/ronaldvdmeer/python-eveonline.svg)](https://github.com/ronaldvdmeer/python-eveonline/blob/main/LICENSE)
38
+ [![GitHub Release](https://img.shields.io/github/v/release/ronaldvdmeer/python-eveonline.svg)](https://github.com/ronaldvdmeer/python-eveonline/releases)
39
+
40
+ Async Python client library for the [Eve Online ESI API](https://esi.evetech.net/ui/).
41
+
42
+ Built for use with [Home Assistant](https://www.home-assistant.io/) but can be used standalone in any async Python project.
43
+
44
+ ## Features
45
+
46
+ - **Fully async** — built on [aiohttp](https://docs.aiohttp.org/)
47
+ - **Typed models** — all API responses are frozen dataclasses
48
+ - **Public endpoints** — server status, character info, corporation info, portraits, universe name resolution
49
+ - **Authenticated endpoints** — online status, location, ship, wallet, skills, skill queue, mail, industry jobs, market orders, jump fatigue
50
+ - **Abstract auth** — implement `AbstractAuth` to provide your own token management (e.g. Home Assistant OAuth2)
51
+ - **Type-safe** — PEP 561 compatible (`py.typed`), strict mypy configuration
52
+ - **Tested** — 100% test coverage
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ pip install python-eveonline
58
+ ```
59
+
60
+ ## Quick start
61
+
62
+ ### Public endpoints (no authentication required)
63
+
64
+ ```python
65
+ import asyncio
66
+ import aiohttp
67
+ from eveonline import EveOnlineClient
68
+
69
+ async def main():
70
+ async with aiohttp.ClientSession() as session:
71
+ client = EveOnlineClient(session=session)
72
+ status = await client.async_get_server_status()
73
+ print(f"{status.players} players online (version {status.server_version})")
74
+
75
+ asyncio.run(main())
76
+ ```
77
+
78
+ ### Authenticated endpoints
79
+
80
+ To access character-specific data, implement the `AbstractAuth` class with your own token management:
81
+
82
+ ```python
83
+ from eveonline import EveOnlineClient
84
+ from eveonline.auth import AbstractAuth
85
+
86
+ class MyAuth(AbstractAuth):
87
+ async def async_get_access_token(self) -> str:
88
+ return "your-access-token"
89
+
90
+ auth = MyAuth(websession)
91
+ client = EveOnlineClient(auth=auth)
92
+
93
+ online = await client.async_get_character_online(character_id)
94
+ wallet = await client.async_get_wallet_balance(character_id)
95
+ ```
96
+
97
+ ## Available endpoints
98
+
99
+ | Method | Scope required | Returns |
100
+ |--------|---------------|---------|
101
+ | `async_get_server_status()` | — | `ServerStatus` |
102
+ | `async_get_character_public(id)` | — | `CharacterPublicInfo` |
103
+ | `async_get_character_portrait(id)` | — | `CharacterPortrait` |
104
+ | `async_get_corporation_public(id)` | — | `CorporationPublicInfo` |
105
+ | `async_resolve_names(ids)` | — | `list[UniverseName]` |
106
+ | `async_get_character_online(id)` | `esi-location.read_online.v1` | `CharacterOnlineStatus` |
107
+ | `async_get_character_location(id)` | `esi-location.read_location.v1` | `CharacterLocation` |
108
+ | `async_get_character_ship(id)` | `esi-location.read_ship_type.v1` | `CharacterShip` |
109
+ | `async_get_wallet_balance(id)` | `esi-wallet.read_character_wallet.v1` | `WalletBalance` |
110
+ | `async_get_skills(id)` | `esi-skills.read_skills.v1` | `CharacterSkillsSummary` |
111
+ | `async_get_skill_queue(id)` | `esi-skills.read_skillqueue.v1` | `list[SkillQueueEntry]` |
112
+ | `async_get_mail_labels(id)` | `esi-mail.read_mail.v1` | `MailLabelsSummary` |
113
+ | `async_get_industry_jobs(id)` | `esi-industry.read_character_jobs.v1` | `list[IndustryJob]` |
114
+ | `async_get_market_orders(id)` | `esi-markets.read_character_orders.v1` | `list[MarketOrder]` |
115
+ | `async_get_jump_fatigue(id)` | `esi-characters.read_fatigue.v1` | `JumpFatigue` |
116
+
117
+ ## Error handling
118
+
119
+ All exceptions inherit from `EveOnlineError`:
120
+
121
+ ```python
122
+ from eveonline import EveOnlineClient, EveOnlineError
123
+ from eveonline.exceptions import (
124
+ EveOnlineAuthenticationError, # 401/403 or missing auth
125
+ EveOnlineConnectionError, # Network/connection failures
126
+ EveOnlineRateLimitError, # 429 with optional retry_after
127
+ )
128
+
129
+ try:
130
+ status = await client.async_get_server_status()
131
+ except EveOnlineAuthenticationError:
132
+ # Token expired or invalid
133
+ ...
134
+ except EveOnlineRateLimitError as err:
135
+ # Back off for err.retry_after seconds
136
+ ...
137
+ except EveOnlineError:
138
+ # Any other ESI error
139
+ ...
140
+ ```
141
+
142
+ ## License
143
+
144
+ [MIT](LICENSE)
@@ -0,0 +1,112 @@
1
+ # python-eveonline
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/python-eveonline.svg)](https://pypi.org/project/python-eveonline/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/python-eveonline.svg)](https://pypi.org/project/python-eveonline/)
5
+ [![License](https://img.shields.io/github/license/ronaldvdmeer/python-eveonline.svg)](https://github.com/ronaldvdmeer/python-eveonline/blob/main/LICENSE)
6
+ [![GitHub Release](https://img.shields.io/github/v/release/ronaldvdmeer/python-eveonline.svg)](https://github.com/ronaldvdmeer/python-eveonline/releases)
7
+
8
+ Async Python client library for the [Eve Online ESI API](https://esi.evetech.net/ui/).
9
+
10
+ Built for use with [Home Assistant](https://www.home-assistant.io/) but can be used standalone in any async Python project.
11
+
12
+ ## Features
13
+
14
+ - **Fully async** — built on [aiohttp](https://docs.aiohttp.org/)
15
+ - **Typed models** — all API responses are frozen dataclasses
16
+ - **Public endpoints** — server status, character info, corporation info, portraits, universe name resolution
17
+ - **Authenticated endpoints** — online status, location, ship, wallet, skills, skill queue, mail, industry jobs, market orders, jump fatigue
18
+ - **Abstract auth** — implement `AbstractAuth` to provide your own token management (e.g. Home Assistant OAuth2)
19
+ - **Type-safe** — PEP 561 compatible (`py.typed`), strict mypy configuration
20
+ - **Tested** — 100% test coverage
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install python-eveonline
26
+ ```
27
+
28
+ ## Quick start
29
+
30
+ ### Public endpoints (no authentication required)
31
+
32
+ ```python
33
+ import asyncio
34
+ import aiohttp
35
+ from eveonline import EveOnlineClient
36
+
37
+ async def main():
38
+ async with aiohttp.ClientSession() as session:
39
+ client = EveOnlineClient(session=session)
40
+ status = await client.async_get_server_status()
41
+ print(f"{status.players} players online (version {status.server_version})")
42
+
43
+ asyncio.run(main())
44
+ ```
45
+
46
+ ### Authenticated endpoints
47
+
48
+ To access character-specific data, implement the `AbstractAuth` class with your own token management:
49
+
50
+ ```python
51
+ from eveonline import EveOnlineClient
52
+ from eveonline.auth import AbstractAuth
53
+
54
+ class MyAuth(AbstractAuth):
55
+ async def async_get_access_token(self) -> str:
56
+ return "your-access-token"
57
+
58
+ auth = MyAuth(websession)
59
+ client = EveOnlineClient(auth=auth)
60
+
61
+ online = await client.async_get_character_online(character_id)
62
+ wallet = await client.async_get_wallet_balance(character_id)
63
+ ```
64
+
65
+ ## Available endpoints
66
+
67
+ | Method | Scope required | Returns |
68
+ |--------|---------------|---------|
69
+ | `async_get_server_status()` | — | `ServerStatus` |
70
+ | `async_get_character_public(id)` | — | `CharacterPublicInfo` |
71
+ | `async_get_character_portrait(id)` | — | `CharacterPortrait` |
72
+ | `async_get_corporation_public(id)` | — | `CorporationPublicInfo` |
73
+ | `async_resolve_names(ids)` | — | `list[UniverseName]` |
74
+ | `async_get_character_online(id)` | `esi-location.read_online.v1` | `CharacterOnlineStatus` |
75
+ | `async_get_character_location(id)` | `esi-location.read_location.v1` | `CharacterLocation` |
76
+ | `async_get_character_ship(id)` | `esi-location.read_ship_type.v1` | `CharacterShip` |
77
+ | `async_get_wallet_balance(id)` | `esi-wallet.read_character_wallet.v1` | `WalletBalance` |
78
+ | `async_get_skills(id)` | `esi-skills.read_skills.v1` | `CharacterSkillsSummary` |
79
+ | `async_get_skill_queue(id)` | `esi-skills.read_skillqueue.v1` | `list[SkillQueueEntry]` |
80
+ | `async_get_mail_labels(id)` | `esi-mail.read_mail.v1` | `MailLabelsSummary` |
81
+ | `async_get_industry_jobs(id)` | `esi-industry.read_character_jobs.v1` | `list[IndustryJob]` |
82
+ | `async_get_market_orders(id)` | `esi-markets.read_character_orders.v1` | `list[MarketOrder]` |
83
+ | `async_get_jump_fatigue(id)` | `esi-characters.read_fatigue.v1` | `JumpFatigue` |
84
+
85
+ ## Error handling
86
+
87
+ All exceptions inherit from `EveOnlineError`:
88
+
89
+ ```python
90
+ from eveonline import EveOnlineClient, EveOnlineError
91
+ from eveonline.exceptions import (
92
+ EveOnlineAuthenticationError, # 401/403 or missing auth
93
+ EveOnlineConnectionError, # Network/connection failures
94
+ EveOnlineRateLimitError, # 429 with optional retry_after
95
+ )
96
+
97
+ try:
98
+ status = await client.async_get_server_status()
99
+ except EveOnlineAuthenticationError:
100
+ # Token expired or invalid
101
+ ...
102
+ except EveOnlineRateLimitError as err:
103
+ # Back off for err.retry_after seconds
104
+ ...
105
+ except EveOnlineError:
106
+ # Any other ESI error
107
+ ...
108
+ ```
109
+
110
+ ## License
111
+
112
+ [MIT](LICENSE)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-eveonline"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Async Python client for the Eve Online ESI API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,15 +1,23 @@
1
1
  """Async Python client for the Eve Online ESI API."""
2
2
 
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import PackageNotFoundError, version
6
+
3
7
  from .auth import AbstractAuth
4
8
  from .client import EveOnlineClient
5
9
  from .exceptions import (
6
10
  EveOnlineAuthenticationError,
7
11
  EveOnlineConnectionError,
8
12
  EveOnlineError,
13
+ EveOnlineNotFoundError,
9
14
  EveOnlineRateLimitError,
10
15
  )
11
16
 
12
- __version__ = "0.2.0"
17
+ try:
18
+ __version__ = version("python-eveonline")
19
+ except PackageNotFoundError:
20
+ __version__ = "0.0.0"
13
21
 
14
22
  __all__ = [
15
23
  "AbstractAuth",
@@ -17,6 +25,7 @@ __all__ = [
17
25
  "EveOnlineClient",
18
26
  "EveOnlineConnectionError",
19
27
  "EveOnlineError",
28
+ "EveOnlineNotFoundError",
20
29
  "EveOnlineRateLimitError",
21
30
  "__version__",
22
31
  ]
@@ -145,7 +145,13 @@ class EveOnlineClient:
145
145
 
146
146
  if response.status in (420, 429):
147
147
  retry_after = response.headers.get("Retry-After")
148
- raise EveOnlineRateLimitError(retry_after=int(retry_after) if retry_after else None)
148
+ retry_after_seconds: int | None = None
149
+ if retry_after is not None:
150
+ try:
151
+ retry_after_seconds = int(retry_after)
152
+ except ValueError:
153
+ retry_after_seconds = None
154
+ raise EveOnlineRateLimitError(retry_after=retry_after_seconds)
149
155
 
150
156
  if response.status >= 400:
151
157
  text = await response.text()
@@ -156,11 +162,19 @@ class EveOnlineClient:
156
162
 
157
163
  @staticmethod
158
164
  def _parse_datetime(value: str | None) -> datetime | None:
159
- """Parse an ISO8601 datetime string from ESI."""
165
+ """Parse an ISO 8601 datetime string from ESI.
166
+
167
+ Args:
168
+ value: An ISO 8601 string (e.g. ``"2025-01-15T12:34:56Z"``) or
169
+ ``None``.
170
+
171
+ Returns:
172
+ A timezone-aware :class:`~datetime.datetime`, or ``None`` when
173
+ *value* is ``None``.
174
+ """
160
175
  if value is None:
161
176
  return None
162
- # ESI returns times like "2025-01-15T12:34:56Z"
163
- return datetime.fromisoformat(value.replace("Z", "+00:00"))
177
+ return datetime.fromisoformat(value)
164
178
 
165
179
  # -------------------------------------------------------------------------
166
180
  # Public endpoints (no auth required)
@@ -176,7 +190,7 @@ class EveOnlineClient:
176
190
  return ServerStatus(
177
191
  players=data["players"],
178
192
  server_version=data["server_version"],
179
- start_time=datetime.fromisoformat(data["start_time"].replace("Z", "+00:00")),
193
+ start_time=datetime.fromisoformat(data["start_time"]),
180
194
  vip=data.get("vip"),
181
195
  )
182
196
 
@@ -194,7 +208,7 @@ class EveOnlineClient:
194
208
  character_id=character_id,
195
209
  name=data["name"],
196
210
  corporation_id=data["corporation_id"],
197
- birthday=datetime.fromisoformat(data["birthday"].replace("Z", "+00:00")),
211
+ birthday=datetime.fromisoformat(data["birthday"]),
198
212
  gender=data["gender"],
199
213
  race_id=data["race_id"],
200
214
  bloodline_id=data["bloodline_id"],
@@ -424,8 +438,8 @@ class EveOnlineClient:
424
438
  job_id=entry["job_id"],
425
439
  activity_id=entry["activity_id"],
426
440
  status=entry["status"],
427
- start_date=datetime.fromisoformat(entry["start_date"].replace("Z", "+00:00")),
428
- end_date=datetime.fromisoformat(entry["end_date"].replace("Z", "+00:00")),
441
+ start_date=datetime.fromisoformat(entry["start_date"]),
442
+ end_date=datetime.fromisoformat(entry["end_date"]),
429
443
  blueprint_type_id=entry["blueprint_type_id"],
430
444
  output_location_id=entry["output_location_id"],
431
445
  runs=entry["runs"],
@@ -458,7 +472,7 @@ class EveOnlineClient:
458
472
  volume_total=entry["volume_total"],
459
473
  location_id=entry["location_id"],
460
474
  region_id=entry["region_id"],
461
- issued=datetime.fromisoformat(entry["issued"].replace("Z", "+00:00")),
475
+ issued=datetime.fromisoformat(entry["issued"]),
462
476
  duration=entry["duration"],
463
477
  range=entry["range"],
464
478
  min_volume=entry.get("min_volume"),
@@ -1,5 +1,7 @@
1
1
  """Constants for the Eve Online ESI API."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from typing import Final
4
6
 
5
7
  # ESI API
@@ -28,7 +30,7 @@ SCOPE_READ_INDUSTRY_JOBS: Final = "esi-industry.read_character_jobs.v1"
28
30
  SCOPE_READ_MARKET_ORDERS: Final = "esi-markets.read_character_orders.v1"
29
31
 
30
32
  # Default scopes for a typical Home Assistant integration
31
- DEFAULT_SCOPES: Final = [
33
+ DEFAULT_SCOPES: Final = (
32
34
  SCOPE_READ_ONLINE,
33
35
  SCOPE_READ_LOCATION,
34
36
  SCOPE_READ_SHIP_TYPE,
@@ -39,4 +41,4 @@ DEFAULT_SCOPES: Final = [
39
41
  SCOPE_READ_MAIL,
40
42
  SCOPE_READ_INDUSTRY_JOBS,
41
43
  SCOPE_READ_MARKET_ORDERS,
42
- ]
44
+ )
@@ -1,5 +1,7 @@
1
1
  """Exceptions for the Eve Online ESI client."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
 
4
6
  class EveOnlineError(Exception):
5
7
  """Base exception for Eve Online ESI errors."""
@@ -14,10 +16,20 @@ class EveOnlineAuthenticationError(EveOnlineError):
14
16
 
15
17
 
16
18
  class EveOnlineRateLimitError(EveOnlineError):
17
- """Exception raised when the ESI rate limit is exceeded."""
19
+ """Exception raised when the ESI rate limit is exceeded.
20
+
21
+ Attributes:
22
+ retry_after: Seconds to wait before retrying, or ``None`` if the
23
+ server did not provide the header.
24
+ """
18
25
 
19
26
  def __init__(self, retry_after: int | None = None) -> None:
20
- """Initialize the rate limit error."""
27
+ """Initialize the rate limit error.
28
+
29
+ Args:
30
+ retry_after: Optional number of seconds the server asks us to
31
+ wait before retrying.
32
+ """
21
33
  self.retry_after = retry_after
22
34
  message = "ESI rate limit exceeded"
23
35
  if retry_after is not None: