blizzardapi3 3.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.
@@ -0,0 +1,13 @@
1
+ """BlizzardAPI v3 - Modern Python wrapper for the Blizzard API."""
2
+
3
+ __version__ = "3.0.0"
4
+
5
+ from .blizzard_api import BlizzardAPI
6
+ from .types import Locale, Region
7
+
8
+ __all__ = [
9
+ "__version__",
10
+ "BlizzardAPI",
11
+ "Region",
12
+ "Locale",
13
+ ]
@@ -0,0 +1,5 @@
1
+ """Game-specific API facades."""
2
+
3
+ from .wow import WowAPI
4
+
5
+ __all__ = ["WowAPI"]
@@ -0,0 +1,76 @@
1
+ """World of Warcraft API facade."""
2
+
3
+ from ..core import BaseClient, EndpointRegistry, MethodFactory, RequestExecutor
4
+
5
+
6
+ class WowGameDataAPI:
7
+ """WoW Game Data API with dynamically generated methods."""
8
+
9
+ def __init__(self, client: BaseClient, executor: RequestExecutor, registry: EndpointRegistry):
10
+ """Initialize WoW Game Data API.
11
+
12
+ Args:
13
+ client: Base client for session management
14
+ executor: Request executor
15
+ registry: Endpoint registry
16
+ """
17
+ self.client = client
18
+ self.executor = executor
19
+ self.registry = registry
20
+
21
+ # Generate and attach all methods
22
+ factory = MethodFactory(executor, registry)
23
+ methods = factory.generate_all_methods("wow", "game_data")
24
+
25
+ for method_name, (sync_method, async_method) in methods.items():
26
+ # Bind methods to this instance
27
+ setattr(self, method_name, sync_method.__get__(self, type(self)))
28
+ setattr(self, f"{method_name}_async", async_method.__get__(self, type(self)))
29
+
30
+
31
+ class WowProfileAPI:
32
+ """WoW Profile API with dynamically generated methods."""
33
+
34
+ def __init__(self, client: BaseClient, executor: RequestExecutor, registry: EndpointRegistry):
35
+ """Initialize WoW Profile API.
36
+
37
+ Args:
38
+ client: Base client for session management
39
+ executor: Request executor
40
+ registry: Endpoint registry
41
+ """
42
+ self.client = client
43
+ self.executor = executor
44
+ self.registry = registry
45
+
46
+ # Generate and attach all methods
47
+ factory = MethodFactory(executor, registry)
48
+ methods = factory.generate_all_methods("wow", "profile")
49
+
50
+ for method_name, (sync_method, async_method) in methods.items():
51
+ # Bind methods to this instance
52
+ setattr(self, method_name, sync_method.__get__(self, type(self)))
53
+ setattr(self, f"{method_name}_async", async_method.__get__(self, type(self)))
54
+
55
+
56
+ class WowAPI:
57
+ """World of Warcraft API facade.
58
+
59
+ Provides access to:
60
+ - game_data: WoW Game Data API (achievements, items, etc.)
61
+ - profile: WoW Profile API (characters, guilds, etc.)
62
+ """
63
+
64
+ def __init__(self, client: BaseClient, registry: EndpointRegistry):
65
+ """Initialize WoW API.
66
+
67
+ Args:
68
+ client: Base client for session management
69
+ registry: Endpoint registry
70
+ """
71
+ self.client = client
72
+ executor = RequestExecutor(client.token_manager)
73
+
74
+ # Initialize sub-APIs
75
+ self.game_data = WowGameDataAPI(client, executor, registry)
76
+ self.profile = WowProfileAPI(client, executor, registry)
@@ -0,0 +1,53 @@
1
+ """Main BlizzardAPI client."""
2
+
3
+ from .api import WowAPI
4
+ from .core import BaseClient, EndpointRegistry
5
+ from .types import Locale, Region
6
+
7
+
8
+ class BlizzardAPI(BaseClient):
9
+ """Main Blizzard API client.
10
+
11
+ Provides access to all Blizzard game APIs with proper session management,
12
+ authentication, and error handling.
13
+
14
+ Example:
15
+ Synchronous usage:
16
+ with BlizzardAPI(client_id, client_secret) as api:
17
+ data = api.wow.game_data.get_achievement(
18
+ region="us",
19
+ locale="en_US",
20
+ achievement_id=6
21
+ )
22
+
23
+ Asynchronous usage:
24
+ async with BlizzardAPI(client_id, client_secret) as api:
25
+ data = await api.wow.game_data.get_achievement_async(
26
+ region="us",
27
+ locale="en_US",
28
+ achievement_id=6
29
+ )
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ client_id: str,
35
+ client_secret: str,
36
+ region: Region | str = Region.US,
37
+ locale: Locale | str | None = None,
38
+ ):
39
+ """Initialize Blizzard API client.
40
+
41
+ Args:
42
+ client_id: Blizzard API client ID
43
+ client_secret: Blizzard API client secret
44
+ region: Default region (defaults to US)
45
+ locale: Default locale (defaults to region's default)
46
+ """
47
+ super().__init__(client_id, client_secret, region, locale)
48
+
49
+ # Initialize endpoint registry
50
+ self.registry = EndpointRegistry()
51
+
52
+ # Initialize game APIs
53
+ self.wow = WowAPI(self, self.registry)
@@ -0,0 +1,17 @@
1
+ """Core framework components."""
2
+
3
+ from .auth import TokenManager
4
+ from .client import BaseClient
5
+ from .context import RequestContext
6
+ from .executor import RequestExecutor
7
+ from .factory import MethodFactory
8
+ from .registry import EndpointRegistry
9
+
10
+ __all__ = [
11
+ "BaseClient",
12
+ "TokenManager",
13
+ "RequestContext",
14
+ "RequestExecutor",
15
+ "MethodFactory",
16
+ "EndpointRegistry",
17
+ ]
@@ -0,0 +1,177 @@
1
+ """OAuth token management."""
2
+
3
+ import time
4
+
5
+ import aiohttp
6
+ import requests
7
+
8
+ from ..exceptions import TokenError
9
+
10
+
11
+ class TokenManager:
12
+ """Manages OAuth token lifecycle with automatic refresh.
13
+
14
+ Attributes:
15
+ TOKEN_BUFFER_SECONDS: Refresh tokens 5 minutes before expiry
16
+ """
17
+
18
+ TOKEN_BUFFER_SECONDS = 300 # 5 minutes
19
+
20
+ def __init__(self, client_id: str, client_secret: str):
21
+ """Initialize token manager.
22
+
23
+ Args:
24
+ client_id: Blizzard API client ID
25
+ client_secret: Blizzard API client secret
26
+ """
27
+ self.client_id = client_id
28
+ self.client_secret = client_secret
29
+
30
+ self._token: str | None = None
31
+ self._token_type: str | None = None
32
+ self._expires_at: float | None = None
33
+
34
+ def is_token_valid(self) -> bool:
35
+ """Check if current token is valid.
36
+
37
+ Returns:
38
+ True if token exists and hasn't expired (with buffer)
39
+ """
40
+ if not self._token or not self._expires_at:
41
+ return False
42
+ return time.time() < (self._expires_at - self.TOKEN_BUFFER_SECONDS)
43
+
44
+ def get_token(self, region: str, session: requests.Session) -> str:
45
+ """Get valid access token (synchronous).
46
+
47
+ Args:
48
+ region: API region for OAuth endpoint
49
+ session: requests Session to use
50
+
51
+ Returns:
52
+ Valid access token
53
+
54
+ Raises:
55
+ TokenError: If token fetch fails
56
+ """
57
+ if self.is_token_valid():
58
+ return self._token
59
+
60
+ return self._fetch_token(region, session)
61
+
62
+ async def get_token_async(self, region: str, session: aiohttp.ClientSession) -> str:
63
+ """Get valid access token (asynchronous).
64
+
65
+ Args:
66
+ region: API region for OAuth endpoint
67
+ session: aiohttp ClientSession to use
68
+
69
+ Returns:
70
+ Valid access token
71
+
72
+ Raises:
73
+ TokenError: If token fetch fails
74
+ """
75
+ if self.is_token_valid():
76
+ return self._token
77
+
78
+ return await self._fetch_token_async(region, session)
79
+
80
+ def _fetch_token(self, region: str, session: requests.Session) -> str:
81
+ """Fetch new access token (synchronous).
82
+
83
+ Args:
84
+ region: API region for OAuth endpoint
85
+ session: requests Session to use
86
+
87
+ Returns:
88
+ New access token
89
+
90
+ Raises:
91
+ TokenError: If token request fails
92
+ """
93
+ url = self._get_oauth_url(region)
94
+
95
+ try:
96
+ response = session.post(
97
+ url, auth=(self.client_id, self.client_secret), data={"grant_type": "client_credentials"}, timeout=10
98
+ )
99
+
100
+ if response.status_code != 200:
101
+ raise TokenError(
102
+ f"Failed to obtain token: {response.status_code}",
103
+ status_code=response.status_code,
104
+ request_url=url,
105
+ response_data=response.json() if response.text else None,
106
+ )
107
+
108
+ data = response.json()
109
+ self._token = data["access_token"]
110
+ self._token_type = data["token_type"]
111
+ self._expires_at = time.time() + data["expires_in"]
112
+
113
+ return self._token
114
+
115
+ except requests.RequestException as e:
116
+ raise TokenError(f"Token request failed: {str(e)}", request_url=url)
117
+
118
+ async def _fetch_token_async(self, region: str, session: aiohttp.ClientSession) -> str:
119
+ """Fetch new access token (asynchronous).
120
+
121
+ Args:
122
+ region: API region for OAuth endpoint
123
+ session: aiohttp ClientSession to use
124
+
125
+ Returns:
126
+ New access token
127
+
128
+ Raises:
129
+ TokenError: If token request fails
130
+ """
131
+ url = self._get_oauth_url(region)
132
+
133
+ try:
134
+ async with session.post(
135
+ url,
136
+ auth=aiohttp.BasicAuth(self.client_id, self.client_secret),
137
+ data={"grant_type": "client_credentials"},
138
+ timeout=aiohttp.ClientTimeout(total=10),
139
+ ) as response:
140
+
141
+ if response.status != 200:
142
+ response_data = await response.json() if response.content_type == "application/json" else None
143
+ raise TokenError(
144
+ f"Failed to obtain token: {response.status}",
145
+ status_code=response.status,
146
+ request_url=url,
147
+ response_data=response_data,
148
+ )
149
+
150
+ data = await response.json()
151
+ self._token = data["access_token"]
152
+ self._token_type = data["token_type"]
153
+ self._expires_at = time.time() + data["expires_in"]
154
+
155
+ return self._token
156
+
157
+ except aiohttp.ClientError as e:
158
+ raise TokenError(f"Token request failed: {str(e)}", request_url=url)
159
+
160
+ def invalidate(self) -> None:
161
+ """Invalidate current token, forcing a refresh on next request."""
162
+ self._token = None
163
+ self._expires_at = None
164
+
165
+ @staticmethod
166
+ def _get_oauth_url(region: str) -> str:
167
+ """Get OAuth URL for region.
168
+
169
+ Args:
170
+ region: API region
171
+
172
+ Returns:
173
+ OAuth token endpoint URL
174
+ """
175
+ if region == "cn":
176
+ return "https://oauth.battlenet.com.cn/token"
177
+ return f"https://{region}.battle.net/oauth/token"
@@ -0,0 +1,144 @@
1
+ """Base API client with session management."""
2
+
3
+ import aiohttp
4
+ import requests
5
+
6
+ from ..types import Locale, Region, get_default_locale
7
+ from .auth import TokenManager
8
+
9
+
10
+ class BaseClient:
11
+ """Base API client with proper session management.
12
+
13
+ Manages HTTP sessions for both sync and async requests,
14
+ with automatic cleanup via context managers.
15
+
16
+ Example:
17
+ Synchronous usage:
18
+ with BaseClient(client_id, client_secret) as client:
19
+ # Use client
20
+ pass
21
+
22
+ Asynchronous usage:
23
+ async with BaseClient(client_id, client_secret) as client:
24
+ # Use client
25
+ pass
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ client_id: str,
31
+ client_secret: str,
32
+ region: Region | str = Region.US,
33
+ locale: Locale | str | None = None,
34
+ ):
35
+ """Initialize API client.
36
+
37
+ Args:
38
+ client_id: Blizzard API client ID
39
+ client_secret: Blizzard API client secret
40
+ region: Default region (defaults to US)
41
+ locale: Default locale (defaults to region's default locale)
42
+ """
43
+ self.client_id = client_id
44
+ self.client_secret = client_secret
45
+
46
+ # Convert region to enum if string
47
+ if isinstance(region, str):
48
+ self.default_region = Region(region)
49
+ else:
50
+ self.default_region = region
51
+
52
+ # Set default locale
53
+ if locale is None:
54
+ self.default_locale = get_default_locale(self.default_region)
55
+ elif isinstance(locale, str):
56
+ self.default_locale = Locale(locale)
57
+ else:
58
+ self.default_locale = locale
59
+
60
+ # Session management
61
+ self._sync_session: requests.Session | None = None
62
+ self._async_session: aiohttp.ClientSession | None = None
63
+
64
+ # Token manager (shared between sync and async)
65
+ self.token_manager = TokenManager(client_id, client_secret)
66
+
67
+ @property
68
+ def sync_session(self) -> requests.Session:
69
+ """Get or create synchronous session.
70
+
71
+ Returns:
72
+ Active requests.Session instance
73
+ """
74
+ if self._sync_session is None:
75
+ self._sync_session = requests.Session()
76
+ return self._sync_session
77
+
78
+ @property
79
+ def async_session(self) -> aiohttp.ClientSession:
80
+ """Get or create asynchronous session.
81
+
82
+ Returns:
83
+ Active aiohttp.ClientSession instance
84
+ """
85
+ if self._async_session is None or self._async_session.closed:
86
+ self._async_session = aiohttp.ClientSession()
87
+ return self._async_session
88
+
89
+ def close(self) -> None:
90
+ """Close synchronous session."""
91
+ if self._sync_session is not None:
92
+ self._sync_session.close()
93
+ self._sync_session = None
94
+
95
+ async def aclose(self) -> None:
96
+ """Close asynchronous session."""
97
+ if self._async_session is not None and not self._async_session.closed:
98
+ await self._async_session.close()
99
+ self._async_session = None
100
+
101
+ # Context manager support (sync)
102
+
103
+ def __enter__(self):
104
+ """Enter synchronous context manager.
105
+
106
+ Returns:
107
+ Self for context manager usage
108
+ """
109
+ return self
110
+
111
+ def __exit__(self, exc_type, exc_val, exc_tb):
112
+ """Exit synchronous context manager.
113
+
114
+ Closes sync session automatically.
115
+ """
116
+ self.close()
117
+
118
+ # Async context manager support
119
+
120
+ async def __aenter__(self):
121
+ """Enter asynchronous context manager.
122
+
123
+ Returns:
124
+ Self for async context manager usage
125
+ """
126
+ return self
127
+
128
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
129
+ """Exit asynchronous context manager.
130
+
131
+ Closes async session automatically.
132
+ """
133
+ await self.aclose()
134
+
135
+ def __del__(self):
136
+ """Cleanup on deletion.
137
+
138
+ Attempts to close sessions if they're still open.
139
+ """
140
+ try:
141
+ if self._sync_session is not None:
142
+ self._sync_session.close()
143
+ except Exception:
144
+ pass
@@ -0,0 +1,23 @@
1
+ """Request context for API calls."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+
7
+ @dataclass
8
+ class RequestContext:
9
+ """Context information for an API request.
10
+
11
+ Attributes:
12
+ region: API region (e.g., "us", "eu")
13
+ path: API endpoint path
14
+ query_params: Query string parameters
15
+ access_token: User-provided OAuth access token (optional)
16
+ auth_type: Type of authentication ("client_credentials" or "oauth")
17
+ """
18
+
19
+ region: str
20
+ path: str
21
+ query_params: dict[str, Any]
22
+ access_token: str | None = None
23
+ auth_type: str = "client_credentials"