Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# URLPath AI Coding Instructions

## Project Overview
URLPath is a Python library that extends `pathlib.PurePath` to provide object-oriented URL manipulation, combining filesystem path operations with URL components (scheme, netloc, query, fragment). The main `URL` class inherits from both `urllib.parse._NetlocResultMixinStr` and `PurePath`, enabling pathlib-style operations on URLs.

## Core Architecture

### URL Class Design Pattern
- **Inheritance**: `URL` extends `PurePath` with custom `_URLFlavour` that treats URLs as filesystem paths
- **Cached Properties**: Heavy use of `@cached_property` decorator for lazy evaluation of URL components
- **Immutability**: URLs are immutable; modifications return new instances via `with_*` methods
- **Path Encoding**: Uses `\x00` as escape character for `/` in query/fragment components during path operations

### Key Components
- **`_URLFlavour`**: Custom pathlib flavour that handles URL parsing via `splitroot()` method
- **`FrozenMultiDict`**: Immutable multi-value dictionary for query parameters with `get_one()` method
- **`JailedURL`**: Sandboxed URL subclass that prevents navigation outside a root URL
- **HTTP Methods**: Built-in `get()`, `post()`, `put()`, `patch()`, `delete()` using requests library

## Development Patterns

### Property Implementation
```python
@property
@cached_property
def scheme(self):
return urllib.parse.urlsplit(self._drv).scheme
```
All URL components follow this pattern: property decorator + cached_property for performance.

### URL Construction Methods
- `with_*` methods for component replacement (e.g., `with_scheme()`, `with_query()`)
- Path operations use `/` operator: `url / 'path'` or `url / '/absolute'`
- Query building via `with_query()` and `add_query()` methods

### Testing Conventions
- Tests in `tests/test_url.py` use pytest with native assert statements
- Comprehensive property testing for all URL components
- HTTP method testing (when possible)
- Optional dependency tests use `@pytest.mark.skipif` decorators
- README examples are automatically tested using pytest-markdown-docs

### Development Workflow

### Setup and Dependencies
```bash
# Initialize development environment
make install

# Or directly with uv
uv sync --group dev
```

### Running Tests
```bash
# Run all tests
make test

# Run unit tests only
make test-unit

# Run README tests only
make test-doctest

# Or use uv directly
uv run pytest tests/
uv run pytest README.md --markdown-docs
```

### Building and Packaging
```bash
# Build package
make build

# Clean artifacts
make clean

# See all available commands
make help
```

### Dependencies
- **Core**: `requests` for HTTP operations
- **Optional**: `jmespath` for JSON parsing, `webob` for request object support
- **Testing**: `pytest` for unit tests, `pytest-markdown-docs` for README testing
- **Build System**: `uv` with `hatchling` backend for modern Python packaging

### CI Configuration
GitHub Actions tests against Python 3.9-3.10 using `uv sync` and matrix strategy. Both unit tests and README doctests must pass.

## Code Conventions

### URL Component Access
- Use properties for read access: `url.scheme`, `url.path`, `url.query`
- Use `with_*` methods for modifications: `url.with_scheme('https')`
- Query parameters via `url.form` (FrozenMultiDict) or `url.form_fields` (tuples)

### Error Handling
- Malformed URLs raise standard `ValueError` from urllib.parse
- JailedURL validates root constraints in `__new__` with assertion
- Optional dependencies gracefully degrade (check `if jmespath:`)

### Performance Considerations
- URL parsing is expensive; use `@cached_property` for derived properties
- `_init()` method handles post-construction setup after pathlib operations
- Path operations use `_make_child()` pattern from pathlib for efficiency

### File Structure
- `urlpath/__init__.py`: Single-file module with all classes
- `tests/test_url.py`: Comprehensive pytest test suite
- `README.md`: Extensive examples with automated pytest validation
- `conftest.py`: pytest configuration for test discovery and path setup
18 changes: 0 additions & 18 deletions .github/workflows/deploy.yml

This file was deleted.

30 changes: 30 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Release
on:
release:
types:
- published

jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v5

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.10"

- name: Install uv
uses: astral-sh/setup-uv@v6

- name: Build package
run: uv build

- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.URLPATH_PYPI_TOKEN }}
skip-existing: true
40 changes: 32 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
name: Test
on: [push]
on: [pull_request]
jobs:
Test:
lint:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v5
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.10"
- name: Install dependencies
run: uv sync --group dev
- name: Run ruff linting
run: uv run ruff check
- name: Check code formatting
run: uv run ruff format --check
- name: Run mypy type checking
run: uv run mypy urlpath/ tests/

test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.4, 3.5, 3.6, 3.7, 3.8, 3.9, '3.10']
container: python:${{ matrix.python-version }}
python-version: ["3.9", "3.10"]
steps:
- name: Check out repository code
uses: actions/checkout@v2
- run: pip install -e .[test]
- run: python -m unittest
- run: python -m doctest README.rst
uses: actions/checkout@v5
- name: Install uv and set Python version
uses: astral-sh/setup-uv@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync --group dev
- name: Run unit tests
run: uv run pytest tests/
- name: Run README tests
run: uv run pytest README.md --markdown-docs
102 changes: 14 additions & 88 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,103 +1,29 @@
# Byte-compiled / optimized / DLL files
# Python
__pycache__/
*.py[cod]

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
# Testing
.coverage
.cache
nosetests.xml
coverage.xml

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/
.pytest_cache/
htmlcov/

# PyBuilder
target/
# Type checking
.mypy_cache/

# =========================
# Operating System Files
# =========================
# Linting
.ruff_cache/

# OSX
# =========================
# Virtual environments
.venv/
venv/

# IDEs
.vscode/
.idea/
.DS_Store
.AppleDouble
.LSOverride

# Thumbnails
._*

# Files that might appear on external disk
.Spotlight-V100
.Trashes

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

# Windows
# =========================

# Windows image file caches
Thumbs.db
ehthumbs.db

# Folder config file
Desktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msm
*.msp

# Windows shortcuts
*.lnk

.idea
31 changes: 31 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Pre-commit hooks for URLPath
# Install: pip install pre-commit && pre-commit install
# Run manually: pre-commit run --all-files

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.0
hooks:
# Run the linter
- id: ruff
args: [--fix]
# Run the formatter
- id: ruff-format

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-yaml
- id: check-toml
- id: check-added-large-files
- id: check-merge-conflict
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: local
hooks:
- id: mypy
name: mypy (uv)
entry: uv run mypy urlpath/ tests/
language: system
pass_filenames: false
2 changes: 2 additions & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
3.10
3.9
Loading