Thank you for contributing! This tap is built with the Meltano Singer SDK.
Be respectful, inclusive, and collaborative.
Check existing issues first, then include:
- Clear description and steps to reproduce
- Expected vs actual behavior
- Environment: Python version, OS, OpenProject instance version
- Relevant logs (remove sensitive data)
- Describe the use case and benefits
- Check if it's supported by the Singer SDK
- Consider if it should be a stream-level or tap-level feature
- Fork and create a feature branch from
main - Follow SDK patterns and conventions
- Add tests for new functionality
- Update documentation (README, CHANGELOG)
- Ensure tests pass:
poetry run pytest - Run linting:
poetry run ruff check . - Submit PR with clear description
- Python 3.8+
- Poetry (recommended) or pip
- Git
- OpenProject API key for testing
# Clone your fork
git clone https://github.com/yourusername/tap-openproject.git
cd tap-openproject
# Install with Poetry
poetry install
poetry shell
# Or with pip
python -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'# Run all tests
poetry run pytest
# With coverage
poetry run pytest --cov=tap_openproject
# Run specific test
poetry run pytest tests/test_projects_stream.py -vWe use ruff for linting and formatting:
# Check code
poetry run ruff check .
# Fix automatically
poetry run ruff check --fix .
# Format code
poetry run ruff format .- Define stream class in
tap_openproject/streams.py:
class NewStream(RESTStream):
name = "new_stream"
path = "/endpoint"
primary_keys = ["id"]
replication_key = "updated_at" # If incremental
schema = th.PropertiesList(
th.Property("id", th.IntegerType, required=True),
# ... more properties
).to_dict()- Register in
tap_openproject/tap.py:
def discover_streams(self) -> List[Stream]:
return [
streams.ProjectsStream(self),
streams.NewStream(self), # Add here
]- Add tests in
tests/test_new_stream.py
- Stream Maps: Inherited automatically from SDK
- Pagination: Override
get_next_page_token()method - Authentication: Customize
OpenProjectAuthenticator - Incremental: Set
replication_keyon stream class
Create config.json (gitignored):
{
"api_key": "YOUR_TEST_API_KEY",
"base_url": "https://community.openproject.org/api/v3"
}Run manually:
poetry run tap-openproject --config config.json --discover
poetry run tap-openproject --config config.json | head -20Update these files when making changes:
- README.md - User-facing documentation
- CHANGELOG.md - Record all changes
- QUICKSTART.md - Quick reference guide
- meltano-hub.yml - Meltano Hub metadata
- Update version in
pyproject.toml - Update
CHANGELOG.mdwith release date - Create git tag:
git tag v0.x.0 - Push tag:
git push --tags - GitHub Actions will handle the rest (if configured)
By contributing, you agree that your contributions will be licensed under the MIT License. cd tap-openproject
python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -e . pip install pytest pytest-cov black flake8 mypy
pytest tests/ -v
## Code Style
### Python Style Guide
We follow [PEP 8](https://www.python.org/dev/peps/pep-0008/) with some modifications:
- **Line length:** 100 characters (not 79)
- **Indentation:** 4 spaces
- **Quotes:** Double quotes for strings
- **Imports:** Organized in three groups (standard library, third-party, local)
### Formatting
Use [Black](https://github.com/psf/black) for code formatting:
```bash
black tap_open_project/
Run flake8 to check for issues:
flake8 tap_open_project/ --max-line-length=100Use type hints for function parameters and return values:
def get_projects(client: HttpClient) -> List[Dict[str, Any]]:
"""Fetch all projects from the API."""
pass# Run all tests
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=tap_open_project --cov-report=html
# Run specific test
pytest tests/test_projects_stream.py -v- Place tests in the
tests/directory - Name test files
test_*.py - Use descriptive test names:
test_should_extract_project_name() - Mock external API calls using
unittest.mockorresponses - Aim for >80% code coverage
Example test:
import pytest
from tap_open_project.http_client import HttpClient
def test_should_handle_authentication_error():
"""Test that 401 errors are handled gracefully."""
client = HttpClient(
base_url="https://test.openproject.com/api/v3",
api_key="invalid-key"
)
with pytest.raises(AuthenticationError):
client.get("projects")Use Google-style docstrings:
def fetch_projects(client: HttpClient, start_date: Optional[str] = None) -> List[Dict]:
"""
Fetch projects from OpenProject API.
Args:
client: Configured HTTP client instance
start_date: Optional ISO 8601 date to filter projects
Returns:
List of project dictionaries with full metadata
Raises:
HTTPError: If API request fails
ValueError: If start_date format is invalid
"""
passWhen adding features, update:
- Feature list in README.md
- Configuration options table
- Usage examples
- Troubleshooting section (if relevant)
Follow Conventional Commits:
<type>(<scope>): <subject>
<body>
<footer>
Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, etc.)refactor: Code refactoringtest: Adding or updating testschore: Maintenance tasks
Examples:
feat(streams): add work packages stream
- Add WorkPackageStream class
- Implement pagination for large datasets
- Add work_packages.json schema
Closes #42
fix(http_client): handle rate limiting correctly
- Add exponential backoff for 429 responses
- Increase default max_retries to 5
- Log retry attempts to stderr
To add a new stream (e.g., work packages, time entries):
- Create schema file:
tap_open_project/schemas/stream_name.json - Add stream class: Inherit from base stream in
streams.py - Implement
get_records()method - Add tests: Create
tests/test_stream_name.py - Update documentation: Add to README.md
Example:
class WorkPackageStream:
"""Stream for extracting work packages."""
name = "work_packages"
schema = "schemas/work_packages.json"
key_properties = ["id"]
def __init__(self, client: HttpClient):
self.client = client
def get_records(self) -> List[Dict[str, Any]]:
"""Fetch all work packages."""
data = self.client.get("work_packages")
# Transform and return records
return data.get("_embedded", {}).get("elements", [])- Update version in
setup.py - Update CHANGELOG.md with release notes
- Create a git tag:
git tag -a v0.2.0 -m "Release v0.2.0" - Push tag:
git push origin v0.2.0 - Create GitHub release with changelog
- Open an issue for general questions
- Join discussions in GitHub Discussions
- Check existing documentation in README.md
By contributing, you agree that your contributions will be licensed under the MIT License.