Contributing to eventkit
Thank you for your interest in contributing to eventkit! This guide will help you get started.
Code of Conduct
This project follows the Contributor Covenant Code of Conduct . By participating, you are expected to uphold this code.
Getting Started
Prerequisites
- Python 3.11 or 3.12
- Git
- Google Cloud SDK (for Firestore emulator)
Fork and Clone
- Fork the repository on GitHub
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/eventkit.git
cd eventkit- Add upstream remote:
git remote add upstream https://github.com/prosdev/eventkit.gitInstall Dependencies
# Install in editable mode with dev dependencies
pip install -e ".[dev]"
# Set up pre-commit hooks (optional but recommended)
pre-commit installStart Firestore Emulator (for integration tests)
gcloud emulators firestore start --host-port=localhost:8080In another terminal:
export FIRESTORE_EMULATOR_HOST=localhost:8080Verify Setup
# Run tests
pytest
# Type check
mypy src/eventkit
# Lint
ruff check src/Development Workflow
We follow Spec-Driven Development (inspired by GitHub’s spec-kit ):
- Spec → Define what to build (
specs/*/spec.md) - Plan → Define how to build it (
specs/*/plan.md) - Tasks → Break into atomic tasks (
specs/*/tasks.md) - Implement → Build it, following TDD
See Spec-Driven Development for detailed process.
Working on a Feature
- Check existing specs in
specs/directory - Pick a task from
specs/*/tasks.md - Create a branch:
git checkout -b feature/P1-T001-raw-event-model- Write tests first (TDD):
# Create test file
touch tests/unit/schema/test_raw.py
# Write failing tests
# Then implement to make them pass- Implement the feature
- Run tests:
pytest tests/unit/schema/test_raw.py -v- Check quality:
# Type check
mypy src/eventkit
# Lint
ruff check src/
# Format
ruff format src/- Commit with conventional commit message (see below)
- Push and create PR
Project Structure
eventkit/
├── src/
│ └── eventkit/ # Source code
│ ├── schema/ # Data models (RawEvent, TypedEvent)
│ ├── adapters/ # Event adapters and validators
│ ├── processing/ # Sequencer, buffer, processor
│ ├── stores/ # Storage interfaces and implementations
│ ├── api/ # FastAPI routes
│ ├── logging/ # Structured logging
│ └── errors/ # Custom exceptions
├── tests/
│ ├── unit/ # Fast, isolated tests
│ ├── integration/ # Multi-component tests
│ └── performance/ # Throughput and latency tests
├── specs/ # Specifications and plans
│ └── core-pipeline/
│ ├── spec.md # User stories
│ ├── plan.md # Implementation plan
│ └── tasks.md # Task breakdown
├── examples/ # Usage examples
├── CLAUDE.md # AI agent context
├── WORKFLOW.md # Spec-driven workflow
├── TESTING.md # Testing guide
└── CONTRIBUTING.md # This fileCoding Standards
Python Style
- PEP 8 compliant (enforced by
ruff) - Type hints required (enforced by
mypyin strict mode) - Docstrings for public APIs (Google style)
- Maximum line length: 100 characters
Code Patterns
1. Use Protocols over Abstract Base Classes
✅ Good:
from typing import Protocol
class EventStore(Protocol):
async def write(self, events: list[TypedEvent]) -> None: ...❌ Avoid:
from abc import ABC, abstractmethod
class EventStore(ABC):
@abstractmethod
async def write(self, events: list[TypedEvent]) -> None: ...2. Use Pydantic v2 Patterns
✅ Good:
from pydantic import BaseModel, ConfigDict, Field
class RawEvent(BaseModel):
model_config = ConfigDict(extra="allow")
payload: dict[str, Any]❌ Avoid:
class RawEvent(BaseModel):
class Config:
extra = "allow" # Pydantic v1 syntax3. Never Reject at Edge
✅ Good:
def adapt(self, raw: RawEvent) -> AdapterResult:
if not self._is_valid(raw):
return AdapterResult.err("Invalid event") # Return error
return AdapterResult.ok(event)❌ Avoid:
def adapt(self, raw: RawEvent) -> TypedEvent:
if not self._is_valid(raw):
raise ValidationError("Invalid event") # Never raise in hot path4. Async Throughout
✅ Good:
async def enqueue(self, event: RawEvent) -> None:
await self.processor.enqueue(event)❌ Avoid:
def enqueue(self, event: RawEvent) -> None:
# Blocking I/O
self.store.write(event)5. Explicit is Better than Implicit
✅ Good:
def get_partition_id(self, event: TypedEvent) -> int:
"""Route event to partition by identity hash.
Uses FNV-1a hash for good distribution. Events with same userId
always route to same partition for ordered processing.
"""
routing_key = self._get_routing_key(event)
hash_value = self._fnv1a_hash(routing_key)
return hash_value % self.num_partitions❌ Avoid:
def get_partition_id(self, event: TypedEvent) -> int:
# Hash and modulo
return hash(event.userId or event.anonymousId) % self.num_partitionsTesting Guidelines
See Testing Guidelines for comprehensive testing guide.
Quick Reference
- Unit tests: Fast, isolated, mock dependencies
- Integration tests: Multi-component, use Firestore emulator
- Performance tests: Validate throughput/latency targets
- Coverage target: >80%
Test-Driven Development (TDD)
Always write tests before implementation:
# 1. Write failing test
# tests/unit/schema/test_raw.py
def test_raw_event_accepts_arbitrary_fields():
event = RawEvent(payload={"custom": "field"}, stream="test")
assert event.get("custom") == "field"
# 2. Run test (should fail)
pytest tests/unit/schema/test_raw.py::test_raw_event_accepts_arbitrary_fields
# 3. Implement feature
# src/eventkit/schema/raw.py
class RawEvent(BaseModel):
# ... implementation
# 4. Run test (should pass)
pytest tests/unit/schema/test_raw.py::test_raw_event_accepts_arbitrary_fieldsCommit Message Guidelines
We use Conventional Commits enforced by gitlint.
Format
<type>(<scope>): <subject>
<body>
<footer>Types
feat: New featurefix: Bug fixdocs: Documentation changestest: Adding or updating testsrefactor: Code refactoring (no behavior change)perf: Performance improvementschore: Build process, dependencies, tooling
Examples
✅ Good:
feat(schema): add RawEvent model with flexible JSON support
Implements Story 1 acceptance criteria: accept any JSON payload
without rejection at edge.
- ConfigDict(extra="allow") for arbitrary fields
- get() helper method for safe field access
- Unit tests with 100% coverage
Closes #12fix(buffer): prevent race condition in partition flush
EventLoader._flush_partition was not thread-safe when called from
both size-based and time-based triggers simultaneously.
Added asyncio.Lock per partition to serialize flushes.test(adapters): add missing test for unknown event type
Story 3 acceptance criteria requires returning error for
unknown event types. Added test case.❌ Avoid:
updated stuffWIPfeat: added some changes to the codePre-commit Hook
Commit messages are validated automatically if you’ve installed pre-commit hooks:
pre-commit install --hook-type commit-msgPull Request Process
Before Submitting
- ✅ All tests pass (
pytest) - ✅ Type checking passes (
mypy src/eventkit) - ✅ Linting passes (
ruff check src/) - ✅ Code is formatted (
ruff format src/) - ✅ Coverage >80% for new code
- ✅
CHANGELOG.mdupdated (if applicable) - ✅ Docstrings added/updated
PR Title
Use conventional commit format:
feat(api): add batch event support to /collect endpointPR Description Template
## Summary
Brief description of what this PR does.
## User Story
Which user story does this implement? (e.g., Story 1: Flexible Event Ingestion)
## Tasks Completed
- [ ] P1-T001: Create schema/__init__.py
- [ ] P1-T002: Implement RawEvent model
- [ ] P1-T004: Write unit tests for RawEvent
## Changes
- Added RawEvent model with ConfigDict(extra="allow")
- Added get() helper method
- Added 5 unit tests with 100% coverage
## Testing
- Unit tests: `pytest tests/unit/schema/test_raw.py`
- Coverage: 100% for new code
- Manual testing: N/A (no API changes)
## Breaking Changes
None
## Screenshots (if applicable)
N/A
## Checklist
- [x] Tests added/updated
- [x] Documentation updated
- [x] Type hints added
- [x] Linting passes
- [x] No breaking changes (or documented)Review Process
- Automated checks run on every PR (GitHub Actions)
- Code review by maintainer
- Address feedback in new commits (don’t force-push during review)
- Squash and merge once approved
Code Review Guidelines
For Reviewers
- Be respectful and constructive
- Focus on code, not the person
- Ask questions, don’t demand changes
- Approve if code meets standards (doesn’t have to be perfect)
- Block if: breaking changes, missing tests, security issues
For Contributors
- Respond to all comments
- Don’t take feedback personally
- Ask for clarification if feedback is unclear
- Update code based on feedback
- Mark conversations as resolved when addressed
Documentation
When to Update Docs
- README.md: New features, API changes, installation steps
- CHANGELOG.md: All user-facing changes
- specs/: New features requiring design discussion
- TESTING.md: New testing patterns or tools
- Docstrings: All public APIs
Docstring Format (Google Style)
def get_partition_id(self, event: TypedEvent) -> int:
"""Route event to partition by identity hash.
Uses FNV-1a hash of userId or anonymousId to ensure consistent
routing. Events for same user always go to same partition.
Args:
event: Typed event to route
Returns:
Partition ID (0 to num_partitions-1)
Examples:
>>> sequencer = Sequencer(num_partitions=16)
>>> event = IdentifyEvent(userId="user_123", ...)
>>> partition = sequencer.get_partition_id(event)
>>> assert 0 <= partition < 16
"""Performance Considerations
Write Efficient Code
- Use async/await for I/O operations
- Batch operations when possible
- Avoid N+1 queries
- Profile before optimizing
Benchmark Changes
If your PR affects performance-critical paths:
pytest tests/performance/ --benchmark-onlyInclude benchmark results in PR description.
Release Process
(For maintainers)
- Update
pyproject.tomlversion - Update
CHANGELOG.md - Create git tag:
git tag v0.1.0 - Push tag:
git push --tags - GitHub Actions will build and publish to PyPI
Questions and Support
Where to Ask
- GitHub Issues: Bug reports, feature requests
- GitHub Discussions: Questions, ideas, general discussion
- Pull Requests: Code review, implementation questions
Issue Templates
When creating an issue, use the appropriate template:
- Bug Report: Describe bug, steps to reproduce, expected vs actual behavior
- Feature Request: Describe feature, use case, acceptance criteria
- Question: Ask question with context
Recognition
Contributors are recognized in:
README.mdContributors section- Release notes for significant contributions
- GitHub Contributors page
Thank you for contributing to eventkit! 🎉