{"id":21528221,"url":"https://github.com/znsio/specmatic-python-extensions","last_synced_at":"2025-04-09T23:42:29.238Z","repository":{"id":163326610,"uuid":"637789597","full_name":"znsio/specmatic-python-extensions","owner":"znsio","description":"Python extensions for running Specmatic","archived":false,"fork":false,"pushed_at":"2025-03-19T15:43:50.000Z","size":77507,"stargazers_count":2,"open_issues_count":7,"forks_count":0,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-03-19T16:02:14.454Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/znsio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":null,"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":"2023-05-08T12:14:17.000Z","updated_at":"2025-03-19T15:36:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"acaf21bf-3c87-4b62-a1be-a14a00985133","html_url":"https://github.com/znsio/specmatic-python-extensions","commit_stats":null,"previous_names":[],"tags_count":137,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/znsio%2Fspecmatic-python-extensions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/znsio%2Fspecmatic-python-extensions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/znsio%2Fspecmatic-python-extensions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/znsio%2Fspecmatic-python-extensions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/znsio","download_url":"https://codeload.github.com/znsio/specmatic-python-extensions/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248131470,"owners_count":21052819,"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","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-11-24T01:52:15.782Z","updated_at":"2025-04-09T23:42:29.222Z","avatar_url":"https://github.com/znsio.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Specmatic Python\nThis is a Python library to run [Specmatic](https://specmatic.io).\nSpecmatic is a contract driven development tool that allows us to turn OpenAPI contracts into executable specifications.\n\u003cbr/\u003eClick below to learn more about Specmatic and Contract Driven Development\u003cbr/\u003e\u003cbr/\u003e\n[![Specmatic - Contract Driven Development](https://img.youtube.com/vi/3HPgpvd8MGg/0.jpg)](https://www.youtube.com/watch?v=3HPgpvd8MGg \"Specmatic - Contract Driven Development\")\n\n  The specmatic python library provides three main functions:\n  - The ability to start and stop a python web app like flask/sanic.\n  - The ability to run specmatic in test mode against an open api contract/spec.\n  - The ability to stub out an api dependency using the specmatic stub feature.\n\n#### Running Contract Tests\nA contract test validates an open api specification against a running api service.  \nThe open api specification can be present either locally or in a [Central Contract Repository](https://specmatic.io/documentation/central_contract_repository.html)  \n[Click here](https://specmatic.io/documentation/contract_tests.html) to learn more about contract tests.  \n\n#### How to use\n- Create a file called test_contract.py in your test folder.  \n- Declare an empty class in it called 'TestContract'.  \n  This is could either be a normal class like:\n  ``````python\n  class TestContract:\n    pass\n  ``````\n  Or you could also have a class which inherits from unittest.TestCase:\n  ``````python\n  class TestContract(unittest.TestCase):\n    pass\n  ``````\n  \n#### How does it work\n- Specmatic uses the TestContract class defined above to inject tests dynamically into it when you run it via PyTest or UnitTest.  \n- The Specmatic Python package, invokes the Specmatic executable jar (via command line) in a separate process to start stubs and run tests.  \n- It is the specmatic jar which runs the contract tests and generates a JUnit test summary report.  \n- The Specmatic Python package ingests the JUnit test summary report and generates test methods corresponding to every contract test.  \n- These dynamic test methods are added to the  ```TestContract``` class and hence we seem them reported seamlessly by PyTest/Unittest like this:\n\n```python\ntest/test_contract_with_coverage.py::TestContract::test_Scenario: GET /products -\u003e 200 | SEARCH_2 PASSED\ntest/test_contract_with_coverage.py::TestContract::test_Scenario: GET /products -\u003e 500 | SEARCH_ERROR PASSED\ntest/test_contract_with_coverage.py::TestContract::test_Scenario: GET /products -\u003e 200 | SEARCH_1 PASSED\n```\n\n\n## WSGI Apps\n\n#### To run contract tests with a stub for a wsgi app (like Flask):  \n\n``````python\nclass TestContract:\n    pass\n    \n    \nSpecmatic() \\\n    .with_project_root(PROJECT_ROOT) \\\n    .with_stub(stub_host, stub_port, [expectation_json_file]) \\\n    .with_wsgi_app(app, app_host, app_port) \\\n    .test(TestContract) \\\n    .run()\n \n \nif __name__ == '__main__':\npytest.main()\n`````` \n\n- In this, we are passing:\n  - an instance of your wsgi app like flask \n  - app_host and app_port. If they are not specified, the app will be started on a random available port on 127.0.0.1.\n  - You would need a [specmatic config](https://specmatic.io/documentation/specmatic_json.html) file to be present in the root directory of your project.\n  - an empty test class.\n  - stub_host, stub_port, optional list of json files to set expectations on the stub.  \n    The stub_host, stub_port will be used to run the specmatic stub server.   \n    If they are not supplied, the stub will be started on a random available port on 127.0.0.1.    \n    [Click here](https://specmatic.io/documentation/service_virtualization_tutorial.html) to learn more about stubbing/service virtualization.\n-  You can run this test from either your IDE or command line by pointing pytest to your test folder:\n   ``````pytest test -v -s``````  \n- NOTE: Please ensure that you set the '-v' and '-s' flags while running pytest as otherwise pytest may swallow up the console output.\n  \n\n#### To run contract tests without a stub:\n\n``````python\nclass TestContract:\n    pass\n    \nSpecmatic() \\\n    .with_project_root(PROJECT_ROOT) \\\n    .with_wsgi_app(app, app_host, app_port) \\\n    .test(TestContract) \\\n    .run()\n``````                        \n\n## ASGI Apps\n\n#### To run contract tests with a stub for an asgi app (like sanic):\n- If you are using an asgi app like sanic, fastapi, use the ``````with_asgi_app`````` function and pass it a string in the 'module:app' format.\n``````python\nclass TestContract:\n    pass\n    \n    \nSpecmatic() \\\n    .with_project_root(PROJECT_ROOT) \\\n    .with_stub(stub_host, stub_port, [expectation_json_file]) \\\n    .with_asgi_app('main:app', app_host, app_port) \\\n    .test(TestContract) \\\n    .run()\n``````\n\n### Passing extra arguments to stub/test\n- To pass arguments like '--strict', '--testBaseUrl', pass them as a list to the 'args' parameter:\n``````python\nclass TestContract:\n    pass\n\n\nSpecmatic() \\\n    .with_project_root(PROJECT_ROOT) \\\n    .with_stub(stub_host, stub_port, [expectation_json_file], ['--strict']) \\\n    .with_wsgi_app(app, port=app_port) \\\n    .test(TestContract, args=['--testBaseURL=http://localhost:5000']) \\\n    .run()\n``````\n\n## Coverage\nSpecmatic can generate a coverage summary report which will list out all the apis exposed by your app/service with a status next to it indicating if it has been covered in your contract tests.\n\n### Enabling api coverage for Flask apps\n\n``````python\nclass TestContract:\n    pass\n\n\nSpecmatic() \\\n    .with_project_root(PROJECT_ROOT) \\\n    .with_stub(stub_host, stub_port, [expectation_json_file]) \\\n    .with_wsgi_app(app, app_host, app_port) \\\n    .test_with_api_coverage_for_flask_app(TestContract, app) \\\n    .run()\n``````\n\n### Enabling api coverage for Sanic apps\n\n``````python\nclass TestContract:\n    pass\n\n\nSpecmatic() \\\n    .with_project_root(PROJECT_ROOT) \\\n    .with_stub(stub_host, stub_port, [expectation_json_file]) \\\n    .with_asgi_app('main:app', app_host, app_port) \\\n    .test_with_api_coverage_for_sanic_app(TestContract, app) \\\n    .run()\n``````\n\n### Enabling api coverage for FastApi apps\n\n``````python\nclass TestContract:\n    pass\n\n\nSpecmatic() \\\n    .with_project_root(PROJECT_ROOT) \\\n    .with_stub(stub_host, stub_port, [expectation_json_file]) \\\n    .with_asgi_app('main:app', app_host, app_port) \\\n    .test_with_api_coverage_for_fastapi_app(TestContract, app) \\\n    .run()\n``````\n\n### Enabling api coverage for any other type of app\nFor any app other than Flask, Sanic, and FastApi, you would need to implement an ``````AppRouteAdapter`````` class.  \nThe idea is to implement ``````to_coverage_routes`````` method, which returns a list of ``````CoverageRoute`````` objects corresponding to all the routes defined in your app.  \nThe ``````CoverageRoute`````` class has two properties:  \n``````url`````` : This represents your route url in this format: `````` /orders/{order_id}``````  \n``````method`````` : A list of HTTP methods supported on the route, for instance : ``````['GET', 'POST']``````  \n\nYou can then enable coverage by passing your adapter like this:  \n\n``````python\nSpecmatic() \\\n    .with_project_root(PROJECT_ROOT) \\\n    .with_stub(stub_host, stub_port, [expectation_json_file]) \\\n    .with_asgi_app('main:app', app_host, app_port) \\\n    .test_with_api_coverage(TestContract, MyAppRouteAdapter(app)) \\\n    .run()\n``````\n\n### Enabling api coverage by setting the EndPointsApi property\nYou can also start your coverage server externally and use the EndPointsApi method to enable coverage.  \nWe have provided ready to use Coverage Server classes for:  \nFlask: ``````FlaskAppCoverageServer``````  \nSanic: ``````SanicAppCoverageServer``````  \nFastApi ``````FastApiAppCoverageServer``````  \n\nYou can also easily implement your own coverage server if you have written a custom implementation of the ``````AppRouteAdapter`````` class.\nThe only point to remember in mind is that the EndPointsApi url should return a list of routes in the format used buy Spring Actuator's ```````/actuator/mappings``````` endpoint\nas described [here](https://docs.spring.io/spring-boot/docs/current/actuator-api/htmlsingle/#mappings).  \n\nHere's an example where we start both our FastApi app and coverage server outside the specmatic api call.  \n``````python\napp_server = ASGIAppServer('test.apps.fast_api:app', app_host, app_port)\ncoverage_server = FastApiAppCoverageServer(app)\n\napp_server.start()\ncoverage_server.start()\n\n\nclass TestContract:\n    pass\n\n\nSpecmatic() \\\n    .with_project_root(PROJECT_ROOT) \\\n    .with_stub(stub_host, stub_port, [expectation_json_file]) \\\n    .with_endpoints_api(coverage_server.endpoints_api) \\\n    .test(TestContract, app_host, app_port) \\\n    .run()\n\napp_server.stop()\ncoverage_server.stop()\n``````\n\n## Common Issues\n- **'Error loading ASGI app'** \n   This error occurs when an incorrect app module string is passed to the ``````with_asgi_app`````` function.  \n \n   #### Solutions:\n   - Try to identify the correct module in which your app variable is instantiated/imported.  \n     For example if your 'app' variable is declared in main.py, try passing 'main:app'.  \n   - Try running the app using uvicorn directly:  \n     `````` uvciron 'main:app' ``````  \n     If you are able to get the app started using uvicorn, it will work with specmatic too.  \n\n## Sample Projects\n- [Check out the Specmatic Order BFF Python repo](https://github.com/znsio/specmatic-order-bff-python/) to see more examples of how to use specmatic with a Flask app.  \n- [Check out the Specmatic Order BFF Python Sanic repo](https://github.com/znsio/specmatic-order-bff-python-sanic/) to see more examples of how to use specmatic with a Sanic app.  \n- [Check out the Specmatic Order API Python repo](https://github.com/znsio/specmatic-order-api-python/) to see an examples of how to just run tests without using a stub.  \n\n\n\n\n  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fznsio%2Fspecmatic-python-extensions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fznsio%2Fspecmatic-python-extensions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fznsio%2Fspecmatic-python-extensions/lists"}