Contributing

Thank you for your interest in contributing to PIPolars! This guide will help you get started.

Development Setup

Prerequisites

  • Windows 10/11 or Windows Server 2016+

  • Python 3.10 or later

  • OSIsoft PI AF SDK 2.x

  • uv (recommended) or pip

Clone and Install

# Clone the repository
git clone https://github.com/pipolars/pipolars.git
cd pipolars

# Install with all development dependencies
uv sync --all-extras

# Or using pip
pip install -e ".[dev,docs]"

Running Tests

# Run all tests
uv run pytest

# Run unit tests only
uv run pytest tests/unit

# Run with coverage
uv run pytest --cov=pipolars --cov-report=html

# Run integration tests (requires PI connection)
PI_SERVER=my-server uv run pytest -m integration

Code Quality

# Type checking
uv run mypy src

# Linting
uv run ruff check src

# Format code
uv run ruff format src

Building Documentation

# Install docs dependencies
uv sync --extra docs

# Build HTML docs
cd docs
make html

# Or on Windows
make.bat html

# View docs (Windows)
start _build/html/index.html

Code Style

PIPolars follows these conventions:

Python Style

  • Formatting: Ruff formatter (line length 100)

  • Imports: isort (via Ruff), first-party imports grouped

  • Docstrings: Google style

  • Type hints: Required for all public APIs

Example:

def recorded_values(
    self,
    tags: str | list[str],
    start: PITimestamp,
    end: PITimestamp,
    max_count: int = 0,
    include_quality: bool = False,
) -> pl.DataFrame:
    """Get recorded values for one or more tags.

    Args:
        tags: Single tag or list of tags
        start: Start time (datetime, string, or AFTime)
        end: End time
        max_count: Maximum values per tag (0 = no limit)
        include_quality: Include quality column

    Returns:
        DataFrame with recorded values

    Raises:
        PIPointNotFoundError: If tag doesn't exist
        PIConnectionError: If not connected

    Example:
        >>> df = client.recorded_values("SINUSOID", "*-1d", "*")
    """

Commit Messages

Use conventional commits:

  • feat: New feature

  • fix: Bug fix

  • docs: Documentation

  • test: Tests

  • refactor: Code refactoring

  • chore: Maintenance

Example:

feat: add bulk snapshot retrieval

- Add snapshots() method to PIClient
- Implement parallel fetching in BulkExtractor
- Add tests for bulk operations

Pull Request Process

  1. Fork the repository

  2. Create a feature branch (git checkout -b feature/my-feature)

  3. Write tests for new functionality

  4. Ensure all tests pass (uv run pytest)

  5. Check types (uv run mypy src)

  6. Check linting (uv run ruff check src)

  7. Commit with meaningful messages

  8. Push to your fork

  9. Open a Pull Request

PR Checklist

  • [ ] Tests pass locally

  • [ ] New code has tests

  • [ ] Type hints added

  • [ ] Docstrings added

  • [ ] Documentation updated (if needed)

  • [ ] CHANGELOG updated (for features/fixes)

  • [ ] No linting errors

Project Structure

pipolars/
├── src/pipolars/
│   ├── api/           # User-facing API
│   │   ├── client.py  # PIClient
│   │   ├── query.py   # PIQuery
│   │   └── lazy.py    # LazyFrame support
│   ├── connection/    # PI connectivity
│   │   ├── server.py  # PI Server connection
│   │   ├── af_database.py
│   │   ├── sdk.py     # AF SDK wrapper
│   │   └── auth.py    # Authentication
│   ├── extraction/    # Data retrieval
│   │   ├── points.py  # Single point extraction
│   │   ├── bulk.py    # Bulk operations
│   │   └── ...
│   ├── transform/     # Data conversion
│   │   ├── converters.py
│   │   └── ...
│   ├── cache/         # Caching
│   │   ├── storage.py
│   │   └── strategies.py
│   └── core/          # Core types
│       ├── config.py
│       ├── types.py
│       └── exceptions.py
├── tests/
│   ├── unit/
│   └── integration/
├── docs/
└── examples/

Testing Guidelines

Unit Tests

  • Test individual functions/methods in isolation

  • Mock external dependencies (PI SDK)

  • Located in tests/unit/

Integration Tests

  • Test against actual PI Server

  • Marked with @pytest.mark.integration

  • Located in tests/integration/

  • Require PI_SERVER environment variable

import pytest
from pipolars import PIClient

@pytest.mark.integration
def test_recorded_values_real_server():
    server = os.environ.get("PI_SERVER")
    with PIClient(server) as client:
        df = client.recorded_values("SINUSOID", "*-1h", "*")
        assert len(df) > 0

Test Fixtures

Common fixtures are in tests/conftest.py:

import pytest
from pipolars import PIConfig
from pipolars.core.config import PIServerConfig

@pytest.fixture
def pi_config():
    return PIConfig(
        server=PIServerConfig(host="test-server"),
    )

Reporting Issues

Bug Reports

Include:

  1. Python version (python --version)

  2. PIPolars version (pip show pipolars)

  3. PI AF SDK version

  4. Windows version

  5. Minimal reproducible example

  6. Full error traceback

Feature Requests

Include:

  1. Use case description

  2. Expected behavior

  3. Example API (if applicable)

License

By contributing to PIPolars, you agree that your contributions will be licensed under the MIT License.

Questions?

  • Open a GitHub Issue

  • Check existing issues and discussions