Developer Guidelines
Docstring Standards
This project follows Google-style docstrings for all Python code. Consistent documentation is crucial for maintainability and API generation.
Docstring Convention
We use Google-style docstrings exclusively. Do not mix with NumPy or other styles.
Basic Structure
def function_name(param1: str, param2: int = 0) -> bool:
"""One-line summary of what the function does.
Longer description if needed, explaining behavior, algorithm,
or providing additional context. This can span multiple lines
and include implementation details.
Args:
param1: Description of param1.
param2: Description of param2. Defaults to 0.
Returns:
Description of return value.
Raises:
ExceptionType: Description of when this exception is raised.
AnotherException: Description of another exception condition.
Example:
>>> result = function_name("hello", 5)
>>> print(result)
True
Note:
Additional notes about usage, performance, or behavior.
"""
Module Docstrings
Every module should have a comprehensive docstring:
"""Module title - brief description.
Longer description explaining the module's purpose, main functionality,
and how it fits into the overall application architecture.
This module provides [key functionality] including [list main features].
It integrates with [other components] to [explain integration].
Example:
>>> from youtrack_cli import auth
>>> auth_manager = auth.AuthManager()
>>> result = await auth_manager.verify_credentials()
"""
Class Docstrings
Classes should document their purpose, key attributes, and usage:
class ExampleClass:
"""Brief description of the class purpose.
Detailed explanation of what the class does, its main responsibilities,
and how it should be used. Include information about state management,
threading safety, or other important behavioral characteristics.
Attributes:
attribute1: Description of public attribute.
attribute2: Description of another attribute.
Example:
>>> instance = ExampleClass("config")
>>> result = instance.do_something()
"""
Method and Function Docstrings
Required Sections
Summary: One-line description
Args: Document all parameters
Returns: Describe return value (if not None)
Raises: List possible exceptions
Optional Sections
Example: Code examples showing usage
Note: Important behavioral notes
Warning: Critical warnings about usage
Parameter Documentation
Args:
param_name: Simple description for basic parameters.
complex_param: More detailed description for complex parameters.
Can span multiple lines when needed. Include type information
if not obvious from type hints.
optional_param: Description. Defaults to None.
Use "Defaults to X" format for default values.
Return Value Documentation
Returns:
Simple description for basic returns.
Returns:
dict: More complex returns should specify the type.
Can include structure information:
{
'key1': 'Description of key1',
'key2': 'Description of key2'
}
Exception Documentation
Raises:
ValueError: When input parameters are invalid.
ConnectionError: When YouTrack API is unreachable.
AuthenticationError: When credentials are invalid or expired.
Examples in Docstrings
Include practical examples for complex functions:
Example:
Basic usage:
>>> manager = AuthManager()
>>> config = AuthConfig(
... base_url="https://youtrack.example.com",
... token="your-api-token"
... )
>>> result = await manager.verify_credentials(config)
>>> print(result.status)
'success'
Error handling:
>>> try:
... result = await manager.verify_credentials(bad_config)
... except AuthenticationError as e:
... print(f"Auth failed: {e}")
Python Doctests in Function Docstrings
This project uses Python’s built-in doctest functionality to ensure that code examples in function docstrings remain accurate and working. Doctests are automatically run as part of our test suite.
When to Use Doctests
Use doctests for:
Pure functions: Functions with predictable inputs and outputs
Utility functions: Helper functions that don’t require complex setup
Data transformation: Functions that format, convert, or process data
Simple validation: Functions that validate input or perform checks
Avoid doctests for:
Async functions: Unless you can mock them properly
Functions requiring external services: API calls, database operations
Functions with complex setup: Those needing multiple dependencies
Functions with unpredictable output: Current time, random values, etc.
Doctest Examples
Basic function with simple examples:
def format_timestamp(timestamp: Union[int, str, None]) -> str:
"""Format a timestamp value for display.
Args:
timestamp: Unix timestamp (int), ISO string, or None
Returns:
Formatted timestamp string or "N/A" if None/empty
Examples:
>>> format_timestamp(None)
'N/A'
>>> format_timestamp('')
'N/A'
>>> format_timestamp(1640995200000) # 2022-01-01 00:00:00
'2022-01-01 00:00:00'
>>> format_timestamp("1640995200000")
'2022-01-01 00:00:00'
"""
Function with multiple test cases:
def optimize_fields(base_params=None, fields=None, exclude_fields=None):
"""Optimize API request parameters.
Examples:
>>> result = optimize_fields(
... base_params={"project": "PROJ"},
... fields=["id", "summary"]
... )
>>> result["project"]
'PROJ'
>>> result["fields"]
'id,summary'
>>> optimize_fields()
{}
"""
Class method with doctest:
class PaginationConfig:
@classmethod
def get_pagination_type(cls, endpoint: str) -> PaginationType:
"""Determine pagination type for endpoint.
Examples:
>>> result = PaginationConfig.get_pagination_type("/api/issues")
>>> result.value
'cursor'
>>> result = PaginationConfig.get_pagination_type("/unknown")
>>> result.value
'offset'
"""
Doctest Best Practices
Use predictable data:
# Good - predictable output
>>> len(['a', 'b', 'c'])
3
# Avoid - unpredictable output
>>> import datetime
>>> datetime.datetime.now() # Changes every time
Handle complex objects:
# Good - test specific attributes
>>> result = create_user("john")
>>> result.name
'john'
>>> result.active
True
# Avoid - complex object representation
>>> create_user("john") # __repr__ may be unstable
Use ellipsis for partial matches:
>>> big_dict = {"key": "value", "items": [1, 2, 3]}
>>> print(big_dict)
{'key': 'value', ...}
Skip problematic examples:
>>> complex_async_operation()
<ComplexResult object at 0x...>
Running Doctests
Doctests run automatically with pytest:
# Run all tests including doctests
uv run pytest
# Run only doctests
uv run python -m doctest youtrack_cli/utils.py
# Run doctests with verbose output
uv run python -m doctest -v youtrack_cli/utils.py
Configuration
Doctest configuration is set in pyproject.toml:
[tool.pytest.ini_options]
addopts = [
# ... other options ...
"--doctest-modules", # Enable doctest discovery
]
doctest_optionflags = [
"NORMALIZE_WHITESPACE", # Ignore whitespace differences
"ELLIPSIS", # Allow ... in expected output
"IGNORE_EXCEPTION_DETAIL", # Ignore exception detail differences
]
Integration with CI/CD
Doctests run automatically in our CI pipeline:
Pull requests: All doctests must pass before merging
Main branch: Doctests run on every commit
Release: Doctests are part of the release verification
Quality Requirements
For functions with doctests:
All examples must be runnable and produce expected output
Examples should demonstrate typical use cases
Edge cases should be covered when relevant
Output should be predictable across different environments
Troubleshooting Common Issues
Import errors in doctests:
# Add necessary imports to the module
from typing import Union, Optional
from .exceptions import ValidationError
Whitespace issues:
# Use NORMALIZE_WHITESPACE flag (already configured)
>>> format_list([1, 2, 3])
'1, 2, 3'
Platform-specific differences:
# Use ellipsis for platform-specific parts
>>> get_file_path() # doctest: +ELLIPSIS
'/...user/.../config'
Async function testing:
# Generally avoid, but if needed:
>>> import asyncio
>>> asyncio.run(my_async_function()) # doctest: +SKIP
'expected result'
Type Hints Integration
Docstrings should complement, not duplicate, type hints:
def process_data(
items: list[dict[str, Any]],
filter_func: Optional[Callable[[dict], bool]] = None
) -> dict[str, int]:
"""Process a list of data items with optional filtering.
Args:
items: List of data dictionaries to process.
filter_func: Optional function to filter items. If None,
all items are processed.
Returns:
Dictionary mapping item types to counts.
"""
CLI Command Docstrings
CLI commands need special attention for help text:
@click.command()
@click.argument("project_id")
@click.option("--assignee", help="Assign issue to user")
def create_issue(project_id: str, assignee: Optional[str]) -> None:
"""Create a new issue in the specified project.
Creates a new YouTrack issue with the provided details. The issue
will be created in the specified project and can optionally be
assigned to a user.
Args:
project_id: Target project identifier (e.g., 'PROJ').
assignee: Optional username to assign the issue to.
Example:
Create a basic issue:
$ yt issues create PROJ "Fix login bug"
Create and assign an issue:
$ yt issues create PROJ "Add feature" --assignee john.doe
"""
Quality Standards
Docstring Linting
We use pydocstyle with Google convention:
# Check docstring compliance
uv run pydocstyle youtrack_cli/
# Run as part of pre-commit
pre-commit run pydocstyle
Configuration
See pyproject.toml for pydocstyle configuration:
[tool.pydocstyle]
convention = "google"
add_ignore = ["D100", "D104", "D105", "D107"]
match_dir = "youtrack_cli"
match = "(?!test_).*\.py"
Sphinx Integration
Docstrings are automatically processed by Sphinx with Napoleon extension:
# Generate documentation
cd docs/
sphinx-build -b html . _build/html
Best Practices
1. Keep It Practical
Focus on what developers need to know:
Purpose: What does this do?
Usage: How do I use it?
Parameters: What do I pass in?
Returns: What do I get back?
Exceptions: What can go wrong?
2. Use Examples Generously
Good examples are worth more than lengthy descriptions:
def search_issues(query: str, project: Optional[str] = None) -> list[Issue]:
"""Search for issues using YouTrack query syntax.
Args:
query: YouTrack search query.
project: Optional project to limit search to.
Returns:
List of matching issues.
Example:
>>> # Find high-priority bugs
>>> issues = search_issues("Type: Bug Priority: High")
>>>
>>> # Find your assigned issues in a project
>>> my_issues = search_issues("assignee: me", project="PROJ")
"""
3. Document Edge Cases
Mention important behavioral details:
def paginate_results(items: list, page_size: int = 50) -> Iterator[list]:
"""Split items into pages for display.
Args:
items: Items to paginate.
page_size: Items per page. Must be positive.
Yields:
Pages of items as lists.
Note:
Empty input returns no pages. Last page may contain fewer
than page_size items.
Raises:
ValueError: When page_size is not positive.
"""
5. Update Documentation with Code
When changing function behavior:
Update the docstring first
Update examples if needed
Update type hints if needed
Update tests
Update user documentation if public API
6. Review Checklist
Before committing, verify:
[ ] Module has comprehensive docstring
[ ] All public classes have docstrings
[ ] All public methods have docstrings
[ ] Parameters and returns are documented
[ ] Exceptions are documented
[ ] Examples are provided for complex functions
[ ]
pydocstylepasses without warnings[ ] Sphinx can generate docs without errors
Tools and Automation
Pre-commit Integration
Docstring quality is enforced via pre-commit hooks:
- id: pydocstyle
name: pydocstyle
entry: uv run pydocstyle
language: system
types: [python]
exclude: ^tests/
Sphinx Configuration
API documentation is generated automatically from docstrings:
# docs/conf.py
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
"sphinx_autodoc_typehints",
]
napoleon_google_docstring = True
napoleon_include_init_with_doc = True
IDE Integration
Configure your IDE for Google-style docstrings:
PyCharm: Settings → Tools → Python Integrated Tools → Docstring format: Google
VS Code: Python Docstring Generator extension with Google style
Vim: Use vim-pydocstring with Google template
Remember: Good documentation is a gift to your future self and your teammates. Take the time to write clear, helpful docstrings that explain not just what the code does, but why and how to use it effectively.
Documentation Testing
This project implements comprehensive documentation testing to ensure code examples stay current and links remain valid.
Overview
Documentation testing includes:
Doctest verification: Code examples in documentation are automatically tested
Link checking: External and internal links are validated
Build verification: Documentation builds without warnings or errors
Pre-commit integration: Documentation quality checks run before commits
CI/CD enforcement: Documentation tests must pass for all pull requests
Configuration
Doctest configuration in pyproject.toml:
[tool.pytest.ini_options]
addopts = "-v --tb=short --strict-markers --doctest-glob='*.rst'"
doctest_optionflags = ["NORMALIZE_WHITESPACE", "ELLIPSIS", "IGNORE_EXCEPTION_DETAIL"]
markers = [
"doctest: marks tests as doctests (documentation code examples)",
]
Sphinx configuration in docs/conf.py:
extensions = [
"sphinx.ext.doctest", # Enable doctest support
"sphinx.ext.linkcheck", # Enable link checking
# ... other extensions
]
# Doctest global setup for consistent testing environment
doctest_global_setup = """
import asyncio
import os
from unittest.mock import AsyncMock, MagicMock
# Mock environment for consistent testing
os.environ.setdefault('YOUTRACK_BASE_URL', 'https://youtrack.example.com')
os.environ.setdefault('YOUTRACK_TOKEN', 'test-token')
"""
# Link checking configuration
linkcheck_ignore = [
r'http://localhost.*',
r'https://youtrack\.example\.com.*',
]
linkcheck_timeout = 30
linkcheck_retries = 2
Running Documentation Tests
Local testing commands:
# Test RST-based doctests
uv run pytest --doctest-glob='*.rst' docs/ -v
# Build documentation with error checking
uv run sphinx-build -b html docs docs/_build/html -W
# Run Sphinx doctests
uv run sphinx-build -b doctest docs docs/_build/doctest
# Check documentation links
uv run sphinx-build -b linkcheck docs docs/_build/linkcheck
# Run pre-commit documentation hooks
pre-commit run doctests
pre-commit run sphinx-build
CI/CD Integration
Documentation testing is integrated into the CI pipeline with a dedicated job:
documentation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: uv sync --dev --group docs
- name: Build documentation
run: uv run sphinx-build -b html docs docs/_build/html -W --keep-going
- name: Run doctests
run: uv run sphinx-build -b doctest docs docs/_build/doctest
- name: Check documentation links
run: uv run sphinx-build -b linkcheck docs docs/_build/linkcheck
- name: Run pytest doctests
run: uv run pytest --doctest-glob='*.rst' docs/ -v
The documentation job is required for all pull requests and must pass before merging.
Pre-commit Hooks
Documentation testing is integrated into pre-commit workflow:
- id: doctests
name: doctests
entry: uv run pytest --doctest-glob='*.rst' docs/
language: system
files: ^docs/.*\.rst$
- id: sphinx-build
name: sphinx-build
entry: uv run sphinx-build -b html docs docs/_build/html -W
language: system
files: ^docs/.*\.rst$
These hooks run automatically when documentation files are modified.
Writing Testable Documentation
Best practices for documentation examples:
Use realistic but simple examples:
Basic usage example:
.. code-block:: python
# Create a configuration manager
config = ConfigManager()
config.set_config('BASE_URL', 'https://youtrack.example.com')
# Retrieve configuration
url = config.get_config('BASE_URL')
print(f"Using YouTrack at: {url}")
Avoid examples that require external dependencies:
Good - uses mocked responses:
.. code-block:: python
# Example with predictable output
result = process_data(['item1', 'item2'])
print(len(result)) # Output: 2
Avoid - requires real API:
.. code-block:: python
# This would fail in testing
api = YouTrackAPI('https://real-server.com')
issues = api.get_issues() # Unpredictable/fails
Use doctest directives when needed:
Example with output normalization:
.. code-block:: python
>>> result = {'key': 'value', 'items': [1, 2, 3]}
>>> print(result) # doctest: +SKIP
{'key': 'value', 'items': [1, 2, 3]}
Troubleshooting
Common doctest failures:
Inconsistent whitespace: Use
NORMALIZE_WHITESPACEflagUnpredictable output: Use
ELLIPSISor+SKIPdirectiveAsync code: Ensure proper async/await handling in examples
Environment differences: Use consistent mock data
Link checking issues:
Temporary failures: Links may be temporarily unavailable
Rate limiting: External sites may rate-limit requests
Authentication required: Some links require login
Local development: localhost URLs should be excluded
Build failures:
Missing dependencies: Ensure all Sphinx extensions are installed
Circular imports: Check for import issues in documented modules
Malformed RST: Validate RST syntax with sphinx-build warnings
Maintenance
Regular maintenance tasks:
Weekly link checking: Review and update broken links
Quarterly example review: Ensure examples reflect current API
Version updates: Update examples when API changes
Performance monitoring: Track documentation build times
Quality Gates
Documentation quality requirements:
All documentation builds without warnings
All doctests pass in CI
External links are validated (with exceptions for known issues)
Pre-commit hooks pass for all documentation changes
RST syntax is valid and consistent
The documentation testing system ensures that:
Code examples in documentation remain accurate
Links to external resources stay valid
Documentation builds successfully in all environments
Contributors receive immediate feedback on documentation quality
Documentation stays synchronized with code changes