{"id":19097271,"url":"https://github.com/lycantropos/consensual","last_synced_at":"2025-08-30T15:12:58.054Z","repository":{"id":62564507,"uuid":"374718591","full_name":"lycantropos/consensual","owner":"lycantropos","description":"Implementation of raft consensus algorithm","archived":false,"fork":false,"pushed_at":"2022-03-18T05:13:24.000Z","size":1387,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-31T09:16:31.602Z","etag":null,"topics":["raft","raft-algorithm","raft-consensus-algorithm"],"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/lycantropos.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-06-07T15:41:08.000Z","updated_at":"2022-11-07T08:34:51.000Z","dependencies_parsed_at":"2022-11-03T16:00:41.864Z","dependency_job_id":null,"html_url":"https://github.com/lycantropos/consensual","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/lycantropos/consensual","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lycantropos%2Fconsensual","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lycantropos%2Fconsensual/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lycantropos%2Fconsensual/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lycantropos%2Fconsensual/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lycantropos","download_url":"https://codeload.github.com/lycantropos/consensual/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lycantropos%2Fconsensual/sbom","scorecard":{"id":606987,"data":{"date":"2025-08-11","repo":{"name":"github.com/lycantropos/consensual","commit":"0dcb850a39a81bbbb7b79fe6e7f8ce2fc4588c69"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: containerImage not pinned by hash: Dockerfile:4","Warn: pipCommand not pinned by hash: Dockerfile:6","Warn: pipCommand not pinned by hash: Dockerfile:11","Warn: pipCommand not pinned by hash: Dockerfile:14","Info:   0 out of   1 containerImage dependencies pinned","Info:   0 out of   3 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":3,"reason":"7 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2014-14 / GHSA-652x-xj99-gmcc","Warn: Project is vulnerable to: GHSA-9hjg-9r4m-mvj7","Warn: Project is vulnerable to: GHSA-9wx4-h78v-vm56","Warn: Project is vulnerable to: PYSEC-2014-13 / GHSA-cfj3-7x9c-4p3h","Warn: Project is vulnerable to: PYSEC-2018-28 / GHSA-x84v-xcm2-53pg","Warn: Project is vulnerable to: PYSEC-2015-17","Warn: Project is vulnerable to: PYSEC-2023-74"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T01:49:59.658Z","repository_id":62564507,"created_at":"2025-08-21T01:49:59.659Z","updated_at":"2025-08-21T01:49:59.659Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272865982,"owners_count":25006299,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["raft","raft-algorithm","raft-consensus-algorithm"],"created_at":"2024-11-09T03:39:43.016Z","updated_at":"2025-08-30T15:12:58.025Z","avatar_url":"https://github.com/lycantropos.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"consensual\n==========\n\n[![](https://dev.azure.com/lycantropos/consensual/_apis/build/status/lycantropos.consensual?branchName=master)](https://dev.azure.com/lycantropos/consensual/_build/latest?definitionId=40\u0026branchName=master \"Azure Pipelines\")\n[![](https://codecov.io/gh/lycantropos/consensual/branch/master/graph/badge.svg)](https://codecov.io/gh/lycantropos/consensual \"Codecov\")\n[![](https://img.shields.io/github/license/lycantropos/consensual.svg)](https://github.com/lycantropos/consensual/blob/master/LICENSE \"License\")\n[![](https://badge.fury.io/py/consensual.svg)](https://badge.fury.io/py/consensual \"PyPI\")\n\nSummary\n-------\n\n`consensual` is a pure-Python library for defining network of nodes\nrunning in a consistent fault-tolerant manner by implementing state-of-the-art\n[`Raft` consensus algorithm](https://raft.github.io/).\n\nCurrently, next features are implemented \u0026 property-based tested\n- leader election \u0026 log replication\n  (described in section 5 of [the article](https://raft.github.io/raft.pdf)),\n- cluster membership changes\n  (described in section 6 of [the article](https://raft.github.io/raft.pdf)),\n  namely consensual addition \u0026 removal of nodes,\n- \"solo mode\": non-consensual separation of a node\n  resulting in running as a cluster-by-itself,\n- separate node state resetting with history deletion.\n\nNext crucial features to implement will be\n- persistence,\n- log compaction\n  (described in section 7 of [the article](https://raft.github.io/raft.pdf)).\n\n---\n\nIn what follows `python` is an alias for `python3.7`\nor any later version (`python3.8` and so on).\n\nInstallation\n------------\n\nInstall the latest `pip` \u0026 `setuptools` packages versions\n```bash\npython -m pip install --upgrade pip setuptools\n```\n\n### User\n\nDownload and install the latest stable version from `PyPI` repository\n```bash\npython -m pip install --upgrade consensual\n```\n\n### Developer\n\nDownload the latest version from `GitHub` repository\n```bash\ngit clone https://github.com/lycantropos/consensual.git\ncd consensual\n```\n\nInstall dependencies\n```bash\npython -m pip install -r requirements.txt\n```\n\nInstall\n```bash\npython setup.py install\n```\n\nUsage\n-----\n\n```python\n\u003e\u003e\u003e from consensual.raft import Node, communication\n\u003e\u003e\u003e from yarl import URL\n\u003e\u003e\u003e node_url = URL.build(scheme='http',\n...                      host='localhost',\n...                      port=6000)\n\u003e\u003e\u003e other_node_url = URL.build(scheme='http',\n...                            host='localhost',\n...                            port=6001)\n\u003e\u003e\u003e heartbeat = 0.1\n\u003e\u003e\u003e from typing import Any, List, Optional\n\u003e\u003e\u003e processed_parameters = []\n\u003e\u003e\u003e def dummy_processor(parameters: Any) -\u003e None:\n...     processed_parameters.append(parameters)\n\u003e\u003e\u003e processors = {'dummy': dummy_processor}\n\u003e\u003e\u003e nodes = {}\n\u003e\u003e\u003e sender = communication.Sender([node_url], nodes)\n\u003e\u003e\u003e other_sender = communication.Sender([other_node_url], nodes)\n\u003e\u003e\u003e node = Node.from_url(node_url,\n...                      heartbeat=heartbeat,\n...                      processors=processors,\n...                      sender=sender)\n\u003e\u003e\u003e other_node = Node.from_url(other_node_url,\n...                            heartbeat=heartbeat,\n...                            processors=processors,\n...                            sender=other_sender)\n\u003e\u003e\u003e receiver = communication.Receiver(node, nodes)\n\u003e\u003e\u003e other_receiver = communication.Receiver(other_node, nodes)\n\u003e\u003e\u003e receiver.start()\n\u003e\u003e\u003e other_receiver.start()\n\u003e\u003e\u003e from asyncio import get_event_loop\n\u003e\u003e\u003e loop = get_event_loop()\n\u003e\u003e\u003e async def run() -\u003e List[Optional[str]]:\n...     return [await node.solo(),\n...             await node.enqueue('dummy', 42),\n...             await node.attach_nodes([other_node.url]),\n...             await node.enqueue('dummy', 42),\n...             await other_node.detach_nodes([node.url]),\n...             await other_node.solo(),\n...             await other_node.detach(),\n...             await other_node.detach()]\n\u003e\u003e\u003e error_messages = loop.run_until_complete(run())\n\u003e\u003e\u003e receiver.stop()\n\u003e\u003e\u003e other_receiver.stop()\n\u003e\u003e\u003e all(error_message is None or isinstance(error_message, str)\n...     for error_message in error_messages)\nTrue\n\u003e\u003e\u003e all(parameters == 42 for parameters in processed_parameters)\nTrue\n\n```\n\nWe can also replace builtin `consensual.raft.communication` communication layer\nwith another one (like [`consensual_http`](https://pypi.org/project/consensual-http/)\nwhich is built on top of HTTP), usage patterns may change as a result.\n\nDevelopment\n-----------\n\n### Bumping version\n\n#### Preparation\n\nInstall\n[bump2version](https://github.com/c4urself/bump2version#installation).\n\n#### Pre-release\n\nChoose which version number category to bump following [semver\nspecification](http://semver.org/).\n\nTest bumping version\n```bash\nbump2version --dry-run --verbose $CATEGORY\n```\n\nwhere `$CATEGORY` is the target version number category name, possible\nvalues are `patch`/`minor`/`major`.\n\nBump version\n```bash\nbump2version --verbose $CATEGORY\n```\n\nThis will set version to `major.minor.patch-alpha`. \n\n#### Release\n\nTest bumping version\n```bash\nbump2version --dry-run --verbose release\n```\n\nBump version\n```bash\nbump2version --verbose release\n```\n\nThis will set version to `major.minor.patch`.\n\n### Running tests\n\nInstall dependencies\n```bash\npython -m pip install -r requirements-tests.txt\n```\n\nPlain\n```bash\npytest\n```\n\nInside `Docker` container:\n```bash\ndocker-compose --file docker-compose.yml up\n```\n\n`Bash` script:\n```bash\n./run-tests.sh\n```\n\n`PowerShell` script:\n```powershell\n.\\run-tests.ps1\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flycantropos%2Fconsensual","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flycantropos%2Fconsensual","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flycantropos%2Fconsensual/lists"}