flexible-matchers


Nameflexible-matchers JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
SummaryLightweight, zero-dependency mock assertion helpers with flexible numeric and string matching
upload_time2025-10-06 14:50:27
maintainerNone
docs_urlNone
authorNone
requires_python>=3.7
licenseMIT
keywords testing mock unittest pytest matchers assertions test-helpers
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # flexible-matchers

[![PyPI version](https://badge.fury.io/py/flexible-matchers.svg)](https://badge.fury.io/py/flexible-matchers)
[![Python Support](https://img.shields.io/pypi/pyversions/flexible-matchers.svg)](https://pypi.org/project/flexible-matchers/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
[![Linting: ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![CI](https://github.com/skippdot/flexible-matchers/workflows/CI/badge.svg)](https://github.com/skippdot/flexible-matchers/actions)
[![codecov](https://codecov.io/gh/skippdot/flexible-matchers/branch/main/graph/badge.svg)](https://codecov.io/gh/skippdot/flexible-matchers)

**Lightweight, zero-dependency mock assertion helpers with flexible numeric and string matching.**

`flexible-matchers` provides intuitive matcher objects for use with Python's `unittest.mock` and general test assertions. Unlike other matcher libraries, it uses Python's native equality operators, making it work seamlessly with standard assertions and mock calls.

## โœจ Key Features

- **๐ŸŽฏ Zero Dependencies** - No external packages required
- **๏ฟฝ๏ฟฝ Numeric Matchers** - Range-based and tolerance-based number matching
- **๐Ÿ“ String Matchers** - Flexible length constraints for string validation
- **๐Ÿ“‹ Collection Matchers** - List validation with length constraints
- **๐Ÿšซ None Handling** - Special matcher for non-None values
- **๐Ÿ Pythonic API** - Uses standard `==` operator, works with any assertion library
- **โšก Lightweight** - Simple, focused implementation
- **๐Ÿงช Well-Tested** - Comprehensive test suite with 100% coverage
- **๐Ÿ“ฆ Type-Hinted** - Full type annotations for better IDE support

## ๐Ÿš€ Installation

```bash
pip install flexible-matchers
```

## ๐Ÿ“– Quick Start

```python
from flexible_matchers import NUMBER, STRING, IS_NUMBER, ANY_NOT_NONE

# In mock assertions
mock_api.assert_called_with(
    user_id=NUMBER(min_value=1),
    name=STRING(min_length=1),
    age=NUMBER(min_value=0, max_value=150)
)

# In data structure comparisons
response = {"id": 123, "name": "Alice", "created_at": "2024-01-01"}
assert response == {
    "id": IS_NUMBER,
    "name": STRING(min_length=1),
    "created_at": ANY_NOT_NONE
}
```

## ๐Ÿ“š Documentation

### NUMBER

Matches numeric values (int or float) with optional min/max constraints.

```python
from flexible_matchers import NUMBER, IS_NUMBER

# Match any number
assert 42 == NUMBER()
assert 3.14 == NUMBER()

# Match with minimum value
assert 42 == NUMBER(min_value=0)
assert -5 != NUMBER(min_value=0)

# Match with maximum value
assert 42 == NUMBER(max_value=100)
assert 150 != NUMBER(max_value=100)

# Match within range
assert 50 == NUMBER(min_value=0, max_value=100)
assert -1 != NUMBER(min_value=0, max_value=100)

# Pre-instantiated matcher for any number
assert 42 == IS_NUMBER
```

### CLOSE_NUMBER

Matches numbers within a tolerance range (useful for floating-point comparisons).

```python
from flexible_matchers import CLOSE_NUMBER

# Default tolerance of 0.5
assert 42.3 == CLOSE_NUMBER(42)
assert 41.7 == CLOSE_NUMBER(42)
assert 42.6 != CLOSE_NUMBER(42)

# Custom tolerance
assert 3.14 == CLOSE_NUMBER(3.1, tolerance=0.1)
assert 100 == CLOSE_NUMBER(99, tolerance=1)
```

**Unique Feature**: Unlike other libraries, `CLOSE_NUMBER` provides tolerance-based matching which is essential for floating-point comparisons in scientific computing and financial applications.

### STRING

Matches strings with optional length constraints.

```python
from flexible_matchers import STRING, IS_STRING

# Match any string
assert "hello" == STRING()
assert "" == STRING()

# Match exact length
assert "hello" == STRING(length=5)
assert "hi" != STRING(length=5)

# Match minimum length
assert "hello" == STRING(min_length=3)
assert "hi" != STRING(min_length=3)

# Match maximum length
assert "hello" == STRING(max_length=10)
assert "very long string" != STRING(max_length=10)

# Match length range
assert "hello" == STRING(min_length=3, max_length=10)

# Pre-instantiated matcher for any string
assert "hello" == IS_STRING
```

**Unique Feature**: Flexible string length constraints (`min_length`, `max_length`) are not available in most other matcher libraries.

### LIST

Matches lists with optional length constraint.

```python
from flexible_matchers import LIST, IS_LIST

# Match any list
assert [1, 2, 3] == LIST()
assert [] == LIST()

# Match exact length
assert [1, 2, 3] == LIST(3)
assert [1, 2] != LIST(3)

# Pre-instantiated matcher for any list
assert [1, 2, 3] == IS_LIST
```

### ANY_NOT_NONE

Matches any value except `None`.

```python
from flexible_matchers import ANY_NOT_NONE

# Matches any non-None value
assert 42 == ANY_NOT_NONE
assert "hello" == ANY_NOT_NONE
assert [] == ANY_NOT_NONE
assert 0 == ANY_NOT_NONE
assert False == ANY_NOT_NONE

# Does not match None
assert None != ANY_NOT_NONE
```

**Use Case**: Perfect for API responses where you want to ensure a field exists but don't care about its specific value.

## ๐Ÿ†š Comparison with Other Libraries

### vs. unittest.mock.ANY

```python
from unittest.mock import ANY
from flexible_matchers import NUMBER, STRING

# unittest.mock.ANY - too permissive
assert {"age": -100} == {"age": ANY}  # Passes, but age is invalid!

# flexible-matchers - precise validation
assert {"age": 30} == {"age": NUMBER(min_value=0, max_value=150)}  # โœ“
assert {"age": -100} == {"age": NUMBER(min_value=0, max_value=150)}  # โœ—
```

### vs. PyHamcrest

```python
# PyHamcrest - requires special syntax
from hamcrest import assert_that, instance_of, greater_than
assert_that(value, instance_of(int))
assert_that(value, greater_than(0))

# flexible-matchers - natural Python syntax
from flexible_matchers import NUMBER
assert value == NUMBER(min_value=0)
```

### vs. dirty-equals

```python
# dirty-equals - close, but missing key features
from dirty_equals import IsPositiveInt
assert 42 == IsPositiveInt

# flexible-matchers - more flexible with ranges and tolerance
from flexible_matchers import NUMBER, CLOSE_NUMBER
assert 42 == NUMBER(min_value=0, max_value=100)
assert 3.14 == CLOSE_NUMBER(3.1, tolerance=0.1)  # Not available in dirty-equals
```

### vs. pychoir

```python
# pychoir - similar approach, but less intuitive
from pychoir import LessThan, GreaterThan, And
assert value == And(GreaterThan(0), LessThan(100))

# flexible-matchers - simpler, more intuitive
from flexible_matchers import NUMBER
assert value == NUMBER(min_value=0, max_value=100)
```

## ๐ŸŽฏ Real-World Examples

### API Testing

```python
from flexible_matchers import NUMBER, STRING, ANY_NOT_NONE

def test_create_user_api():
    response = api.create_user(name="Alice", email="alice@example.com")
    
    assert response == {
        "id": NUMBER(min_value=1),
        "name": STRING(min_length=1, max_length=100),
        "email": STRING(min_length=5),
        "created_at": ANY_NOT_NONE,
        "updated_at": ANY_NOT_NONE,
        "is_active": True,
    }
```

### Mock Assertions

```python
from unittest.mock import Mock
from flexible_matchers import NUMBER, STRING

def test_user_service():
    mock_db = Mock()
    service = UserService(mock_db)
    
    service.create_user(name="Alice", age=30)
    
    mock_db.insert.assert_called_once_with(
        table="users",
        data={
            "name": STRING(min_length=1),
            "age": NUMBER(min_value=0, max_value=150),
            "created_at": ANY_NOT_NONE,
        }
    )
```

### Nested Data Structures

```python
from flexible_matchers import NUMBER, STRING, LIST, IS_NUMBER

def test_complex_response():
    response = {
        "users": [
            {"id": 1, "name": "Alice", "scores": [95, 87, 92]},
            {"id": 2, "name": "Bob", "scores": [88, 91, 85]},
        ],
        "total": 2,
        "page": 1,
    }
    
    assert response == {
        "users": [
            {
                "id": IS_NUMBER,
                "name": STRING(min_length=1),
                "scores": LIST(3),
            },
            {
                "id": IS_NUMBER,
                "name": STRING(min_length=1),
                "scores": LIST(3),
            },
        ],
        "total": NUMBER(min_value=0),
        "page": NUMBER(min_value=1),
    }
```

### Floating-Point Comparisons

```python
from flexible_matchers import CLOSE_NUMBER

def test_scientific_calculation():
    result = calculate_pi()
    assert result == CLOSE_NUMBER(3.14159, tolerance=0.00001)
    
def test_financial_calculation():
    total = calculate_total([10.10, 20.20, 30.30])
    assert total == CLOSE_NUMBER(60.60, tolerance=0.01)
```

## ๐Ÿ› ๏ธ Development

### Setup

```bash
# Clone the repository
git clone https://github.com/skippdot/flexible-matchers.git
cd flexible-matchers

# Install development dependencies
pip install -e ".[dev]"
```

### Running Tests

```bash
# Run all tests
pytest

# Run with coverage
pytest --cov=flexible_matchers --cov-report=html

# Run specific test file
pytest tests/test_matchers.py

# Run specific test
pytest tests/test_matchers.py::TestNUMBER::test_range
```

### Code Quality

```bash
# Format code
black src tests
isort src tests

# Lint code
ruff check src tests
flake8 src tests
pylint src tests

# Type checking
mypy src
```

### Running All Checks

```bash
# Format
black src tests && isort src tests

# Lint
ruff check src tests && flake8 src tests && pylint src tests

# Test
pytest --cov=flexible_matchers --cov-report=term-missing

# Type check
mypy src
```

## ๐Ÿ“‹ Requirements

- Python >= 3.7
- No runtime dependencies!

## ๐Ÿ“„ License

MIT License - see [LICENSE](LICENSE) file for details.

## ๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## ๐Ÿ™ Acknowledgments

Inspired by:
- [pychoir](https://github.com/kajaste/pychoir) - Modern matcher library
- [dirty-equals](https://github.com/samuelcolvin/dirty-equals) - Flexible equality testing
- [PyHamcrest](https://github.com/hamcrest/PyHamcrest) - Mature matcher framework
- [callee](https://github.com/Xion/callee) - Argument matchers (now abandoned)

## ๐Ÿ“Š Project Stats

- **Zero Dependencies**: No external packages required
- **100% Test Coverage**: Comprehensive test suite
- **Type Hinted**: Full type annotations
- **Python 3.7+**: Modern Python support
- **Active Maintenance**: Regular updates and improvements

## ๐Ÿ”— Links

- **PyPI**: https://pypi.org/project/flexible-matchers/
- **GitHub**: https://github.com/skippdot/flexible-matchers
- **Issues**: https://github.com/skippdot/flexible-matchers/issues
- **Changelog**: https://github.com/skippdot/flexible-matchers/releases

---

Made with โค๏ธ by [Stepan Shamaiev](https://github.com/skippdot)

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "flexible-matchers",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": null,
    "keywords": "testing, mock, unittest, pytest, matchers, assertions, test-helpers",
    "author": null,
    "author_email": "Stepan Shamaiev <59963936+skippdot@users.noreply.github.com>",
    "download_url": "https://files.pythonhosted.org/packages/fa/d9/7c7c38533e471c8485815b238cde526c2587c6a7f19c9dd8484aa22403de/flexible_matchers-0.1.0.tar.gz",
    "platform": null,
    "description": "# flexible-matchers\n\n[![PyPI version](https://badge.fury.io/py/flexible-matchers.svg)](https://badge.fury.io/py/flexible-matchers)\n[![Python Support](https://img.shields.io/pypi/pyversions/flexible-matchers.svg)](https://pypi.org/project/flexible-matchers/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)\n[![Linting: ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n[![CI](https://github.com/skippdot/flexible-matchers/workflows/CI/badge.svg)](https://github.com/skippdot/flexible-matchers/actions)\n[![codecov](https://codecov.io/gh/skippdot/flexible-matchers/branch/main/graph/badge.svg)](https://codecov.io/gh/skippdot/flexible-matchers)\n\n**Lightweight, zero-dependency mock assertion helpers with flexible numeric and string matching.**\n\n`flexible-matchers` provides intuitive matcher objects for use with Python's `unittest.mock` and general test assertions. Unlike other matcher libraries, it uses Python's native equality operators, making it work seamlessly with standard assertions and mock calls.\n\n## \u2728 Key Features\n\n- **\ud83c\udfaf Zero Dependencies** - No external packages required\n- **\ufffd\ufffd Numeric Matchers** - Range-based and tolerance-based number matching\n- **\ud83d\udcdd String Matchers** - Flexible length constraints for string validation\n- **\ud83d\udccb Collection Matchers** - List validation with length constraints\n- **\ud83d\udeab None Handling** - Special matcher for non-None values\n- **\ud83d\udc0d Pythonic API** - Uses standard `==` operator, works with any assertion library\n- **\u26a1 Lightweight** - Simple, focused implementation\n- **\ud83e\uddea Well-Tested** - Comprehensive test suite with 100% coverage\n- **\ud83d\udce6 Type-Hinted** - Full type annotations for better IDE support\n\n## \ud83d\ude80 Installation\n\n```bash\npip install flexible-matchers\n```\n\n## \ud83d\udcd6 Quick Start\n\n```python\nfrom flexible_matchers import NUMBER, STRING, IS_NUMBER, ANY_NOT_NONE\n\n# In mock assertions\nmock_api.assert_called_with(\n    user_id=NUMBER(min_value=1),\n    name=STRING(min_length=1),\n    age=NUMBER(min_value=0, max_value=150)\n)\n\n# In data structure comparisons\nresponse = {\"id\": 123, \"name\": \"Alice\", \"created_at\": \"2024-01-01\"}\nassert response == {\n    \"id\": IS_NUMBER,\n    \"name\": STRING(min_length=1),\n    \"created_at\": ANY_NOT_NONE\n}\n```\n\n## \ud83d\udcda Documentation\n\n### NUMBER\n\nMatches numeric values (int or float) with optional min/max constraints.\n\n```python\nfrom flexible_matchers import NUMBER, IS_NUMBER\n\n# Match any number\nassert 42 == NUMBER()\nassert 3.14 == NUMBER()\n\n# Match with minimum value\nassert 42 == NUMBER(min_value=0)\nassert -5 != NUMBER(min_value=0)\n\n# Match with maximum value\nassert 42 == NUMBER(max_value=100)\nassert 150 != NUMBER(max_value=100)\n\n# Match within range\nassert 50 == NUMBER(min_value=0, max_value=100)\nassert -1 != NUMBER(min_value=0, max_value=100)\n\n# Pre-instantiated matcher for any number\nassert 42 == IS_NUMBER\n```\n\n### CLOSE_NUMBER\n\nMatches numbers within a tolerance range (useful for floating-point comparisons).\n\n```python\nfrom flexible_matchers import CLOSE_NUMBER\n\n# Default tolerance of 0.5\nassert 42.3 == CLOSE_NUMBER(42)\nassert 41.7 == CLOSE_NUMBER(42)\nassert 42.6 != CLOSE_NUMBER(42)\n\n# Custom tolerance\nassert 3.14 == CLOSE_NUMBER(3.1, tolerance=0.1)\nassert 100 == CLOSE_NUMBER(99, tolerance=1)\n```\n\n**Unique Feature**: Unlike other libraries, `CLOSE_NUMBER` provides tolerance-based matching which is essential for floating-point comparisons in scientific computing and financial applications.\n\n### STRING\n\nMatches strings with optional length constraints.\n\n```python\nfrom flexible_matchers import STRING, IS_STRING\n\n# Match any string\nassert \"hello\" == STRING()\nassert \"\" == STRING()\n\n# Match exact length\nassert \"hello\" == STRING(length=5)\nassert \"hi\" != STRING(length=5)\n\n# Match minimum length\nassert \"hello\" == STRING(min_length=3)\nassert \"hi\" != STRING(min_length=3)\n\n# Match maximum length\nassert \"hello\" == STRING(max_length=10)\nassert \"very long string\" != STRING(max_length=10)\n\n# Match length range\nassert \"hello\" == STRING(min_length=3, max_length=10)\n\n# Pre-instantiated matcher for any string\nassert \"hello\" == IS_STRING\n```\n\n**Unique Feature**: Flexible string length constraints (`min_length`, `max_length`) are not available in most other matcher libraries.\n\n### LIST\n\nMatches lists with optional length constraint.\n\n```python\nfrom flexible_matchers import LIST, IS_LIST\n\n# Match any list\nassert [1, 2, 3] == LIST()\nassert [] == LIST()\n\n# Match exact length\nassert [1, 2, 3] == LIST(3)\nassert [1, 2] != LIST(3)\n\n# Pre-instantiated matcher for any list\nassert [1, 2, 3] == IS_LIST\n```\n\n### ANY_NOT_NONE\n\nMatches any value except `None`.\n\n```python\nfrom flexible_matchers import ANY_NOT_NONE\n\n# Matches any non-None value\nassert 42 == ANY_NOT_NONE\nassert \"hello\" == ANY_NOT_NONE\nassert [] == ANY_NOT_NONE\nassert 0 == ANY_NOT_NONE\nassert False == ANY_NOT_NONE\n\n# Does not match None\nassert None != ANY_NOT_NONE\n```\n\n**Use Case**: Perfect for API responses where you want to ensure a field exists but don't care about its specific value.\n\n## \ud83c\udd9a Comparison with Other Libraries\n\n### vs. unittest.mock.ANY\n\n```python\nfrom unittest.mock import ANY\nfrom flexible_matchers import NUMBER, STRING\n\n# unittest.mock.ANY - too permissive\nassert {\"age\": -100} == {\"age\": ANY}  # Passes, but age is invalid!\n\n# flexible-matchers - precise validation\nassert {\"age\": 30} == {\"age\": NUMBER(min_value=0, max_value=150)}  # \u2713\nassert {\"age\": -100} == {\"age\": NUMBER(min_value=0, max_value=150)}  # \u2717\n```\n\n### vs. PyHamcrest\n\n```python\n# PyHamcrest - requires special syntax\nfrom hamcrest import assert_that, instance_of, greater_than\nassert_that(value, instance_of(int))\nassert_that(value, greater_than(0))\n\n# flexible-matchers - natural Python syntax\nfrom flexible_matchers import NUMBER\nassert value == NUMBER(min_value=0)\n```\n\n### vs. dirty-equals\n\n```python\n# dirty-equals - close, but missing key features\nfrom dirty_equals import IsPositiveInt\nassert 42 == IsPositiveInt\n\n# flexible-matchers - more flexible with ranges and tolerance\nfrom flexible_matchers import NUMBER, CLOSE_NUMBER\nassert 42 == NUMBER(min_value=0, max_value=100)\nassert 3.14 == CLOSE_NUMBER(3.1, tolerance=0.1)  # Not available in dirty-equals\n```\n\n### vs. pychoir\n\n```python\n# pychoir - similar approach, but less intuitive\nfrom pychoir import LessThan, GreaterThan, And\nassert value == And(GreaterThan(0), LessThan(100))\n\n# flexible-matchers - simpler, more intuitive\nfrom flexible_matchers import NUMBER\nassert value == NUMBER(min_value=0, max_value=100)\n```\n\n## \ud83c\udfaf Real-World Examples\n\n### API Testing\n\n```python\nfrom flexible_matchers import NUMBER, STRING, ANY_NOT_NONE\n\ndef test_create_user_api():\n    response = api.create_user(name=\"Alice\", email=\"alice@example.com\")\n    \n    assert response == {\n        \"id\": NUMBER(min_value=1),\n        \"name\": STRING(min_length=1, max_length=100),\n        \"email\": STRING(min_length=5),\n        \"created_at\": ANY_NOT_NONE,\n        \"updated_at\": ANY_NOT_NONE,\n        \"is_active\": True,\n    }\n```\n\n### Mock Assertions\n\n```python\nfrom unittest.mock import Mock\nfrom flexible_matchers import NUMBER, STRING\n\ndef test_user_service():\n    mock_db = Mock()\n    service = UserService(mock_db)\n    \n    service.create_user(name=\"Alice\", age=30)\n    \n    mock_db.insert.assert_called_once_with(\n        table=\"users\",\n        data={\n            \"name\": STRING(min_length=1),\n            \"age\": NUMBER(min_value=0, max_value=150),\n            \"created_at\": ANY_NOT_NONE,\n        }\n    )\n```\n\n### Nested Data Structures\n\n```python\nfrom flexible_matchers import NUMBER, STRING, LIST, IS_NUMBER\n\ndef test_complex_response():\n    response = {\n        \"users\": [\n            {\"id\": 1, \"name\": \"Alice\", \"scores\": [95, 87, 92]},\n            {\"id\": 2, \"name\": \"Bob\", \"scores\": [88, 91, 85]},\n        ],\n        \"total\": 2,\n        \"page\": 1,\n    }\n    \n    assert response == {\n        \"users\": [\n            {\n                \"id\": IS_NUMBER,\n                \"name\": STRING(min_length=1),\n                \"scores\": LIST(3),\n            },\n            {\n                \"id\": IS_NUMBER,\n                \"name\": STRING(min_length=1),\n                \"scores\": LIST(3),\n            },\n        ],\n        \"total\": NUMBER(min_value=0),\n        \"page\": NUMBER(min_value=1),\n    }\n```\n\n### Floating-Point Comparisons\n\n```python\nfrom flexible_matchers import CLOSE_NUMBER\n\ndef test_scientific_calculation():\n    result = calculate_pi()\n    assert result == CLOSE_NUMBER(3.14159, tolerance=0.00001)\n    \ndef test_financial_calculation():\n    total = calculate_total([10.10, 20.20, 30.30])\n    assert total == CLOSE_NUMBER(60.60, tolerance=0.01)\n```\n\n## \ud83d\udee0\ufe0f Development\n\n### Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/skippdot/flexible-matchers.git\ncd flexible-matchers\n\n# Install development dependencies\npip install -e \".[dev]\"\n```\n\n### Running Tests\n\n```bash\n# Run all tests\npytest\n\n# Run with coverage\npytest --cov=flexible_matchers --cov-report=html\n\n# Run specific test file\npytest tests/test_matchers.py\n\n# Run specific test\npytest tests/test_matchers.py::TestNUMBER::test_range\n```\n\n### Code Quality\n\n```bash\n# Format code\nblack src tests\nisort src tests\n\n# Lint code\nruff check src tests\nflake8 src tests\npylint src tests\n\n# Type checking\nmypy src\n```\n\n### Running All Checks\n\n```bash\n# Format\nblack src tests && isort src tests\n\n# Lint\nruff check src tests && flake8 src tests && pylint src tests\n\n# Test\npytest --cov=flexible_matchers --cov-report=term-missing\n\n# Type check\nmypy src\n```\n\n## \ud83d\udccb Requirements\n\n- Python >= 3.7\n- No runtime dependencies!\n\n## \ud83d\udcc4 License\n\nMIT License - see [LICENSE](LICENSE) file for details.\n\n## \ud83e\udd1d Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add some amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## \ud83d\ude4f Acknowledgments\n\nInspired by:\n- [pychoir](https://github.com/kajaste/pychoir) - Modern matcher library\n- [dirty-equals](https://github.com/samuelcolvin/dirty-equals) - Flexible equality testing\n- [PyHamcrest](https://github.com/hamcrest/PyHamcrest) - Mature matcher framework\n- [callee](https://github.com/Xion/callee) - Argument matchers (now abandoned)\n\n## \ud83d\udcca Project Stats\n\n- **Zero Dependencies**: No external packages required\n- **100% Test Coverage**: Comprehensive test suite\n- **Type Hinted**: Full type annotations\n- **Python 3.7+**: Modern Python support\n- **Active Maintenance**: Regular updates and improvements\n\n## \ud83d\udd17 Links\n\n- **PyPI**: https://pypi.org/project/flexible-matchers/\n- **GitHub**: https://github.com/skippdot/flexible-matchers\n- **Issues**: https://github.com/skippdot/flexible-matchers/issues\n- **Changelog**: https://github.com/skippdot/flexible-matchers/releases\n\n---\n\nMade with \u2764\ufe0f by [Stepan Shamaiev](https://github.com/skippdot)\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Lightweight, zero-dependency mock assertion helpers with flexible numeric and string matching",
    "version": "0.1.0",
    "project_urls": {
        "Homepage": "https://github.com/skippdot/flexible-matchers",
        "Issues": "https://github.com/skippdot/flexible-matchers/issues",
        "Repository": "https://github.com/skippdot/flexible-matchers"
    },
    "split_keywords": [
        "testing",
        " mock",
        " unittest",
        " pytest",
        " matchers",
        " assertions",
        " test-helpers"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "112582e24e55ac3228a6f508072c0d6d14d22ab1220c52bc48766d6aa5586b6d",
                "md5": "84d8283bb3d7fb550c2849e2a4c003bd",
                "sha256": "b8d88be8bb406dfd22adae0b4bd4287855da836b46956a1b619bf369325b3037"
            },
            "downloads": -1,
            "filename": "flexible_matchers-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "84d8283bb3d7fb550c2849e2a4c003bd",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 8206,
            "upload_time": "2025-10-06T14:50:25",
            "upload_time_iso_8601": "2025-10-06T14:50:25.935241Z",
            "url": "https://files.pythonhosted.org/packages/11/25/82e24e55ac3228a6f508072c0d6d14d22ab1220c52bc48766d6aa5586b6d/flexible_matchers-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "fad97c7c38533e471c8485815b238cde526c2587c6a7f19c9dd8484aa22403de",
                "md5": "cc9d3edc3065b5d780c81b279d7796b6",
                "sha256": "86191acecaf34d87aa4f5b016738c61de709931df5f31e29f5320e4b1776966e"
            },
            "downloads": -1,
            "filename": "flexible_matchers-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "cc9d3edc3065b5d780c81b279d7796b6",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 13901,
            "upload_time": "2025-10-06T14:50:27",
            "upload_time_iso_8601": "2025-10-06T14:50:27.023421Z",
            "url": "https://files.pythonhosted.org/packages/fa/d9/7c7c38533e471c8485815b238cde526c2587c6a7f19c9dd8484aa22403de/flexible_matchers-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-06 14:50:27",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "skippdot",
    "github_project": "flexible-matchers",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "flexible-matchers"
}
        
Elapsed time: 1.73916s