yjdata 0.2.3__tar.gz → 0.2.4__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 (39) hide show
  1. yjdata-0.2.4/PKG-INFO +66 -0
  2. yjdata-0.2.4/README.md +55 -0
  3. {yjdata-0.2.3 → yjdata-0.2.4}/pyproject.toml +1 -1
  4. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/web/calendar.py +3 -3
  5. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/web/client.py +12 -0
  6. yjdata-0.2.4/src/yjdata/web/cn_future.py +110 -0
  7. yjdata-0.2.4/src/yjdata/web/cn_stock.py +108 -0
  8. yjdata-0.2.4/src/yjdata.egg-info/PKG-INFO +66 -0
  9. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata.egg-info/SOURCES.txt +2 -0
  10. yjdata-0.2.3/PKG-INFO +0 -130
  11. yjdata-0.2.3/README.md +0 -119
  12. yjdata-0.2.3/src/yjdata.egg-info/PKG-INFO +0 -130
  13. {yjdata-0.2.3 → yjdata-0.2.4}/setup.cfg +0 -0
  14. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/__init__.py +0 -0
  15. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/_optional.py +0 -0
  16. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/dataset/__init__.py +0 -0
  17. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/dataset/common.py +0 -0
  18. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/dataset/quote.py +0 -0
  19. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/dataset/snapshot.py +0 -0
  20. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/dataset/trade.py +0 -0
  21. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/db/__init__.py +0 -0
  22. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/db/adapter.py +0 -0
  23. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/db/expression.py +0 -0
  24. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/db/mysql.py +0 -0
  25. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/db/postgres.py +0 -0
  26. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/db/sql.py +0 -0
  27. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/dtype.py +0 -0
  28. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/utlity/__init__.py +0 -0
  29. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/utlity/s3.py +0 -0
  30. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/web/__init__.py +0 -0
  31. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/web/_context.py +0 -0
  32. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/web/_input.py +0 -0
  33. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata/web/time_util.py +0 -0
  34. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata.egg-info/dependency_links.txt +0 -0
  35. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata.egg-info/requires.txt +0 -0
  36. {yjdata-0.2.3 → yjdata-0.2.4}/src/yjdata.egg-info/top_level.txt +0 -0
  37. {yjdata-0.2.3 → yjdata-0.2.4}/tests/test_batch_writes.py +0 -0
  38. {yjdata-0.2.3 → yjdata-0.2.4}/tests/test_last_of_group.py +0 -0
  39. {yjdata-0.2.3 → yjdata-0.2.4}/tests/test_mysql_batch_updates.py +0 -0
yjdata-0.2.4/PKG-INFO ADDED
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: yjdata
3
+ Version: 0.2.4
4
+ Summary: yjdata SDK for Python
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: httpx>=0.25
8
+ Requires-Dist: polars>=1.30
9
+ Requires-Dist: pyarrow>=18
10
+ Requires-Dist: pydantic>=2.11
11
+
12
+ # yjdata
13
+
14
+ Python SDK for the YJData web API, MySQL/PostgreSQL adapters, and Parquet utilities.
15
+
16
+ ## Web API
17
+
18
+ ```python
19
+ from yjdata import WebDataClient
20
+
21
+ client = WebDataClient()
22
+ prices = client.cn_stock.stk_eod_md("2026-11-06", ids="000001.XSHE")
23
+ for price in prices:
24
+ print(price.id, price.close)
25
+
26
+ trading_days = client.calendar.range("2026-11-01", "2026-11-06")
27
+ ```
28
+
29
+ | Method | Data |
30
+ | --- | --- |
31
+ | `cn_stock.stk_eod_md(date, ids=None, stable_id=True)` | Stock end-of-day prices |
32
+ | `cn_stock.stk_sod_ref(date, ids=None)` | Stock pre-market reference data |
33
+ | `cn_future.fut_eod_md(date, ids=None)` | Futures end-of-day prices |
34
+ | `cn_future.fut_sod_ref(date, ids=None)` | Futures pre-market reference data |
35
+ | `calendar.next_n(start, n)`, `last_n(end, n)`, `range(start, end)`, `is_trading_day(date)` | Trading calendar; optional `calendar="XSHG"` |
36
+
37
+ - Dates accept `YYYY-MM-DD`, `YYYYMMDD`, `datetime.date`, or `datetime.datetime`.
38
+ - IDs accept lists or comma-separated strings. `None` returns all records;
39
+ `[]` or a blank string returns none.
40
+ - Stock prices use first-listing IDs by default. `stable_id=False` uses the
41
+ ticker on the requested date.
42
+ - Market data returns Pydantic models with friendly fields such as `close`,
43
+ `volume`, and `display_id`; `time` is a `YYYY-MM-DD` string. Models accept
44
+ friendly names and API aliases. `model_dump(by_alias=True)` exports API names.
45
+ - Use `AsyncWebDataClient` with `await` for the same methods.
46
+
47
+ See the [OpenAPI schema](https://yjdata-api.yaojingriver.internal/openapi.json)
48
+ for endpoint and field details.
49
+
50
+ ## Database adapters
51
+
52
+ `MysqlAdapter` (MySQL 8.0+) and `PostgresAdapter` support
53
+ `select_last_of_group(table, group_by=..., sort_by=...)` and
54
+ `select_last_of_group_polars(...)`, returning dictionaries or a Polars DataFrame.
55
+
56
+ These methods select the last complete row per group in SQL. Use `ascending`
57
+ to control sort direction, `filter` before selection, `post_filter` afterward,
58
+ and `columns` for projection. NULLs sort below non-NULL values; include a unique
59
+ tie-breaker in `sort_by` for deterministic selection. Group output order is
60
+ unspecified.
61
+
62
+ ## Tests
63
+
64
+ ```sh
65
+ PYTHONPATH=src python -m unittest discover -s tests -v
66
+ ```
yjdata-0.2.4/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # yjdata
2
+
3
+ Python SDK for the YJData web API, MySQL/PostgreSQL adapters, and Parquet utilities.
4
+
5
+ ## Web API
6
+
7
+ ```python
8
+ from yjdata import WebDataClient
9
+
10
+ client = WebDataClient()
11
+ prices = client.cn_stock.stk_eod_md("2026-11-06", ids="000001.XSHE")
12
+ for price in prices:
13
+ print(price.id, price.close)
14
+
15
+ trading_days = client.calendar.range("2026-11-01", "2026-11-06")
16
+ ```
17
+
18
+ | Method | Data |
19
+ | --- | --- |
20
+ | `cn_stock.stk_eod_md(date, ids=None, stable_id=True)` | Stock end-of-day prices |
21
+ | `cn_stock.stk_sod_ref(date, ids=None)` | Stock pre-market reference data |
22
+ | `cn_future.fut_eod_md(date, ids=None)` | Futures end-of-day prices |
23
+ | `cn_future.fut_sod_ref(date, ids=None)` | Futures pre-market reference data |
24
+ | `calendar.next_n(start, n)`, `last_n(end, n)`, `range(start, end)`, `is_trading_day(date)` | Trading calendar; optional `calendar="XSHG"` |
25
+
26
+ - Dates accept `YYYY-MM-DD`, `YYYYMMDD`, `datetime.date`, or `datetime.datetime`.
27
+ - IDs accept lists or comma-separated strings. `None` returns all records;
28
+ `[]` or a blank string returns none.
29
+ - Stock prices use first-listing IDs by default. `stable_id=False` uses the
30
+ ticker on the requested date.
31
+ - Market data returns Pydantic models with friendly fields such as `close`,
32
+ `volume`, and `display_id`; `time` is a `YYYY-MM-DD` string. Models accept
33
+ friendly names and API aliases. `model_dump(by_alias=True)` exports API names.
34
+ - Use `AsyncWebDataClient` with `await` for the same methods.
35
+
36
+ See the [OpenAPI schema](https://yjdata-api.yaojingriver.internal/openapi.json)
37
+ for endpoint and field details.
38
+
39
+ ## Database adapters
40
+
41
+ `MysqlAdapter` (MySQL 8.0+) and `PostgresAdapter` support
42
+ `select_last_of_group(table, group_by=..., sort_by=...)` and
43
+ `select_last_of_group_polars(...)`, returning dictionaries or a Polars DataFrame.
44
+
45
+ These methods select the last complete row per group in SQL. Use `ascending`
46
+ to control sort direction, `filter` before selection, `post_filter` afterward,
47
+ and `columns` for projection. NULLs sort below non-NULL values; include a unique
48
+ tie-breaker in `sort_by` for deterministic selection. Group output order is
49
+ unspecified.
50
+
51
+ ## Tests
52
+
53
+ ```sh
54
+ PYTHONPATH=src python -m unittest discover -s tests -v
55
+ ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "yjdata"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "yjdata SDK for Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -49,7 +49,7 @@ class CalendarWebClient:
49
49
  return self._context.post(f'{self._api_root}/next-n', payload={
50
50
  'start': parse_date_str_arg(start, False),
51
51
  'n': n,
52
- 'calendar': calendar
52
+ 'exchange': calendar
53
53
  })
54
54
 
55
55
  def last_n(
@@ -83,7 +83,7 @@ class CalendarWebClient:
83
83
  return self._context.post(f'{self._api_root}/last-n', payload={
84
84
  'end': parse_date_str_arg(end, False),
85
85
  'n': n,
86
- 'calendar': calendar
86
+ 'exchange': calendar
87
87
  })
88
88
 
89
89
  def range(
@@ -118,7 +118,7 @@ class CalendarWebClient:
118
118
  return self._context.post(f'{self._api_root}/range', payload={
119
119
  'start': parse_date_str_arg(start, False),
120
120
  'end': parse_date_str_arg(end, False),
121
- 'calendar': calendar
121
+ 'exchange': calendar
122
122
  })
123
123
 
124
124
  @abstractmethod
@@ -1,6 +1,8 @@
1
1
  import os
2
2
 
3
3
  from .calendar import SyncCalenderWebClient, AsyncCalendarWebClient
4
+ from .cn_future import CnFutureWebClient
5
+ from .cn_stock import CnStockWebClient
4
6
  from ._context import RequestContext, SyncRequestContext, AsyncRequestContext
5
7
 
6
8
 
@@ -23,6 +25,16 @@ class _WebClient:
23
25
  self._host = host
24
26
  self._context = context
25
27
 
28
+ @property
29
+ def cn_stock(self) -> CnStockWebClient:
30
+ """API set for domestic stock market data."""
31
+ return CnStockWebClient(self._host, self._context)
32
+
33
+ @property
34
+ def cn_future(self) -> CnFutureWebClient:
35
+ """API set for domestic futures market data."""
36
+ return CnFutureWebClient(self._host, self._context)
37
+
26
38
 
27
39
  class WebDataClient(_WebClient):
28
40
  """Client for accessing YJData web API."""
@@ -0,0 +1,110 @@
1
+ from typing import Awaitable, List, Union
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+ from ._context import RequestContext
6
+ from ._input import DateStrArgType, StrListArgType, parse_date_str_arg, parse_str_list_arg
7
+
8
+
9
+ __all__ = ['CnFutureWebClient', 'FutEodMd', 'FutSodRef']
10
+
11
+
12
+ class FutEodMd(BaseModel):
13
+ """Futures end-of-day prices, volume, turnover and open interest."""
14
+
15
+ model_config = ConfigDict(validate_by_name=True)
16
+
17
+ id: str
18
+ time: str = Field(alias='date')
19
+ pre_settle: float = Field(alias='ps')
20
+ pre_close: float = Field(alias='pc')
21
+ open: float = Field(alias='o')
22
+ high: float = Field(alias='h')
23
+ low: float = Field(alias='l')
24
+ close: float = Field(alias='c')
25
+ settle: float = Field(alias='s')
26
+ volume: int = Field(alias='vol')
27
+ amount: float = Field(alias='amt')
28
+ open_interest: int = Field(alias='oi')
29
+
30
+
31
+ class FutSodRef(BaseModel):
32
+ """Futures pre-market reference data, including contract specifications."""
33
+
34
+ model_config = ConfigDict(validate_by_name=True)
35
+
36
+ id: str
37
+ time: str = Field(alias='date')
38
+ name: str
39
+ display_id: str = Field(alias='dispId')
40
+ ticker: str
41
+ trading_status: str = Field(alias='status')
42
+ underlying_id: Union[str, None] = Field(alias='ulId')
43
+ product: str = Field(alias='prod')
44
+ multiplier: float = Field(alias='mult')
45
+ tick: float
46
+ pre_close: float = Field(alias='pc')
47
+ pre_settle: float = Field(alias='ps')
48
+ auct_hours: str = Field(alias='auct')
49
+ cont_hours: str = Field(alias='cont')
50
+ upper_limit: float = Field(alias='ul')
51
+ lower_limit: float = Field(alias='ll')
52
+
53
+
54
+ class CnFutureWebClient:
55
+
56
+ def __init__(self, host: str, context: RequestContext):
57
+ self._api_root = f'http://{host}/v1/cn_future'
58
+ self._context = context
59
+
60
+ def fut_eod_md(
61
+ self,
62
+ date: DateStrArgType,
63
+ ids: StrListArgType = None
64
+ ) -> Union[List[FutEodMd], Awaitable[List[FutEodMd]]]:
65
+ """Get domestic futures end-of-day market data for a trading date.
66
+
67
+ Parameters
68
+ ----------
69
+ date : str, datetime.date or datetime.datetime
70
+ Trading date; strings accept YYYY-MM-DD or YYYYMMDD.
71
+ ids : str or list[str], optional
72
+ IDs as a list or comma-separated string. None returns all records;
73
+ an empty list or blank string returns no records.
74
+
75
+ Returns
76
+ -------
77
+ list[FutEodMd] or Awaitable[list[FutEodMd]]
78
+ Validated records; await the result with AsyncWebDataClient.
79
+ """
80
+ return self._context.post_model_list(FutEodMd, f'{self._api_root}/fut-eod-md', payload={
81
+ 'date': parse_date_str_arg(date, False),
82
+ 'ids': None if ids is None else parse_str_list_arg(ids)
83
+ })
84
+
85
+ def fut_sod_ref(
86
+ self,
87
+ date: DateStrArgType,
88
+ ids: StrListArgType = None
89
+ ) -> Union[List[FutSodRef], Awaitable[List[FutSodRef]]]:
90
+ """Get domestic futures pre-market reference data for a trading date.
91
+
92
+ Parameters
93
+ ----------
94
+ date : str, datetime.date or datetime.datetime
95
+ Trading date; strings accept YYYY-MM-DD or YYYYMMDD.
96
+ ids : str or list[str], optional
97
+ IDs as a list or comma-separated string. None returns all records;
98
+ an empty list or blank string returns no records.
99
+
100
+ Returns
101
+ -------
102
+ list[FutSodRef] or Awaitable[list[FutSodRef]]
103
+ Validated records; await the result with AsyncWebDataClient.
104
+ API fields dispId and ulId use display_id and underlying_id.
105
+ underlying_id is None for contracts without an underlying index.
106
+ """
107
+ return self._context.post_model_list(FutSodRef, f'{self._api_root}/fut-sod-ref', payload={
108
+ 'date': parse_date_str_arg(date, False),
109
+ 'ids': None if ids is None else parse_str_list_arg(ids)
110
+ })
@@ -0,0 +1,108 @@
1
+ from typing import Awaitable, List, Union
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+ from ._context import RequestContext
6
+ from ._input import DateStrArgType, StrListArgType, parse_date_str_arg, parse_str_list_arg
7
+
8
+
9
+ __all__ = ['CnStockWebClient', 'StkEodMd', 'StkSodRef']
10
+
11
+
12
+ class StkEodMd(BaseModel):
13
+ """Stock end-of-day prices, volume and turnover."""
14
+
15
+ model_config = ConfigDict(validate_by_name=True)
16
+
17
+ id: str
18
+ time: str = Field(alias='date')
19
+ pre_close: float = Field(alias='pc')
20
+ open: float = Field(alias='o')
21
+ high: float = Field(alias='h')
22
+ low: float = Field(alias='l')
23
+ close: float = Field(alias='c')
24
+ volume: int = Field(alias='vol')
25
+ amount: float = Field(alias='amt')
26
+
27
+
28
+ class StkSodRef(BaseModel):
29
+ """Stock pre-market reference data. IDs use the first listing ticker."""
30
+
31
+ model_config = ConfigDict(validate_by_name=True)
32
+
33
+ id: str
34
+ time: str = Field(alias='date')
35
+ name: str
36
+ display_id: str = Field(alias='dispId')
37
+ ticker: str
38
+ trading_status: str = Field(alias='status')
39
+ st_status: str = Field(alias='st')
40
+ pre_close: float = Field(alias='pc')
41
+ prior_close: float = Field(alias='prc')
42
+ upper_limit: float = Field(alias='ul')
43
+ lower_limit: float = Field(alias='ll')
44
+ auct_hours: str = Field(alias='auct')
45
+ cont_hours: str = Field(alias='cont')
46
+
47
+
48
+ class CnStockWebClient:
49
+
50
+ def __init__(self, host: str, context: RequestContext):
51
+ self._api_root = f'http://{host}/v1/cn_stock'
52
+ self._context = context
53
+
54
+ def stk_eod_md(
55
+ self,
56
+ date: DateStrArgType,
57
+ ids: StrListArgType = None,
58
+ stable_id: bool = True
59
+ ) -> Union[List[StkEodMd], Awaitable[List[StkEodMd]]]:
60
+ """Get stock end-of-day market data for a trading date.
61
+
62
+ Parameters
63
+ ----------
64
+ date : str, datetime.date or datetime.datetime
65
+ Trading date; strings accept YYYY-MM-DD or YYYYMMDD.
66
+ ids : str or list[str], optional
67
+ IDs as a list or comma-separated string. None returns all records;
68
+ an empty list or blank string returns no records.
69
+ stable_id : bool, default: True
70
+ Use the first listing ticker plus exchange for both the ID filter
71
+ and returned IDs. False uses the ticker on the requested date.
72
+
73
+ Returns
74
+ -------
75
+ list[StkEodMd] or Awaitable[list[StkEodMd]]
76
+ Validated records; await the result with AsyncWebDataClient.
77
+ """
78
+ return self._context.post_model_list(StkEodMd, f'{self._api_root}/stk-eod-md', payload={
79
+ 'date': parse_date_str_arg(date, False),
80
+ 'ids': None if ids is None else parse_str_list_arg(ids),
81
+ 'stableId': stable_id
82
+ })
83
+
84
+ def stk_sod_ref(
85
+ self,
86
+ date: DateStrArgType,
87
+ ids: StrListArgType = None
88
+ ) -> Union[List[StkSodRef], Awaitable[List[StkSodRef]]]:
89
+ """Get stock pre-market reference data for a trading date.
90
+
91
+ Parameters
92
+ ----------
93
+ date : str, datetime.date or datetime.datetime
94
+ Trading date; strings accept YYYY-MM-DD or YYYYMMDD.
95
+ ids : str or list[str], optional
96
+ IDs as a list or comma-separated string. None returns all records;
97
+ an empty list or blank string returns no records.
98
+
99
+ Returns
100
+ -------
101
+ list[StkSodRef] or Awaitable[list[StkSodRef]]
102
+ Validated records; await the result with AsyncWebDataClient.
103
+ Fields use backend Python names, such as display_id for dispId.
104
+ """
105
+ return self._context.post_model_list(StkSodRef, f'{self._api_root}/stk-sod-ref', payload={
106
+ 'date': parse_date_str_arg(date, False),
107
+ 'ids': None if ids is None else parse_str_list_arg(ids)
108
+ })
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: yjdata
3
+ Version: 0.2.4
4
+ Summary: yjdata SDK for Python
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: httpx>=0.25
8
+ Requires-Dist: polars>=1.30
9
+ Requires-Dist: pyarrow>=18
10
+ Requires-Dist: pydantic>=2.11
11
+
12
+ # yjdata
13
+
14
+ Python SDK for the YJData web API, MySQL/PostgreSQL adapters, and Parquet utilities.
15
+
16
+ ## Web API
17
+
18
+ ```python
19
+ from yjdata import WebDataClient
20
+
21
+ client = WebDataClient()
22
+ prices = client.cn_stock.stk_eod_md("2026-11-06", ids="000001.XSHE")
23
+ for price in prices:
24
+ print(price.id, price.close)
25
+
26
+ trading_days = client.calendar.range("2026-11-01", "2026-11-06")
27
+ ```
28
+
29
+ | Method | Data |
30
+ | --- | --- |
31
+ | `cn_stock.stk_eod_md(date, ids=None, stable_id=True)` | Stock end-of-day prices |
32
+ | `cn_stock.stk_sod_ref(date, ids=None)` | Stock pre-market reference data |
33
+ | `cn_future.fut_eod_md(date, ids=None)` | Futures end-of-day prices |
34
+ | `cn_future.fut_sod_ref(date, ids=None)` | Futures pre-market reference data |
35
+ | `calendar.next_n(start, n)`, `last_n(end, n)`, `range(start, end)`, `is_trading_day(date)` | Trading calendar; optional `calendar="XSHG"` |
36
+
37
+ - Dates accept `YYYY-MM-DD`, `YYYYMMDD`, `datetime.date`, or `datetime.datetime`.
38
+ - IDs accept lists or comma-separated strings. `None` returns all records;
39
+ `[]` or a blank string returns none.
40
+ - Stock prices use first-listing IDs by default. `stable_id=False` uses the
41
+ ticker on the requested date.
42
+ - Market data returns Pydantic models with friendly fields such as `close`,
43
+ `volume`, and `display_id`; `time` is a `YYYY-MM-DD` string. Models accept
44
+ friendly names and API aliases. `model_dump(by_alias=True)` exports API names.
45
+ - Use `AsyncWebDataClient` with `await` for the same methods.
46
+
47
+ See the [OpenAPI schema](https://yjdata-api.yaojingriver.internal/openapi.json)
48
+ for endpoint and field details.
49
+
50
+ ## Database adapters
51
+
52
+ `MysqlAdapter` (MySQL 8.0+) and `PostgresAdapter` support
53
+ `select_last_of_group(table, group_by=..., sort_by=...)` and
54
+ `select_last_of_group_polars(...)`, returning dictionaries or a Polars DataFrame.
55
+
56
+ These methods select the last complete row per group in SQL. Use `ascending`
57
+ to control sort direction, `filter` before selection, `post_filter` afterward,
58
+ and `columns` for projection. NULLs sort below non-NULL values; include a unique
59
+ tie-breaker in `sort_by` for deterministic selection. Group output order is
60
+ unspecified.
61
+
62
+ ## Tests
63
+
64
+ ```sh
65
+ PYTHONPATH=src python -m unittest discover -s tests -v
66
+ ```
@@ -26,6 +26,8 @@ src/yjdata/web/_context.py
26
26
  src/yjdata/web/_input.py
27
27
  src/yjdata/web/calendar.py
28
28
  src/yjdata/web/client.py
29
+ src/yjdata/web/cn_future.py
30
+ src/yjdata/web/cn_stock.py
29
31
  src/yjdata/web/time_util.py
30
32
  tests/test_batch_writes.py
31
33
  tests/test_last_of_group.py
yjdata-0.2.3/PKG-INFO DELETED
@@ -1,130 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: yjdata
3
- Version: 0.2.3
4
- Summary: yjdata SDK for Python
5
- Requires-Python: >=3.10
6
- Description-Content-Type: text/markdown
7
- Requires-Dist: httpx>=0.25
8
- Requires-Dist: polars>=1.30
9
- Requires-Dist: pyarrow>=18
10
- Requires-Dist: pydantic>=2.11
11
-
12
- # yjdata
13
-
14
- Python SDK for yjdata, including MySQL and PostgreSQL database adapters.
15
-
16
- ## Select the last row of each group
17
-
18
- `MysqlAdapter` (MySQL 8.0+) and `PostgresAdapter` provide:
19
-
20
- - `select_last_of_group(...) -> list[dict[str, Any]]`
21
- - `select_last_of_group_polars(...) -> polars.DataFrame`
22
-
23
- Both methods execute grouping, row selection, filtering, and projection in SQL
24
- using `ROW_NUMBER()`. They accept the table name followed by keyword arguments:
25
-
26
- | Parameter | Meaning |
27
- | --- | --- |
28
- | `table` | Table name, optionally qualified by schema or database. |
29
- | `group_by: Sequence[str]` | Required nonempty sequence of grouping columns. |
30
- | `sort_by: Sequence[str]` | Required nonempty sequence of sort columns, in priority order. |
31
- | `ascending: bool \| Sequence[bool] = True` | One direction for every sort column, or one boolean per column. |
32
- | `columns: Sequence[str] \| None = None` | Nonempty sequence of output columns; `None` returns all original columns. |
33
- | `filter: Expression \| None = None` | Filter source rows before choosing a row per group. |
34
- | `post_filter: Expression \| None = None` | Filter the chosen rows, before projecting output columns. |
35
-
36
- The operation first applies `filter`, orders each group, selects its last complete
37
- source row, applies `post_filter`, and finally projects `columns`. Values come
38
- from the same row, including NULL values; this is not a separate last-non-NULL
39
- aggregation for each column. A post-filter rejection removes the group entirely,
40
- without falling back to an older row.
41
-
42
- NULLs sort below all non-NULL values on both backends: first for ascending order
43
- and last for descending order. Consequently, ascending selection prefers the
44
- largest non-NULL value, while descending selection prefers NULL when present.
45
- This rule applies to each sort column. NULL grouping keys group together.
46
- For deterministic selection among ties, include a unique column in `sort_by`,
47
- such as `sort_by=["change_time", "record_id"]`. Output order across groups is
48
- unspecified.
49
-
50
- Group, sort, and filter columns need not appear in `columns`. Internal ranking
51
- columns are excluded by the SQL projection, even if the table has similarly
52
- named columns. With `columns=None`, an additional zero-row query reads column
53
- metadata so the adapter can explicitly project all original columns.
54
- An empty result is `[]` for dictionaries or a DataFrame with no columns for
55
- Polars, matching the existing selection methods. Polars infers types from all
56
- returned rows.
57
-
58
- ### THS concept snapshots
59
-
60
- For an existing adapter `client` and a cutoff value `cutoff`:
61
-
62
- ```python
63
- import yjdata.db.expression as ye
64
-
65
- data = client.select_last_of_group_polars(
66
- table="thsetl.concept_change_record",
67
- group_by=["concept_id"],
68
- sort_by=["change_time"],
69
- ascending=True,
70
- columns=["concept_id", "concept_name"],
71
- filter=(
72
- (ye.col("change_time") <= cutoff)
73
- & (ye.col("isvalid") == "1")
74
- ),
75
- post_filter=ye.col("change_type") != "conceptNegation",
76
- )
77
- ```
78
-
79
- This selects the latest valid change at or before the cutoff, then excludes
80
- concepts whose selected change is a negation. The MySQL query has this form:
81
-
82
- ```sql
83
- SELECT `concept_id`, `concept_name`
84
- FROM (
85
- SELECT `concept_id`, `concept_name`, `change_type`,
86
- ROW_NUMBER() OVER (
87
- PARTITION BY `concept_id`
88
- ORDER BY (`change_time` IS NULL) ASC, `change_time` DESC
89
- ) AS `_yjdata_row_number`
90
- FROM `thsetl`.`concept_change_record`
91
- WHERE (`change_time` <= %s) AND (`isvalid` = %s)
92
- ) AS `_yjdata_latest`
93
- WHERE `_yjdata_row_number` = 1 AND (`change_type` <> %s)
94
- ```
95
-
96
- Values are bound in order: `[cutoff, "1", "conceptNegation"]`. PostgreSQL uses
97
- the same query structure with double-quoted identifiers. Expressions retain
98
- normal SQL NULL semantics: for example, `change_type <> 'conceptNegation'`
99
- also excludes NULL `change_type` values.
100
-
101
- ## Batch writes
102
-
103
- PostgreSQL inserts, upserts and updates use `psycopg2.extras.execute_batch`.
104
- MySQL inserts and upserts use PyMySQL's optimized `executemany`. MySQL updates
105
- combine driver-escaped UPDATE statements into one request, preserving input
106
- order, repeated keys and ordinary `=` matching (NULL keys do not match).
107
- MySQL connections enable `CLIENT.MULTI_STATEMENTS` for these update batches.
108
-
109
- `batch_size` limits rows per batch. MySQL update batches also respect the
110
- cursor's `max_stmt_length` in encoded bytes (normally about 1 MB); an individual
111
- larger statement is sent alone and remains subject to the server's packet limit.
112
- Every result is consumed so errors in later statements propagate before commit.
113
- With the default transactional connection settings and transactional tables,
114
- all batches commit together or roll back on failure. This reduces network
115
- round trips; the server still executes one UPDATE per input row.
116
-
117
- ## Tests
118
-
119
- ```sh
120
- python -m unittest discover -s tests -v
121
- ```
122
-
123
- The grouped-selection tests exercise both adapters' validation, quoting,
124
- parameter ordering, and result conversion. Generated queries execute against
125
- SQLite's window engine with DB-API placeholders translated, so tests require
126
- neither optional database drivers nor running servers. This does not replace
127
- integration testing against MySQL and PostgreSQL.
128
-
129
- With `pymysql` installed, the MySQL batch-update tests also exercise the real
130
- driver's escaping, encoded request sizes and result handling without a server.
yjdata-0.2.3/README.md DELETED
@@ -1,119 +0,0 @@
1
- # yjdata
2
-
3
- Python SDK for yjdata, including MySQL and PostgreSQL database adapters.
4
-
5
- ## Select the last row of each group
6
-
7
- `MysqlAdapter` (MySQL 8.0+) and `PostgresAdapter` provide:
8
-
9
- - `select_last_of_group(...) -> list[dict[str, Any]]`
10
- - `select_last_of_group_polars(...) -> polars.DataFrame`
11
-
12
- Both methods execute grouping, row selection, filtering, and projection in SQL
13
- using `ROW_NUMBER()`. They accept the table name followed by keyword arguments:
14
-
15
- | Parameter | Meaning |
16
- | --- | --- |
17
- | `table` | Table name, optionally qualified by schema or database. |
18
- | `group_by: Sequence[str]` | Required nonempty sequence of grouping columns. |
19
- | `sort_by: Sequence[str]` | Required nonempty sequence of sort columns, in priority order. |
20
- | `ascending: bool \| Sequence[bool] = True` | One direction for every sort column, or one boolean per column. |
21
- | `columns: Sequence[str] \| None = None` | Nonempty sequence of output columns; `None` returns all original columns. |
22
- | `filter: Expression \| None = None` | Filter source rows before choosing a row per group. |
23
- | `post_filter: Expression \| None = None` | Filter the chosen rows, before projecting output columns. |
24
-
25
- The operation first applies `filter`, orders each group, selects its last complete
26
- source row, applies `post_filter`, and finally projects `columns`. Values come
27
- from the same row, including NULL values; this is not a separate last-non-NULL
28
- aggregation for each column. A post-filter rejection removes the group entirely,
29
- without falling back to an older row.
30
-
31
- NULLs sort below all non-NULL values on both backends: first for ascending order
32
- and last for descending order. Consequently, ascending selection prefers the
33
- largest non-NULL value, while descending selection prefers NULL when present.
34
- This rule applies to each sort column. NULL grouping keys group together.
35
- For deterministic selection among ties, include a unique column in `sort_by`,
36
- such as `sort_by=["change_time", "record_id"]`. Output order across groups is
37
- unspecified.
38
-
39
- Group, sort, and filter columns need not appear in `columns`. Internal ranking
40
- columns are excluded by the SQL projection, even if the table has similarly
41
- named columns. With `columns=None`, an additional zero-row query reads column
42
- metadata so the adapter can explicitly project all original columns.
43
- An empty result is `[]` for dictionaries or a DataFrame with no columns for
44
- Polars, matching the existing selection methods. Polars infers types from all
45
- returned rows.
46
-
47
- ### THS concept snapshots
48
-
49
- For an existing adapter `client` and a cutoff value `cutoff`:
50
-
51
- ```python
52
- import yjdata.db.expression as ye
53
-
54
- data = client.select_last_of_group_polars(
55
- table="thsetl.concept_change_record",
56
- group_by=["concept_id"],
57
- sort_by=["change_time"],
58
- ascending=True,
59
- columns=["concept_id", "concept_name"],
60
- filter=(
61
- (ye.col("change_time") <= cutoff)
62
- & (ye.col("isvalid") == "1")
63
- ),
64
- post_filter=ye.col("change_type") != "conceptNegation",
65
- )
66
- ```
67
-
68
- This selects the latest valid change at or before the cutoff, then excludes
69
- concepts whose selected change is a negation. The MySQL query has this form:
70
-
71
- ```sql
72
- SELECT `concept_id`, `concept_name`
73
- FROM (
74
- SELECT `concept_id`, `concept_name`, `change_type`,
75
- ROW_NUMBER() OVER (
76
- PARTITION BY `concept_id`
77
- ORDER BY (`change_time` IS NULL) ASC, `change_time` DESC
78
- ) AS `_yjdata_row_number`
79
- FROM `thsetl`.`concept_change_record`
80
- WHERE (`change_time` <= %s) AND (`isvalid` = %s)
81
- ) AS `_yjdata_latest`
82
- WHERE `_yjdata_row_number` = 1 AND (`change_type` <> %s)
83
- ```
84
-
85
- Values are bound in order: `[cutoff, "1", "conceptNegation"]`. PostgreSQL uses
86
- the same query structure with double-quoted identifiers. Expressions retain
87
- normal SQL NULL semantics: for example, `change_type <> 'conceptNegation'`
88
- also excludes NULL `change_type` values.
89
-
90
- ## Batch writes
91
-
92
- PostgreSQL inserts, upserts and updates use `psycopg2.extras.execute_batch`.
93
- MySQL inserts and upserts use PyMySQL's optimized `executemany`. MySQL updates
94
- combine driver-escaped UPDATE statements into one request, preserving input
95
- order, repeated keys and ordinary `=` matching (NULL keys do not match).
96
- MySQL connections enable `CLIENT.MULTI_STATEMENTS` for these update batches.
97
-
98
- `batch_size` limits rows per batch. MySQL update batches also respect the
99
- cursor's `max_stmt_length` in encoded bytes (normally about 1 MB); an individual
100
- larger statement is sent alone and remains subject to the server's packet limit.
101
- Every result is consumed so errors in later statements propagate before commit.
102
- With the default transactional connection settings and transactional tables,
103
- all batches commit together or roll back on failure. This reduces network
104
- round trips; the server still executes one UPDATE per input row.
105
-
106
- ## Tests
107
-
108
- ```sh
109
- python -m unittest discover -s tests -v
110
- ```
111
-
112
- The grouped-selection tests exercise both adapters' validation, quoting,
113
- parameter ordering, and result conversion. Generated queries execute against
114
- SQLite's window engine with DB-API placeholders translated, so tests require
115
- neither optional database drivers nor running servers. This does not replace
116
- integration testing against MySQL and PostgreSQL.
117
-
118
- With `pymysql` installed, the MySQL batch-update tests also exercise the real
119
- driver's escaping, encoded request sizes and result handling without a server.
@@ -1,130 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: yjdata
3
- Version: 0.2.3
4
- Summary: yjdata SDK for Python
5
- Requires-Python: >=3.10
6
- Description-Content-Type: text/markdown
7
- Requires-Dist: httpx>=0.25
8
- Requires-Dist: polars>=1.30
9
- Requires-Dist: pyarrow>=18
10
- Requires-Dist: pydantic>=2.11
11
-
12
- # yjdata
13
-
14
- Python SDK for yjdata, including MySQL and PostgreSQL database adapters.
15
-
16
- ## Select the last row of each group
17
-
18
- `MysqlAdapter` (MySQL 8.0+) and `PostgresAdapter` provide:
19
-
20
- - `select_last_of_group(...) -> list[dict[str, Any]]`
21
- - `select_last_of_group_polars(...) -> polars.DataFrame`
22
-
23
- Both methods execute grouping, row selection, filtering, and projection in SQL
24
- using `ROW_NUMBER()`. They accept the table name followed by keyword arguments:
25
-
26
- | Parameter | Meaning |
27
- | --- | --- |
28
- | `table` | Table name, optionally qualified by schema or database. |
29
- | `group_by: Sequence[str]` | Required nonempty sequence of grouping columns. |
30
- | `sort_by: Sequence[str]` | Required nonempty sequence of sort columns, in priority order. |
31
- | `ascending: bool \| Sequence[bool] = True` | One direction for every sort column, or one boolean per column. |
32
- | `columns: Sequence[str] \| None = None` | Nonempty sequence of output columns; `None` returns all original columns. |
33
- | `filter: Expression \| None = None` | Filter source rows before choosing a row per group. |
34
- | `post_filter: Expression \| None = None` | Filter the chosen rows, before projecting output columns. |
35
-
36
- The operation first applies `filter`, orders each group, selects its last complete
37
- source row, applies `post_filter`, and finally projects `columns`. Values come
38
- from the same row, including NULL values; this is not a separate last-non-NULL
39
- aggregation for each column. A post-filter rejection removes the group entirely,
40
- without falling back to an older row.
41
-
42
- NULLs sort below all non-NULL values on both backends: first for ascending order
43
- and last for descending order. Consequently, ascending selection prefers the
44
- largest non-NULL value, while descending selection prefers NULL when present.
45
- This rule applies to each sort column. NULL grouping keys group together.
46
- For deterministic selection among ties, include a unique column in `sort_by`,
47
- such as `sort_by=["change_time", "record_id"]`. Output order across groups is
48
- unspecified.
49
-
50
- Group, sort, and filter columns need not appear in `columns`. Internal ranking
51
- columns are excluded by the SQL projection, even if the table has similarly
52
- named columns. With `columns=None`, an additional zero-row query reads column
53
- metadata so the adapter can explicitly project all original columns.
54
- An empty result is `[]` for dictionaries or a DataFrame with no columns for
55
- Polars, matching the existing selection methods. Polars infers types from all
56
- returned rows.
57
-
58
- ### THS concept snapshots
59
-
60
- For an existing adapter `client` and a cutoff value `cutoff`:
61
-
62
- ```python
63
- import yjdata.db.expression as ye
64
-
65
- data = client.select_last_of_group_polars(
66
- table="thsetl.concept_change_record",
67
- group_by=["concept_id"],
68
- sort_by=["change_time"],
69
- ascending=True,
70
- columns=["concept_id", "concept_name"],
71
- filter=(
72
- (ye.col("change_time") <= cutoff)
73
- & (ye.col("isvalid") == "1")
74
- ),
75
- post_filter=ye.col("change_type") != "conceptNegation",
76
- )
77
- ```
78
-
79
- This selects the latest valid change at or before the cutoff, then excludes
80
- concepts whose selected change is a negation. The MySQL query has this form:
81
-
82
- ```sql
83
- SELECT `concept_id`, `concept_name`
84
- FROM (
85
- SELECT `concept_id`, `concept_name`, `change_type`,
86
- ROW_NUMBER() OVER (
87
- PARTITION BY `concept_id`
88
- ORDER BY (`change_time` IS NULL) ASC, `change_time` DESC
89
- ) AS `_yjdata_row_number`
90
- FROM `thsetl`.`concept_change_record`
91
- WHERE (`change_time` <= %s) AND (`isvalid` = %s)
92
- ) AS `_yjdata_latest`
93
- WHERE `_yjdata_row_number` = 1 AND (`change_type` <> %s)
94
- ```
95
-
96
- Values are bound in order: `[cutoff, "1", "conceptNegation"]`. PostgreSQL uses
97
- the same query structure with double-quoted identifiers. Expressions retain
98
- normal SQL NULL semantics: for example, `change_type <> 'conceptNegation'`
99
- also excludes NULL `change_type` values.
100
-
101
- ## Batch writes
102
-
103
- PostgreSQL inserts, upserts and updates use `psycopg2.extras.execute_batch`.
104
- MySQL inserts and upserts use PyMySQL's optimized `executemany`. MySQL updates
105
- combine driver-escaped UPDATE statements into one request, preserving input
106
- order, repeated keys and ordinary `=` matching (NULL keys do not match).
107
- MySQL connections enable `CLIENT.MULTI_STATEMENTS` for these update batches.
108
-
109
- `batch_size` limits rows per batch. MySQL update batches also respect the
110
- cursor's `max_stmt_length` in encoded bytes (normally about 1 MB); an individual
111
- larger statement is sent alone and remains subject to the server's packet limit.
112
- Every result is consumed so errors in later statements propagate before commit.
113
- With the default transactional connection settings and transactional tables,
114
- all batches commit together or roll back on failure. This reduces network
115
- round trips; the server still executes one UPDATE per input row.
116
-
117
- ## Tests
118
-
119
- ```sh
120
- python -m unittest discover -s tests -v
121
- ```
122
-
123
- The grouped-selection tests exercise both adapters' validation, quoting,
124
- parameter ordering, and result conversion. Generated queries execute against
125
- SQLite's window engine with DB-API placeholders translated, so tests require
126
- neither optional database drivers nor running servers. This does not replace
127
- integration testing against MySQL and PostgreSQL.
128
-
129
- With `pymysql` installed, the MySQL batch-update tests also exercise the real
130
- driver's escaping, encoded request sizes and result handling without a server.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes