tejhq 0.2.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.
tejhq-0.2.0/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ venv/
9
+ .env
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .coverage
14
+ htmlcov/
15
+ *.swp
16
+ .DS_Store
tejhq-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TejHQ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
tejhq-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,257 @@
1
+ Metadata-Version: 2.5
2
+ Name: tejhq
3
+ Version: 0.2.0
4
+ Summary: Python SDK for tej-api: open EOD market data for NSE and BSE.
5
+ Project-URL: Homepage, https://tejhq.dev
6
+ Project-URL: Documentation, https://tejhq.dev/docs
7
+ Project-URL: Repository, https://github.com/tejhq/tej-sdk-py
8
+ Project-URL: Issues, https://github.com/tejhq/tej-sdk-py/issues
9
+ Project-URL: Changelog, https://github.com/tejhq/tej-sdk-py/blob/master/CHANGELOG.md
10
+ Author-email: TejHQ <support@tejhq.dev>
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: bhavcopy,bse,india,market-data,nse,ohlcv,stocks
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Financial and Insurance Industry
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Topic :: Office/Business :: Financial :: Investment
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.9
28
+ Provides-Extra: dev
29
+ Requires-Dist: mypy>=1.10; extra == 'dev'
30
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
31
+ Requires-Dist: pytest>=8.0; extra == 'dev'
32
+ Requires-Dist: ruff>=0.6; extra == 'dev'
33
+ Provides-Extra: pandas
34
+ Requires-Dist: pandas>=2.0; extra == 'pandas'
35
+ Provides-Extra: polars
36
+ Requires-Dist: polars>=1.0; extra == 'polars'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # tej
40
+
41
+ [![PyPI](https://img.shields.io/pypi/v/tejhq.svg)](https://pypi.org/project/tejhq/)
42
+ [![Python](https://img.shields.io/pypi/pyversions/tejhq.svg)](https://pypi.org/project/tejhq/)
43
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
44
+
45
+ Python SDK for [tej-api](https://api.tejhq.dev): open, free, end-of-day market data for NSE and BSE. No signup, no keys, no catch.
46
+
47
+ **Zero runtime dependencies.** Sync and async clients with identical surface. Returns plain `list[dict]` rows so you can feed them straight to polars, pandas, or your own code without an extra conversion step.
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pip install tejhq
53
+ ```
54
+
55
+ That is the entire install. No `httpx`, no `aiohttp`, no `pydantic`. Just stdlib. Distribution is `tejhq`; import as `tej`.
56
+
57
+ ## Quick start (sync)
58
+
59
+ ```python
60
+ from tej import Client
61
+
62
+ c = Client()
63
+
64
+ # Last 90 days of RELIANCE (default range)
65
+ rows = c.ohlcv("RELIANCE", "nse")
66
+ print(rows[-1])
67
+ # {'date': '2026-06-05', 'open': 1442.0, 'high': 1455.7, ...}
68
+
69
+ # Full history since 2010
70
+ rows = c.ohlcv("RELIANCE", "nse", "2010-01-04", "2026-06-05")
71
+ print(len(rows)) # ~4040
72
+
73
+ # Full-market snapshot for one trading date
74
+ snap = c.snapshot("nse", "2025-05-28")
75
+ print(len(snap)) # ~2700 symbols
76
+
77
+ # Corporate actions (dividends, splits, bonuses)
78
+ acts = c.actions("RELIANCE")
79
+ print(acts[0])
80
+ # {'exchange': 'NSE', 'type': 'dividend', 'ex_date': '2024-10-28', 'cash_amount': 10.0, ...}
81
+ ```
82
+
83
+ ## Quick start (async)
84
+
85
+ ```python
86
+ import asyncio
87
+ from tej import AsyncClient
88
+
89
+ async def main():
90
+ async with AsyncClient() as c:
91
+ # Fan out 10 symbols concurrently
92
+ symbols = ["RELIANCE", "TCS", "INFY", "HDFCBANK", "ICICIBANK",
93
+ "SBIN", "HINDUNILVR", "ITC", "LT", "AXISBANK"]
94
+ results = await asyncio.gather(*[c.ohlcv(s, "nse") for s in symbols])
95
+ for sym, rows in zip(symbols, results):
96
+ print(sym, len(rows))
97
+
98
+ asyncio.run(main())
99
+ ```
100
+
101
+ ## Use with polars / pandas
102
+
103
+ `tej` returns `list[dict]` so you bring your own dataframe library.
104
+
105
+ ```python
106
+ import polars as pl
107
+ from tej import Client
108
+
109
+ rows = Client().ohlcv("RELIANCE", "nse", "2010-01-04", "2026-06-05")
110
+ df = pl.DataFrame(rows)
111
+ df = df.with_columns(pl.col("date").str.to_date())
112
+ df.head()
113
+ ```
114
+
115
+ ```python
116
+ import pandas as pd
117
+ from tej import Client
118
+
119
+ rows = Client().ohlcv("RELIANCE", "nse", "2025-01-01", "2025-06-01")
120
+ df = pd.DataFrame(rows)
121
+ df["date"] = pd.to_datetime(df["date"])
122
+ df.set_index("date", inplace=True)
123
+ df["close"].plot()
124
+ ```
125
+
126
+ Optional extras install the dataframe library alongside the SDK:
127
+
128
+ ```bash
129
+ pip install "tejhq[polars]"
130
+ pip install "tejhq[pandas]"
131
+ ```
132
+
133
+ ## Free key endpoints
134
+
135
+ Get a key at [tejhq.dev/keys](https://tejhq.dev/keys), no card, no password.
136
+
137
+ ```python
138
+ from tej import Client
139
+
140
+ c = Client(api_key="tej_live_...")
141
+
142
+ # Back-adjusted prices, continuous through splits, bonuses and dividends
143
+ adj = c.adjusted("RELIANCE", "nse", "2024-10-01", "2024-11-30")
144
+ print(adj[-1]["adj_close"], adj[-1]["adj_factor_cumulative"])
145
+
146
+ # Symbol history by ISIN or symbol
147
+ c.symbols("nse", isin="INE040A01034")
148
+ c.symbols("nse", symbol="HDFCBANK")
149
+
150
+ # Account, keys, today's usage
151
+ c.me()
152
+ ```
153
+
154
+ ## Pro endpoints
155
+
156
+ ```python
157
+ c = Client(api_key="tej_live_...") # a key on the Pro tier
158
+
159
+ # Derived metrics per day: returns, 52w high/low, average volume and turnover
160
+ m = c.metrics("RELIANCE", "nse", "2026-01-01")
161
+
162
+ # Point-in-time liquidity universe, survivorship-bias-free
163
+ members = c.universe("liquid500", "nse", as_of="2019-03-15")
164
+ symbols = [x["symbol"] for x in members]
165
+
166
+ # Up to 50 symbols in one request, dict keyed by symbol
167
+ bars = c.batch(["RELIANCE", "TCS", "INFY"], "nse", "2026-01-01")
168
+
169
+ # Free text to symbol
170
+ c.resolve("tata motors", exchange="nse", limit=3)
171
+ c.resolve("zomato") # former ticker resolves to the current one
172
+ ```
173
+
174
+ ## API reference
175
+
176
+ | Method | Endpoint | Tier | Returns |
177
+ | --- | --- | --- | --- |
178
+ | `c.ohlcv(symbol, exchange, from_=None, to=None)` | `GET /v1/ohlcv/{exchange}/{symbol}` | keyless | `list[OHLCV]` |
179
+ | `c.snapshot(exchange, date)` | `GET /v1/snapshot/{exchange}?date=` | keyless | `list[SnapshotRow]` |
180
+ | `c.actions(symbol)` | `GET /v1/actions/{symbol}` | keyless | `list[Action]` |
181
+ | `c.adjusted(symbol, exchange, from_=None, to=None)` | `GET /v1/adjusted/{exchange}/{symbol}` | free key | `list[AdjustedRow]` |
182
+ | `c.symbols(exchange, symbol=None, isin=None)` | `GET /v1/symbols/{exchange}` | free key | `list[SymbolInterval]` |
183
+ | `c.me()` | `GET /v1/me` | free key | `dict` |
184
+ | `c.metrics(symbol, exchange, from_=None, to=None)` | `GET /v1/metrics/{exchange}/{symbol}` | pro | `list[MetricsRow]` |
185
+ | `c.universe(name, exchange="nse", as_of=None)` | `GET /v1/universe/{name}` | pro | `list[UniverseMember]` |
186
+ | `c.batch(symbols, exchange="nse", from_=None, to=None)` | `GET /v1/batch` | pro | `dict[str, list[OHLCV]]` |
187
+ | `c.resolve(q, exchange="both", limit=5)` | `GET /v1/resolve` | pro | `list[ResolveHit]` |
188
+ | `c.health()`, `c.ready()` | `GET /health`, `GET /ready` | keyless | `dict` |
189
+
190
+ Need the response envelope (with `meta`)? Use `c.ohlcv_envelope(...)`, which returns the raw `{"data": [...], "meta": {...}}` dict.
191
+
192
+ `AsyncClient` exposes the same surface with `await`.
193
+
194
+ ## Configuration
195
+
196
+ ```python
197
+ from tej import Client
198
+
199
+ c = Client(
200
+ base_url="https://api.tejhq.dev", # override for self-hosted or staging
201
+ api_key=None, # tej_live_... from https://tejhq.dev/keys; keyless works for ohlcv, snapshot, actions
202
+ timeout=30.0, # seconds
203
+ max_retries=3, # exponential backoff on 5xx/429/network
204
+ user_agent_suffix="my-app/1.0", # for attribution
205
+ default_headers={"X-My-Header": "hi"},
206
+ )
207
+ ```
208
+
209
+ ## Errors
210
+
211
+ All errors inherit from `tej.TejError`. The specific subclass tells you what happened:
212
+
213
+ | Exception | When |
214
+ | --- | --- |
215
+ | `BadRequestError` | HTTP 400, bad path or query parameter (also raised locally on invalid args before the request goes out, as a plain `ValueError`) |
216
+ | `AuthError` | HTTP 401, `key_required` when no key was sent to a gated endpoint, `invalid_key` when the key is malformed, unknown, or revoked |
217
+ | `NotFoundError` | HTTP 404 |
218
+ | `ProRequiredError` | HTTP 402, the key's tier is too low for this endpoint (`/v1/universe`, `/v1/metrics` need Pro) |
219
+ | `TejError` with `error_code == "key_required"` | HTTP 401, the endpoint needs a key and none was sent (`/v1/adjusted`, `/v1/symbols`) |
220
+ | `TejError` with `error_code == "invalid_key"` | HTTP 401, the key is malformed, unknown, or revoked |
221
+ | `RateLimitError` | HTTP 429 |
222
+ | `ServerError` | HTTP 5xx |
223
+ | `NetworkError` | DNS, connection, TLS, or timeout failure |
224
+ | `TejError` | Anything else |
225
+
226
+ ```python
227
+ from tej import Client, ProRequiredError
228
+
229
+ try:
230
+ Client().ohlcv("RELIANCE", "nse")
231
+ except ProRequiredError as e:
232
+ print(e.error_code) # 'pro_required'
233
+ print(e.request_id) # tej-api request id from response headers
234
+ ```
235
+
236
+ ## Why a separate package
237
+
238
+ [tej-api](https://github.com/tejhq/tej-api) is a Go service. [tej-bazaar](https://github.com/tejhq/tej-bazaar) is the Python ingestion pipeline. This repo is the thin client that anyone can `pip install` without dragging in either, and that releases on its own cadence as the API evolves.
239
+
240
+ ## Coverage
241
+
242
+ - **NSE bhavcopy + corp actions**: 2010-01-04 to today, ~4,047 trading days, ~7M rows
243
+ - **BSE bhavcopy + corp actions**: 2024-07-08 to today (the SEBI CMTS cutover), ~470 days, ~1M rows
244
+ - **Cron refresh**: weekdays 20:00 IST
245
+
246
+ Keyless endpoints are served at the Cloudflare edge from pre-rendered JSON, so most requests return in well under a second and repeat requests are cache hits. Keyless access is rate limited to 100 requests per 10 seconds per IP at the edge and 120 per minute at origin; the SDK retries 429s with backoff. Keyed requests bypass the edge cache and carry `X-RateLimit-Limit-Day` and `X-RateLimit-Remaining-Day` headers: 1,000 a day and 300 a minute on a free key.
247
+
248
+ ## License
249
+
250
+ MIT. Use it, fork it, ship products on top of it.
251
+
252
+ ## Links
253
+
254
+ - API docs: [tejhq.dev/docs](https://tejhq.dev/docs)
255
+ - OpenAPI spec: [api.tejhq.dev/openapi.yaml](https://api.tejhq.dev/openapi.yaml)
256
+ - Bulk parquet downloads: [data.tejhq.dev](https://data.tejhq.dev) and [huggingface.co/datasets/tejhq/indian-markets](https://huggingface.co/datasets/tejhq/indian-markets)
257
+ - Issues: [github.com/tejhq/tej-sdk-py/issues](https://github.com/tejhq/tej-sdk-py/issues)
tejhq-0.2.0/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # tej
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/tejhq.svg)](https://pypi.org/project/tejhq/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/tejhq.svg)](https://pypi.org/project/tejhq/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
6
+
7
+ Python SDK for [tej-api](https://api.tejhq.dev): open, free, end-of-day market data for NSE and BSE. No signup, no keys, no catch.
8
+
9
+ **Zero runtime dependencies.** Sync and async clients with identical surface. Returns plain `list[dict]` rows so you can feed them straight to polars, pandas, or your own code without an extra conversion step.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install tejhq
15
+ ```
16
+
17
+ That is the entire install. No `httpx`, no `aiohttp`, no `pydantic`. Just stdlib. Distribution is `tejhq`; import as `tej`.
18
+
19
+ ## Quick start (sync)
20
+
21
+ ```python
22
+ from tej import Client
23
+
24
+ c = Client()
25
+
26
+ # Last 90 days of RELIANCE (default range)
27
+ rows = c.ohlcv("RELIANCE", "nse")
28
+ print(rows[-1])
29
+ # {'date': '2026-06-05', 'open': 1442.0, 'high': 1455.7, ...}
30
+
31
+ # Full history since 2010
32
+ rows = c.ohlcv("RELIANCE", "nse", "2010-01-04", "2026-06-05")
33
+ print(len(rows)) # ~4040
34
+
35
+ # Full-market snapshot for one trading date
36
+ snap = c.snapshot("nse", "2025-05-28")
37
+ print(len(snap)) # ~2700 symbols
38
+
39
+ # Corporate actions (dividends, splits, bonuses)
40
+ acts = c.actions("RELIANCE")
41
+ print(acts[0])
42
+ # {'exchange': 'NSE', 'type': 'dividend', 'ex_date': '2024-10-28', 'cash_amount': 10.0, ...}
43
+ ```
44
+
45
+ ## Quick start (async)
46
+
47
+ ```python
48
+ import asyncio
49
+ from tej import AsyncClient
50
+
51
+ async def main():
52
+ async with AsyncClient() as c:
53
+ # Fan out 10 symbols concurrently
54
+ symbols = ["RELIANCE", "TCS", "INFY", "HDFCBANK", "ICICIBANK",
55
+ "SBIN", "HINDUNILVR", "ITC", "LT", "AXISBANK"]
56
+ results = await asyncio.gather(*[c.ohlcv(s, "nse") for s in symbols])
57
+ for sym, rows in zip(symbols, results):
58
+ print(sym, len(rows))
59
+
60
+ asyncio.run(main())
61
+ ```
62
+
63
+ ## Use with polars / pandas
64
+
65
+ `tej` returns `list[dict]` so you bring your own dataframe library.
66
+
67
+ ```python
68
+ import polars as pl
69
+ from tej import Client
70
+
71
+ rows = Client().ohlcv("RELIANCE", "nse", "2010-01-04", "2026-06-05")
72
+ df = pl.DataFrame(rows)
73
+ df = df.with_columns(pl.col("date").str.to_date())
74
+ df.head()
75
+ ```
76
+
77
+ ```python
78
+ import pandas as pd
79
+ from tej import Client
80
+
81
+ rows = Client().ohlcv("RELIANCE", "nse", "2025-01-01", "2025-06-01")
82
+ df = pd.DataFrame(rows)
83
+ df["date"] = pd.to_datetime(df["date"])
84
+ df.set_index("date", inplace=True)
85
+ df["close"].plot()
86
+ ```
87
+
88
+ Optional extras install the dataframe library alongside the SDK:
89
+
90
+ ```bash
91
+ pip install "tejhq[polars]"
92
+ pip install "tejhq[pandas]"
93
+ ```
94
+
95
+ ## Free key endpoints
96
+
97
+ Get a key at [tejhq.dev/keys](https://tejhq.dev/keys), no card, no password.
98
+
99
+ ```python
100
+ from tej import Client
101
+
102
+ c = Client(api_key="tej_live_...")
103
+
104
+ # Back-adjusted prices, continuous through splits, bonuses and dividends
105
+ adj = c.adjusted("RELIANCE", "nse", "2024-10-01", "2024-11-30")
106
+ print(adj[-1]["adj_close"], adj[-1]["adj_factor_cumulative"])
107
+
108
+ # Symbol history by ISIN or symbol
109
+ c.symbols("nse", isin="INE040A01034")
110
+ c.symbols("nse", symbol="HDFCBANK")
111
+
112
+ # Account, keys, today's usage
113
+ c.me()
114
+ ```
115
+
116
+ ## Pro endpoints
117
+
118
+ ```python
119
+ c = Client(api_key="tej_live_...") # a key on the Pro tier
120
+
121
+ # Derived metrics per day: returns, 52w high/low, average volume and turnover
122
+ m = c.metrics("RELIANCE", "nse", "2026-01-01")
123
+
124
+ # Point-in-time liquidity universe, survivorship-bias-free
125
+ members = c.universe("liquid500", "nse", as_of="2019-03-15")
126
+ symbols = [x["symbol"] for x in members]
127
+
128
+ # Up to 50 symbols in one request, dict keyed by symbol
129
+ bars = c.batch(["RELIANCE", "TCS", "INFY"], "nse", "2026-01-01")
130
+
131
+ # Free text to symbol
132
+ c.resolve("tata motors", exchange="nse", limit=3)
133
+ c.resolve("zomato") # former ticker resolves to the current one
134
+ ```
135
+
136
+ ## API reference
137
+
138
+ | Method | Endpoint | Tier | Returns |
139
+ | --- | --- | --- | --- |
140
+ | `c.ohlcv(symbol, exchange, from_=None, to=None)` | `GET /v1/ohlcv/{exchange}/{symbol}` | keyless | `list[OHLCV]` |
141
+ | `c.snapshot(exchange, date)` | `GET /v1/snapshot/{exchange}?date=` | keyless | `list[SnapshotRow]` |
142
+ | `c.actions(symbol)` | `GET /v1/actions/{symbol}` | keyless | `list[Action]` |
143
+ | `c.adjusted(symbol, exchange, from_=None, to=None)` | `GET /v1/adjusted/{exchange}/{symbol}` | free key | `list[AdjustedRow]` |
144
+ | `c.symbols(exchange, symbol=None, isin=None)` | `GET /v1/symbols/{exchange}` | free key | `list[SymbolInterval]` |
145
+ | `c.me()` | `GET /v1/me` | free key | `dict` |
146
+ | `c.metrics(symbol, exchange, from_=None, to=None)` | `GET /v1/metrics/{exchange}/{symbol}` | pro | `list[MetricsRow]` |
147
+ | `c.universe(name, exchange="nse", as_of=None)` | `GET /v1/universe/{name}` | pro | `list[UniverseMember]` |
148
+ | `c.batch(symbols, exchange="nse", from_=None, to=None)` | `GET /v1/batch` | pro | `dict[str, list[OHLCV]]` |
149
+ | `c.resolve(q, exchange="both", limit=5)` | `GET /v1/resolve` | pro | `list[ResolveHit]` |
150
+ | `c.health()`, `c.ready()` | `GET /health`, `GET /ready` | keyless | `dict` |
151
+
152
+ Need the response envelope (with `meta`)? Use `c.ohlcv_envelope(...)`, which returns the raw `{"data": [...], "meta": {...}}` dict.
153
+
154
+ `AsyncClient` exposes the same surface with `await`.
155
+
156
+ ## Configuration
157
+
158
+ ```python
159
+ from tej import Client
160
+
161
+ c = Client(
162
+ base_url="https://api.tejhq.dev", # override for self-hosted or staging
163
+ api_key=None, # tej_live_... from https://tejhq.dev/keys; keyless works for ohlcv, snapshot, actions
164
+ timeout=30.0, # seconds
165
+ max_retries=3, # exponential backoff on 5xx/429/network
166
+ user_agent_suffix="my-app/1.0", # for attribution
167
+ default_headers={"X-My-Header": "hi"},
168
+ )
169
+ ```
170
+
171
+ ## Errors
172
+
173
+ All errors inherit from `tej.TejError`. The specific subclass tells you what happened:
174
+
175
+ | Exception | When |
176
+ | --- | --- |
177
+ | `BadRequestError` | HTTP 400, bad path or query parameter (also raised locally on invalid args before the request goes out, as a plain `ValueError`) |
178
+ | `AuthError` | HTTP 401, `key_required` when no key was sent to a gated endpoint, `invalid_key` when the key is malformed, unknown, or revoked |
179
+ | `NotFoundError` | HTTP 404 |
180
+ | `ProRequiredError` | HTTP 402, the key's tier is too low for this endpoint (`/v1/universe`, `/v1/metrics` need Pro) |
181
+ | `TejError` with `error_code == "key_required"` | HTTP 401, the endpoint needs a key and none was sent (`/v1/adjusted`, `/v1/symbols`) |
182
+ | `TejError` with `error_code == "invalid_key"` | HTTP 401, the key is malformed, unknown, or revoked |
183
+ | `RateLimitError` | HTTP 429 |
184
+ | `ServerError` | HTTP 5xx |
185
+ | `NetworkError` | DNS, connection, TLS, or timeout failure |
186
+ | `TejError` | Anything else |
187
+
188
+ ```python
189
+ from tej import Client, ProRequiredError
190
+
191
+ try:
192
+ Client().ohlcv("RELIANCE", "nse")
193
+ except ProRequiredError as e:
194
+ print(e.error_code) # 'pro_required'
195
+ print(e.request_id) # tej-api request id from response headers
196
+ ```
197
+
198
+ ## Why a separate package
199
+
200
+ [tej-api](https://github.com/tejhq/tej-api) is a Go service. [tej-bazaar](https://github.com/tejhq/tej-bazaar) is the Python ingestion pipeline. This repo is the thin client that anyone can `pip install` without dragging in either, and that releases on its own cadence as the API evolves.
201
+
202
+ ## Coverage
203
+
204
+ - **NSE bhavcopy + corp actions**: 2010-01-04 to today, ~4,047 trading days, ~7M rows
205
+ - **BSE bhavcopy + corp actions**: 2024-07-08 to today (the SEBI CMTS cutover), ~470 days, ~1M rows
206
+ - **Cron refresh**: weekdays 20:00 IST
207
+
208
+ Keyless endpoints are served at the Cloudflare edge from pre-rendered JSON, so most requests return in well under a second and repeat requests are cache hits. Keyless access is rate limited to 100 requests per 10 seconds per IP at the edge and 120 per minute at origin; the SDK retries 429s with backoff. Keyed requests bypass the edge cache and carry `X-RateLimit-Limit-Day` and `X-RateLimit-Remaining-Day` headers: 1,000 a day and 300 a minute on a free key.
209
+
210
+ ## License
211
+
212
+ MIT. Use it, fork it, ship products on top of it.
213
+
214
+ ## Links
215
+
216
+ - API docs: [tejhq.dev/docs](https://tejhq.dev/docs)
217
+ - OpenAPI spec: [api.tejhq.dev/openapi.yaml](https://api.tejhq.dev/openapi.yaml)
218
+ - Bulk parquet downloads: [data.tejhq.dev](https://data.tejhq.dev) and [huggingface.co/datasets/tejhq/indian-markets](https://huggingface.co/datasets/tejhq/indian-markets)
219
+ - Issues: [github.com/tejhq/tej-sdk-py/issues](https://github.com/tejhq/tej-sdk-py/issues)
@@ -0,0 +1,72 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tejhq"
7
+ dynamic = ["version"]
8
+ description = "Python SDK for tej-api: open EOD market data for NSE and BSE."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "TejHQ", email = "support@tejhq.dev" }]
13
+ keywords = ["nse", "bse", "bhavcopy", "ohlcv", "market-data", "india", "stocks"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Intended Audience :: Financial and Insurance Industry",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Office/Business :: Financial :: Investment",
27
+ "Typing :: Typed",
28
+ ]
29
+ dependencies = []
30
+
31
+ [project.optional-dependencies]
32
+ polars = ["polars>=1.0"]
33
+ pandas = ["pandas>=2.0"]
34
+ dev = [
35
+ "pytest>=8.0",
36
+ "pytest-asyncio>=0.23",
37
+ "ruff>=0.6",
38
+ "mypy>=1.10",
39
+ ]
40
+
41
+ [project.urls]
42
+ Homepage = "https://tejhq.dev"
43
+ Documentation = "https://tejhq.dev/docs"
44
+ Repository = "https://github.com/tejhq/tej-sdk-py"
45
+ Issues = "https://github.com/tejhq/tej-sdk-py/issues"
46
+ Changelog = "https://github.com/tejhq/tej-sdk-py/blob/master/CHANGELOG.md"
47
+
48
+ [tool.hatch.version]
49
+ path = "src/tej/_version.py"
50
+
51
+ [tool.hatch.build.targets.wheel]
52
+ packages = ["src/tej"]
53
+
54
+ [tool.hatch.build.targets.sdist]
55
+ include = ["src/tej", "README.md", "LICENSE", "pyproject.toml"]
56
+
57
+ [tool.pytest.ini_options]
58
+ asyncio_mode = "auto"
59
+ testpaths = ["tests"]
60
+
61
+ [tool.ruff]
62
+ line-length = 100
63
+ target-version = "py39"
64
+
65
+ [tool.ruff.lint]
66
+ select = ["E", "F", "W", "I", "UP", "B", "SIM"]
67
+
68
+ [tool.mypy]
69
+ # mypy dropped 3.9 target support; runtime 3.9 compat is covered by the CI matrix
70
+ python_version = "3.10"
71
+ strict = true
72
+ files = ["src/tej"]
@@ -0,0 +1,73 @@
1
+ """Python SDK for `tej-api <https://api.tejhq.dev>`_.
2
+
3
+ Zero runtime dependencies. Sync + async clients with identical surface.
4
+
5
+ Quick start::
6
+
7
+ from tej import Client
8
+
9
+ c = Client()
10
+ rows = c.ohlcv("RELIANCE", "nse", "2025-01-01", "2025-01-31")
11
+ print(len(rows))
12
+
13
+ Async::
14
+
15
+ import asyncio
16
+ from tej import AsyncClient
17
+
18
+ async def main():
19
+ async with AsyncClient() as c:
20
+ return await c.ohlcv("RELIANCE", "nse", "2025-01-01", "2025-01-31")
21
+
22
+ asyncio.run(main())
23
+ """
24
+
25
+ from ._version import __version__
26
+ from .async_client import AsyncClient
27
+ from .client import Client
28
+ from .exceptions import (
29
+ AuthError,
30
+ BadRequestError,
31
+ NetworkError,
32
+ NotFoundError,
33
+ ProRequiredError,
34
+ RateLimitError,
35
+ ServerError,
36
+ TejError,
37
+ )
38
+ from .models import (
39
+ OHLCV,
40
+ Action,
41
+ AdjustedRow,
42
+ Envelope,
43
+ Exchange,
44
+ MetricsRow,
45
+ ResolveHit,
46
+ SnapshotRow,
47
+ SymbolInterval,
48
+ UniverseMember,
49
+ )
50
+
51
+ __all__ = [
52
+ "__version__",
53
+ "Action",
54
+ "AdjustedRow",
55
+ "AsyncClient",
56
+ "AuthError",
57
+ "BadRequestError",
58
+ "Client",
59
+ "Envelope",
60
+ "Exchange",
61
+ "MetricsRow",
62
+ "NetworkError",
63
+ "NotFoundError",
64
+ "OHLCV",
65
+ "ProRequiredError",
66
+ "RateLimitError",
67
+ "ResolveHit",
68
+ "ServerError",
69
+ "SnapshotRow",
70
+ "SymbolInterval",
71
+ "TejError",
72
+ "UniverseMember",
73
+ ]