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.
Files changed (76) hide show
  1. pykalshi_client-1.0.1/.env_demo +20 -0
  2. pykalshi_client-1.0.1/.github/workflows/asyncapi-validation.yml +37 -0
  3. pykalshi_client-1.0.1/.github/workflows/ci.yml +220 -0
  4. pykalshi_client-1.0.1/.github/workflows/openapi-validation.yml +37 -0
  5. pykalshi_client-1.0.1/.github/workflows/pr-title-lint.yml +31 -0
  6. pykalshi_client-1.0.1/.github/workflows/publish.yml +19 -0
  7. pykalshi_client-1.0.1/.github/workflows/version-bump.yml +145 -0
  8. pykalshi_client-1.0.1/.gitignore +16 -0
  9. pykalshi_client-1.0.1/CHANGELOG.md +13 -0
  10. pykalshi_client-1.0.1/CONTRIBUTING.md +78 -0
  11. pykalshi_client-1.0.1/LICENSE +191 -0
  12. pykalshi_client-1.0.1/PKG-INFO +318 -0
  13. pykalshi_client-1.0.1/README.md +271 -0
  14. pykalshi_client-1.0.1/docs/API_COVERAGE.md +224 -0
  15. pykalshi_client-1.0.1/pyproject.toml +62 -0
  16. pykalshi_client-1.0.1/src/pykalshi/__init__.py +93 -0
  17. pykalshi_client-1.0.1/src/pykalshi/_observability.py +70 -0
  18. pykalshi_client-1.0.1/src/pykalshi/_version.py +1 -0
  19. pykalshi_client-1.0.1/src/pykalshi/api/__init__.py +0 -0
  20. pykalshi_client-1.0.1/src/pykalshi/api/_utils.py +34 -0
  21. pykalshi_client-1.0.1/src/pykalshi/api/account.py +31 -0
  22. pykalshi_client-1.0.1/src/pykalshi/api/api_keys.py +67 -0
  23. pykalshi_client-1.0.1/src/pykalshi/api/communications.py +208 -0
  24. pykalshi_client-1.0.1/src/pykalshi/api/event_orders.py +145 -0
  25. pykalshi_client-1.0.1/src/pykalshi/api/events.py +157 -0
  26. pykalshi_client-1.0.1/src/pykalshi/api/exchange.py +54 -0
  27. pykalshi_client-1.0.1/src/pykalshi/api/historical.py +162 -0
  28. pykalshi_client-1.0.1/src/pykalshi/api/incentive_programs.py +34 -0
  29. pykalshi_client-1.0.1/src/pykalshi/api/live_data.py +83 -0
  30. pykalshi_client-1.0.1/src/pykalshi/api/markets.py +198 -0
  31. pykalshi_client-1.0.1/src/pykalshi/api/milestones.py +55 -0
  32. pykalshi_client-1.0.1/src/pykalshi/api/multivariate_collections.py +106 -0
  33. pykalshi_client-1.0.1/src/pykalshi/api/order_groups.py +124 -0
  34. pykalshi_client-1.0.1/src/pykalshi/api/orders.py +256 -0
  35. pykalshi_client-1.0.1/src/pykalshi/api/portfolio.py +102 -0
  36. pykalshi_client-1.0.1/src/pykalshi/api/search.py +30 -0
  37. pykalshi_client-1.0.1/src/pykalshi/api/series.py +79 -0
  38. pykalshi_client-1.0.1/src/pykalshi/api/structured_targets.py +47 -0
  39. pykalshi_client-1.0.1/src/pykalshi/auth.py +84 -0
  40. pykalshi_client-1.0.1/src/pykalshi/config.py +63 -0
  41. pykalshi_client-1.0.1/src/pykalshi/exceptions.py +62 -0
  42. pykalshi_client-1.0.1/src/pykalshi/http_client.py +735 -0
  43. pykalshi_client-1.0.1/src/pykalshi/models/__init__.py +436 -0
  44. pykalshi_client-1.0.1/src/pykalshi/models/common.py +18 -0
  45. pykalshi_client-1.0.1/src/pykalshi/models/core.py +677 -0
  46. pykalshi_client-1.0.1/src/pykalshi/models/enums.py +43 -0
  47. pykalshi_client-1.0.1/src/pykalshi/models/requests.py +222 -0
  48. pykalshi_client-1.0.1/src/pykalshi/models/responses.py +596 -0
  49. pykalshi_client-1.0.1/src/pykalshi/models/ws.py +521 -0
  50. pykalshi_client-1.0.1/src/pykalshi/protocols.py +295 -0
  51. pykalshi_client-1.0.1/src/pykalshi/py.typed +0 -0
  52. pykalshi_client-1.0.1/src/pykalshi/rate_limiter.py +162 -0
  53. pykalshi_client-1.0.1/src/pykalshi/testing/__init__.py +6 -0
  54. pykalshi_client-1.0.1/src/pykalshi/testing/fixtures.py +23 -0
  55. pykalshi_client-1.0.1/src/pykalshi/testing/mock_transport.py +44 -0
  56. pykalshi_client-1.0.1/src/pykalshi/ws_client.py +391 -0
  57. pykalshi_client-1.0.1/tests/__init__.py +0 -0
  58. pykalshi_client-1.0.1/tests/conftest.py +16 -0
  59. pykalshi_client-1.0.1/tests/mock_data.py +138 -0
  60. pykalshi_client-1.0.1/tests/test_api/__init__.py +0 -0
  61. pykalshi_client-1.0.1/tests/test_asyncapi_validation.py +151 -0
  62. pykalshi_client-1.0.1/tests/test_auth.py +83 -0
  63. pykalshi_client-1.0.1/tests/test_config.py +78 -0
  64. pykalshi_client-1.0.1/tests/test_exceptions.py +68 -0
  65. pykalshi_client-1.0.1/tests/test_http_client.py +952 -0
  66. pykalshi_client-1.0.1/tests/test_integration.py +866 -0
  67. pykalshi_client-1.0.1/tests/test_models.py +127 -0
  68. pykalshi_client-1.0.1/tests/test_observability.py +62 -0
  69. pykalshi_client-1.0.1/tests/test_openapi_validation.py +212 -0
  70. pykalshi_client-1.0.1/tests/test_rate_limiter.py +131 -0
  71. pykalshi_client-1.0.1/tests/test_ws_client.py +401 -0
  72. pykalshi_client-1.0.1/tests/test_ws_integration.py +443 -0
  73. pykalshi_client-1.0.1/tools/generate_coverage_report.py +345 -0
  74. pykalshi_client-1.0.1/tools/generate_models.py +503 -0
  75. pykalshi_client-1.0.1/tools/generate_ws_models.py +407 -0
  76. 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,16 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ *.egg-info/
5
+ *.egg
6
+ .eggs/
7
+ *.whl
8
+ dist/
9
+ build/
10
+ .coverage
11
+ .pytest_cache/
12
+ test-results.json
13
+ .mypy_cache/
14
+ .ruff_cache/
15
+ .env
16
+ uv.lock
@@ -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
+ ```