{"id":13937064,"url":"https://github.com/drslump/pyshould","last_synced_at":"2026-04-02T02:22:15.462Z","repository":{"id":2869767,"uuid":"3875338","full_name":"drslump/pyshould","owner":"drslump","description":"Should style asserts based on pyhamcrest","archived":false,"fork":false,"pushed_at":"2018-03-05T18:28:48.000Z","size":144,"stargazers_count":38,"open_issues_count":7,"forks_count":6,"subscribers_count":4,"default_branch":"master","last_synced_at":"2026-03-11T07:53:01.905Z","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/drslump.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":"2012-03-30T09:47:25.000Z","updated_at":"2024-08-27T06:40:53.000Z","dependencies_parsed_at":"2022-07-31T14:09:33.054Z","dependency_job_id":null,"html_url":"https://github.com/drslump/pyshould","commit_stats":null,"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/drslump/pyshould","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drslump%2Fpyshould","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drslump%2Fpyshould/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drslump%2Fpyshould/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drslump%2Fpyshould/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/drslump","download_url":"https://codeload.github.com/drslump/pyshould/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drslump%2Fpyshould/sbom","scorecard":{"id":357010,"data":{"date":"2025-08-11","repo":{"name":"github.com/drslump/pyshould","commit":"7210859d4c84cfbaa64f91b30c2a541aea788ddf"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.3,"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":"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":"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":"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":"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":-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":2,"reason":"Found 8/27 approved changesets -- score normalized to 2","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":"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 12 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-18T09:51:53.072Z","repository_id":2869767,"created_at":"2025-08-18T09:51:53.072Z","updated_at":"2025-08-18T09:51:53.072Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31294527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T01:43:37.129Z","status":"online","status_checked_at":"2026-04-02T02:00:08.535Z","response_time":89,"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":[],"created_at":"2024-08-07T23:03:14.594Z","updated_at":"2026-04-02T02:22:15.410Z","avatar_url":"https://github.com/drslump.png","language":"Python","funding_links":[],"categories":["资源列表","Python","Testing"],"sub_categories":["测试"],"readme":"PyShould\n========\n\n[![Version](https://pypip.in/v/pyshould/badge.png)](https://crate.io/packages/pyshould)\n[![Downloads](https://pypip.in/d/pyshould/badge.png)](https://crate.io/packages/pyshould)\n[![Build Status](https://travis-ci.org/drslump/pyshould.png)](https://travis-ci.org/drslump/pyshould)\n\n**PyShould** is a Python DSL allowing to write expectations or assertions in \n_almost_ natural language. The goal is to offer an expressive yet readable syntax\nto define the expectations in detail. \n\nUnder the hood it uses the [PyHamcrest](http://packages.python.org/PyHamcrest/) \nlibrary of matchers to **build complex matching predicates with ease** and offer \n**great explanations** when there is a mismatch.\n\nIts primary use case is in unit testing, replacing the need for Python's native\n`assertX` methods. Its use is completely transparent to the unit testing runner\nused, since mismatches are reported using the standard `AssertionError`.\n\n\n## Installation and basic usage\n\nIf you don't want to mess with the code the easier way to install is with `pip`:\n\n    pip install pyshould\n\nThe installation procedure is based on _setuptools_ so it's pretty simple to get \ngoing. In order to install from a checkout of the repository you can just issue the\nfollowing command:\n\n    python setup.py install\n\nNow you'll be able to access **PyShould** by importing the package `pyshould`. It's\ndesigned to be used with a _wildcard import_ although if you prefer you can just \nimport those DSL keywords you need. Here are some examples on different ways to import\nthe library in your code:\n\n```python\nfrom pyshould import *\nfrom pyshould import should\nfrom pyshould import should, should_not, all_of\n```\n\n\n## Expectations\n\nExpectations are defined in by using a subject-predicate form that mimics\nenglish natural language. Basically they take the form:\n\n`subject` `|` **should**.`predicate`\n\nWhere `subject` is a python expression and `predicate` defines matchers and \nexpected values.\n\nAny python expression can be used before _should_, although lambdas and other\ncomplex expressions should be enclosed in parens to ensure a proper interpretation.\n\nMatchers in the `predicate` part can have an expected value, this value should be\ngiven between parens, as with a normal method call. It will be used as an argument \nto the matcher function. If the matcher doesn't require an expected value there is \nno need to use it as a method call.\n\n\u003e **TIP** Run the following to print all the configured expectations with\na description of what they do: `python -m pyshould` (use `python -m pyshould.__main__`\nfor Python 2.6)\n\nSee the following examples of expectations:\n\n```python\nfrom pyshould import *\n\nresult | should.be_integer()\n(1+1) | should_not.equal(1)\n\"foo\" | should.equal('foo')\nlen([1,2,3]) | should.be_greater_than(2);\nresult | should.equal(1/2 + 5)\n1 | should_not.eq(2)\n# Matchers not requiring a param can skip the call parens\nTrue | should.be_truthy\n\n# Check for exceptions with the context manager interface\nwith should.throw(TypeError):\n    raise TypeError('foo')\n\nwith should.not_raise:  # will report a failure\n    fp = open('does-not-exists.txt')\n\n# Apply our custom logic for a test\n'FooBarBaz' | should.pass_callback(lambda x: x[3:6] == 'Bar')\n```\n\n\n## Coordination\n\nComplex expectations can be _coordinated_ by using operators `and`, `or` and\n`but`. Also `not` is supported to negate the result of an expectation. It's\nimportant to understand the operator precedence rules before using them,\nalthough they try to follow common conventions for the english language there\nmight be cases where they don't quite do what they look like.\n\nAll operators are left-associative and take two operands, except for `not` which\nis an unary operator, thus the precedence rules are very simple:\n\n      operator  |  precedence index\n    ---------------------------------\n        not     |        4\n        and     |        3\n        or      |        2\n        but     |        1\n\nExpectations should be kept simple, when in doubt break up complex expectations \ninto simpler ones.\n\nPlease review the following examples to see how these precedence rules\napply.\n\n```python\nshould.be_an_integer.or_string.and_equal(1)\n# (integer) OR (string AND equal 1)\n\nshould.be_an_integer.or_a_float.or_a_string\n# (integer) OR (float) OR (string)\nshould.be_an_integer.or_a_string.and_equal_to(10).or_a_float\n# (integer) OR (string AND equal 10) OR (float)\n\nshould.be_an_integer.or_a_string.but_less_than(10)\n# (integer OR string) AND (less than 10)\n\n# Note: we can use spacing to make them easier to read\nshould.be_an_integer  .or_a_string.and_equal(0)  .or_a_float\n# (integer) OR (string AND equal 0) OR (float)\n\n# Note: in this case we use capitalization to make them more obvious\nshould.be_an_integer .Or_a_string.And_equal(1) .But_Not_be_a_float\n# ( (integer) OR (string AND equal 1) ) AND (not float)\n\n# Note: if no matchers are given the last one is used\nshould.be_equal_to(10).Or(20).Or(30)\n# (equal 10) OR (equal 20) OR (equal 30)\n\n# Note: If no combinator is given AND is used by default\nshould.integer.greater_than(10).less_than(20)\n# (integer) AND (greater than 10) AND (less than 20)\n\n# Note: But by using should_either we can set OR as default\nshould_either.equal(10).equal(20).equal(30)\n# (equal 10) OR (equal 20) OR (equal 30)\n```\n\n\n## Quantifiers\n\nUsing the standard syntax it's possible to define a matcher in conjunction\nwith a quantifier. These are specially useful when working with iterable\nvalues.\n\n```python\n[1, 2] | should_all.be_int\n(1, 2) | should_any.equal(1)\niterable | should_none.be_empty\n```\n\nWe can also define directly a matcher without using the pipe syntax by\nwrapping the value to test in a quantifier keyword.\n\n```python\nit(1).should_equal(1)\nit(0).to_equal(0)\nany_of(1, 3).to_equal(1)\nall_of([1, 3]).should_be_int()\nnone_of(1, 3).to_eq(0)\n```\n\n\n## Alternative syntax\n\nBesides the standard syntax shown above (aka _pipe syntax_) it's also possible\nto use other syntaxes by using the `expect` module, although it doesn't support\ncoordinated expressions (use of and, or, but).\n\n```python\nfrom pyshould.expect import expect, expect_all, expect_any, expect_none\n\nexpect(1).to_equal(1)\n# Note that matchers without params need the call parens when using this syntax\nexpect_all(1, 3).to_be_int()\nexpect_any([1, 3]).to_equal(1)\nexpect(any_of(1,3)).to_equal(1)\n```\n\n\n### Patching the root object\n\n\u003e **NOTE** This is only supported when running under cpython.\n\nThanks to the great folks @clarete and @gabrielfalcao from \n[sure](https://github.com/gabrielfalcao/sure) it's possible to patch the root\nobject to expose special properties for testing any value. To enable this\nfeature you must import the `pyshould.patch` module.\n\n\u003e **WARNING** Once the `patch` module is imported it will *monkey patch* the\nroot object from which all variables are extended from in your Python\nruntime. This means that every object will be extended with the following\nproperties: `should`, `should_not`, `should_all`, `should_any` and `should_none`.\n\n    import pyshould.patch\n\n    'foo'.should.eq('foo')\n    data = {'foo': 'bar'}\n    data.should_not.have_key('lorem')\n    data.keys().should_all.be_string()\n\n\n## Integration with third parties\n\nBroadly speaking, *pyshould* expressions overload Python's equality operator, so\nit's possible to use them with almost any third party library. Here is an example\nof the integration with the standard unittest assertion library.\n\n    m = should.be_an_int.and_greater_than(3)\n    self.assertEqual(2, m)\n    # AssertionError: 2 != (an integer and a value greater than \u003c3\u003e)\n\nMichael Foord's [Mock](http://www.voidspace.org.uk/python/mock/), which is available\nunder `unittest.mock` from Python 3.3, will also work out of the box:\n\n    mock(10, 1)\n    mock.assert_called_with(should.any, should.be_greater_than(3))\n    # AssertionError: Expected call: mock(ANYTHING, a value greater than \u003c3\u003e)\n\n[Mockito](https://code.google.com/p/mockito-python/) has also been tested and works\nout of the box:\n\n    verify(MyClass).my_method(should.be_truthy, should.eq(10))\n\n\n## Transforming values\n\nAn useful feature when integrating the expectations with a mocking library is to be\nable to transform a value before asserting it. This can be triggered by passing a\ncallable as only argument to a non initialized expression (ie: `should(callable)`).\n\nFor instance you've mocked a call that will receive a Json serialized string but you\nwant to test if it contains a given key. Check the following example:\n\n    mock('{\"username\": \"drslump\", \"password\": \"foobar\"}')\n    mock.assert_called_with( should(json.loads).have_key('username') )\n\nIf you need to use the same transform often you could also create an expectation for\nit:\n\n    should_json = should(json.loads)\n    mock.assert_called_with( should_json.have_key('username') )\n    d | should_json.have_key('username')\n\n\n## Custom expectations\n\nCreating your custom expectations is fairly easy, have a look at the `matchers.py`\nfile for a set of examples. Basically expectations are just a thin layer of \nsyntax sugar over Hamcrest's matchers, so the most difficult part is to implement\nthe actual matcher.\n\nAlternatively, for those cases where we just want to test something complicated\nbut not create a reusable matcher, it's possible to use the `callback` expectation:\n\n    def my_complex_assert(value):\n      # NOTE: we can also return an expectation instead of a bool\n      # ie: return value | should.eq(10).Or(20).but_be_greater_than(100)\n      resp = request.get('http://foo.com/bar', {'q': value})\n      return resp.status_code == 200\n\n    self.foo | should.pass_callback(my_complex_assert)\n\n\n## Caveats\n\nThe *pipe syntax* has an incompatibility when the subject under tests has an\noverloaded `__or__` operator [and is not a builtin type](http://docs.python.org/2/reference/datamodel.html#coercion-rules).\nIn those cases the only option is to use an alternative syntax for that test.\n\n\n## Miscellanea\n\nI find it extremely convenient to include `should` as a keyword under my editor \nhighlighting. This simple change makes the tests much more obvious and easier to \nread. Here is an example syntax for Sublime Text, just place it in a file under \nyour user package directory.\n\n    \u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n    \u003c!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"\u003e\n    \u003cplist version=\"1.0\"\u003e\n    \u003cdict\u003e\n      \u003ckey\u003ecomment\u003c/key\u003e\n      \u003cstring\u003eCustomizations to the Python syntax\u003c/string\u003e\n      \u003ckey\u003ename\u003c/key\u003e\n      \u003cstring\u003ePython (Customized)\u003c/string\u003e\n      \u003ckey\u003escopeName\u003c/key\u003e\n      \u003cstring\u003esource.custom.python\u003c/string\u003e\n      \u003ckey\u003efileTypes\u003c/key\u003e\n      \u003carray\u003e\n        \u003cstring\u003epy\u003c/string\u003e\n      \u003c/array\u003e\n      \u003ckey\u003epatterns\u003c/key\u003e\n      \u003carray\u003e\n        \u003cdict\u003e\n          \u003ckey\u003ematch\u003c/key\u003e\n          \u003cstring\u003e\\|\\s*should(_(any|all|none|not|either))?\\.\u003c/string\u003e\n          \u003ckey\u003ename\u003c/key\u003e\n          \u003cstring\u003ekeyword.custom.python\u003c/string\u003e\n        \u003c/dict\u003e\n        \u003cdict\u003e\n          \u003ckey\u003einclude\u003c/key\u003e\n          \u003cstring\u003esource.python\u003c/string\u003e\n        \u003c/dict\u003e\n      \u003c/array\u003e\n    \u003c/dict\u003e\n    \u003c/plist\u003e\n\n\n## License\n\n    The MIT License\n\n    Copyright (c) 2012, 2013 Iván -DrSlump- Montes\n\n    Permission is hereby granted, free of charge, to any person obtaining\n    a copy of this software and associated documentation files (the\n    'Software'), to deal in the Software without restriction, including\n    without limitation the rights to use, copy, modify, merge, publish,\n    distribute, sublicense, and/or sell copies of the Software, and to\n    permit persons to whom the Software is furnished to do so, subject to\n    the following conditions:\n\n    The above copyright notice and this permission notice shall be\n    included in all copies or substantial portions of the Software.\n\n    THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\n    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrslump%2Fpyshould","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdrslump%2Fpyshould","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrslump%2Fpyshould/lists"}