{"id":18806050,"url":"https://github.com/ktdreyer/txkoji","last_synced_at":"2025-10-11T11:32:13.913Z","repository":{"id":31628666,"uuid":"110876314","full_name":"ktdreyer/txkoji","owner":"ktdreyer","description":"Access Koji's XML-RPC API asynchronously (non-blocking) using the Twisted framework.","archived":false,"fork":false,"pushed_at":"2023-12-14T22:25:59.000Z","size":202,"stargazers_count":7,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-02T15:45:50.210Z","etag":null,"topics":["koji","twisted"],"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/ktdreyer.png","metadata":{"files":{"readme":"README.rst","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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-15T19:21:19.000Z","updated_at":"2024-09-08T09:21:59.000Z","dependencies_parsed_at":"2022-09-16T20:01:42.337Z","dependency_job_id":"40c0df58-a7f4-4069-817c-30e232bb80e3","html_url":"https://github.com/ktdreyer/txkoji","commit_stats":{"total_commits":221,"total_committers":1,"mean_commits":221.0,"dds":0.0,"last_synced_commit":"4ac61ada6bbc1f8df5fab1f7c57414f395cfef74"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/ktdreyer/txkoji","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktdreyer%2Ftxkoji","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktdreyer%2Ftxkoji/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktdreyer%2Ftxkoji/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktdreyer%2Ftxkoji/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ktdreyer","download_url":"https://codeload.github.com/ktdreyer/txkoji/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktdreyer%2Ftxkoji/sbom","scorecard":{"id":571958,"data":{"date":"2025-08-11","repo":{"name":"github.com/ktdreyer/txkoji","commit":"6762e8aab3ba50fca98faa431171c3f5f38d555d"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.7,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","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":"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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Info: no jobLevel write permissions found"],"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":"Code-Review","score":0,"reason":"Found 0/29 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":"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":"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":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/ktdreyer/txkoji/tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/ktdreyer/txkoji/tests.yml/main?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:24","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 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":"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":"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":"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":"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":"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":"Branch-Protection","score":3,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'main'","Info: 'force pushes' disabled on branch 'main'","Warn: branch 'main' does not require approvers","Warn: codeowners review is not required on branch 'main'","Warn: no status checks found to merge onto branch 'main'"],"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 2 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-20T16:34:23.536Z","repository_id":31628666,"created_at":"2025-08-20T16:34:23.536Z","updated_at":"2025-08-20T16:34:23.536Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279007031,"owners_count":26084227,"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-10-11T02:00:06.511Z","response_time":55,"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":["koji","twisted"],"created_at":"2024-11-07T22:46:03.792Z","updated_at":"2025-10-11T11:32:13.893Z","avatar_url":"https://github.com/ktdreyer.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Async interface to Koji, using Twisted\n======================================\n\n.. image:: https://github.com/ktdreyer/txkoji/workflows/tests/badge.svg\n             :target: https://github.com/ktdreyer/txkoji/actions\n\n.. image:: https://badge.fury.io/py/txkoji.svg\n             :target: https://badge.fury.io/py/txkoji\n\nAccess Koji's XML-RPC API asynchronously (non-blocking) using the Twisted\nframework.\n\nThis supports the GSSAPI or Client SSL login methods.\n\nSimple Example: Fetching a user's name\n--------------------------------------\n\n.. code-block:: python\n\n    from txkoji import Connection\n    from twisted.internet import defer\n    from twisted.internet.task import react\n\n\n    @defer.inlineCallbacks\n    def example(reactor):\n        koji = Connection('brew')\n        # Fetch a user.\n        # You may pass an ID or a krb principal here\n        user = yield koji.getUser(3595)\n        # user is a Munch (dict-like) object.\n        print(user.name)\n\n\n    if __name__ == '__main__':\n        react(example)\n\nConnecting to a Koji Hub\n------------------------\n\nTo connect to a Koji hub, create a new ``txkoji.Connection`` instance.\n\nYou must pass a string to the constructor. This string is a profile name. For\nexample, if you call ``Connector('mykoji')``, then txkoji will search\n``~/.koji/config.d/*.conf`` and ``/etc/koji.conf.d/*.conf`` for the\n``[mykoji]`` config section. This matches what the regular Koji client code\ndoes.\n\nMaking XML-RPC calls\n--------------------\n\nKoji Hub is an XML-RPC server. You can call any method on the ``Connection``\nclass instance and txkoji will treat it as an XML-RPC call to the hub. For\nexample, this Twisted ``inlineCallbacks``-style code looks up information about\na given task ID and tag ID:\n\n.. code-block:: python\n\n    @defer.inlineCallbacks\n    def example(reactor):\n        koji = Connection('mykoji')\n\n        task = yield koji.getTaskInfo(10000)\n        print(task.method)  # \"createImage\"\n\n        tag = yield koji.getTag(2000)\n        print(tag.name)  # \"foo-build\"\n\n\nTo learn the full Koji XML-RPC API::\n\n  koji list-api\n\nYou can also look at the `web interface\n\u003chttps://koji.fedoraproject.org/koji/api\u003e`_ or read the `koji source code\n\u003chttps://pagure.io/koji/\u003e`_ to find out details about how each method works.\n\n\nLogging in\n----------\n\nYour Koji hub must support GSSAPI or Client SSL authentication. You must have a\nvalid Kerberos ticket or SSL keypair.\n\n.. code-block:: python\n\n    @defer.inlineCallbacks\n    def example(reactor):\n        koji = Connection('mykoji')\n\n        result = yield login()\n        print(result)  # \"True\"\n        print('session-id: %s' % koji.session_id)\n\n        # \"Who am I?\"\n        user = yield koji.getLoggedInUser()\n        print(user)\n\nEstimating build durations\n--------------------------\n\nThe ``txkoji.estimates`` module provides methods for estimating build times.\nThe ``average_build_duration()`` method calls Koji's\n``getAverageBuildDuration`` RPC and gives you a ``datetime.timedelta`` for a\npackage. For container packages, we do something similar client-side with the\n``average_last_builds()`` method, averaging the last five builds' durations.\n\n\n\nCaching long-lived object names\n-------------------------------\n\nSometimes all you have is a user id number or tag id number, and you want the\nuser's name or tag's name instead.\n\ntxkoji includes a read-through cache for obtaining the user name or tag name.\nSee ``examples/cache.py`` for an example. txkoji's cache module stores its data\nin a ``txkoji`` subdirectory of the location specified with the\n``$XDG_CACHE_HOME`` environment variable if that is set. It will fall back to\nusing ``~/.cache/txkoji`` if the ``$XDG_CACHE_HOME`` environment variable is\nnot set.\n\n\nRich objects\n------------\n\nThe following RPC methods will return special classes that inherit from the\nMunch class:\n\n* ``getBuild`` returns ``txkoji.build.Build``\n* ``getChannel`` returns ``txkoji.channel.Channel``\n* ``listBuilds`` and ``listTagged`` returns a ``list`` of ``txkoji.build.Build``\n* ``getTaskInfo`` returns ``txkoji.task.Task``\n* ``getPackage`` returns ``txkoji.package.Package``\n\nThese classes have their own special helper methods to implement things I found\ninteresting:\n\n* ``datetime`` conversions for the start/completion timestamps,\n* ``url`` properties for representing the objects in Kojiweb,\n* Unified property attributes across task methods, like ``tag``, ``package`` or\n  ``is_scratch``.\n\nMore special return values:\n\n* ``getAverageBuildDuration`` returns a ``datetime.timedelta`` object instead\n  of a raw float, because this is more useful to do time arithmetic.\n\n* The ``task_id`` property is populated on OSBS's CG container builds (a\n  workaround for https://pagure.io/koji/issue/215).\n\n\nMulti-call support\n------------------\n\nIf you have to submit many RPCs to koji-hub at once, you can optimize this\nwith \"multicall\".\n\nKoji's XML-RPC implementation allows you to batch or \"boxcar\" many methods up\ninto one single \"multicall\" RPC and send it to the server as one single HTTP\nrequest.\n\n.. code-block:: python\n\n    @defer.inlineCallbacks\n    def example(reactor):\n        koji = Connection('mykoji')\n\n        multicall = koji.MultiCall()\n        # Query the task information for several tasks in one shot:\n        multicall.getTaskInfo(123)\n        multicall.getTaskInfo(456)\n        multicall.getTaskInfo(789)\n        results = yield multicall()\n        # results is a xmlrpc.client.MultiCallIterator\n        for task in iter(results):\n            print(task.id)  # eg. \"123\" or \"456\" or \"789\"\n            print(task.method)  # eg. \"tagBuild\"\n\nThis is a bit similar to Twisted's ``DeferredList`` / ``gatherResults``,\nalthough it happens server-side instead of purely client-side.\n\nIf the hub returns an error for any of the calls within the multicall, the\niterator will raise ``KojiException`` when iterating over the specific call\nresult that had the error.\n\nMessage Parsing\n---------------\n\nKoji's messagebus plugin emits messages to an AMQP broker when certain events\nhappen. The ``txkoji.messages`` module has support for parsing these messages\ninto the relevant txkoji ``Task`` or ``Build`` classes.\n\n\nTODO:\n=====\n* More KojiException subclasses for other possible XML-RPC faults?\n* `MikeM noted\n  \u003chttps://lists.fedorahosted.org/archives/list/koji-devel@lists.fedorahosted.org/message/ICFTEETD5MZMDY4S5FWFTO5LPKIAQIVW/\u003e`_,\n  the callnum parameter will need special handling. We might need Twisted's\n  ``DeferredLock`` to ensure we only have one auth'd RPC in flight at a time.\n  It's not really clear to me if we can actually hit a callnum error here. More\n  integration testing needed for this.\n* Ensure that Brew's \"build time\" equals the longest \"buildArch\" time for a\n  task, and not something else, like the buildSRPMFromSCM time, nor even the\n  overall build task's time. This has implications for estimating scratch\n  builds. (comparing our tasks' times to getAverageBuildDuration)\n\nPackages that use this package\n==============================\n\n* `helga-koji \u003chttps://github.com/ktdreyer/helga-koji\u003e`_\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fktdreyer%2Ftxkoji","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fktdreyer%2Ftxkoji","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fktdreyer%2Ftxkoji/lists"}