{"id":28407627,"url":"https://github.com/level12/pals","last_synced_at":"2026-03-01T15:33:45.714Z","repository":{"id":49435888,"uuid":"149837631","full_name":"level12/pals","owner":"level12","description":"Easy distributed locking using PostgreSQL Advisory Locks.","archived":false,"fork":false,"pushed_at":"2026-01-27T14:27:59.000Z","size":69,"stargazers_count":48,"open_issues_count":6,"forks_count":8,"subscribers_count":7,"default_branch":"master","last_synced_at":"2026-01-28T01:44:10.541Z","etag":null,"topics":["database","locks","postgresql","python"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/level12.png","metadata":{"files":{"readme":"readme.rst","changelog":"changelog.rst","contributing":null,"funding":null,"license":"license.txt","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}},"created_at":"2018-09-22T02:16:07.000Z","updated_at":"2026-01-27T14:29:03.000Z","dependencies_parsed_at":"2023-02-06T08:17:27.893Z","dependency_job_id":"5d32d919-1714-4c1c-b3c9-0d6747760abd","html_url":"https://github.com/level12/pals","commit_stats":{"total_commits":48,"total_committers":10,"mean_commits":4.8,"dds":"0.39583333333333337","last_synced_commit":"5daa1396f8e07420b0e15f4e304503b906ecaddf"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/level12/pals","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/level12%2Fpals","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/level12%2Fpals/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/level12%2Fpals/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/level12%2Fpals/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/level12","download_url":"https://codeload.github.com/level12/pals/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/level12%2Fpals/sbom","scorecard":{"id":586106,"data":{"date":"2025-08-11","repo":{"name":"github.com/level12/pals","commit":"f329bd15f194166888ec77b5e61e3c950ea4effc"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.9,"checks":[{"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":"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":"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":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"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":"Code-Review","score":3,"reason":"Found 6/16 approved changesets -- score normalized to 3","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":"Maintained","score":0,"reason":"0 commit(s) and 1 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":"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":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: license.txt:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"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":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 21 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T20:33:15.375Z","repository_id":49435888,"created_at":"2025-08-20T20:33:15.375Z","updated_at":"2025-08-20T20:33:15.375Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29973320,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T15:29:09.406Z","status":"ssl_error","status_checked_at":"2026-03-01T15:28:28.558Z","response_time":124,"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":["database","locks","postgresql","python"],"created_at":"2025-06-02T01:30:32.920Z","updated_at":"2026-03-01T15:33:45.682Z","avatar_url":"https://github.com/level12.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":".. default-role:: code\n\nPostgreSQL Advisory Locks (PALs)\n################################\n\n.. image:: https://circleci.com/gh/level12/pals.svg?style=shield\n    :target: https://circleci.com/gh/level12/pals\n.. image:: https://codecov.io/gh/level12/pals/branch/master/graph/badge.svg\n    :target: https://codecov.io/gh/level12/pals\n\n\nIntroduction\n============\n\nPALs makes it easy to use `PostgreSQL Advisory Locks`_ to do distributed application level\nlocking.\n\nDo not confuse this type of locking with table or row locking in PostgreSQL.  It's not the same\nthing.\n\nDistributed application level locking can be implemented by using Redis, Memcache, ZeroMQ and\nothers.  But for those who are already using PostgreSQL, setup \u0026 management of another service is\nunnecessary.\n\n.. _PostgreSQL Advisory Locks: https://www.postgresql.org/docs/current/static/explicit-locking.html#ADVISORY-LOCKS\n\n\nUsage\n========\n\nInstall with::\n\n    pip install PALs\n\nThen usage is as follows:\n\n.. code:: python\n\n    import datetime as dt\n    import pals\n\n    # Think of the Locker instance as a Lock factory.\n    locker = pals.Locker('my-app-name', 'postgresql://user:pass@server/dbname')\n\n    lock1 = locker.lock('my-lock')\n    lock2 = locker.lock('my-lock')\n\n    # The first acquire works\n    assert lock1.acquire() is True\n\n    # Non blocking version should fail immediately\n    assert lock2.acquire(blocking=False) is False\n\n    # Blocking version should fail after a short time\n    start = dt.datetime.now()\n    acquired = lock2.acquire(acquire_timeout=300)\n    waited_ms = duration(start)\n\n    assert acquired is False\n    assert waited_ms \u003e= 300 and waited_ms \u003c 350\n\n    # Release the lock\n    lock1.release()\n\n    # Non-blocking usage pattern\n    if not lock1.acquire(blocking=False):\n        # Aquire returned False, indicating we did not get the lock.\n        return\n    try:\n        # do your work here\n    finally:\n        lock1.release()\n\n    # If you want to block, you can use a context manager:\n    try:\n        with lock1:\n            # Do your work here\n            pass\n    except pals.AcquireFailure:\n        # This indicates the aquire_timeout was reached before the lock could be aquired.\n        pass\n\nDocs\n========\n\nJust this readme, the code, and tests.  It a small project, should be easy to understand.\n\nFeel free to open an issue with questions.\n\nRunning Tests Locally\n=====================\n\nSetup Database Connection\n-------------------------\n\nWe have provided a docker-compose file to ease running the tests::\n\n    $ docker-compose up -d\n    $ export PALS_DB_URL=postgresql://postgres:password@localhost:54321/postgres\n\n\nRun the Tests\n-------------\n\nWith tox::\n\n    $ tox\n\nOr, manually (assuming an activated virtualenv)::\n\n    $ pip install -e .[tests]\n    $ pytest pals/tests/\n\n\nLock Releasing \u0026 Expiration\n---------------------------\n\nUnlike locking systems built on cache services like Memcache and Redis, whose keys can be expired\nby the service, there is no faculty for expiring an advisory lock in PostgreSQL.  If a client\nholds a lock and then sleeps/hangs for mins/hours/days, no other client will be able to get that\nlock until the client releases it.  This actually seems like a good thing to us, if a lock is\nacquired, it should be kept until released.\n\nBut what about accidental failures to release the lock?\n\n1. If a developer uses `lock.acquire()` but doesn't later call `lock.release()`?\n2. If code inside a lock accidentally throws an exception (and .release() is not called)?\n3. If the process running the application crashes or the process' server dies?\n\nPALs helps #1 and #2 above in a few different ways:\n\n* Locks work as context managers.  Use them as much as possible to guarantee a lock is released.\n* Locks release their lock when garbage collected.\n* PALs uses a dedicated SQLAlchemy connection pool.  When a connection is returned to the pool,\n  either because a connection `.close()` is called or due to garbage collection of the connection,\n  PALs issues a `pg_advisory_unlock_all()`.  It should therefore be impossible for an idle\n  connection in the pool to ever still be holding a lock.\n\nRegarding #3 above, `pg_advisory_unlock_all()` is implicitly invoked by PostgreSQL whenever a\nconnection (a.k.a session) ends, even if the client disconnects ungracefully.  So if a process\ncrashes or otherwise disappears, PostgreSQL should notice and remove all locks held by that\nconnection/session.\n\nThe possibility could exist that PostgreSQL does not detect a connection has closed and keeps\na lock open indefinitely.  However, in manual testing using `scripts/hang.py` no way was found\nto end the Python process without PostgreSQL detecting it.\n\n\nSee Also\n==========\n\n* https://vladmihalcea.com/how-do-postgresql-advisory-locks-work/\n* https://github.com/binded/advisory-lock\n* https://github.com/vaidik/sherlock\n* https://github.com/Xof/django-pglocks\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flevel12%2Fpals","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flevel12%2Fpals","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flevel12%2Fpals/lists"}