tickflow 0.1.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,36 @@
1
+ Metadata-Version: 2.4
2
+ Name: tickflow
3
+ Version: 0.1.0
4
+ Summary: TickFlow Python Client - High-performance market data API for A-shares, US stocks, and Hong Kong stocks
5
+ Author: TickFlow Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://tickflow.org
8
+ Project-URL: Documentation, https://docs.tickflow.org
9
+ Project-URL: Repository, https://github.com/tickflow/tickflow-python
10
+ Keywords: finance,market-data,stocks,trading,api-client,async
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Financial and Insurance Industry
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Office/Business :: Financial :: Investment
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: httpx>=0.28.1
27
+ Requires-Dist: pandas>=1.3.0
28
+ Requires-Dist: typing-extensions>=4.0.0
29
+ Provides-Extra: pandas
30
+ Requires-Dist: pandas>=1.3.0; extra == "pandas"
31
+ Provides-Extra: all
32
+ Requires-Dist: pandas>=1.3.0; extra == "all"
33
+ Provides-Extra: dev
34
+ Requires-Dist: datamodel-code-generator[http]>=0.45.0; extra == "dev"
35
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
36
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
File without changes
@@ -0,0 +1,62 @@
1
+ [project]
2
+ name = "tickflow"
3
+ version = "0.1.0"
4
+ description = "TickFlow Python Client - High-performance market data API for A-shares, US stocks, and Hong Kong stocks"
5
+ readme = "README.md"
6
+ requires-python = ">=3.9"
7
+ license = { text = "MIT" }
8
+ authors = [
9
+ { name = "TickFlow Team" }
10
+ ]
11
+ keywords = ["finance", "market-data", "stocks", "trading", "api-client", "async"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Intended Audience :: Developers",
15
+ "Intended Audience :: Financial and Insurance Industry",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Office/Business :: Financial :: Investment",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ "Typing :: Typed",
26
+ ]
27
+
28
+ dependencies = [
29
+ "httpx>=0.28.1",
30
+ "pandas>=1.3.0",
31
+ "typing-extensions>=4.0.0",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ pandas = ["pandas>=1.3.0"]
36
+ all = ["pandas>=1.3.0"]
37
+ dev = [
38
+ "datamodel-code-generator[http]>=0.45.0",
39
+ "pytest>=7.0.0",
40
+ "pytest-asyncio>=0.21.0",
41
+ ]
42
+
43
+ [project.urls]
44
+ Homepage = "https://tickflow.org"
45
+ Documentation = "https://docs.tickflow.org"
46
+ Repository = "https://github.com/tickflow/tickflow-python"
47
+
48
+ [build-system]
49
+ requires = ["setuptools>=61.0", "wheel"]
50
+ build-backend = "setuptools.build_meta"
51
+
52
+ [tool.setuptools.packages.find]
53
+ where = ["."]
54
+ include = ["tickflow*"]
55
+
56
+ [tool.setuptools.package-data]
57
+ tickflow = ["py.typed"]
58
+
59
+
60
+ [tool.pytest.ini_options]
61
+ asyncio_mode = "auto"
62
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,136 @@
1
+ """TickFlow Python SDK - Market Data API Client.
2
+
3
+ A high-quality Python client for TickFlow market data API, supporting
4
+ A-shares (China), US stocks, and Hong Kong stocks.
5
+
6
+ Quick Start
7
+ -----------
8
+ >>> from tickflow import TickFlow
9
+ >>>
10
+ >>> # Initialize client
11
+ >>> client = TickFlow(api_key="your-api-key")
12
+ >>>
13
+ >>> # Get K-line data as pandas DataFrame
14
+ >>> df = client.klines.get("600000.SH", period="1d", count=100, as_dataframe=True)
15
+ >>> print(df.tail())
16
+ >>>
17
+ >>> # Get real-time quotes
18
+ >>> quotes = client.quotes.get(symbols=["600000.SH", "AAPL.US"])
19
+ >>> for q in quotes:
20
+ ... print(f"{q['symbol']}: {q['last_price']}")
21
+
22
+ Async Usage
23
+ -----------
24
+ >>> import asyncio
25
+ >>> from tickflow import AsyncTickFlow
26
+ >>>
27
+ >>> async def main():
28
+ ... async with AsyncTickFlow(api_key="your-api-key") as client:
29
+ ... df = await client.klines.get("AAPL.US", as_dataframe=True)
30
+ ... print(df.tail())
31
+ >>>
32
+ >>> asyncio.run(main())
33
+
34
+ Environment Variables
35
+ ---------------------
36
+ - TICKFLOW_API_KEY: API key for authentication
37
+ - TICKFLOW_BASE_URL: Custom base URL (optional)
38
+ """
39
+
40
+ from ._exceptions import (
41
+ APIError,
42
+ AuthenticationError,
43
+ BadRequestError,
44
+ ConnectionError,
45
+ InternalServerError,
46
+ NotFoundError,
47
+ PermissionError,
48
+ RateLimitError,
49
+ TickFlowError,
50
+ TimeoutError,
51
+ )
52
+ from ._types import NOT_GIVEN, NotGiven
53
+ from .client import AsyncTickFlow, TickFlow
54
+
55
+ # Re-export generated types for convenience
56
+ from .generated_model import ( # Core types; K-line types; Quote types; Symbol types; Exchange types; Universe types; Error types
57
+ ApiError,
58
+ BidAsk,
59
+ CNQuoteExt,
60
+ CNSymbolExt,
61
+ CompactKlineData,
62
+ ExchangeListResponse,
63
+ ExchangeSummary,
64
+ ExchangeSymbolsResponse,
65
+ HKQuoteExt,
66
+ HKSymbolExt,
67
+ Kline,
68
+ KlinesBatchResponse,
69
+ KlinesResponse,
70
+ Period,
71
+ Quote,
72
+ QuotesResponse,
73
+ Region,
74
+ SessionStatus,
75
+ SymbolMeta,
76
+ SymbolMetaResponse,
77
+ Universe,
78
+ UniverseDetail,
79
+ UniverseDetailResponse,
80
+ UniverseListResponse,
81
+ UniverseSummary,
82
+ USQuoteExt,
83
+ USSymbolExt,
84
+ )
85
+
86
+ __version__ = "0.1.0"
87
+
88
+ __all__ = [
89
+ # Main clients
90
+ "TickFlow",
91
+ "AsyncTickFlow",
92
+ # Exceptions
93
+ "TickFlowError",
94
+ "APIError",
95
+ "AuthenticationError",
96
+ "PermissionError",
97
+ "NotFoundError",
98
+ "BadRequestError",
99
+ "RateLimitError",
100
+ "InternalServerError",
101
+ "ConnectionError",
102
+ "TimeoutError",
103
+ # Sentinel
104
+ "NOT_GIVEN",
105
+ "NotGiven",
106
+ # Generated types
107
+ "Period",
108
+ "Region",
109
+ "SessionStatus",
110
+ "CompactKlineData",
111
+ "Kline",
112
+ "KlinesResponse",
113
+ "KlinesBatchResponse",
114
+ "Quote",
115
+ "QuotesResponse",
116
+ "BidAsk",
117
+ "CNQuoteExt",
118
+ "USQuoteExt",
119
+ "HKQuoteExt",
120
+ "SymbolMeta",
121
+ "SymbolMetaResponse",
122
+ "CNSymbolExt",
123
+ "USSymbolExt",
124
+ "HKSymbolExt",
125
+ "ExchangeSummary",
126
+ "ExchangeListResponse",
127
+ "ExchangeSymbolsResponse",
128
+ "Universe",
129
+ "UniverseSummary",
130
+ "UniverseDetail",
131
+ "UniverseListResponse",
132
+ "UniverseDetailResponse",
133
+ "ApiError",
134
+ # Version
135
+ "__version__",
136
+ ]
@@ -0,0 +1,353 @@
1
+ """Base HTTP client implementation for sync and async operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any, Generic, Optional, TypeVar, Union
7
+
8
+ import httpx
9
+
10
+ from ._exceptions import ConnectionError, TimeoutError, raise_for_status
11
+ from ._types import NOT_GIVEN, Headers, NotGiven, Query, Timeout, strip_not_given
12
+
13
+ __all__ = ["SyncAPIClient", "AsyncAPIClient"]
14
+
15
+ DEFAULT_BASE_URL = "https://api.tickflow.org"
16
+ DEFAULT_TIMEOUT = 30.0
17
+
18
+ T = TypeVar("T")
19
+
20
+
21
+ class BaseClient:
22
+ """Base class with shared configuration for API clients."""
23
+
24
+ def __init__(
25
+ self,
26
+ api_key: Optional[str] = None,
27
+ base_url: Optional[str] = None,
28
+ timeout: Timeout = DEFAULT_TIMEOUT,
29
+ default_headers: Optional[Headers] = None,
30
+ ) -> None:
31
+ self.api_key = api_key or os.environ.get("TICKFLOW_API_KEY")
32
+ if not self.api_key:
33
+ raise ValueError(
34
+ "API key is required. Pass `api_key` or set TICKFLOW_API_KEY environment variable."
35
+ )
36
+
37
+ self.base_url = (
38
+ base_url or os.environ.get("TICKFLOW_BASE_URL") or DEFAULT_BASE_URL
39
+ ).rstrip("/")
40
+ self.timeout = timeout
41
+ self._default_headers = dict(default_headers) if default_headers else {}
42
+
43
+ def _build_headers(self, extra_headers: Optional[Headers] = None) -> dict[str, str]:
44
+ """Build request headers with authentication."""
45
+ headers = {
46
+ "x-api-key": self.api_key,
47
+ "Content-Type": "application/json",
48
+ "Accept": "application/json",
49
+ **self._default_headers,
50
+ }
51
+ if extra_headers:
52
+ headers.update(extra_headers)
53
+ return headers
54
+
55
+ def _build_url(self, path: str) -> str:
56
+ """Build full URL from path."""
57
+ return f"{self.base_url}{path}"
58
+
59
+
60
+ class SyncAPIClient(BaseClient):
61
+ """Synchronous HTTP client for TickFlow API.
62
+
63
+ Parameters
64
+ ----------
65
+ api_key : str, optional
66
+ API key for authentication. If not provided, reads from TICKFLOW_API_KEY
67
+ environment variable.
68
+ base_url : str, optional
69
+ Base URL for the API. Defaults to https://api.tickflow.org.
70
+ timeout : float, optional
71
+ Request timeout in seconds. Defaults to 30.0.
72
+ default_headers : dict, optional
73
+ Default headers to include in all requests.
74
+
75
+ Examples
76
+ --------
77
+ >>> client = SyncAPIClient(api_key="your-api-key")
78
+ >>> response = client.get("/v1/exchanges")
79
+ """
80
+
81
+ def __init__(
82
+ self,
83
+ api_key: Optional[str] = None,
84
+ base_url: Optional[str] = None,
85
+ timeout: Timeout = DEFAULT_TIMEOUT,
86
+ default_headers: Optional[Headers] = None,
87
+ ) -> None:
88
+ super().__init__(api_key, base_url, timeout, default_headers)
89
+ self._client = httpx.Client(timeout=timeout)
90
+
91
+ def __enter__(self) -> "SyncAPIClient":
92
+ return self
93
+
94
+ def __exit__(self, *args: Any) -> None:
95
+ self.close()
96
+
97
+ def close(self) -> None:
98
+ """Close the underlying HTTP client."""
99
+ self._client.close()
100
+
101
+ def _request(
102
+ self,
103
+ method: str,
104
+ path: str,
105
+ *,
106
+ params: Optional[Query] = None,
107
+ json: Optional[dict[str, Any]] = None,
108
+ extra_headers: Optional[Headers] = None,
109
+ timeout: Union[Timeout, NotGiven] = NOT_GIVEN,
110
+ ) -> Any:
111
+ """Make an HTTP request and return the JSON response.
112
+
113
+ Parameters
114
+ ----------
115
+ method : str
116
+ HTTP method (GET, POST, etc.).
117
+ path : str
118
+ API endpoint path.
119
+ params : dict, optional
120
+ Query parameters.
121
+ json : dict, optional
122
+ JSON request body.
123
+ extra_headers : dict, optional
124
+ Additional headers for this request.
125
+ timeout : float, optional
126
+ Override timeout for this request.
127
+
128
+ Returns
129
+ -------
130
+ Any
131
+ Parsed JSON response.
132
+
133
+ Raises
134
+ ------
135
+ APIError
136
+ If the API returns an error response.
137
+ ConnectionError
138
+ If there's a network connection issue.
139
+ TimeoutError
140
+ If the request times out.
141
+ """
142
+ url = self._build_url(path)
143
+ headers = self._build_headers(extra_headers)
144
+ request_timeout = timeout if not isinstance(timeout, NotGiven) else self.timeout
145
+
146
+ # Filter out None values from params
147
+ if params:
148
+ params = {k: v for k, v in params.items() if v is not None}
149
+
150
+ try:
151
+ response = self._client.request(
152
+ method,
153
+ url,
154
+ params=params,
155
+ json=json,
156
+ headers=headers,
157
+ timeout=request_timeout,
158
+ )
159
+ except httpx.ConnectError as e:
160
+ raise ConnectionError(f"Failed to connect to {url}: {e}") from e
161
+ except httpx.TimeoutException as e:
162
+ raise TimeoutError(f"Request to {url} timed out") from e
163
+
164
+ # Parse response
165
+ try:
166
+ response_body = response.json()
167
+ except Exception:
168
+ response_body = {"message": response.text, "code": "PARSE_ERROR"}
169
+
170
+ # Check for errors
171
+ raise_for_status(response.status_code, response_body)
172
+
173
+ return response_body
174
+
175
+ def get(
176
+ self,
177
+ path: str,
178
+ *,
179
+ params: Optional[Query] = None,
180
+ extra_headers: Optional[Headers] = None,
181
+ timeout: Union[Timeout, NotGiven] = NOT_GIVEN,
182
+ ) -> Any:
183
+ """Make a GET request."""
184
+ return self._request(
185
+ "GET", path, params=params, extra_headers=extra_headers, timeout=timeout
186
+ )
187
+
188
+ def post(
189
+ self,
190
+ path: str,
191
+ *,
192
+ json: Optional[dict[str, Any]] = None,
193
+ params: Optional[Query] = None,
194
+ extra_headers: Optional[Headers] = None,
195
+ timeout: Union[Timeout, NotGiven] = NOT_GIVEN,
196
+ ) -> Any:
197
+ """Make a POST request."""
198
+ return self._request(
199
+ "POST",
200
+ path,
201
+ json=json,
202
+ params=params,
203
+ extra_headers=extra_headers,
204
+ timeout=timeout,
205
+ )
206
+
207
+
208
+ class AsyncAPIClient(BaseClient):
209
+ """Asynchronous HTTP client for TickFlow API.
210
+
211
+ Parameters
212
+ ----------
213
+ api_key : str, optional
214
+ API key for authentication. If not provided, reads from TICKFLOW_API_KEY
215
+ environment variable.
216
+ base_url : str, optional
217
+ Base URL for the API. Defaults to https://api.tickflow.org.
218
+ timeout : float, optional
219
+ Request timeout in seconds. Defaults to 30.0.
220
+ default_headers : dict, optional
221
+ Default headers to include in all requests.
222
+
223
+ Examples
224
+ --------
225
+ >>> async with AsyncAPIClient(api_key="your-api-key") as client:
226
+ ... response = await client.get("/v1/exchanges")
227
+ """
228
+
229
+ def __init__(
230
+ self,
231
+ api_key: Optional[str] = None,
232
+ base_url: Optional[str] = None,
233
+ timeout: Timeout = DEFAULT_TIMEOUT,
234
+ default_headers: Optional[Headers] = None,
235
+ ) -> None:
236
+ super().__init__(api_key, base_url, timeout, default_headers)
237
+ self._client = httpx.AsyncClient(timeout=timeout)
238
+
239
+ async def __aenter__(self) -> "AsyncAPIClient":
240
+ return self
241
+
242
+ async def __aexit__(self, *args: Any) -> None:
243
+ await self.close()
244
+
245
+ async def close(self) -> None:
246
+ """Close the underlying HTTP client."""
247
+ await self._client.aclose()
248
+
249
+ async def _request(
250
+ self,
251
+ method: str,
252
+ path: str,
253
+ *,
254
+ params: Optional[Query] = None,
255
+ json: Optional[dict[str, Any]] = None,
256
+ extra_headers: Optional[Headers] = None,
257
+ timeout: Union[Timeout, NotGiven] = NOT_GIVEN,
258
+ ) -> Any:
259
+ """Make an async HTTP request and return the JSON response.
260
+
261
+ Parameters
262
+ ----------
263
+ method : str
264
+ HTTP method (GET, POST, etc.).
265
+ path : str
266
+ API endpoint path.
267
+ params : dict, optional
268
+ Query parameters.
269
+ json : dict, optional
270
+ JSON request body.
271
+ extra_headers : dict, optional
272
+ Additional headers for this request.
273
+ timeout : float, optional
274
+ Override timeout for this request.
275
+
276
+ Returns
277
+ -------
278
+ Any
279
+ Parsed JSON response.
280
+
281
+ Raises
282
+ ------
283
+ APIError
284
+ If the API returns an error response.
285
+ ConnectionError
286
+ If there's a network connection issue.
287
+ TimeoutError
288
+ If the request times out.
289
+ """
290
+ url = self._build_url(path)
291
+ headers = self._build_headers(extra_headers)
292
+ request_timeout = timeout if not isinstance(timeout, NotGiven) else self.timeout
293
+
294
+ # Filter out None values from params
295
+ if params:
296
+ params = {k: v for k, v in params.items() if v is not None}
297
+
298
+ try:
299
+ response = await self._client.request(
300
+ method,
301
+ url,
302
+ params=params,
303
+ json=json,
304
+ headers=headers,
305
+ timeout=request_timeout,
306
+ )
307
+ except httpx.ConnectError as e:
308
+ raise ConnectionError(f"Failed to connect to {url}: {e}") from e
309
+ except httpx.TimeoutException as e:
310
+ raise TimeoutError(f"Request to {url} timed out") from e
311
+
312
+ # Parse response
313
+ try:
314
+ response_body = response.json()
315
+ except Exception:
316
+ response_body = {"message": response.text, "code": "PARSE_ERROR"}
317
+
318
+ # Check for errors
319
+ raise_for_status(response.status_code, response_body)
320
+
321
+ return response_body
322
+
323
+ async def get(
324
+ self,
325
+ path: str,
326
+ *,
327
+ params: Optional[Query] = None,
328
+ extra_headers: Optional[Headers] = None,
329
+ timeout: Union[Timeout, NotGiven] = NOT_GIVEN,
330
+ ) -> Any:
331
+ """Make an async GET request."""
332
+ return await self._request(
333
+ "GET", path, params=params, extra_headers=extra_headers, timeout=timeout
334
+ )
335
+
336
+ async def post(
337
+ self,
338
+ path: str,
339
+ *,
340
+ json: Optional[dict[str, Any]] = None,
341
+ params: Optional[Query] = None,
342
+ extra_headers: Optional[Headers] = None,
343
+ timeout: Union[Timeout, NotGiven] = NOT_GIVEN,
344
+ ) -> Any:
345
+ """Make an async POST request."""
346
+ return await self._request(
347
+ "POST",
348
+ path,
349
+ json=json,
350
+ params=params,
351
+ extra_headers=extra_headers,
352
+ timeout=timeout,
353
+ )