{"id":36943470,"url":"https://github.com/diegojromerolopez/mypy-pure","last_synced_at":"2026-01-13T11:01:03.366Z","repository":{"id":326754830,"uuid":"1062854743","full_name":"diegojromerolopez/mypy-pure","owner":"diegojromerolopez","description":"A mypy extension that provides a decorator to mark functions as pure","archived":false,"fork":false,"pushed_at":"2025-12-02T22:28:07.000Z","size":43,"stargazers_count":5,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-09T08:26:00.386Z","etag":null,"topics":["mypy","mypy-plugins","purity","static-analysis"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/diegojromerolopez.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-23T20:31:50.000Z","updated_at":"2025-12-08T13:56:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/diegojromerolopez/mypy-pure","commit_stats":null,"previous_names":["diegojromerolopez/mypy-pure"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/diegojromerolopez/mypy-pure","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/diegojromerolopez%2Fmypy-pure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/diegojromerolopez%2Fmypy-pure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/diegojromerolopez%2Fmypy-pure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/diegojromerolopez%2Fmypy-pure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/diegojromerolopez","download_url":"https://codeload.github.com/diegojromerolopez/mypy-pure/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/diegojromerolopez%2Fmypy-pure/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28383961,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T10:34:27.190Z","status":"ssl_error","status_checked_at":"2026-01-13T10:34:26.289Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mypy","mypy-plugins","purity","static-analysis"],"created_at":"2026-01-13T11:00:28.504Z","updated_at":"2026-01-13T11:01:03.312Z","avatar_url":"https://github.com/diegojromerolopez.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mypy-pure\n\n![test](https://github.com/diegojromerolopez/mypy-pure/actions/workflows/test.yml/badge.svg)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/diegojromerolopez/mypy-pure/graphs/commit-activity)\n[![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/)\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\u0026labelColor=ef8336)](https://pycqa.github.io/isort/)\n[![PyPI pyversions](https://img.shields.io/pypi/pyversions/mypy-pure.svg)](https://pypi.python.org/pypi/mypy-pure/)\n[![PyPI version mypy-pure](https://badge.fury.io/py/mypy-pure.svg)](https://pypi.python.org/pypi/mypy-pure/)\n[![PyPI status](https://img.shields.io/pypi/status/mypy-pure.svg)](https://pypi.python.org/pypi/mypy-pure/)\n[![PyPI download month](https://img.shields.io/pypi/dm/mypy-pure.svg)](https://pypi.python.org/pypi/mypy-pure/)\n\n**Enforce functional purity in Python with static type checking.**\n\nA mypy plugin that helps you write safer, more predictable code by detecting side effects in functions marked as `@pure`.\n\n## Why mypy-pure?\n\nPure functions are:\n- **Easier to test** - No mocks needed, same inputs always give same outputs\n- **Easier to reason about** - No hidden state changes or side effects\n- **Easier to refactor** - Can be moved, renamed, or reordered safely\n- **Easier to parallelize** - No race conditions or shared state issues\n- **Easier to cache** - Results can be memoized safely\n\nBut enforcing purity manually is error-prone. **mypy-pure** catches impure code at type-check time, before it reaches production.\n\n## What is a Pure Function?\n\nA pure function:\n1. **Always returns the same output for the same inputs** (deterministic)\n2. **Has no side effects** (no I/O, no mutations, no external state changes)\n\n```python\n# ✅ Pure - deterministic, no side effects\ndef add(x: int, y: int) -\u003e int:\n    return x + y\n\n# ❌ Impure - side effect (I/O)\ndef add_and_log(x: int, y: int) -\u003e int:\n    print(f\"Adding {x} + {y}\")  # Side effect!\n    return x + y\n```\n\n## Installation\n\n```bash\npip install mypy-pure\n```\n\nEnable the plugin in your `mypy.ini` or `pyproject.toml`:\n\n```ini\n[mypy]\nplugins = mypy_pure.plugin\n```\n\n## Quick Start\n\nMark functions as pure with the `@pure` decorator:\n\n```python\nfrom mypy_pure import pure\n\n@pure\ndef calculate_total(prices: list[float], tax_rate: float) -\u003e float:\n    subtotal = sum(prices)\n    return subtotal * (1 + tax_rate)\n```\n\nRun mypy to check for purity violations:\n\n```bash\nmypy your_code.py\n```\n\n## Examples\n\n### ✅ Valid Pure Functions\n\n```python\nfrom mypy_pure import pure\n\n@pure\ndef fibonacci(n: int) -\u003e int:\n    \"\"\"Pure recursive function.\"\"\"\n    if n \u003c= 1:\n        return n\n    return fibonacci(n - 1) + fibonacci(n - 2)\n\n@pure\ndef process_data(items: list[dict]) -\u003e list[str]:\n    \"\"\"Pure data transformation.\"\"\"\n    return [item['name'].upper() for item in items if item['active']]\n\n@pure\ndef calculate_discount(price: float, discount_pct: float) -\u003e float:\n    \"\"\"Pure business logic.\"\"\"\n    return price * (1 - discount_pct / 100)\n```\n\n### ❌ Detected Impurity Violations\n\n```python\nfrom mypy_pure import pure\nimport os\n\n@pure\ndef read_config() -\u003e dict:\n    # Error: Function 'read_config' is annotated as pure but calls impure functions.\n    with open('config.json') as f:  # I/O is impure!\n        return json.load(f)\n\n@pure\ndef delete_temp_files(directory: str) -\u003e None:\n    # Error: Function 'delete_temp_files' is annotated as pure but calls impure functions.\n    for file in os.listdir(directory):\n        os.remove(file)  # File system modification is impure!\n\n@pure\ndef log_and_calculate(x: int, y: int) -\u003e int:\n    # Error: Function 'log_and_calculate' is annotated as pure but calls impure functions.\n    print(f\"Calculating {x} + {y}\")  # Logging is impure!\n    return x + y\n```\n\n### More examples\n\nMore examples can be found in the [mypy-pure-examples](https://github.com/diegojromerolopez/mypy-pure-examples) repository.\n\n## Advanced Usage\n\n### Configuration Options\n\nmypy-pure supports two configuration options in the `[mypy-pure]` section of your `mypy.ini`:\n\n#### 1. `impure_functions` (Blacklist)\n\nAdd custom impure functions that should be flagged as side-effecting:\n\n```ini\n[mypy-pure]\nimpure_functions = my_module.send_email, analytics.track_event, cache.set\n```\n\n**Use cases:**\n- Your own functions that have side effects\n- Third-party library functions not in the built-in blacklist\n- Project-specific impure operations\n\n**Example:**\n\n```python\n# my_module.py\ndef send_email(to: str, subject: str) -\u003e None:\n    # Sends an email (side effect)\n    ...\n\n# main.py\nfrom mypy_pure import pure\nfrom my_module import send_email\n\n@pure\ndef process_user(user: dict) -\u003e dict:\n    send_email(user['email'], 'Welcome')  # ❌ Error: calls impure function\n    return user\n```\n\n#### 2. `pure_functions` (Whitelist)\n\nMark third-party library functions as pure, overriding the default assumption:\n\n```ini\n[mypy-pure]\npure_functions = requests.utils.quote, pandas.DataFrame.copy, my_lib.helper\n```\n\n**Use cases:**\n- Pure utility functions from third-party libraries\n- Functions you've verified have no side effects\n- Overriding false positives\n\n**Example:**\n\n```python\nfrom mypy_pure import pure\nimport requests.utils\n\n@pure\ndef sanitize_url(url: str) -\u003e str:\n    # OK because requests.utils.quote is in pure_functions config\n    return requests.utils.quote(url)\n```\n\n#### Combining Both\n\nYou can use both options together:\n\n```ini\n[mypy-pure]\n# Blacklist your impure functions\nimpure_functions = my_module.send_email, my_module.log_event\n\n# Whitelist pure third-party functions\npure_functions = requests.utils.quote, requests.utils.unquote\n```\n\n**Priority:** `pure_functions` (whitelist) takes precedence over `impure_functions` (blacklist).\n\n### Library Authors: Auto-Discovery with `__mypy_pure__`\n\nIf you're a library author, you can declare your pure functions using the `__mypy_pure__` module-level list. This enables **zero-configuration** purity checking for your users.\n\n#### Declaring Pure Functions\n\nAdd a `__mypy_pure__` list to your module:\n\n```python\n# my_library.py\n__mypy_pure__ = [\n    'pure_helper',\n    'utils.calculate',\n    'ClassName.method_name',\n]\n\ndef pure_helper(x: int) -\u003e int:\n    \"\"\"A pure utility function.\"\"\"\n    return x * 2\n\nclass utils:\n    @staticmethod\n    def calculate(a: int, b: int) -\u003e int:\n        \"\"\"A pure calculation.\"\"\"\n        return a + b\n\ndef impure_logger(msg: str) -\u003e None:\n    \"\"\"Not in __mypy_pure__, so treated as impure.\"\"\"\n    print(msg)\n```\n\n#### User Experience\n\nUsers of your library automatically benefit without any configuration:\n\n```python\nfrom mypy_pure import pure\nimport my_library\n\n@pure\ndef process(x: int) -\u003e int:\n    # ✅ OK - pure_helper is in __mypy_pure__\n    return my_library.pure_helper(x)\n\n@pure\ndef log_process(x: int) -\u003e int:\n    # ❌ Error: Function 'log_process' is impure because it calls 'my_library.impure_logger'\n    my_library.impure_logger(f\"Processing {x}\")\n    return x\n```\n\n#### Benefits\n\n- **Zero configuration** for library users\n- **Self-documenting API** - pure functions are explicitly declared\n- **Compile-time guarantees** - purity violations caught during type checking\n- **Better IDE support** - users see which functions are safe to use in pure contexts\n\n### Cross-Module References\n\nReference functions from other modules using fully qualified names:\n\n```ini\n[mypy-pure]\nimpure_functions = external_lib.impure_function\n```\n\n## Supported Function Types\n\nmypy-pure works with all Python function and method types:\n\n- ✅ Regular functions\n- ✅ Instance methods\n- ✅ Class methods (`@classmethod`)\n- ✅ Static methods (`@staticmethod`)\n- ✅ Async functions (`async def`)\n- ✅ Async methods\n- ✅ Property methods (`@property`)\n- ✅ Nested/inner functions\n\n## Built-in Impurity Detection\n\nmypy-pure includes a comprehensive blacklist of 200+ impure functions from Python's standard library:\n\n- **File I/O**: `open()`, `pathlib.Path.write_text()`, etc.\n- **System operations**: `os.remove()`, `subprocess.run()`, etc.\n- **Network**: `socket.socket()`, `urllib.request.urlopen()`, etc.\n- **Logging**: `logging.info()`, `print()`, etc.\n- **State modification**: `random.seed()`, `sys.exit()`, etc.\n- **Databases**: `sqlite3.connect()`, etc.\n- **And many more...**\n\n[See full blacklist](mypy_pure/configuration.py)\n\n## Limitations\n\nmypy-pure performs **static analysis** and has some limitations:\n\n### What it CAN detect:\n- ✅ Direct calls to known impure functions\n- ✅ Indirect calls through pure functions calling impure functions\n- ✅ Deeply nested impure calls\n\n### What it CANNOT detect:\n- ❌ Mutations of mutable arguments (e.g., `list.append()`)\n- ❌ Global variable modifications\n- ❌ Impure functions not in the blacklist\n- ❌ Dynamic function calls (e.g., `getattr()`, `eval()`)\n- ❌ Side effects in third-party libraries (unless configured)\n\n**Recommendation**: Use mypy-pure as a helpful guard rail, not a guarantee of purity. Combine it with code reviews and testing.\n\n## Real-World Use Cases\n\n### Data Processing Pipelines\n```python\n@pure\ndef transform_user_data(raw_data: dict) -\u003e dict:\n    \"\"\"Pure transformation - easy to test and parallelize.\"\"\"\n    return {\n        'id': raw_data['user_id'],\n        'name': raw_data['full_name'].title(),\n        'age': calculate_age(raw_data['birth_date']),\n    }\n```\n\n### Business Logic\n```python\n@pure\ndef calculate_shipping_cost(\n    weight_kg: float,\n    distance_km: float,\n    is_express: bool\n) -\u003e float:\n    \"\"\"Pure business logic - deterministic and testable.\"\"\"\n    base_cost = weight_kg * 0.5 + distance_km * 0.1\n    return base_cost * 1.5 if is_express else base_cost\n```\n\n### Configuration Processing\n```python\n@pure\ndef merge_configs(default: dict, user: dict) -\u003e dict:\n    \"\"\"Pure config merging - no file I/O.\"\"\"\n    return {**default, **user}\n```\n\n## Contributing\n\nContributions are welcome! Here's how you can help:\n\n1. **Expand the blacklist** - Add more impure functions from stdlib or popular libraries\n2. **Report bugs** - Open an issue if you find incorrect behavior\n3. **Suggest features** - Ideas for improving purity detection\n4. **Improve documentation** - Help make the docs clearer\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for details.\n\n## Development\n\n```bash\n# Clone the repository\ngit clone https://github.com/diegojromerolopez/mypy-pure.git\ncd mypy-pure\n\n# Install dependencies\nuv sync\n\n# Run tests\npytest\n\n# Run mypy\nmypy mypy_pure\n```\n\n## License\n\nMIT License - see [LICENSE](LICENSE) for details.\n\n## Acknowledgments\n\nThis project was created with the assistance of AI tools (ChatGPT and Antigravity/Gemini) and manually reviewed and refined.\n\n## Related Projects\n\n- [mypy](https://github.com/python/mypy) - Optional static typing for Python\n- [mypy-raise](https://github.com/diegojromerolopez/mypy-raise) - A mypy plugin that enforces exception declarations in function signatures, ensuring functions explicitly declare all exceptions they may raise.\n- [mypy-plugins-examples](https://github.com/diegojromerolopez/mypy-plugins-examples) - A project that contains some examples for my mypy-pure and mypy-raise plugins.\n\n---\n\n**Made with ❤️ for the Python community**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiegojromerolopez%2Fmypy-pure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdiegojromerolopez%2Fmypy-pure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiegojromerolopez%2Fmypy-pure/lists"}