Thanks for your interest in contributing to gdpy! This document provides guidelines and instructions for contributing.
- Getting Started
- Development Setup
- Project Structure
- Coding Standards
- Commit Messages
- Pull Requests
- Adding New Endpoints
- Documentation
- Testing
- Fork the repository on GitHub
- Clone your fork locally:
git clone https://github.com/YOUR_USERNAME/gdpy.git cd gdpy - Create a branch for your changes:
git checkout -b feature/your-feature-name
- Python 3.10 or higher
- uv (recommended) or pip
Install the project with development dependencies:
# Using uv (recommended)
uv sync --all-extras
# Or using pip
pip install -e ".[dev]"Before submitting changes, run all checks:
# Linting
uv run ruff check gdpy/
# Formatting
uv run ruff format gdpy/
# Type checking
uv run mypy gdpy/
# Run tests
uv run pytest
# Run all checks at once
uv run ruff check gdpy/ && uv run mypy gdpy/ && uv run pytestgdpy/
├── gdpy/
│ ├── __init__.py # Public API exports
│ ├── client.py # Client and AsyncClient classes
│ ├── constants.py # API constants (URLs, secrets)
│ ├── exceptions.py # Exception classes
│ ├── crypto/ # Cryptographic utilities
│ │ ├── base64.py # Base64 encoding/decoding
│ │ ├── chk.py # CHK generation
│ │ ├── gjp.py # GJP2 encoding
│ │ └── xor.py # XOR cipher
│ ├── models/ # Pydantic models
│ │ ├── __init__.py # Model exports
│ │ └── user.py # User, Level, Comment, etc.
│ └── utils/ # Utility functions
│ └── parsing.py # Response parsing
├── docs/ # Documentation
├── tests/ # Test files
├── pyproject.toml # Project configuration
└── README.md
- Follow PEP 8 conventions
- Use type hints for all function signatures
- Write docstrings for all public functions and classes
- Keep functions focused and under 50 lines when possible
We use Ruff for formatting and linting:
# Format code
uv run ruff format gdpy/
# Fix linting issues
uv run ruff check gdpy/ --fixAll functions must have type annotations:
# Good
def get_level(self, level_id: int) -> Level:
...
# Bad
def get_level(self, level_id):
...Use Google-style docstrings:
def get_user(self, account_id: int) -> User:
"""Get a user by their account ID.
Args:
account_id: The account ID of the user.
Returns:
A User object with the user's information.
Raises:
NotFoundError: If the user is not found.
Example:
```python
user = client.get_user(account_id=71)
print(user.username)
```
"""We use Conventional Commits:
feat:- New featuresfix:- Bug fixesdocs:- Documentation changesstyle:- Code style changes (formatting, etc.)refactor:- Code refactoringtest:- Adding or updating testschore:- Maintenance tasks
Examples:
feat: add upload_level endpoint
fix: handle rate limiting errors correctly
docs: update API coverage table
refactor: simplify LevelDifficulty enum
- Create a feature branch from
main - Make your changes following the coding standards
- Run all checks before pushing:
uv run ruff check gdpy/ && uv run mypy gdpy/ && uv run pytest
- Push your branch and open a Pull Request
- Describe your changes in the PR description
- Link any related issues using GitHub keywords (e.g., "Fixes #123")
- Code follows the project's style guidelines
- All checks pass (ruff, mypy, pytest)
- New features have appropriate tests
- Documentation is updated if needed
- Commit messages follow conventional commits
When adding a new API endpoint:
Refer to the gd-docs repository for endpoint specifications.
Add the method to both Client (sync) and AsyncClient (async) classes:
# In Client class
def new_endpoint(self, param: int) -> ReturnType:
"""Method description.
Args:
param: Parameter description.
Returns:
Return type description.
Raises:
RuntimeError: If not authenticated.
"""
if not self.is_authenticated:
raise RuntimeError("Must be authenticated")
data = {
"accountID": str(self._account_id),
"gjp2": self._get_gjp2(),
"param": str(param),
}
response = self._request("endpointName.php", data)
return ReturnType.model_validate(parse_response(response))
# In AsyncClient class
async def new_endpoint(self, param: int) -> ReturnType:
"""Method description."""
if not self.is_authenticated:
raise RuntimeError("Must be authenticated")
data = {
"accountID": str(self._account_id),
"gjp2": self._get_gjp2(),
"param": str(param),
}
response = await self._request("endpointName.php", data)
return ReturnType.model_validate(parse_response(response))Add new Pydantic models in gdpy/models/user.py:
class NewModel(BaseModel):
"""Description of the model.
Attributes:
field1: Field description.
field2: Field description.
"""
model_config = ConfigDict(populate_by_name=True, extra="allow")
field1: int = Field(alias="1")
field2: str = Field(default="", alias="2")Add the model to gdpy/models/__init__.py.
- Update
docs/api-coverage.mdwith the new endpoint status - Update
docs/index.mdcoverage summary - Update the "Missing Features" section if applicable
uv run mkdocs serveThis starts a local server at http://127.0.0.1:8000.
When adding endpoints, update the coverage in both files:
docs/api-coverage.md- Detailed endpoint tabledocs/index.md- Summary table
Calculate correct percentages using Python:
total_implemented = sum([3, 4, 10, 7, 3, 12, 4, 2, 1, 2]) # Add your values
total_endpoints = sum([5, 4, 11, 7, 6, 12, 4, 3, 3, 2]) # Add your values
print(f"{total_implemented}/{total_endpoints} = {total_implemented/total_endpoints*100:.0f}%")# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=gdpy
# Run specific test file
uv run pytest tests/test_crypto.py
# Run specific test
uv run pytest tests/test_crypto.py::TestCrypto::test_xor_cipherTests should be placed in the tests/ directory:
import pytest
from gdpy import Client
def test_new_feature():
"""Test description."""
with Client() as client:
result = client.new_endpoint(param=123)
assert result.field1 == 123- Open an issue for bugs or feature requests
- Check existing issues before creating new ones
- Ask questions in issue discussions
Thank you for contributing! 🎮