{"id":36951618,"url":"https://github.com/apiad/purely","last_synced_at":"2026-01-17T15:46:21.874Z","repository":{"id":327771599,"uuid":"1110715538","full_name":"apiad/purely","owner":"apiad","description":"Lightweight functional primitives for cleaner Python","archived":false,"fork":false,"pushed_at":"2026-01-13T01:14:21.000Z","size":95,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-13T05:08:50.305Z","etag":null,"topics":["functional-programming","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/apiad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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-12-05T15:58:25.000Z","updated_at":"2026-01-13T01:14:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/apiad/purely","commit_stats":null,"previous_names":["apiad/purely"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/apiad/purely","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apiad%2Fpurely","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apiad%2Fpurely/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apiad%2Fpurely/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apiad%2Fpurely/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/apiad","download_url":"https://codeload.github.com/apiad/purely/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apiad%2Fpurely/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28385632,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T12:01:30.995Z","status":"ssl_error","status_checked_at":"2026-01-13T12:00:09.625Z","response_time":56,"last_error":"SSL_read: 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":["functional-programming","python3"],"created_at":"2026-01-13T12:13:04.211Z","updated_at":"2026-01-17T15:46:21.859Z","avatar_url":"https://github.com/apiad.png","language":"Python","readme":"# Purely 💧\n\n**A lightweight elixir for cleaner, safer, and more fluent Python.**\n\n![PyPI - Version](https://img.shields.io/pypi/v/purely)\n![PyPi - Python Version](https://img.shields.io/pypi/pyversions/purely)\n![Github - Open Issues](https://img.shields.io/github/issues-raw/apiad/purely)\n![PyPi - Downloads (Monthly)](https://img.shields.io/pypi/dm/purely)\n![Github - Commits](https://img.shields.io/github/commit-activity/m/apiad/purely)\n\n---\n\n**Purely** is a zero-dependency library designed to bring the best parts of functional programming—safety, pipelines, and immutability—into Python without the academic overhead or complex types. It allows you to write code that reads from top to bottom, rather than inside out.\n\n## 📦 Installation\n\n**Purely** leverages modern Python features (generics) and requires **Python 3.12+**.\n\n```bash\npip install purely\n```\n\n## ✨ Features\n\n* **Functional Containers**: `Option` for null-safety and `Result` for explicit error handling.\n* **Safe Navigation**: Access deeply nested attributes without worrying about `AttributeError`.\n* **Fluent Pipelines**: Chain operations and vectorize list transformations with `Chain` and `pipe`.\n* **Architectural Decoupling**: Interface-aware Dependency Injection and Multiple Dispatch.\n* **Ergonomic Currying**: Partial application with full type-hint support for IDEs.\n\n## 📚 User Guide\n\n### 1. Functional Containers (Low-Level)\n\n#### Option\n\nHandles missing values without explicit `if x is None` checks.\n\n```python\nfrom purely import Option\n\n# Option(10) -\u003e convert to 20 -\u003e keep because \u003e 5\nval = Option(10).convert(lambda x: x * 2).keepif(lambda x: x \u003e 5)\nassert val.unwrap() == 20\n\n# Option(None) -\u003e chain broken\nempty = Option(None).convert(lambda x: x + 1)\nassert empty.is_none()\n```\n\n#### Result\n\nExplicit success (`Ok`) or failure (`Err`) states for \"Railway Oriented Programming\".\n\n```python\nfrom purely.result import Ok, Err\n\ndef divide(a, b):\n    return Ok(a / b) if b != 0 else Err(\"Division by zero\")\n\n# Fluent error handling without try/except blocks\nres = divide(10, 2).then(lambda x: x * 2).unwrap()\nassert res == 10.0\n```\n\n#### ensure\n\nAsserts the existence of a value. It unwraps an `Option` or checks if a raw value is `None`, raising a custom error if the check fails.\n\n```python\nfrom purely import ensure, Option\n\n# Unwraps a Some(value)\nval = ensure(Option(10))\nassert val == 10\n\n# Raises ValueError if None\ntry:\n    ensure(None, error=\"Missing data\")\nexcept ValueError as e:\n    print(e)  # \"Missing data\"\n```\n\n### 2. Flow \u0026 Navigation (Middle-Level)\n\n#### safe\n\nEnables null-safe navigation through nested objects while maintaining IDE autocompletion.\n\n```python\nfrom purely import safe, ensure\n\nuser = get_user_or_none() # Could be None\n\n# Access .address.city.name safely. Returns Option(None) if any step fails.\ncity_name = safe(user).address.city.name\n\n# Crash intentionally only when you must have the value\nprint(ensure(city_name, \"City name is required\"))\n```\n\n#### pipe\n\nA simple utility to pass a value through a sequence of functions.\n\n```python\nfrom purely import pipe\n\n# 5 -\u003e 10 -\u003e 20 -\u003e \"20\"\nresult = pipe(5, lambda x: x * 2, lambda x: x + 10, str)\nassert result == \"20\"\n```\n\n#### Chain\n\nA unified container for pipelines, vectorized list operations, and captured error handling.\n\n```python\nfrom purely import Chain\n\nnames = (\n    Chain([\"alice\", \"bob\", \"charlie\"])\n    .filter(lambda n: len(n) \u003e 3)  # [\"alice\", \"charlie\"]\n    .map(lambda n: n.upper())      # [\"ALICE\", \"CHARLIE\"]\n    .unwrap()\n)\n\n# Exception handling inside the chain\nrecovered = (\n    Chain(10)\n    .then(lambda x: x / 0)        # Captures ZeroDivisionError\n    .catch(lambda e: 0)           # Recovers with fallback\n    .unwrap()\n)\nassert recovered == 0\n```\n\n### 3. High-Level Architecture\n\n#### curry\n\nTransforms functions of multiple arguments into a series of partial applications.\n\n```python\nfrom purely import curry\n\n@curry\ndef multiply(a, b):\n    return a * b\n\ndouble = multiply(2)\nassert double(10) == 20\n```\n\n#### Registry (Dependency Injection)\n\nA scoped, interface-aware DI system with automatic MRO (Method Resolution Order) discovery.\n\n```python\nfrom purely import Registry, depends\n\nreg = Registry()\n# Registering Postgres automatically satisfies Database and Storage interfaces\nreg.register(PostgresDatabase())\n\n@reg.inject\ndef save_user(user, db: Database = depends(Database)):\n    db.save(user)\n\nsave_user(new_user)\n```\n\n#### dispatcher (Multiple Dispatch)\n\nRuntime polymorphism based on type signatures and value-based predicates.\n\n```python\nfrom purely import dispatcher\n\n@dispatcher\ndef process(data):\n    return \"Generic data\"\n\n@process.dispatch\ndef _(data: list):\n    return f\"List of length {len(data)}\"\n\n@process.when(lambda d: isinstance(d, int) and d \u003c 0)\ndef _(data):\n    return \"Negative number\"\n\nassert process([1, 2]) == \"List of length 2\"\nassert process(-5) == \"Negative number\"\n```\n\n## 🛠 Contribution\n\nWe use `uv` for dependency management and a `makefile` for orchestration.\n\n1. **Setup**: Clone the repository and run `make`.\n2. **Formatting**: Ensure code adheres to `black` formatting using `make format`.\n3. **Testing**: Run the full test suite with `make test-all`.\n\n## 📝 License\n\nDistributed under the MIT License.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapiad%2Fpurely","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapiad%2Fpurely","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapiad%2Fpurely/lists"}