Skip to content

Commit bd75a91

Browse files
committed
Release v1.0.1
1 parent 3a725dc commit bd75a91

File tree

9 files changed

+886
-0
lines changed

9 files changed

+886
-0
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Description
2+
3+
<!-- Describe what this PR does -->
4+
5+
## Related Issues
6+
7+
<!-- Link any related issues: Fixes #123, Closes #456 -->
8+
9+
## Type of Change
10+
11+
- [ ] Bug fix (non-breaking change that fixes an issue)
12+
- [ ] New feature (non-breaking change that adds functionality)
13+
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
14+
- [ ] Documentation update
15+
16+
## Checklist
17+
18+
- [ ] I have read the [CONTRIBUTING](../CONTRIBUTING.md) guidelines
19+
- [ ] My code follows PEP 8 style guidelines
20+
- [ ] I have added tests for my changes (if applicable)
21+
- [ ] All tests pass locally (`pytest`)
22+
- [ ] I have updated documentation (if applicable)
23+
24+
---
25+
26+
> **Note:** This repository is a read-only mirror of our internal monorepo. If your PR is accepted, we will manually port your changes and credit you as a co-author. Your changes will appear with the next release. Thank you for contributing!
27+

.gitignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
__pycache__/
2+
*.py[cod]
3+
*$py.class
4+
*.so
5+
.Python
6+
build/
7+
develop-eggs/
8+
dist/
9+
downloads/
10+
eggs/
11+
.eggs/
12+
lib/
13+
lib64/
14+
parts/
15+
sdist/
16+
var/
17+
wheels/
18+
*.egg-info/
19+
.installed.cfg
20+
*.egg
21+
.venv/
22+
venv/
23+
ENV/
24+
env/
25+
.pytest_cache/
26+
.coverage
27+
htmlcov/
28+
.tox/
29+
.DS_Store
30+

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [1.0.1] - 2025-11-27
9+
10+
- Initial release
11+

CONTRIBUTING.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Contributing to Klime Python SDK
2+
3+
Thank you for your interest in contributing to the Klime Python SDK!
4+
5+
## Important: Repository Structure
6+
7+
This repository is a **read-only mirror** of our internal monorepo. We develop and maintain the SDK internally, then mirror releases to this public repository.
8+
9+
### What This Means for Contributors
10+
11+
- **Pull requests are welcome** and will be reviewed by our team
12+
- If accepted, we'll **manually port your changes** to our internal monorepo
13+
- Your changes will appear in this repository with the **next release**
14+
- You'll be credited as a co-author in the commit
15+
16+
## How to Contribute
17+
18+
### Reporting Bugs
19+
20+
1. Check if the bug has already been reported in [Issues](../../issues)
21+
2. If not, create a new issue with:
22+
- A clear, descriptive title
23+
- Steps to reproduce the bug
24+
- Expected vs actual behavior
25+
- Your environment (Python version, OS, etc.)
26+
- Any relevant code snippets or error messages
27+
28+
### Suggesting Features
29+
30+
1. Check if the feature has already been suggested in [Issues](../../issues)
31+
2. Create a new issue describing:
32+
- The problem you're trying to solve
33+
- Your proposed solution
34+
- Any alternatives you've considered
35+
36+
### Submitting Code Changes
37+
38+
1. Fork this repository
39+
2. Create a feature branch (`git checkout -b feat/amazing-feature`)
40+
3. Make your changes
41+
4. Write or update tests as needed
42+
5. Ensure all tests pass (`pytest`)
43+
6. Commit using [Conventional Commits](https://www.conventionalcommits.org/):
44+
```
45+
feat: add new tracking method
46+
fix: handle edge case in batch processing
47+
docs: update README examples
48+
```
49+
7. Push to your fork and open a Pull Request
50+
51+
### Pull Request Guidelines
52+
53+
- Provide a clear description of what the PR does
54+
- Reference any related issues
55+
- Include tests for new functionality
56+
- Update documentation if needed
57+
- Keep PRs focused - one feature or fix per PR
58+
59+
## Development Setup
60+
61+
```bash
62+
# Clone your fork
63+
git clone https://github.com/YOUR_USERNAME/klime-python.git
64+
cd klime-python
65+
66+
# Create a virtual environment
67+
python -m venv .venv
68+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
69+
70+
# Install in development mode
71+
pip install -e ".[dev]"
72+
73+
# Run tests
74+
pytest
75+
```
76+
77+
## Project Structure
78+
79+
```
80+
klime-python/
81+
├── klime/
82+
│ ├── __init__.py
83+
│ ├── client.py # Main client implementation
84+
│ └── types.py # Type definitions
85+
├── tests/
86+
├── pyproject.toml # Project configuration
87+
└── README.md
88+
```
89+
90+
## Code Style
91+
92+
- We follow [PEP 8](https://pep8.org/) style guidelines
93+
- Use type hints for all function signatures
94+
- Use meaningful variable and function names
95+
- Add docstrings for public APIs
96+
97+
## Running Tests
98+
99+
```bash
100+
# Run all tests
101+
pytest
102+
103+
# Run with coverage
104+
pytest --cov=klime
105+
106+
# Run specific test file
107+
pytest tests/test_client.py
108+
```
109+
110+
## Questions?
111+
112+
If you have questions about contributing, feel free to open an issue and we'll be happy to help!
113+
114+
## License
115+
116+
By contributing, you agree that your contributions will be licensed under the MIT License.
117+

README.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# klime-analytics
2+
3+
Klime Analytics SDK for Python.
4+
5+
## Installation
6+
7+
```bash
8+
pip install klime-analytics
9+
```
10+
11+
## Quick Start
12+
13+
```python
14+
from klime import KlimeClient
15+
16+
client = KlimeClient(
17+
write_key='your-write-key'
18+
)
19+
20+
# Track an event
21+
client.track('Button Clicked', {
22+
'button_name': 'Sign up',
23+
'plan': 'pro'
24+
})
25+
26+
# Identify a user
27+
client.identify('user_123', {
28+
'email': 'user@example.com',
29+
'name': 'Stefan'
30+
})
31+
32+
# Group a user with an organization
33+
client.group('org_456', {
34+
'name': 'Acme Inc',
35+
'plan': 'enterprise'
36+
}, user_id='user_123')
37+
38+
# Shutdown gracefully
39+
client.shutdown()
40+
```
41+
42+
## API Reference
43+
44+
### Constructor
45+
46+
```python
47+
KlimeClient(
48+
write_key: str, # Required: Your Klime write key
49+
endpoint: Optional[str] = None, # Optional: API endpoint (default: https://ingest.klime.com)
50+
flush_interval: Optional[int] = None, # Optional: Milliseconds between flushes (default: 2000)
51+
max_batch_size: Optional[int] = None, # Optional: Max events per batch (default: 20, max: 100)
52+
max_queue_size: Optional[int] = None, # Optional: Max queued events (default: 1000)
53+
retry_max_attempts: Optional[int] = None, # Optional: Max retry attempts (default: 5)
54+
retry_initial_delay: Optional[int] = None, # Optional: Initial retry delay in ms (default: 1000)
55+
flush_on_shutdown: Optional[bool] = None # Optional: Auto-flush on exit (default: True)
56+
)
57+
```
58+
59+
### Methods
60+
61+
#### `track(event: str, properties: Optional[Dict] = None, user_id: Optional[str] = None, group_id: Optional[str] = None, ip: Optional[str] = None) -> None`
62+
63+
Track a user event.
64+
65+
```python
66+
# Simple usage
67+
client.track('Button Clicked', {
68+
'button_name': 'Sign up',
69+
'plan': 'pro'
70+
})
71+
72+
# With user context and IP
73+
client.track('Button Clicked', {
74+
'button_name': 'Sign up',
75+
'plan': 'pro'
76+
}, user_id='user_123', group_id='org_456', ip='192.168.1.1')
77+
```
78+
79+
#### `identify(user_id: str, traits: Optional[Dict] = None, ip: Optional[str] = None) -> None`
80+
81+
Identify a user with traits.
82+
83+
```python
84+
client.identify('user_123', {
85+
'email': 'user@example.com',
86+
'name': 'Stefan'
87+
}, ip='192.168.1.1')
88+
```
89+
90+
#### `group(group_id: str, traits: Optional[Dict] = None, user_id: Optional[str] = None, ip: Optional[str] = None) -> None`
91+
92+
Associate a user with a group and set group traits.
93+
94+
```python
95+
# Simple usage
96+
client.group('org_456', {
97+
'name': 'Acme Inc',
98+
'plan': 'enterprise'
99+
})
100+
101+
# With user ID and IP
102+
client.group('org_456', {
103+
'name': 'Acme Inc',
104+
'plan': 'enterprise'
105+
}, user_id='user_123', ip='192.168.1.1')
106+
```
107+
108+
#### `flush() -> None`
109+
110+
Manually flush queued events immediately.
111+
112+
```python
113+
client.flush()
114+
```
115+
116+
#### `shutdown() -> None`
117+
118+
Gracefully shutdown the client, flushing remaining events.
119+
120+
```python
121+
client.shutdown()
122+
```
123+
124+
## Features
125+
126+
- **Automatic Batching**: Events are automatically batched and sent every 2 seconds or when the batch size reaches 20 events
127+
- **Automatic Retries**: Failed requests are automatically retried with exponential backoff
128+
- **Thread-Safe**: Safe to use from multiple threads
129+
- **Process Exit Handling**: Automatically flushes events on process exit (via `atexit`)
130+
- **Zero Dependencies**: Uses only Python standard library
131+
132+
## Configuration
133+
134+
### Default Values
135+
136+
- `flush_interval`: 2000ms
137+
- `max_batch_size`: 20 events
138+
- `max_queue_size`: 1000 events
139+
- `retry_max_attempts`: 5 attempts
140+
- `retry_initial_delay`: 1000ms
141+
- `flush_on_shutdown`: True
142+
143+
## Error Handling
144+
145+
The SDK automatically handles:
146+
147+
- **Transient errors** (429, 503, network failures): Retries with exponential backoff
148+
- **Permanent errors** (400, 401): Logs error and drops event
149+
- **Rate limiting**: Respects `Retry-After` header
150+
151+
## Size Limits
152+
153+
- Maximum event size: 200KB
154+
- Maximum batch size: 10MB
155+
- Maximum events per batch: 100
156+
157+
Events exceeding these limits are rejected and logged.
158+
159+
## Flask Example
160+
161+
```python
162+
from flask import Flask, request
163+
from klime import KlimeClient
164+
165+
app = Flask(__name__)
166+
client = KlimeClient(write_key='your-write-key')
167+
168+
@app.route('/api/button-clicked', methods=['POST'])
169+
def button_clicked():
170+
client.track('Button Clicked', {
171+
'button_name': request.json.get('buttonName')
172+
}, user_id=request.json.get('userId'), ip=request.remote_addr)
173+
174+
return {'success': True}
175+
176+
# Graceful shutdown
177+
import atexit
178+
atexit.register(client.shutdown)
179+
```
180+
181+
## Django Example
182+
183+
```python
184+
from django.http import JsonResponse
185+
from django.views import View
186+
from klime import KlimeClient
187+
188+
client = KlimeClient(write_key='your-write-key')
189+
190+
class ButtonClickView(View):
191+
def post(self, request):
192+
client.track('Button Clicked', {
193+
'button_name': request.POST.get('buttonName')
194+
}, user_id=str(request.user.id), ip=request.META.get('REMOTE_ADDR'))
195+
196+
return JsonResponse({'success': True})
197+
```
198+
199+
## Requirements
200+
201+
- Python 3.7 or higher
202+
- No external dependencies (uses only standard library)
203+
204+
## License
205+
206+
MIT
207+

0 commit comments

Comments
 (0)