undersort


Nameundersort JSON
Version 0.1.4 PyPI version JSON
download
home_pageNone
SummaryA tool to sort class methods by visibility (public, protected, private)
upload_time2025-10-23 19:49:16
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT
keywords class-methods code-formatter linter python sorting
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # undersort

A Python tool that automatically sorts class methods by visibility (public, protected, private) and type (class, static, instance).

## Features

- Automatically reorders class methods based on visibility and method type
- Two-level sorting: primary by visibility, secondary by method type
- Fully configurable ordering via `pyproject.toml`
- Pre-commit hook integration
- Colored output for better readability
- Check mode for CI/CD validation
- Diff mode to preview changes

## Installation

```bash
# Using uv (recommended)
uv add undersort

# Using pip
pip install undersort

# For development
git clone https://github.com/kivicode/undersort
cd undersort
uv sync
```

## Configuration

Configure the method ordering in your `pyproject.toml`:

```toml
[tool.undersort]
# Method visibility ordering (primary sort)
# Options: "public", "protected", "private"
order = ["public", "protected", "private"]

# Method type ordering within each visibility level (secondary sort, optional)
# Options: "class" (classmethod), "static" (staticmethod), "instance" (regular methods)
# Default: ["instance", "class", "static"]
method_type_order = ["instance", "class", "static"]
```

### Method Visibility Rules

- **Public methods**: No underscore prefix (e.g., `def method()`) or magic methods (e.g., `__init__`, `__str__`)
- **Protected methods**: Single underscore prefix (e.g., `def _method()`)
- **Private methods**: Double underscore prefix, not magic (e.g., `def __method()`)

### Method Type Rules

- **Class methods**: Decorated with `@classmethod`
- **Static methods**: Decorated with `@staticmethod`
- **Instance methods**: Regular methods (no special decorator)

### Sorting Behavior

Methods are sorted in two levels:

1. **Primary**: By visibility (public → protected → private)
2. **Secondary**: Within each visibility level, by method type (instance → class → static by default)

The sorting algorithm **minimizes movement** to preserve the original order as much as possible:

- Methods that need to move DOWN (to a later section) are placed at the **beginning** of their target section
- Methods that need to move UP (to an earlier section) are placed at the **end** of their target section
- Methods already in the correct section maintain their relative order

Example order with default configuration:

1. Public instance methods
2. Public class methods
3. Public static methods
4. Protected instance methods
5. Protected class methods
6. Protected static methods
7. Private instance methods
8. Private class methods
9. Private static methods

### Skipping Sorting with `# nosort`

You can prevent sorting at different levels using `# nosort` comments (case-insensitive):

**File-level**: Skip entire file

```python
# nosort: file
class Example:
    def _protected(self):
        pass
    def public(self):
        pass  # File won't be sorted
```

**Class-level**: Skip specific class

```python
class Example:  # nosort
    def _protected(self):
        pass
    def public(self):
        pass  # This class won't be sorted

class Other:
    def _protected(self):
        pass
    def public(self):
        pass  # This class WILL be sorted
```

**Method-level**: Keep method in its current position

```python
class Example:
    def public_a(self):
        pass

    def _protected(self):  # nosort
        pass  # Stays here, between public methods

    def public_b(self):
        pass  # Will move up, but _protected stays in place
```

## Usage

### Command Line

```bash
# Sort a single file
undersort example.py

# Sort multiple files
undersort file1.py file2.py file3.py

# Sort all Python files in a directory (recursive by default)
undersort src/

# Sort all Python files in current directory and subdirectories
undersort .

# Non-recursive directory sorting (only files in the directory, not subdirectories)
undersort src/ --no-recursive

# Wildcards work too (expanded by shell)
undersort *.py
undersort src/**/*.py

# Check if files need sorting (useful for CI)
undersort --check example.py
undersort --check src/

# Show diff of changes
undersort --diff example.py

# Combine flags
undersort --check --diff src/
```

**Note**: By default, undersort excludes all dot-prefixed directories (e.g., `.venv`, `.git`, `.pytest_cache`) and common build directories (`venv`, `__pycache__`, `node_modules`) when scanning directories recursively.

### Pre-commit Integration

Add to your `.pre-commit-config.yaml`:

```yaml
repos:
  - repo: local
    hooks:
      - id: undersort
        name: undersort
        entry: undersort
        language: python
        types: [python]
        additional_dependencies: ["undersort"]
```

Then install the hook:

```bash
pip install pre-commit
pre-commit install
```

## Example

### Before

```python
class Example:
    def _protected_instance(self):
        pass

    @staticmethod
    def public_static():
        pass

    def __init__(self):
        pass

    @classmethod
    def _protected_class(cls):
        pass

    def public_instance(self):
        pass

    def __private_method(self):
        pass

    @classmethod
    def public_class(cls):
        pass
```

### After (with default config)

```python
class Example:
    def __init__(self):
        pass

    def public_instance(self):
        pass

    @classmethod
    def public_class(cls):
        pass

    @staticmethod
    def public_static():
        pass

    def _protected_instance(self):
        pass

    @classmethod
    def _protected_class(cls):
        pass

    def __private_method(self):
        pass
```

The methods are now organized by:

1. **Visibility**: public (including `__init__`) → protected → private
2. **Type** (within each visibility): instance → class → static

## Development

```bash
# Install dependencies
uv sync

# Run on example file
uv run undersort example.py

# Test with check mode
uv run undersort --check example.py

# View diff
uv run undersort --diff example.py
```

## License

MIT

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "undersort",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "class-methods, code-formatter, linter, python, sorting",
    "author": null,
    "author_email": "KiviCode <kivicode.dev@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/3c/8d/8b1857df50140c28c17d009a769c766bd03e00647b553bec00910b57c18e/undersort-0.1.4.tar.gz",
    "platform": null,
    "description": "# undersort\n\nA Python tool that automatically sorts class methods by visibility (public, protected, private) and type (class, static, instance).\n\n## Features\n\n- Automatically reorders class methods based on visibility and method type\n- Two-level sorting: primary by visibility, secondary by method type\n- Fully configurable ordering via `pyproject.toml`\n- Pre-commit hook integration\n- Colored output for better readability\n- Check mode for CI/CD validation\n- Diff mode to preview changes\n\n## Installation\n\n```bash\n# Using uv (recommended)\nuv add undersort\n\n# Using pip\npip install undersort\n\n# For development\ngit clone https://github.com/kivicode/undersort\ncd undersort\nuv sync\n```\n\n## Configuration\n\nConfigure the method ordering in your `pyproject.toml`:\n\n```toml\n[tool.undersort]\n# Method visibility ordering (primary sort)\n# Options: \"public\", \"protected\", \"private\"\norder = [\"public\", \"protected\", \"private\"]\n\n# Method type ordering within each visibility level (secondary sort, optional)\n# Options: \"class\" (classmethod), \"static\" (staticmethod), \"instance\" (regular methods)\n# Default: [\"instance\", \"class\", \"static\"]\nmethod_type_order = [\"instance\", \"class\", \"static\"]\n```\n\n### Method Visibility Rules\n\n- **Public methods**: No underscore prefix (e.g., `def method()`) or magic methods (e.g., `__init__`, `__str__`)\n- **Protected methods**: Single underscore prefix (e.g., `def _method()`)\n- **Private methods**: Double underscore prefix, not magic (e.g., `def __method()`)\n\n### Method Type Rules\n\n- **Class methods**: Decorated with `@classmethod`\n- **Static methods**: Decorated with `@staticmethod`\n- **Instance methods**: Regular methods (no special decorator)\n\n### Sorting Behavior\n\nMethods are sorted in two levels:\n\n1. **Primary**: By visibility (public \u2192 protected \u2192 private)\n2. **Secondary**: Within each visibility level, by method type (instance \u2192 class \u2192 static by default)\n\nThe sorting algorithm **minimizes movement** to preserve the original order as much as possible:\n\n- Methods that need to move DOWN (to a later section) are placed at the **beginning** of their target section\n- Methods that need to move UP (to an earlier section) are placed at the **end** of their target section\n- Methods already in the correct section maintain their relative order\n\nExample order with default configuration:\n\n1. Public instance methods\n2. Public class methods\n3. Public static methods\n4. Protected instance methods\n5. Protected class methods\n6. Protected static methods\n7. Private instance methods\n8. Private class methods\n9. Private static methods\n\n### Skipping Sorting with `# nosort`\n\nYou can prevent sorting at different levels using `# nosort` comments (case-insensitive):\n\n**File-level**: Skip entire file\n\n```python\n# nosort: file\nclass Example:\n    def _protected(self):\n        pass\n    def public(self):\n        pass  # File won't be sorted\n```\n\n**Class-level**: Skip specific class\n\n```python\nclass Example:  # nosort\n    def _protected(self):\n        pass\n    def public(self):\n        pass  # This class won't be sorted\n\nclass Other:\n    def _protected(self):\n        pass\n    def public(self):\n        pass  # This class WILL be sorted\n```\n\n**Method-level**: Keep method in its current position\n\n```python\nclass Example:\n    def public_a(self):\n        pass\n\n    def _protected(self):  # nosort\n        pass  # Stays here, between public methods\n\n    def public_b(self):\n        pass  # Will move up, but _protected stays in place\n```\n\n## Usage\n\n### Command Line\n\n```bash\n# Sort a single file\nundersort example.py\n\n# Sort multiple files\nundersort file1.py file2.py file3.py\n\n# Sort all Python files in a directory (recursive by default)\nundersort src/\n\n# Sort all Python files in current directory and subdirectories\nundersort .\n\n# Non-recursive directory sorting (only files in the directory, not subdirectories)\nundersort src/ --no-recursive\n\n# Wildcards work too (expanded by shell)\nundersort *.py\nundersort src/**/*.py\n\n# Check if files need sorting (useful for CI)\nundersort --check example.py\nundersort --check src/\n\n# Show diff of changes\nundersort --diff example.py\n\n# Combine flags\nundersort --check --diff src/\n```\n\n**Note**: By default, undersort excludes all dot-prefixed directories (e.g., `.venv`, `.git`, `.pytest_cache`) and common build directories (`venv`, `__pycache__`, `node_modules`) when scanning directories recursively.\n\n### Pre-commit Integration\n\nAdd to your `.pre-commit-config.yaml`:\n\n```yaml\nrepos:\n  - repo: local\n    hooks:\n      - id: undersort\n        name: undersort\n        entry: undersort\n        language: python\n        types: [python]\n        additional_dependencies: [\"undersort\"]\n```\n\nThen install the hook:\n\n```bash\npip install pre-commit\npre-commit install\n```\n\n## Example\n\n### Before\n\n```python\nclass Example:\n    def _protected_instance(self):\n        pass\n\n    @staticmethod\n    def public_static():\n        pass\n\n    def __init__(self):\n        pass\n\n    @classmethod\n    def _protected_class(cls):\n        pass\n\n    def public_instance(self):\n        pass\n\n    def __private_method(self):\n        pass\n\n    @classmethod\n    def public_class(cls):\n        pass\n```\n\n### After (with default config)\n\n```python\nclass Example:\n    def __init__(self):\n        pass\n\n    def public_instance(self):\n        pass\n\n    @classmethod\n    def public_class(cls):\n        pass\n\n    @staticmethod\n    def public_static():\n        pass\n\n    def _protected_instance(self):\n        pass\n\n    @classmethod\n    def _protected_class(cls):\n        pass\n\n    def __private_method(self):\n        pass\n```\n\nThe methods are now organized by:\n\n1. **Visibility**: public (including `__init__`) \u2192 protected \u2192 private\n2. **Type** (within each visibility): instance \u2192 class \u2192 static\n\n## Development\n\n```bash\n# Install dependencies\nuv sync\n\n# Run on example file\nuv run undersort example.py\n\n# Test with check mode\nuv run undersort --check example.py\n\n# View diff\nuv run undersort --diff example.py\n```\n\n## License\n\nMIT\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A tool to sort class methods by visibility (public, protected, private)",
    "version": "0.1.4",
    "project_urls": {
        "Homepage": "https://github.com/kivicode/undersort",
        "Issues": "https://github.com/kivicode/undersort/issues",
        "Repository": "https://github.com/kivicode/undersort"
    },
    "split_keywords": [
        "class-methods",
        " code-formatter",
        " linter",
        " python",
        " sorting"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "ece3f6aba50d010723cbd511ef1a1b4e9074084ffffe97bd0a1a7f3b79ed18fe",
                "md5": "bc5f3d9cc0fdf3d38cdab80336242513",
                "sha256": "0b5fe39eb3e6792f1edcc2e41e511450e315400349c461675779d975252f3728"
            },
            "downloads": -1,
            "filename": "undersort-0.1.4-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "bc5f3d9cc0fdf3d38cdab80336242513",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 9719,
            "upload_time": "2025-10-23T19:49:15",
            "upload_time_iso_8601": "2025-10-23T19:49:15.838312Z",
            "url": "https://files.pythonhosted.org/packages/ec/e3/f6aba50d010723cbd511ef1a1b4e9074084ffffe97bd0a1a7f3b79ed18fe/undersort-0.1.4-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "3c8d8b1857df50140c28c17d009a769c766bd03e00647b553bec00910b57c18e",
                "md5": "9b3004e37b2814e01cca4dff96f96eee",
                "sha256": "61280958408b8f200a450a44a147d2a0f2f97d025c1ece10e2e0fe125fe9dd70"
            },
            "downloads": -1,
            "filename": "undersort-0.1.4.tar.gz",
            "has_sig": false,
            "md5_digest": "9b3004e37b2814e01cca4dff96f96eee",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 68079,
            "upload_time": "2025-10-23T19:49:16",
            "upload_time_iso_8601": "2025-10-23T19:49:16.850409Z",
            "url": "https://files.pythonhosted.org/packages/3c/8d/8b1857df50140c28c17d009a769c766bd03e00647b553bec00910b57c18e/undersort-0.1.4.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-23 19:49:16",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "kivicode",
    "github_project": "undersort",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "undersort"
}
        
Elapsed time: 1.28352s