{"id":37066841,"url":"https://github.com/bendemott/txrest","last_synced_at":"2026-01-14T07:50:44.016Z","repository":{"id":32324292,"uuid":"35899564","full_name":"bendemott/txrest","owner":"bendemott","description":"Build JSON or XML Rest Resources in Twisted and Python","archived":false,"fork":false,"pushed_at":"2015-07-14T18:17:15.000Z","size":223,"stargazers_count":5,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-09-22T23:21:07.908Z","etag":null,"topics":[],"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/bendemott.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}},"created_at":"2015-05-19T18:06:21.000Z","updated_at":"2018-08-13T13:54:07.000Z","dependencies_parsed_at":"2022-09-05T04:10:59.243Z","dependency_job_id":null,"html_url":"https://github.com/bendemott/txrest","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/bendemott/txrest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bendemott%2Ftxrest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bendemott%2Ftxrest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bendemott%2Ftxrest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bendemott%2Ftxrest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bendemott","download_url":"https://codeload.github.com/bendemott/txrest/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bendemott%2Ftxrest/sbom","scorecard":{"id":231787,"data":{"date":"2025-08-11","repo":{"name":"github.com/bendemott/txrest","commit":"afcd8894fb68b3d91068ec4cd581511a28436f0f"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"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":"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":"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":"Code-Review","score":0,"reason":"Found 1/26 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":"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":-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":"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":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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 1 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-17T04:58:47.081Z","repository_id":32324292,"created_at":"2025-08-17T04:58:47.082Z","updated_at":"2025-08-17T04:58:47.082Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28413510,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T05:26:33.345Z","status":"ssl_error","status_checked_at":"2026-01-14T05:21:57.251Z","response_time":107,"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":[],"created_at":"2026-01-14T07:50:43.486Z","updated_at":"2026-01-14T07:50:44.004Z","avatar_url":"https://github.com/bendemott.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"txrest\n======\nTxRest provides a suite of Resources to implement **Restful** apis in Twisted.\n\nThe api is implemented by exposing custom Resource() classes.\nRest-Resources behave almost identical to ``Twisteds`` ``resource.Resource()`` classes.\n\nThe major difference is we support returning a **deferred** and you use the methods \n``rest_GET``, ``rest_POST``... instead of ``render_GET`` and ``render_POST``.\n\nInstallation\n------------\n\n**install from PyPI**::\n    \n    sudo pip install txrest\n\n**Directly from github (newest)**::\n\n    sudo pip install git+https://github.com/bendemott/txrest.git\n    \n    \n**Note**:\n\n    The only dependency required by ``txrest`` is ``twisted``.  If you do not want\n    Twisted installed from pip you can install txrest without dependencies using:\n    \n    ``pip install txrest --no-deps``\n\nRestful JSON\n============\nTo implement a restful JSON client we'll be using the ``txrest.json.JsonResource`` class.\n\n**Quickstart (Simple)**::\n\n    from twisted.internet import reactor\n    from twisted.web import server\n    from txrest.json import JsonResource\n    \n    class MyJsonResource(JsonResource):\n        isLeaf = True\n\n        def rest(self, request, post=None):\n            # For all Methods return hello world JSON\n            return {\"hello\": \"world\"}\n            \n    site = server.Site(MyJsonResource())\n    reactor.listenTCP(8080, site)\n    reactor.run()\n\n**Quickstart (GET)**::\n\n    import sys, time\n    from twisted.internet import reactor, defer, task\n    from twisted.web import server\n    from twisted.python import log\n    log.startLogging(sys.stdout)\n    from txrest.json import JsonResource\n\n    class MyJsonResource(JsonResource):\n        isLeaf = True\n\n        @defer.inlineCallbacks\n        def rest_GET(self, request):\n            _ = yield task.deferLater(reactor, 1, log.msg, 'the wait is over!')\n            defer.returnValue({\"hello\": \"world %s\" % time.time()})\n\n    defer.setDebugging(True)\n    site = server.Site(MyJsonResource())\n    reactor.listenTCP(8080, site)\n    reactor.run()\n \n**Quickstart (POST)**::\n            \n    import sys, time\n    from twisted.internet import reactor, defer, task\n    from twisted.web import server\n    from twisted.python import log\n    log.startLogging(sys.stdout)      \n    from txrest.json import JsonResource\n    \n    class MyJsonResource(JsonResource):\n        isLeaf = True\n\n        @defer.inlineCallbacks\n        def rest_POST(self, request, post):\n            # post will be a dictionary or a list\n            post['hello'] = 'world'\n            _ = yield task.deferLater(reactor, 1, log.msg, 'the wait is over!')\n            defer.returnValue(post)  # return the contents of what we posted.\n            \n    site = server.Site(MyJsonResource())\n    reactor.listenTCP(8080, site)\n    reactor.run()\n            \nStandard vs TxRest Comparison\n-----------------------------\nThis is a comparison of the standard way, vs our way...\n\nThe goal is to return the contents of `example.com` in a JSON response object.\n\n**Standard Way**::\n\n    class NormalDeferred(resource.Resource):\n        isLeaf = True\n\n        def render_GET(self, request):\n        \n            def fail(failure):\n                request.write('we failed %s' % failure)\n                request.finish()\n        \n            def return_body(body):\n                \"\"\"Called when we have a full response\"\"\"\n                response = {'web-request': body}\n                response = json.dumps(response, ensure_ascii=False, encoding='utf-8').encode('utf-8')\n                request.write(body)\n                request.finish()\n        \n            def get_body(result):\n                # now that we have the body, \n                # we can return the result, using ready body\n                # which is also an async operation.\n                d2 = readBody(result) # get the  contents of the page.\n                d2.addCallback(return_body)\n                d2.addErrback(fail)\n        \n            # setup the deferred/callback for the first asynchronous \n            # call...\n            agent = Agent(reactor)\n            d1 = agent.request('GET', 'http://example.com/')\n            d1.addCallback(get_body)\n            d1.addErrback(fail)\n            \n            return server.NOT_DONE_YET\n        \n**Using TxRest**::\n\n    class RestDeferred(JsonResource):\n        isLeaf = True\n\n        @defer.inlineCallbacks\n        def rest_GET(self, request):\n            agent = Agent(reactor)\n            result = yield agent.request('GET', 'http://example.com/')\n            body = yield readBody(result) # get the  contents of the page.\n            defer.returnValue({'web-request': str(body)})\n        \nHopefully from the above example it's clear that automating the encoding, and decoding\nof responses and POST bodies to JSON types offers a fair amount of conveniance.\n\nIn addition we support returning resources from the ``rest_*`` methods, which means \nyou can return a Resource object as a response.\n\nHandling Errors in your Resource\n--------------------------------\nTwisted has a built in version of an \"error page\" ``twisted.web.resource.ErrorPage``\nthat sets the http response code for you and formats an error.  \nThis page is returned whenever there is an unhandled exception.\n\nUnhandled exceptions will automatically return an error page for you.  But it's useful to\nuse this Resource yourself.\n\nIn addition to returning an error response, ``JsonErrorPage`` will log to twisteds log\nthe error as well.  This can be prevented by passing log=False to the constructor, but typically\nthis functionality is useful.\n\n**Return 400 Bad Request**::\n\n    from twisted.internet import defer\n    from twisted.web.http import BAD_REQUEST\n    from twisted.web.client import Agent, readBody\n    from txrest.json import JsonResource, JsonErrorPage\n\n    class RestDeferred(JsonResource):\n        isLeaf = True\n\n        @defer.inlineCallbacks\n        def rest_GET(self, request):\n        \n            if 'argument' not in request.args:\n                return JsonErrorPage(BAD_REQUEST, '`argument` missing', 'additional info')\n        \n            agent = Agent(reactor)\n            result = yield agent.request('GET', 'http://example.com/')\n            body = yield readBody(result)\n            defer.returnValue({'web-request': str(body)})\n            \n            \n            \nRestful XML\n===========\nThe Restful XML API is identical to the JSON api except it expects valid xml via an Element object\nfrom any ``etree`` compatible xml api.  Note that ``lxml`` and ``xml.etree`` are supported.\n\n``Element`` objects returned from ``etree.fromstring('\u003celement\u003evalue\u003c/element\u003e')`` are supported.\n\n\n**Basic XML Get**::\n\n    import xml.etree.ElementTree as etree \n    from txrest.xml import XmlResource\n    \n    class RestBasic(XmlResource):\n        \"\"\"\n        return xml from a rest method. (simple)\n        \"\"\"\n        \n        def rest_GET(self, request):\n            element = etree.Element('example')\n            element.attrib['is_example'] = 'True'\n            element.text = \"Hello World!\"\n            return element\n\nMixins\n======\nIf you want to modify the way a particular resource you implement handles it's POST bodies\nor it's responses we have mixins you can use that decorate your ``Resource`` class.\n\nMixins are located in the module ``txrest.mixins`` - They can be used with both ``JsonResource``\nand ``XmlResource``\n\nHere's a basic example that allows us to return non-standard responses, in this case\na string instead of an XML object.\n\n::\n\n    from txrest.xml import XmlResource\n    from txrest.mixin import StringResponse\n\n    @StringResponse.mixin\n    class StringMixinTest(XmlResource):\n        \"\"\"\n        Normally XmlResource() wants us to output an Element()\n        object.  By decorating the resource we allow ourselves\n        to return a byte string.\n        \"\"\"\n        isLeaf = True\n        \n        def rest_GET(self, request):\n            request.setHeader('content-type', 'text/plain')\n            return \"string response!\"\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbendemott%2Ftxrest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbendemott%2Ftxrest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbendemott%2Ftxrest/lists"}