Contributing to Carbonic¶
Thank you for your interest in contributing to Carbonic! This guide will help you get started with development.
Getting Started¶
Prerequisites¶
- Python 3.12 or higher
- UV package manager
- Git
Development Setup¶
-
Clone the repository:
-
Install development dependencies:
-
Activate the virtual environment:
-
Install pre-commit hooks:
Development Workflow¶
Running Tests¶
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=carbonic --cov-report=html
# Run specific test file
uv run pytest tests/test_datetime.py
# Run with verbose output
uv run pytest -v
Code Quality¶
Carbonic uses several tools to maintain code quality:
# Type checking
uv run mypy carbonic
# Linting
uv run ruff check carbonic
# Formatting
uv run ruff format carbonic
# Run all checks
uv run pre-commit run --all-files
Documentation¶
Build and serve documentation locally:
# Install docs dependencies (if not already installed)
uv sync --group docs
# Serve documentation locally
uv run mkdocs serve
# Build documentation
uv run mkdocs build
Project Structure¶
carbonic/
├── carbonic/ # Main package
│ ├── core/ # Core classes (DateTime, Date, etc.)
│ │ ├── datetime.py # DateTime implementation
│ │ ├── date.py # Date implementation
│ │ ├── duration.py # Duration implementation
│ │ ├── period.py # Period constants and logic
│ │ ├── interval.py # Interval implementation
│ │ └── exceptions.py # Custom exceptions
│ ├── locale/ # Localization support
│ │ ├── base.py # Base locale class and registry
│ │ ├── en.py # English locale
│ │ ├── pl.py # Polish locale
│ │ ├── es.py # Spanish locale
│ │ ├── fr.py # French locale
│ │ ├── de.py # German locale
│ │ └── pt.py # Portuguese locale
│ └── __init__.py # Public API
├── tests/ # Test suite
├── docs/ # Documentation
├── pyproject.toml # Project configuration
├── mkdocs.yml # Documentation configuration
└── README.md # Project overview
Coding Standards¶
Code Style¶
- Follow PEP 8 and use Ruff for formatting
- Use type hints for all public APIs
- Prefer descriptive names over comments
- Keep functions focused and small
Docstrings¶
Use Google-style docstrings:
def add_days(self, days: int) -> "DateTime":
"""Add the specified number of days.
Args:
days: Number of days to add (can be negative).
Returns:
New DateTime instance with added days.
Example:
>>> dt = DateTime(2024, 1, 15, tz="UTC")
>>> future = dt.add_days(7)
>>> print(future)
2024-01-22T00:00:00+00:00
"""
return self._replace_dt(self._dt + timedelta(days=days))
Type Annotations¶
- Use modern type hints (Python 3.12+ syntax)
- Import types from
typingwhen needed - Use
Selffor methods returning the same type - Be explicit about optional parameters
from typing import Self, Optional
from datetime import datetime
def from_datetime(cls, dt: datetime) -> Self:
"""Create from standard library datetime."""
...
Testing Guidelines¶
Test Structure¶
- Place tests in the
tests/directory - Mirror the package structure in test files
- Use descriptive test method names
- Group related tests in classes
class TestDateTimeArithmetic:
def test_add_days_positive(self):
"""Test adding positive number of days."""
dt = DateTime(2024, 1, 15, tz="UTC")
result = dt.add_days(7)
expected = DateTime(2024, 1, 22, tz="UTC")
assert result == expected
def test_add_days_negative(self):
"""Test adding negative number of days."""
dt = DateTime(2024, 1, 15, tz="UTC")
result = dt.add_days(-7)
expected = DateTime(2024, 1, 8, tz="UTC")
assert result == expected
Test Categories¶
- Unit Tests: Test individual methods in isolation
- Integration Tests: Test interactions between classes
- Property Tests: Test invariants and edge cases
- Performance Tests: Ensure operations meet performance requirements
Writing Good Tests¶
- Test both happy path and edge cases
- Use meaningful assertions with clear error messages
- Avoid testing implementation details
- Prefer multiple focused tests over complex tests
Making Changes¶
Before You Start¶
- Check existing issues to avoid duplication
- For large changes, open an issue to discuss the approach
- Fork the repository and create a feature branch
Development Process¶
-
Create a branch:
-
Make your changes:
- Write code following the established patterns
- Add tests for new functionality
-
Update documentation if needed
-
Test your changes:
-
Commit your changes:
-
Push and create PR:
Commit Message Guidelines¶
Follow conventional commit format:
type: brief description
Longer description if needed
- List any breaking changes
- Reference relevant issues
Types:
- feat: New feature
- fix: Bug fix
- docs: Documentation changes
- test: Adding or updating tests
- refactor: Code changes that don't add features or fix bugs
- perf: Performance improvements
- chore: Maintenance tasks
Design Principles¶
Immutability¶
All datetime objects must be immutable:
from dataclasses import dataclass
from datetime import datetime, timedelta
@dataclass(frozen=True, slots=True)
class DateTime:
_dt: datetime
def add_days(self, days: int) -> "DateTime":
# Return new instance, never modify self
return DateTime(self._dt + timedelta(days=days))
Fluent API¶
Methods should be chainable and read naturally:
from carbonic import DateTime
# Create a sample datetime for the example
dt = DateTime.now()
# Good - fluent and readable
result = (dt
.add(days=1)
.start_of("day")
)
# Avoid - requires intermediate variables
dt1 = dt.add(days=1)
dt2 = dt1.start_of("day")
result = dt2 # Note: timezone conversion would be done during creation
Type Safety¶
Maintain strict typing throughout:
from typing import Any
from datetime import datetime
# Good - explicit types
def diff_in_days(self, other: "DateTime") -> float:
delta = other._dt - self._dt
return delta.total_seconds() / 86400
# Avoid - untyped or Any
def diff_in_days(self, other) -> Any:
pass # Using pass instead of ... for valid syntax
Error Handling¶
Use specific exceptions:
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
from carbonic.core.exceptions import CarbonicError
# Define a custom exception for this example
class InvalidTimezone(CarbonicError):
pass
def _validate_timezone(tz_name: str) -> ZoneInfo:
try:
return ZoneInfo(tz_name)
except ZoneInfoNotFoundError:
raise InvalidTimezone(f"Unknown timezone: {tz_name}")
Contributing Guidelines¶
What We're Looking For¶
- Bug fixes: Always welcome with tests
- Performance improvements: With benchmarks showing improvement
- New features: Discuss in an issue first
- Documentation: Improvements and examples
- Tests: Additional test coverage
What We're Not Looking For¶
- Breaking changes without strong justification
- Features that significantly increase complexity
- Code that doesn't follow established patterns
- Changes without tests
Pull Request Process¶
- Fork and branch: Create a feature branch from
main - Implement: Make your changes with tests
- Document: Update docs if needed
- Test: Ensure all tests pass
- Submit: Create a pull request with clear description
PR Requirements¶
- [ ] All tests pass
- [ ] Type checking passes (mypy)
- [ ] Linting passes (ruff)
- [ ] New code has tests
- [ ] Documentation updated if needed
- [ ] CHANGELOG.md updated for user-facing changes
Release Process¶
Carbonic follows semantic versioning:
- MAJOR: Breaking changes
- MINOR: New features (backward compatible)
- PATCH: Bug fixes (backward compatible)
Creating a Release¶
- Update version in
pyproject.toml - Update
CHANGELOG.md - Create release PR
- Tag release after merge
- GitHub Actions handles PyPI publication
Getting Help¶
- Issues: Use GitHub issues for bugs and feature requests
- Discussions: Use GitHub discussions for questions
- Email: Contact maintainers for private concerns
Recognition¶
Contributors are recognized in:
CONTRIBUTORS.mdfile- GitHub contributors page
- Release notes for significant contributions
Thank you for contributing to Carbonic!