pykalshi-client 1.0.1__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.
- pykalshi_client-1.0.1/.env_demo +20 -0
- pykalshi_client-1.0.1/.github/workflows/asyncapi-validation.yml +37 -0
- pykalshi_client-1.0.1/.github/workflows/ci.yml +220 -0
- pykalshi_client-1.0.1/.github/workflows/openapi-validation.yml +37 -0
- pykalshi_client-1.0.1/.github/workflows/pr-title-lint.yml +31 -0
- pykalshi_client-1.0.1/.github/workflows/publish.yml +19 -0
- pykalshi_client-1.0.1/.github/workflows/version-bump.yml +145 -0
- pykalshi_client-1.0.1/.gitignore +16 -0
- pykalshi_client-1.0.1/CHANGELOG.md +13 -0
- pykalshi_client-1.0.1/CONTRIBUTING.md +78 -0
- pykalshi_client-1.0.1/LICENSE +191 -0
- pykalshi_client-1.0.1/PKG-INFO +318 -0
- pykalshi_client-1.0.1/README.md +271 -0
- pykalshi_client-1.0.1/docs/API_COVERAGE.md +224 -0
- pykalshi_client-1.0.1/pyproject.toml +62 -0
- pykalshi_client-1.0.1/src/pykalshi/__init__.py +93 -0
- pykalshi_client-1.0.1/src/pykalshi/_observability.py +70 -0
- pykalshi_client-1.0.1/src/pykalshi/_version.py +1 -0
- pykalshi_client-1.0.1/src/pykalshi/api/__init__.py +0 -0
- pykalshi_client-1.0.1/src/pykalshi/api/_utils.py +34 -0
- pykalshi_client-1.0.1/src/pykalshi/api/account.py +31 -0
- pykalshi_client-1.0.1/src/pykalshi/api/api_keys.py +67 -0
- pykalshi_client-1.0.1/src/pykalshi/api/communications.py +208 -0
- pykalshi_client-1.0.1/src/pykalshi/api/event_orders.py +145 -0
- pykalshi_client-1.0.1/src/pykalshi/api/events.py +157 -0
- pykalshi_client-1.0.1/src/pykalshi/api/exchange.py +54 -0
- pykalshi_client-1.0.1/src/pykalshi/api/historical.py +162 -0
- pykalshi_client-1.0.1/src/pykalshi/api/incentive_programs.py +34 -0
- pykalshi_client-1.0.1/src/pykalshi/api/live_data.py +83 -0
- pykalshi_client-1.0.1/src/pykalshi/api/markets.py +198 -0
- pykalshi_client-1.0.1/src/pykalshi/api/milestones.py +55 -0
- pykalshi_client-1.0.1/src/pykalshi/api/multivariate_collections.py +106 -0
- pykalshi_client-1.0.1/src/pykalshi/api/order_groups.py +124 -0
- pykalshi_client-1.0.1/src/pykalshi/api/orders.py +256 -0
- pykalshi_client-1.0.1/src/pykalshi/api/portfolio.py +102 -0
- pykalshi_client-1.0.1/src/pykalshi/api/search.py +30 -0
- pykalshi_client-1.0.1/src/pykalshi/api/series.py +79 -0
- pykalshi_client-1.0.1/src/pykalshi/api/structured_targets.py +47 -0
- pykalshi_client-1.0.1/src/pykalshi/auth.py +84 -0
- pykalshi_client-1.0.1/src/pykalshi/config.py +63 -0
- pykalshi_client-1.0.1/src/pykalshi/exceptions.py +62 -0
- pykalshi_client-1.0.1/src/pykalshi/http_client.py +735 -0
- pykalshi_client-1.0.1/src/pykalshi/models/__init__.py +436 -0
- pykalshi_client-1.0.1/src/pykalshi/models/common.py +18 -0
- pykalshi_client-1.0.1/src/pykalshi/models/core.py +677 -0
- pykalshi_client-1.0.1/src/pykalshi/models/enums.py +43 -0
- pykalshi_client-1.0.1/src/pykalshi/models/requests.py +222 -0
- pykalshi_client-1.0.1/src/pykalshi/models/responses.py +596 -0
- pykalshi_client-1.0.1/src/pykalshi/models/ws.py +521 -0
- pykalshi_client-1.0.1/src/pykalshi/protocols.py +295 -0
- pykalshi_client-1.0.1/src/pykalshi/py.typed +0 -0
- pykalshi_client-1.0.1/src/pykalshi/rate_limiter.py +162 -0
- pykalshi_client-1.0.1/src/pykalshi/testing/__init__.py +6 -0
- pykalshi_client-1.0.1/src/pykalshi/testing/fixtures.py +23 -0
- pykalshi_client-1.0.1/src/pykalshi/testing/mock_transport.py +44 -0
- pykalshi_client-1.0.1/src/pykalshi/ws_client.py +391 -0
- pykalshi_client-1.0.1/tests/__init__.py +0 -0
- pykalshi_client-1.0.1/tests/conftest.py +16 -0
- pykalshi_client-1.0.1/tests/mock_data.py +138 -0
- pykalshi_client-1.0.1/tests/test_api/__init__.py +0 -0
- pykalshi_client-1.0.1/tests/test_asyncapi_validation.py +151 -0
- pykalshi_client-1.0.1/tests/test_auth.py +83 -0
- pykalshi_client-1.0.1/tests/test_config.py +78 -0
- pykalshi_client-1.0.1/tests/test_exceptions.py +68 -0
- pykalshi_client-1.0.1/tests/test_http_client.py +952 -0
- pykalshi_client-1.0.1/tests/test_integration.py +866 -0
- pykalshi_client-1.0.1/tests/test_models.py +127 -0
- pykalshi_client-1.0.1/tests/test_observability.py +62 -0
- pykalshi_client-1.0.1/tests/test_openapi_validation.py +212 -0
- pykalshi_client-1.0.1/tests/test_rate_limiter.py +131 -0
- pykalshi_client-1.0.1/tests/test_ws_client.py +401 -0
- pykalshi_client-1.0.1/tests/test_ws_integration.py +443 -0
- pykalshi_client-1.0.1/tools/generate_coverage_report.py +345 -0
- pykalshi_client-1.0.1/tools/generate_models.py +503 -0
- pykalshi_client-1.0.1/tools/generate_ws_models.py +407 -0
- pykalshi_client-1.0.1/tools/sync_docstrings.py +184 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# --- System Control ---
|
|
2
|
+
KALSHI_ENVIRONMENT=DEMO
|
|
3
|
+
|
|
4
|
+
# --- API Credentials ---
|
|
5
|
+
KALSHI_DEMO_API_KEY_ID=<INSERT_PUB_API_KEY>
|
|
6
|
+
KALSHI_DEMO_PRIVATE_KEY_FILE=<INSERT_PRIV_API_KEY_FILE_PATH>
|
|
7
|
+
|
|
8
|
+
# For PROD environment (uncomment when ready):
|
|
9
|
+
KALSHI_PROD_API_KEY_ID=<INSERT_PUB_API_KEY>
|
|
10
|
+
KALSHI_PROD_PRIVATE_KEY_FILE=<INSERT_PRIV_API_KEY_FILE_PATH>
|
|
11
|
+
|
|
12
|
+
# --- Rate Limit Fallbacks ---
|
|
13
|
+
# Initial limits used before /account/limits resolves, and fallbacks on fetch failure.
|
|
14
|
+
# Defaults below match Kalshi's standard tier (20 read/sec, 10 write/sec).
|
|
15
|
+
# Successful /account/limits calls override these with the user's actual tier.
|
|
16
|
+
KALSHI_RATE_LIMIT_GLOBAL=20.0
|
|
17
|
+
KALSHI_RATE_LIMIT_WRITE=10.0
|
|
18
|
+
|
|
19
|
+
# --- Dev / Debug ---
|
|
20
|
+
# KALARB_LOG_LEVEL=DEBUG
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: AsyncAPI Validation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: "0 14 * * *"
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
asyncapi-validation:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
timeout-minutes: 3
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v5
|
|
16
|
+
|
|
17
|
+
- uses: astral-sh/setup-uv@v6
|
|
18
|
+
with:
|
|
19
|
+
enable-cache: false
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: uv sync --extra testing
|
|
23
|
+
|
|
24
|
+
- name: Validate WS channels against AsyncAPI spec
|
|
25
|
+
id: validate
|
|
26
|
+
continue-on-error: true
|
|
27
|
+
run: uv run pytest tests/test_asyncapi_validation.py -v -s
|
|
28
|
+
|
|
29
|
+
- name: Update badge
|
|
30
|
+
uses: Schneegans/dynamic-badges-action@v1.7.0
|
|
31
|
+
with:
|
|
32
|
+
auth: ${{ secrets.GIST_SECRET }}
|
|
33
|
+
gistID: 65579a629076066fcbf09520ca76301a
|
|
34
|
+
filename: asyncapi-status.json
|
|
35
|
+
label: AsyncAPI
|
|
36
|
+
message: ${{ steps.validate.outcome == 'success' && 'Up-to-date' || 'Behind' }}
|
|
37
|
+
color: ${{ steps.validate.outcome == 'success' && 'green' || 'yellow' }}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: "0 14 * * *"
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
lint:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
timeout-minutes: 2
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v5
|
|
17
|
+
|
|
18
|
+
- uses: astral-sh/setup-uv@v6
|
|
19
|
+
with:
|
|
20
|
+
enable-cache: false
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: uv sync --extra dev
|
|
24
|
+
|
|
25
|
+
- name: Ruff check
|
|
26
|
+
run: uv run ruff check src/ tests/
|
|
27
|
+
|
|
28
|
+
rest-unit-tests:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
timeout-minutes: 3
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v5
|
|
33
|
+
|
|
34
|
+
- uses: astral-sh/setup-uv@v6
|
|
35
|
+
with:
|
|
36
|
+
enable-cache: false
|
|
37
|
+
|
|
38
|
+
- name: Install dependencies
|
|
39
|
+
run: uv sync --extra testing
|
|
40
|
+
|
|
41
|
+
- name: Run REST unit tests
|
|
42
|
+
run: uv run pytest tests/test_auth.py tests/test_rate_limiter.py tests/test_http_client.py tests/test_config.py tests/test_exceptions.py tests/test_models.py tests/test_observability.py -v --cov=pykalshi --cov-report=term-missing
|
|
43
|
+
|
|
44
|
+
ws-unit-tests:
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
timeout-minutes: 3
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v5
|
|
49
|
+
|
|
50
|
+
- uses: astral-sh/setup-uv@v6
|
|
51
|
+
with:
|
|
52
|
+
enable-cache: false
|
|
53
|
+
|
|
54
|
+
- name: Install dependencies
|
|
55
|
+
run: uv sync --extra testing
|
|
56
|
+
|
|
57
|
+
- name: Run WS unit tests
|
|
58
|
+
run: uv run pytest tests/test_ws_client.py -v
|
|
59
|
+
|
|
60
|
+
exchange-gate:
|
|
61
|
+
if: github.event_name != 'pull_request'
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
timeout-minutes: 2
|
|
64
|
+
outputs:
|
|
65
|
+
active: ${{ steps.gate.outputs.active }}
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v5
|
|
68
|
+
|
|
69
|
+
- uses: astral-sh/setup-uv@v6
|
|
70
|
+
with:
|
|
71
|
+
enable-cache: false
|
|
72
|
+
|
|
73
|
+
- name: Install dependencies
|
|
74
|
+
run: uv sync --extra testing
|
|
75
|
+
|
|
76
|
+
- name: Write credentials
|
|
77
|
+
run: |
|
|
78
|
+
echo "$KALSHI_DEMO_PRIVATE_KEY_PEM" > /tmp/demo-key.pem
|
|
79
|
+
chmod 600 /tmp/demo-key.pem
|
|
80
|
+
env:
|
|
81
|
+
KALSHI_DEMO_PRIVATE_KEY_PEM: ${{ secrets.KALSHI_DEMO_PRIVATE_KEY_PEM }}
|
|
82
|
+
|
|
83
|
+
- name: Check exchange status
|
|
84
|
+
id: gate
|
|
85
|
+
run: |
|
|
86
|
+
uv run python -c "
|
|
87
|
+
import httpx, os, time, base64
|
|
88
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
89
|
+
from cryptography.hazmat.primitives.asymmetric import padding
|
|
90
|
+
|
|
91
|
+
key_id = os.environ['KALSHI_DEMO_API_KEY_ID']
|
|
92
|
+
with open('/tmp/demo-key.pem', 'rb') as f:
|
|
93
|
+
key = serialization.load_pem_private_key(f.read(), password=None)
|
|
94
|
+
ts = str(int(time.time() * 1000))
|
|
95
|
+
path = '/trade-api/v2/exchange/status'
|
|
96
|
+
msg = (ts + 'GET' + path).encode()
|
|
97
|
+
sig = base64.b64encode(key.sign(msg, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH), hashes.SHA256())).decode()
|
|
98
|
+
headers = {'KALSHI-ACCESS-KEY': key_id, 'KALSHI-ACCESS-SIGNATURE': sig, 'KALSHI-ACCESS-TIMESTAMP': ts}
|
|
99
|
+
resp = httpx.get('https://demo-api.kalshi.co' + path, headers=headers)
|
|
100
|
+
data = resp.json()
|
|
101
|
+
active = data.get('exchange_active') or data.get('trading_active', False)
|
|
102
|
+
if not active:
|
|
103
|
+
print('::notice::Exchange is in maintenance - skipping')
|
|
104
|
+
print('active=false')
|
|
105
|
+
else:
|
|
106
|
+
print('Exchange is active')
|
|
107
|
+
print('active=true')
|
|
108
|
+
" | tee /tmp/gate-output.txt
|
|
109
|
+
if grep -q "active=true" /tmp/gate-output.txt; then
|
|
110
|
+
echo "active=true" >> "$GITHUB_OUTPUT"
|
|
111
|
+
else
|
|
112
|
+
echo "active=false" >> "$GITHUB_OUTPUT"
|
|
113
|
+
fi
|
|
114
|
+
env:
|
|
115
|
+
KALSHI_DEMO_API_KEY_ID: ${{ secrets.KALSHI_DEMO_API_KEY_ID }}
|
|
116
|
+
|
|
117
|
+
rest-integration:
|
|
118
|
+
needs: [exchange-gate, rest-unit-tests]
|
|
119
|
+
if: needs.exchange-gate.outputs.active == 'true'
|
|
120
|
+
runs-on: ubuntu-latest
|
|
121
|
+
timeout-minutes: 5
|
|
122
|
+
steps:
|
|
123
|
+
- uses: actions/checkout@v5
|
|
124
|
+
|
|
125
|
+
- uses: astral-sh/setup-uv@v6
|
|
126
|
+
with:
|
|
127
|
+
enable-cache: false
|
|
128
|
+
|
|
129
|
+
- name: Install dependencies
|
|
130
|
+
run: uv sync --extra testing
|
|
131
|
+
|
|
132
|
+
- name: Write credentials
|
|
133
|
+
run: |
|
|
134
|
+
echo "$KALSHI_DEMO_PRIVATE_KEY_PEM" > /tmp/demo-key.pem
|
|
135
|
+
chmod 600 /tmp/demo-key.pem
|
|
136
|
+
cat > .env << EOF
|
|
137
|
+
KALSHI_DEMO_API_KEY_ID=$KALSHI_DEMO_API_KEY_ID
|
|
138
|
+
KALSHI_DEMO_PRIVATE_KEY_FILE=/tmp/demo-key.pem
|
|
139
|
+
EOF
|
|
140
|
+
env:
|
|
141
|
+
KALSHI_DEMO_API_KEY_ID: ${{ secrets.KALSHI_DEMO_API_KEY_ID }}
|
|
142
|
+
KALSHI_DEMO_PRIVATE_KEY_PEM: ${{ secrets.KALSHI_DEMO_PRIVATE_KEY_PEM }}
|
|
143
|
+
|
|
144
|
+
- name: Run REST integration tests
|
|
145
|
+
run: uv run pytest tests/test_integration.py -v
|
|
146
|
+
|
|
147
|
+
ws-integration:
|
|
148
|
+
if: github.event_name != 'pull_request'
|
|
149
|
+
needs: [ws-unit-tests]
|
|
150
|
+
runs-on: ubuntu-latest
|
|
151
|
+
timeout-minutes: 5
|
|
152
|
+
steps:
|
|
153
|
+
- uses: actions/checkout@v5
|
|
154
|
+
|
|
155
|
+
- uses: astral-sh/setup-uv@v6
|
|
156
|
+
with:
|
|
157
|
+
enable-cache: false
|
|
158
|
+
|
|
159
|
+
- name: Install dependencies
|
|
160
|
+
run: uv sync --extra testing
|
|
161
|
+
|
|
162
|
+
- name: Write credentials
|
|
163
|
+
run: |
|
|
164
|
+
echo "$KALSHI_PROD_READ_ONLY_PRIVATE_KEY_PEM" > /tmp/prod-ro-key.pem
|
|
165
|
+
chmod 600 /tmp/prod-ro-key.pem
|
|
166
|
+
cat > .env << EOF
|
|
167
|
+
KALSHI_PROD_READ_ONLY_API_KEY_ID=$KALSHI_PROD_READ_ONLY_API_KEY_ID
|
|
168
|
+
KALSHI_PROD_READ_ONLY_PRIVATE_KEY_FILE=/tmp/prod-ro-key.pem
|
|
169
|
+
EOF
|
|
170
|
+
env:
|
|
171
|
+
KALSHI_PROD_READ_ONLY_API_KEY_ID: ${{ secrets.KALSHI_PROD_READ_ONLY_API_KEY_ID }}
|
|
172
|
+
KALSHI_PROD_READ_ONLY_PRIVATE_KEY_PEM: ${{ secrets.KALSHI_PROD_READ_ONLY_PRIVATE_KEY_PEM }}
|
|
173
|
+
|
|
174
|
+
- name: Run WebSocket integration tests
|
|
175
|
+
run: uv run pytest tests/test_ws_integration.py -v
|
|
176
|
+
|
|
177
|
+
coverage-report:
|
|
178
|
+
if: github.ref == 'refs/heads/main'
|
|
179
|
+
needs: [rest-unit-tests, ws-unit-tests]
|
|
180
|
+
runs-on: ubuntu-latest
|
|
181
|
+
timeout-minutes: 3
|
|
182
|
+
permissions:
|
|
183
|
+
contents: write
|
|
184
|
+
steps:
|
|
185
|
+
- uses: actions/create-github-app-token@v2
|
|
186
|
+
id: app-token
|
|
187
|
+
with:
|
|
188
|
+
app-id: ${{ secrets.APP_ID }}
|
|
189
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
190
|
+
|
|
191
|
+
- uses: actions/checkout@v5
|
|
192
|
+
with:
|
|
193
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
194
|
+
|
|
195
|
+
- uses: astral-sh/setup-uv@v6
|
|
196
|
+
with:
|
|
197
|
+
enable-cache: false
|
|
198
|
+
|
|
199
|
+
- name: Install dependencies
|
|
200
|
+
run: uv sync --extra testing
|
|
201
|
+
|
|
202
|
+
- name: Run unit tests with JSON report
|
|
203
|
+
run: |
|
|
204
|
+
uv run pytest tests/ \
|
|
205
|
+
--ignore=tests/test_integration.py \
|
|
206
|
+
--ignore=tests/test_ws_integration.py \
|
|
207
|
+
--ignore=tests/test_openapi_validation.py \
|
|
208
|
+
--ignore=tests/test_asyncapi_validation.py \
|
|
209
|
+
--json-report --json-report-file=test-results.json -q
|
|
210
|
+
|
|
211
|
+
- name: Generate coverage report
|
|
212
|
+
run: uv run python tools/generate_coverage_report.py
|
|
213
|
+
|
|
214
|
+
- name: Commit and push
|
|
215
|
+
run: |
|
|
216
|
+
git config user.name "github-actions[bot]"
|
|
217
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
218
|
+
git add docs/API_COVERAGE.md
|
|
219
|
+
git diff --cached --quiet || git commit -m "docs: update API coverage report"
|
|
220
|
+
git push
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: OpenAPI Validation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: "0 14 * * *"
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
openapi-validation:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
timeout-minutes: 3
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v5
|
|
16
|
+
|
|
17
|
+
- uses: astral-sh/setup-uv@v6
|
|
18
|
+
with:
|
|
19
|
+
enable-cache: false
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: uv sync --extra testing
|
|
23
|
+
|
|
24
|
+
- name: Validate REST API against OpenAPI spec
|
|
25
|
+
id: validate
|
|
26
|
+
continue-on-error: true
|
|
27
|
+
run: uv run pytest tests/test_openapi_validation.py -v -s
|
|
28
|
+
|
|
29
|
+
- name: Update badge
|
|
30
|
+
uses: Schneegans/dynamic-badges-action@v1.7.0
|
|
31
|
+
with:
|
|
32
|
+
auth: ${{ secrets.GIST_SECRET }}
|
|
33
|
+
gistID: 65579a629076066fcbf09520ca76301a
|
|
34
|
+
filename: openapi-status.json
|
|
35
|
+
label: OpenAPI
|
|
36
|
+
message: ${{ steps.validate.outcome == 'success' && 'Up-to-date' || 'Behind' }}
|
|
37
|
+
color: ${{ steps.validate.outcome == 'success' && 'green' || 'yellow' }}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: PR Title Lint
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request_target:
|
|
5
|
+
types: [opened, edited, synchronize]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
pull-requests: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
pr-title-lint:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
timeout-minutes: 1
|
|
14
|
+
steps:
|
|
15
|
+
- uses: amannn/action-semantic-pull-request@v5
|
|
16
|
+
env:
|
|
17
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
18
|
+
with:
|
|
19
|
+
types: |
|
|
20
|
+
feat
|
|
21
|
+
fix
|
|
22
|
+
refactor
|
|
23
|
+
perf
|
|
24
|
+
docs
|
|
25
|
+
ci
|
|
26
|
+
test
|
|
27
|
+
chore
|
|
28
|
+
style
|
|
29
|
+
requireScope: false
|
|
30
|
+
subjectPattern: ^.+$
|
|
31
|
+
subjectPatternError: "PR title must have a description after the type prefix."
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.11"
|
|
17
|
+
- run: pip install build
|
|
18
|
+
- run: python -m build
|
|
19
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
name: Version Bump & Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: version-bump
|
|
12
|
+
cancel-in-progress: false
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
version-bump:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
timeout-minutes: 3
|
|
18
|
+
outputs:
|
|
19
|
+
new_version: ${{ steps.bump.outputs.new_version }}
|
|
20
|
+
bumped: ${{ steps.bump.outputs.bumped }}
|
|
21
|
+
steps:
|
|
22
|
+
- name: Generate app token
|
|
23
|
+
id: app-token
|
|
24
|
+
uses: actions/create-github-app-token@v1
|
|
25
|
+
with:
|
|
26
|
+
app-id: ${{ secrets.APP_ID }}
|
|
27
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
28
|
+
|
|
29
|
+
- uses: actions/checkout@v5
|
|
30
|
+
with:
|
|
31
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
32
|
+
|
|
33
|
+
- name: Determine bump type and compute new version
|
|
34
|
+
id: bump
|
|
35
|
+
env:
|
|
36
|
+
COMMIT_MSG: ${{ github.event.head_commit.message }}
|
|
37
|
+
run: |
|
|
38
|
+
msg="$COMMIT_MSG"
|
|
39
|
+
|
|
40
|
+
# Extract conventional commit type from first line (strip leading whitespace)
|
|
41
|
+
type=$(echo "$msg" | head -1 | sed -n 's/^[[:space:]]*\([a-z]*\).*/\1/p')
|
|
42
|
+
|
|
43
|
+
# Skip version bump for non-functional changes
|
|
44
|
+
case "$type" in
|
|
45
|
+
docs|ci|test|chore|style)
|
|
46
|
+
echo "bumped=false" >> "$GITHUB_OUTPUT"
|
|
47
|
+
echo "Skipping version bump for commit type: $type"
|
|
48
|
+
exit 0
|
|
49
|
+
;;
|
|
50
|
+
feat|fix|refactor|perf)
|
|
51
|
+
;; # continue to bump
|
|
52
|
+
*)
|
|
53
|
+
echo "bumped=false" >> "$GITHUB_OUTPUT"
|
|
54
|
+
echo "Skipping version bump for unrecognized type: $type"
|
|
55
|
+
exit 0
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
|
|
59
|
+
# Determine bump level
|
|
60
|
+
if echo "$msg" | grep -qP 'BREAKING CHANGE:' || echo "$msg" | head -1 | grep -qP '^\s*[a-z]+(\(.*\))?!:'; then
|
|
61
|
+
bump="major"
|
|
62
|
+
elif [ "$type" = "feat" ]; then
|
|
63
|
+
bump="minor"
|
|
64
|
+
else
|
|
65
|
+
bump="patch"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Read current version
|
|
69
|
+
current=$(grep -oP '(?<=__version__ = ")[^"]+' src/pykalshi/_version.py)
|
|
70
|
+
IFS='.' read -r major minor patch <<< "$current"
|
|
71
|
+
|
|
72
|
+
# Compute new version
|
|
73
|
+
case "$bump" in
|
|
74
|
+
major) new="$((major+1)).0.0" ;;
|
|
75
|
+
minor) new="${major}.$((minor+1)).0" ;;
|
|
76
|
+
patch) new="${major}.${minor}.$((patch+1))" ;;
|
|
77
|
+
esac
|
|
78
|
+
|
|
79
|
+
echo "new_version=${new}" >> "$GITHUB_OUTPUT"
|
|
80
|
+
echo "bumped=true" >> "$GITHUB_OUTPUT"
|
|
81
|
+
echo "Bumping $current -> $new ($bump from $type)"
|
|
82
|
+
|
|
83
|
+
- name: Update version file
|
|
84
|
+
if: steps.bump.outputs.bumped == 'true'
|
|
85
|
+
run: |
|
|
86
|
+
echo "__version__ = \"${{ steps.bump.outputs.new_version }}\"" > src/pykalshi/_version.py
|
|
87
|
+
|
|
88
|
+
- name: Commit and push version bump
|
|
89
|
+
if: steps.bump.outputs.bumped == 'true'
|
|
90
|
+
run: |
|
|
91
|
+
git config user.name "github-actions[bot]"
|
|
92
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
93
|
+
git add src/pykalshi/_version.py
|
|
94
|
+
git commit -m "chore: bump version to ${{ steps.bump.outputs.new_version }}"
|
|
95
|
+
git pull --rebase
|
|
96
|
+
git push
|
|
97
|
+
|
|
98
|
+
release:
|
|
99
|
+
needs: version-bump
|
|
100
|
+
if: needs.version-bump.outputs.bumped == 'true'
|
|
101
|
+
runs-on: ubuntu-latest
|
|
102
|
+
timeout-minutes: 3
|
|
103
|
+
permissions:
|
|
104
|
+
contents: write
|
|
105
|
+
steps:
|
|
106
|
+
- name: Generate app token
|
|
107
|
+
id: app-token
|
|
108
|
+
uses: actions/create-github-app-token@v1
|
|
109
|
+
with:
|
|
110
|
+
app-id: ${{ secrets.APP_ID }}
|
|
111
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
112
|
+
|
|
113
|
+
- uses: actions/checkout@v5
|
|
114
|
+
with:
|
|
115
|
+
ref: main
|
|
116
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
117
|
+
|
|
118
|
+
- name: Install uv
|
|
119
|
+
uses: astral-sh/setup-uv@v6
|
|
120
|
+
|
|
121
|
+
- name: Set up Python
|
|
122
|
+
run: uv python install 3.12
|
|
123
|
+
|
|
124
|
+
- name: Build package
|
|
125
|
+
run: uv build
|
|
126
|
+
|
|
127
|
+
- name: Create and push tag
|
|
128
|
+
run: |
|
|
129
|
+
git config user.name "github-actions[bot]"
|
|
130
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
131
|
+
tag="v${{ needs.version-bump.outputs.new_version }}"
|
|
132
|
+
if git rev-parse "$tag" >/dev/null 2>&1; then
|
|
133
|
+
echo "Tag $tag already exists, skipping"
|
|
134
|
+
exit 1
|
|
135
|
+
fi
|
|
136
|
+
git tag "$tag"
|
|
137
|
+
git push origin "$tag"
|
|
138
|
+
|
|
139
|
+
- name: Create GitHub Release
|
|
140
|
+
uses: softprops/action-gh-release@v2
|
|
141
|
+
with:
|
|
142
|
+
tag_name: v${{ needs.version-bump.outputs.new_version }}
|
|
143
|
+
name: v${{ needs.version-bump.outputs.new_version }}
|
|
144
|
+
generate_release_notes: true
|
|
145
|
+
files: dist/*
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.5.0 — Initial Open-Source Release
|
|
4
|
+
|
|
5
|
+
- Async HTTP client (KalshiHttpClient) — 50+ endpoints across 23 API domains
|
|
6
|
+
- Async WebSocket client (KalshiWebSocketClient) — 11+ subscription channels
|
|
7
|
+
- 144 typed Pydantic v2 models (auto-generated from OpenAPI/AsyncAPI specs)
|
|
8
|
+
- RSA-PSS authentication
|
|
9
|
+
- Read/write rate limiting with token bucket
|
|
10
|
+
- Exponential backoff retry for 429s and network errors
|
|
11
|
+
- Optional OpenTelemetry instrumentation
|
|
12
|
+
- Protocol-based typed contracts for testing
|
|
13
|
+
- Mock transport factory + pytest fixtures
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Contributions are welcome! Whether it's a bug report, feature request, or pull request, we appreciate your help making this library better.
|
|
4
|
+
|
|
5
|
+
## Opening Issues
|
|
6
|
+
|
|
7
|
+
Please open an issue before starting work on large changes. This lets us discuss the approach and avoid duplicate effort.
|
|
8
|
+
|
|
9
|
+
## Branch Workflow
|
|
10
|
+
|
|
11
|
+
- All changes go through a pull request against `main` — no direct pushes
|
|
12
|
+
- PRs are **squash merged** (repo enforced), so the PR title becomes the commit message
|
|
13
|
+
- Required checks must pass before merging: `lint`, `rest-unit-tests`, `ws-unit-tests`
|
|
14
|
+
|
|
15
|
+
## PR Title Format
|
|
16
|
+
|
|
17
|
+
PR titles **must** follow [Conventional Commits](https://www.conventionalcommits.org/) format:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
type(optional-scope): description
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The PR title drives automatic version bumping on merge:
|
|
24
|
+
|
|
25
|
+
| Type | Version Bump | Example |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `feat` | **minor** (0.1.x → 0.2.0) | `feat: add batch order amendment` |
|
|
28
|
+
| `fix` | **patch** (0.1.1 → 0.1.2) | `fix: handle nullable fields in responses` |
|
|
29
|
+
| `refactor` | **patch** | `refactor: extract rate limiter into separate module` |
|
|
30
|
+
| `perf` | **patch** | `perf: use orjson for message parsing` |
|
|
31
|
+
| `docs` | no bump | `docs: update API coverage tables` |
|
|
32
|
+
| `ci` | no bump | `ci: add OpenAPI validation workflow` |
|
|
33
|
+
| `test` | no bump | `test: add resubscribe_channel integration test` |
|
|
34
|
+
| `chore` | no bump | `chore: update dependencies` |
|
|
35
|
+
| `style` | no bump | `style: fix ruff formatting` |
|
|
36
|
+
|
|
37
|
+
## Breaking Changes
|
|
38
|
+
|
|
39
|
+
Breaking changes trigger a **major** version bump. Signal them in one of two ways:
|
|
40
|
+
|
|
41
|
+
1. Add `!` after the type in the PR title:
|
|
42
|
+
```
|
|
43
|
+
feat!: remove deprecated get_live_data_legacy
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
2. Include `BREAKING CHANGE:` in the PR description body:
|
|
47
|
+
```
|
|
48
|
+
BREAKING CHANGE: get_live_data_legacy has been removed. Use get_live_data instead.
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Running Tests Locally
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Unit tests
|
|
55
|
+
uv run pytest tests/test_http_client.py tests/test_auth.py tests/test_rate_limiter.py -v
|
|
56
|
+
uv run pytest tests/test_ws_client.py -v
|
|
57
|
+
|
|
58
|
+
# Lint
|
|
59
|
+
uv run ruff check src/ tests/
|
|
60
|
+
|
|
61
|
+
# Integration tests (requires credentials in .env)
|
|
62
|
+
uv run pytest tests/test_integration.py -v # REST — DEMO API
|
|
63
|
+
uv run pytest tests/test_ws_integration.py -v # WebSocket — PROD API (read-only)
|
|
64
|
+
|
|
65
|
+
# Spec validation
|
|
66
|
+
uv run pytest tests/test_openapi_validation.py -v
|
|
67
|
+
uv run pytest tests/test_asyncapi_validation.py -v
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Code Generation
|
|
71
|
+
|
|
72
|
+
Models in `src/pykalshi/models/` are **auto-generated** from the Kalshi OpenAPI and AsyncAPI specs. Do not edit them manually. Instead:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
uv run python tools/generate_models.py # HTTP models
|
|
76
|
+
uv run python tools/generate_ws_models.py # WebSocket models
|
|
77
|
+
uv run python tools/sync_docstrings.py # API function docstrings
|
|
78
|
+
```
|