fastapi-overrider


Namefastapi-overrider JSON
Version 0.7.2 PyPI version JSON
download
home_pagehttps://github.com/phha/fastapi-overrider
SummaryFastAPI Dependency overrides made easy.
upload_time2024-01-04 19:42:51
maintainer
docs_urlNone
authorPhilipp Hack
requires_python>=3.10,<4.0
licenseMIT
keywords fastapi pytest
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # fastapi-overrider

Easy and safe dependency overrides for your [FastAPI](https://fastapi.tiangolo.com/) tests. 

## Installation

`pip install fastapi-overrider`

## Motivation

FastAPI provided a nice mechanism to override dependencies, but there are a few gotchas:

- Overrides are not cleaned up automatically and can't be scoped easily.
- Lots of boilerplate code required when you just want *some* test data.
- Using `unittest.mock.Mock` is non-trivial due to the way FastAPI relies on inspection
  of signatures when calling dependencies.
- Likewise, mocking async dependencies is cumbersome.

The goal of fastapi-override is to make dependency overriding easy, safe, reusable, composable,
and extendable.

## Usage

### General usage

Use it as pytest fixture to ensure every test is run with a clean set of overrides.

```python
override = create_fixture(app)

def test_get_item_from_value(client: TestClient, override: Overrider) -> None:
    override_item = Item(item_id=0, name="Bar")
    override.value(lookup_item, override_item)

    response = client.get("/item/0").json()

    assert Item(**response) == override_item
```

Alternatively use it as a context manager:

```python
def test_get_item_context_manager(client: TestClient, app: FastAPI) -> None:
    with Overrider(app) as override:
        override_item = Item(item_id=0, name="Bar")
        override.value(lookup_item, override_item)

        response = client.get("/item/0").json()

        assert Item(**response) == override_item
```

In both cases the overrides will be cleaned up after the test.

The above examples also show how to override a dependency with just the desired return
value. Overrider will take care of creating a matching wrapper function and setting it
as an override.

It doesn't matter if your dependency is async or not. Overrider will do the right thing.

### Basic overrides

`override.value()` returns the override value:

```python
def test_get_item_return_value(client: TestClient, override: Overrider) -> None:
    item = override.value(lookup_item, Item(item_id=0, name="Bar"))

    response = client.get("/item/0").json()

    assert Item(**response) == item
```

`override.function()` accepts a callable:

```python
def test_get_item_function(client: TestClient, override: Overrider) -> None:
    item = Item(item_id=0, name="Bar")
    override.function(lookup_item, lambda item_id: item)  # noqa: ARG005

    response = client.get("/item/0").json()

    assert Item(**response) == item
```

Use it as a drop-in replacement for `app.dependency_overrides`:

```python
def test_get_item_drop_in(client: TestClient, override: Overrider) -> None:
    item = Item(item_id=0, name="Bar")
    def override_lookup_item(item_id: int) -> Item:  # noqa: ARG001
        return item
    override[lookup_item] = override_lookup_item

    response = client.get("/item/0").json()

    assert Item(**response) == item
```

### Mocks and spies

Overrider can create mocks for you:

```python
def test_get_item_mock(client: TestClient, override: Overrider) -> None:
    item = Item(item_id=0, name="Bar")
    mock_lookup = override.mock(lookup_item)
    mock_lookup.return_value = item

    response = client.get("/item/0")

    mock_lookup.assert_called_once_with(item_id=0)
    assert Item(**response.json()) == item
```

Spy on a dependency. The original dependency will still be called, but you can call assertions
and inspect it like a `unittest.mock.Mock`:

```python
def test_get_item_spy(client: TestClient, override: Overrider) -> None:
    spy = override.spy(lookup_item)

    client.get("/item/0")

    spy.assert_called_with(item_id=0)
```

### Auto-generated overrides

Overrider can auto-generate mock objects using [Unifactory](https://github.com/phha/unifactory).

To enable this extra feature, use `pip install fastapi-overrider[unifactory]`.

Overrider will automatically use a matching factory from
[Polyfactory's inventory](https://polyfactory.litestar.dev/usage/library_factories/index.html)
for the given dependency.

Generate a single override value. You can provide optional keyword arguments to any of the
auto-generator methods in order to pin an attribute to a specific value, like `name` in
this example:

```python
def test_get_some_item(client: TestClient, override: Overrider) -> None:
    item = override.some(lookup_item, name="Foo")

    response = client.get(f"/item/{item.item_id}")

    assert item.name == "Foo"
    assert item == Item(**response.json())
```

You can also let Overrider generate multiple override values:

```python
def test_get_five_items(client: TestClient, override: Overrider) -> None:
    items = override.batch(lookup_item, 5)

    for item in items:
        response = client.get(f"/item/{item.item_id}")
        assert item == Item(**response.json())
```

Attempt to cover the full range of forms that a model can take:

```python
def test_cover_get_items(client: TestClient, override: Overrider) -> None:
    items = override.cover(lookup_item)

    for item in items:
        response = client.get(f"/item/{item.item_id}")
        assert item == Item(**response.json())
```

### Shortcuts

You can call Overrider directly and it will guess what you want to do:

If you pass in a callable, it will act like `override.function()`:

```python
def test_get_item_call_callable(client: TestClient, override: Overrider) -> None:
    item = Item(item_id=0, name="Bar")
    override(lookup_item, lambda item_id: item)  # noqa: ARG005

    response = client.get("/item/0").json()

    assert Item(**response) == item
```

If you pass in a non-callable, it will act like `override.value()`:

```python
def test_get_item_call_value(client: TestClient, override: Overrider) -> None:
    item = override(lookup_item, Item(item_id=0, name="Bar"))

    response = client.get("/item/0").json()

    assert Item(**response) == item
```

If you don't pass in anything, it will create a mock:

```python
def test_get_item_call_mock(client: TestClient, override: Overrider) -> None:
    item = Item(item_id=0, name="Bar")
    mock_lookup = override(lookup_item)
    mock_lookup.return_value = item

    response = client.get("/item/0")

    mock_lookup.assert_called_once_with(item_id=0)
    assert Item(**response.json()) == item
```

### Advanced patterns

Reuse common overrides. They are composable, you can have multiple:

```python
@pytest.fixture()
def as_dave(app: FastAPI) -> Iterator[Overrider]:
    with Overrider(app) as override:
        override(get_user, User(name="Dave", authenticated=True))
        yield override

@pytest.fixture()
def in_the_morning(app: FastAPI) -> Iterator[Overrider]:
    with Overrider(app) as override:
        override(get_time_of_day, "morning")
        yield override

def test_get_greeting(client: TestClient, as_dave: Overrider, in_the_morning: Overrider) -> None:
    response = client.get("/")

    assert response.text == '"Good morning, Dave."'
```

Extend it with your own convenience methods:

```python
class MyOverrider(Overrider):
    def user(self, *, name: str, authenticated: bool = False) -> None:
        self(get_user, User(name=name, authenticated=authenticated))

@pytest.fixture()
def override(app: FastAPI):
    with MyOverrider(app) as override:
        yield override

def test_open_pod_bay_doors(client: TestClient, my_override: MyOverrider) -> None:
    my_override.user(name="Dave", authenticated=False)

    response = client.get("/open/pod_bay_doors")

    assert response.text == "\"I'm afraid I can't let you do that, Dave.\""
```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/phha/fastapi-overrider",
    "name": "fastapi-overrider",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.10,<4.0",
    "maintainer_email": "",
    "keywords": "fastapi,pytest",
    "author": "Philipp Hack",
    "author_email": "philipp.hack@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/5f/85/b9e8553c31abdff67f4d654a0056a082841be428b3249c4f7eec69f72507/fastapi_overrider-0.7.2.tar.gz",
    "platform": null,
    "description": "# fastapi-overrider\n\nEasy and safe dependency overrides for your [FastAPI](https://fastapi.tiangolo.com/) tests. \n\n## Installation\n\n`pip install fastapi-overrider`\n\n## Motivation\n\nFastAPI provided a nice mechanism to override dependencies, but there are a few gotchas:\n\n- Overrides are not cleaned up automatically and can't be scoped easily.\n- Lots of boilerplate code required when you just want *some* test data.\n- Using `unittest.mock.Mock` is non-trivial due to the way FastAPI relies on inspection\n  of signatures when calling dependencies.\n- Likewise, mocking async dependencies is cumbersome.\n\nThe goal of fastapi-override is to make dependency overriding easy, safe, reusable, composable,\nand extendable.\n\n## Usage\n\n### General usage\n\nUse it as pytest fixture to ensure every test is run with a clean set of overrides.\n\n```python\noverride = create_fixture(app)\n\ndef test_get_item_from_value(client: TestClient, override: Overrider) -> None:\n    override_item = Item(item_id=0, name=\"Bar\")\n    override.value(lookup_item, override_item)\n\n    response = client.get(\"/item/0\").json()\n\n    assert Item(**response) == override_item\n```\n\nAlternatively use it as a context manager:\n\n```python\ndef test_get_item_context_manager(client: TestClient, app: FastAPI) -> None:\n    with Overrider(app) as override:\n        override_item = Item(item_id=0, name=\"Bar\")\n        override.value(lookup_item, override_item)\n\n        response = client.get(\"/item/0\").json()\n\n        assert Item(**response) == override_item\n```\n\nIn both cases the overrides will be cleaned up after the test.\n\nThe above examples also show how to override a dependency with just the desired return\nvalue. Overrider will take care of creating a matching wrapper function and setting it\nas an override.\n\nIt doesn't matter if your dependency is async or not. Overrider will do the right thing.\n\n### Basic overrides\n\n`override.value()` returns the override value:\n\n```python\ndef test_get_item_return_value(client: TestClient, override: Overrider) -> None:\n    item = override.value(lookup_item, Item(item_id=0, name=\"Bar\"))\n\n    response = client.get(\"/item/0\").json()\n\n    assert Item(**response) == item\n```\n\n`override.function()` accepts a callable:\n\n```python\ndef test_get_item_function(client: TestClient, override: Overrider) -> None:\n    item = Item(item_id=0, name=\"Bar\")\n    override.function(lookup_item, lambda item_id: item)  # noqa: ARG005\n\n    response = client.get(\"/item/0\").json()\n\n    assert Item(**response) == item\n```\n\nUse it as a drop-in replacement for `app.dependency_overrides`:\n\n```python\ndef test_get_item_drop_in(client: TestClient, override: Overrider) -> None:\n    item = Item(item_id=0, name=\"Bar\")\n    def override_lookup_item(item_id: int) -> Item:  # noqa: ARG001\n        return item\n    override[lookup_item] = override_lookup_item\n\n    response = client.get(\"/item/0\").json()\n\n    assert Item(**response) == item\n```\n\n### Mocks and spies\n\nOverrider can create mocks for you:\n\n```python\ndef test_get_item_mock(client: TestClient, override: Overrider) -> None:\n    item = Item(item_id=0, name=\"Bar\")\n    mock_lookup = override.mock(lookup_item)\n    mock_lookup.return_value = item\n\n    response = client.get(\"/item/0\")\n\n    mock_lookup.assert_called_once_with(item_id=0)\n    assert Item(**response.json()) == item\n```\n\nSpy on a dependency. The original dependency will still be called, but you can call assertions\nand inspect it like a `unittest.mock.Mock`:\n\n```python\ndef test_get_item_spy(client: TestClient, override: Overrider) -> None:\n    spy = override.spy(lookup_item)\n\n    client.get(\"/item/0\")\n\n    spy.assert_called_with(item_id=0)\n```\n\n### Auto-generated overrides\n\nOverrider can auto-generate mock objects using [Unifactory](https://github.com/phha/unifactory).\n\nTo enable this extra feature, use `pip install fastapi-overrider[unifactory]`.\n\nOverrider will automatically use a matching factory from\n[Polyfactory's inventory](https://polyfactory.litestar.dev/usage/library_factories/index.html)\nfor the given dependency.\n\nGenerate a single override value. You can provide optional keyword arguments to any of the\nauto-generator methods in order to pin an attribute to a specific value, like `name` in\nthis example:\n\n```python\ndef test_get_some_item(client: TestClient, override: Overrider) -> None:\n    item = override.some(lookup_item, name=\"Foo\")\n\n    response = client.get(f\"/item/{item.item_id}\")\n\n    assert item.name == \"Foo\"\n    assert item == Item(**response.json())\n```\n\nYou can also let Overrider generate multiple override values:\n\n```python\ndef test_get_five_items(client: TestClient, override: Overrider) -> None:\n    items = override.batch(lookup_item, 5)\n\n    for item in items:\n        response = client.get(f\"/item/{item.item_id}\")\n        assert item == Item(**response.json())\n```\n\nAttempt to cover the full range of forms that a model can take:\n\n```python\ndef test_cover_get_items(client: TestClient, override: Overrider) -> None:\n    items = override.cover(lookup_item)\n\n    for item in items:\n        response = client.get(f\"/item/{item.item_id}\")\n        assert item == Item(**response.json())\n```\n\n### Shortcuts\n\nYou can call Overrider directly and it will guess what you want to do:\n\nIf you pass in a callable, it will act like `override.function()`:\n\n```python\ndef test_get_item_call_callable(client: TestClient, override: Overrider) -> None:\n    item = Item(item_id=0, name=\"Bar\")\n    override(lookup_item, lambda item_id: item)  # noqa: ARG005\n\n    response = client.get(\"/item/0\").json()\n\n    assert Item(**response) == item\n```\n\nIf you pass in a non-callable, it will act like `override.value()`:\n\n```python\ndef test_get_item_call_value(client: TestClient, override: Overrider) -> None:\n    item = override(lookup_item, Item(item_id=0, name=\"Bar\"))\n\n    response = client.get(\"/item/0\").json()\n\n    assert Item(**response) == item\n```\n\nIf you don't pass in anything, it will create a mock:\n\n```python\ndef test_get_item_call_mock(client: TestClient, override: Overrider) -> None:\n    item = Item(item_id=0, name=\"Bar\")\n    mock_lookup = override(lookup_item)\n    mock_lookup.return_value = item\n\n    response = client.get(\"/item/0\")\n\n    mock_lookup.assert_called_once_with(item_id=0)\n    assert Item(**response.json()) == item\n```\n\n### Advanced patterns\n\nReuse common overrides. They are composable, you can have multiple:\n\n```python\n@pytest.fixture()\ndef as_dave(app: FastAPI) -> Iterator[Overrider]:\n    with Overrider(app) as override:\n        override(get_user, User(name=\"Dave\", authenticated=True))\n        yield override\n\n@pytest.fixture()\ndef in_the_morning(app: FastAPI) -> Iterator[Overrider]:\n    with Overrider(app) as override:\n        override(get_time_of_day, \"morning\")\n        yield override\n\ndef test_get_greeting(client: TestClient, as_dave: Overrider, in_the_morning: Overrider) -> None:\n    response = client.get(\"/\")\n\n    assert response.text == '\"Good morning, Dave.\"'\n```\n\nExtend it with your own convenience methods:\n\n```python\nclass MyOverrider(Overrider):\n    def user(self, *, name: str, authenticated: bool = False) -> None:\n        self(get_user, User(name=name, authenticated=authenticated))\n\n@pytest.fixture()\ndef override(app: FastAPI):\n    with MyOverrider(app) as override:\n        yield override\n\ndef test_open_pod_bay_doors(client: TestClient, my_override: MyOverrider) -> None:\n    my_override.user(name=\"Dave\", authenticated=False)\n\n    response = client.get(\"/open/pod_bay_doors\")\n\n    assert response.text == \"\\\"I'm afraid I can't let you do that, Dave.\\\"\"\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "FastAPI Dependency overrides made easy.",
    "version": "0.7.2",
    "project_urls": {
        "Homepage": "https://github.com/phha/fastapi-overrider",
        "Repository": "https://github.com/phha/fastapi-overrider"
    },
    "split_keywords": [
        "fastapi",
        "pytest"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b302697151ab6b76bb2a339e1008ab36410bd9df22c677f803cee2658805c783",
                "md5": "c778bc2f10459da6650cd391b8aa6a23",
                "sha256": "1702dfb28d21f71656f03d29c2a18132d65787d0694a7691f36e9b5ffd95de45"
            },
            "downloads": -1,
            "filename": "fastapi_overrider-0.7.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c778bc2f10459da6650cd391b8aa6a23",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10,<4.0",
            "size": 5949,
            "upload_time": "2024-01-04T19:42:49",
            "upload_time_iso_8601": "2024-01-04T19:42:49.099539Z",
            "url": "https://files.pythonhosted.org/packages/b3/02/697151ab6b76bb2a339e1008ab36410bd9df22c677f803cee2658805c783/fastapi_overrider-0.7.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5f85b9e8553c31abdff67f4d654a0056a082841be428b3249c4f7eec69f72507",
                "md5": "5a792d88d5b03a30d26acb399c616a79",
                "sha256": "4f9dede4ef76afc663f578b35f4536e3433e23a7779d0335445e1916f0dbf662"
            },
            "downloads": -1,
            "filename": "fastapi_overrider-0.7.2.tar.gz",
            "has_sig": false,
            "md5_digest": "5a792d88d5b03a30d26acb399c616a79",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10,<4.0",
            "size": 5726,
            "upload_time": "2024-01-04T19:42:51",
            "upload_time_iso_8601": "2024-01-04T19:42:51.601348Z",
            "url": "https://files.pythonhosted.org/packages/5f/85/b9e8553c31abdff67f4d654a0056a082841be428b3249c4f7eec69f72507/fastapi_overrider-0.7.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-01-04 19:42:51",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "phha",
    "github_project": "fastapi-overrider",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "fastapi-overrider"
}
        
Elapsed time: 0.19124s