203 Commits

Author SHA1 Message Date
dependabot[bot]
b7a6549bf5 Bump fastapi from 0.115.0 to 0.115.12
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.0 to 0.115.12.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.115.0...0.115.12)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 08:16:56 +00:00
long2ice
157a91359b chore: update deps 2024-09-19 15:38:04 +08:00
long2ice
567934300b ci: remove py37 2024-07-24 23:42:19 +08:00
long2ice
5f985a8506 ci: fix 2024-07-24 22:24:30 +08:00
long2ice
701d83fec3 Merge pull request #425 from PaleNeutron/fix-no-cache
fix #424, no-cache should store the result to cache
2024-07-24 21:46:19 +08:00
long2ice
8b5601cfdc Merge branch 'main' into fix-no-cache 2024-07-24 21:46:11 +08:00
long2ice
00d34e4ad8 Merge pull request #433 from nikstuckenbrock/feature/update-project-docs
Update project docs and examples
2024-07-24 21:42:53 +08:00
Nik Stuckenbrock
552d54c1ca Fix redis example 2024-07-19 15:09:39 +02:00
Nik Stuckenbrock
63f9bdd068 Fix in memory example 2024-07-19 15:08:43 +02:00
Nik Stuckenbrock
5ba03ca6f5 Correct example in README.md 2024-07-19 15:07:23 +02:00
long2ice
865dba19a5 Merge pull request #426 from CharlesPerrotMinotHCHB/patch-2
Switch from on_event to lifespan asynccontextmanager #422
2024-05-13 13:59:39 +08:00
Charles Perrot-Minot
f203d23194 Switch from on_event to lifespan asynccontextmanager
on_event is now deprecated, and to be replaced with lifespan: https://fastapi.tiangolo.com/advanced/events/
2024-05-12 22:57:14 -07:00
John Lyu
2739b2d0fe lint 2024-05-09 15:01:55 +08:00
John Lyu
74e827d3de fix tests 2024-05-09 14:51:58 +08:00
John Lyu
6ef59f0eb3 Merge branch 'fix-no-cache' into fix 2024-05-09 14:44:15 +08:00
John Lyu
6f8994b843 fix test 2024-05-09 14:43:53 +08:00
John Lyu
120553a36f version 2024-05-09 14:42:56 +08:00
John Lyu
af6301fffa Merge branch 'fix-no-cache' into fix 2024-05-09 14:34:03 +08:00
John Lyu
e4a0df62dd fix #424, no-cache should store the result to cache 2024-05-09 14:32:02 +08:00
John Lyu
19c4d0271a fix latest bug 2024-05-09 14:09:42 +08:00
long2ice
91ba6d7552 Merge pull request #353 from long2ice/dependabot/github_actions/main/actions/setup-python-5
Bump actions/setup-python from 4 to 5
2023-12-07 21:37:57 +08:00
dependabot[bot]
583df36da1 Bump actions/setup-python from 4 to 5
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-07 08:11:07 +00:00
dependabot[bot]
005bac9d11 Bump pyright from 1.1.332 to 1.1.333 (#328)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.332 to 1.1.333.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.332...v1.1.333)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-26 08:52:27 +00:00
dependabot[bot]
a315bcf6a7 Bump pytest from 7.4.2 to 7.4.3 (#327)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.2 to 7.4.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.4.2...7.4.3)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-25 08:26:35 +00:00
dependabot[bot]
fab700af41 Bump mypy from 1.6.0 to 1.6.1 (#322)
Bumps [mypy](https://github.com/python/mypy) from 1.6.0 to 1.6.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-20 08:30:32 +00:00
dependabot[bot]
0af1638d54 Bump ruff from 0.1.0 to 0.1.1 (#324)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.0 to 0.1.1.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.0...v0.1.1)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-20 08:26:14 +00:00
dependabot[bot]
8a257f2f7d Bump pyright from 1.1.331 to 1.1.332 (#323)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.331 to 1.1.332.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.331...v1.1.332)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 09:12:50 +00:00
dependabot[bot]
2747470ab2 Bump types-aiobotocore from 2.6.0 to 2.7.0 (#321)
Bumps [types-aiobotocore](https://github.com/youtype/mypy_boto3_builder) from 2.6.0 to 2.7.0.
- [Release notes](https://github.com/youtype/mypy_boto3_builder/releases)
- [Commits](https://github.com/youtype/mypy_boto3_builder/commits)

---
updated-dependencies:
- dependency-name: types-aiobotocore
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-18 09:06:21 +00:00
dependabot[bot]
c9d4d2afc9 Bump ruff from 0.0.292 to 0.1.0 (#320)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.292 to 0.1.0.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.292...v0.1.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 08:44:00 +00:00
dependabot[bot]
d465e82c1c Bump pyright from 1.1.330.post0 to 1.1.331 (#319)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.330.post0 to 1.1.331.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.330.post0...v1.1.331)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-12 08:48:47 +00:00
dependabot[bot]
eeef78d3aa Bump mypy from 1.5.1 to 1.6.0 (#318)
Bumps [mypy](https://github.com/python/mypy) from 1.5.1 to 1.6.0.
- [Commits](https://github.com/python/mypy/compare/v1.5.1...v1.6.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 08:29:41 +00:00
dependabot[bot]
42d868542c Bump pyright from 1.1.329 to 1.1.330.post0 (#317)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.329 to 1.1.330.post0.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.329...v1.1.330.post0)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 08:58:02 +00:00
dependabot[bot]
d65b12aaf7 Bump ruff from 0.0.290 to 0.0.292 (#313)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.290 to 0.0.292.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.290...v0.0.292)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 08:57:51 +00:00
dependabot[bot]
4ba67db7e9 Bump fastapi from 0.103.1 to 0.103.2 (#312)
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.103.1 to 0.103.2.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.103.1...0.103.2)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-29 08:19:06 +00:00
dependabot[bot]
0ce7c61770 Bump pyright from 1.1.328 to 1.1.329 (#311)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.328 to 1.1.329.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.328...v1.1.329)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-28 08:37:45 +00:00
dependabot[bot]
6983c83124 Bump pyright from 1.1.327 to 1.1.328 (#310)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.327 to 1.1.328.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.327...v1.1.328)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 08:58:27 +00:00
dependabot[bot]
f54facf5bd Bump types-redis from 4.6.0.6 to 4.6.0.7 (#307)
Bumps [types-redis](https://github.com/python/typeshed) from 4.6.0.6 to 4.6.0.7.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-redis
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 08:16:13 +00:00
dependabot[bot]
a540ee30fe Bump pyright from 1.1.326 to 1.1.327 (#304)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.326 to 1.1.327.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.326...v1.1.327)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 09:08:36 +00:00
dependabot[bot]
3e905353e3 Bump ruff from 0.0.288 to 0.0.290 (#305)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.288 to 0.0.290.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.288...v0.0.290)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 09:03:13 +00:00
dependabot[bot]
e2eedace48 Bump types-redis from 4.6.0.5 to 4.6.0.6 (#301)
Bumps [types-redis](https://github.com/python/typeshed) from 4.6.0.5 to 4.6.0.6.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-redis
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 08:59:32 +00:00
dependabot[bot]
0af7761dff Bump pytest from 7.3.2 to 7.4.2 (#297)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.2 to 7.4.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.3.2...7.4.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-12 08:27:51 +00:00
dependabot[bot]
ddde88c7cf Bump ruff from 0.0.286 to 0.0.288 (#300)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.286 to 0.0.288.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.286...v0.0.288)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-12 08:23:56 +00:00
dependabot[bot]
c2d9308057 Bump pyright from 1.1.325 to 1.1.326 (#296)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.325 to 1.1.326.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.325...v1.1.326)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-08 08:54:12 +00:00
dependabot[bot]
d748abd8c6 Bump fastapi from 0.101.1 to 0.103.1 (#289)
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.101.1 to 0.103.1.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.101.1...0.103.1)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-04 08:15:56 +00:00
dependabot[bot]
6586ea96c4 Bump pyright from 1.1.324 to 1.1.325 (#288)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.324 to 1.1.325.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.324...v1.1.325)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-31 08:22:10 +00:00
dependabot[bot]
812ada8620 Bump mypy from 1.5.0 to 1.5.1 (#276)
Bumps [mypy](https://github.com/python/mypy) from 1.5.0 to 1.5.1.
- [Commits](https://github.com/python/mypy/compare/v1.5.0...v1.5.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 09:07:52 +00:00
dependabot[bot]
762c677786 Bump aiobotocore from 2.5.4 to 2.6.0 (#268)
Bumps [aiobotocore](https://github.com/aio-libs/aiobotocore) from 2.5.4 to 2.6.0.
- [Release notes](https://github.com/aio-libs/aiobotocore/releases)
- [Changelog](https://github.com/aio-libs/aiobotocore/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiobotocore/compare/2.5.4...2.6.0)

---
updated-dependencies:
- dependency-name: aiobotocore
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 09:04:05 +00:00
dependabot[bot]
daea1a4b66 Bump ruff from 0.0.285 to 0.0.286 (#286)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.285 to 0.0.286.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.285...v0.0.286)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 08:59:42 +00:00
dependabot[bot]
294d6151f9 Bump pyright from 1.1.320 to 1.1.324 (#284)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.320 to 1.1.324.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.320...v1.1.324)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-24 08:44:06 +00:00
dependabot[bot]
8e07be5b5d Bump types-redis from 4.6.0.4 to 4.6.0.5 (#282)
Bumps [types-redis](https://github.com/python/typeshed) from 4.6.0.4 to 4.6.0.5.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-redis
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-23 09:14:37 +00:00
long2ice
4e485d4bdb Merge pull request #280 from s-rigaud/update-readme
📝 Use create_some_model as a function
2023-08-22 10:00:15 +08:00
Samuel Rigaud
9217c85d94 📝 Use create_some_model as a function 2023-08-20 01:27:10 +02:00
dependabot[bot]
24c1f3d187 Bump tox from 4.6.4 to 4.8.0 (#269)
Bumps [tox](https://github.com/tox-dev/tox) from 4.6.4 to 4.8.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.6.4...4.8.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-18 08:47:03 +00:00
dependabot[bot]
541ddbc0c1 Bump ruff from 0.0.284 to 0.0.285 (#278)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.284 to 0.0.285.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.284...v0.0.285)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-18 08:42:33 +00:00
dependabot[bot]
3b78f211b2 Bump types-redis from 4.6.0.3 to 4.6.0.4 (#275)
Bumps [types-redis](https://github.com/python/typeshed) from 4.6.0.3 to 4.6.0.4.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-redis
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-17 08:30:23 +00:00
dependabot[bot]
2be6a8cfdc Bump fastapi from 0.101.0 to 0.101.1 (#271)
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.101.0 to 0.101.1.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.101.0...0.101.1)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-15 08:40:30 +00:00
dependabot[bot]
14df5a39ed Bump types-aiobotocore from 2.5.4 to 2.6.0 (#267)
Bumps [types-aiobotocore](https://github.com/youtype/mypy_boto3_builder) from 2.5.4 to 2.6.0.
- [Release notes](https://github.com/youtype/mypy_boto3_builder/releases)
- [Commits](https://github.com/youtype/mypy_boto3_builder/commits)

---
updated-dependencies:
- dependency-name: types-aiobotocore
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-14 08:45:24 +00:00
dependabot[bot]
229fbf3bef Bump mypy from 1.4.0 to 1.5.0 (#265)
Bumps [mypy](https://github.com/python/mypy) from 1.4.0 to 1.5.0.
- [Commits](https://github.com/python/mypy/compare/v1.4.0...v1.5.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-11 08:18:32 +00:00
dependabot[bot]
63c9fd5f8c Bump fastapi from 0.100.1 to 0.101.0 (#254)
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.100.1 to 0.101.0.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.100.1...0.101.0)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-10 08:30:48 +00:00
dependabot[bot]
ea2dc76da1 Bump ruff from 0.0.282 to 0.0.284 (#261)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.282 to 0.0.284.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.282...v0.0.284)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-10 08:26:36 +00:00
dependabot[bot]
e87716cf99 Bump types-aiobotocore from 2.5.2 to 2.5.4 (#259)
Bumps [types-aiobotocore](https://github.com/youtype/mypy_boto3_builder) from 2.5.2 to 2.5.4.
- [Release notes](https://github.com/youtype/mypy_boto3_builder/releases)
- [Commits](https://github.com/youtype/mypy_boto3_builder/commits)

---
updated-dependencies:
- dependency-name: types-aiobotocore
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-09 08:27:54 +00:00
dependabot[bot]
c222ba078a Bump aiobotocore from 2.5.3 to 2.5.4 (#257)
Bumps [aiobotocore](https://github.com/aio-libs/aiobotocore) from 2.5.3 to 2.5.4.
- [Release notes](https://github.com/aio-libs/aiobotocore/releases)
- [Changelog](https://github.com/aio-libs/aiobotocore/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiobotocore/compare/2.5.3...2.5.4)

---
updated-dependencies:
- dependency-name: aiobotocore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 08:51:14 +00:00
dependabot[bot]
36aef927f9 Bump aiobotocore from 2.5.2 to 2.5.3 (#253)
Bumps [aiobotocore](https://github.com/aio-libs/aiobotocore) from 2.5.2 to 2.5.3.
- [Release notes](https://github.com/aio-libs/aiobotocore/releases)
- [Changelog](https://github.com/aio-libs/aiobotocore/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiobotocore/compare/2.5.2...2.5.3)

---
updated-dependencies:
- dependency-name: aiobotocore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 08:45:56 +00:00
dependabot[bot]
58eece7830 Bump pyright from 1.1.318 to 1.1.320 (#252)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.318 to 1.1.320.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.318...v1.1.320)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-03 09:12:02 +00:00
dependabot[bot]
9bb5e9475b Bump aiobotocore from 2.5.0 to 2.5.2 (#235)
Bumps [aiobotocore](https://github.com/aio-libs/aiobotocore) from 2.5.0 to 2.5.2.
- [Release notes](https://github.com/aio-libs/aiobotocore/releases)
- [Changelog](https://github.com/aio-libs/aiobotocore/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiobotocore/compare/2.5.0...2.5.2)

---
updated-dependencies:
- dependency-name: aiobotocore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 08:23:17 +00:00
dependabot[bot]
bd179f530f Bump ruff from 0.0.281 to 0.0.282 (#251)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.281 to 0.0.282.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.281...v0.0.282)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 08:19:24 +00:00
dependabot[bot]
a0d0e7b130 Bump typing-extensions from 4.7.0 to 4.7.1 (#228)
Bumps [typing-extensions](https://github.com/python/typing_extensions) from 4.7.0 to 4.7.1.
- [Release notes](https://github.com/python/typing_extensions/releases)
- [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/python/typing_extensions/compare/4.7.0...4.7.1)

---
updated-dependencies:
- dependency-name: typing-extensions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-01 08:56:59 +00:00
dependabot[bot]
f614bc2de5 Bump ruff from 0.0.276 to 0.0.281 (#250)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.276 to 0.0.281.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.276...v0.0.281)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-01 08:52:18 +00:00
dependabot[bot]
30c0acc4d5 Bump fastapi from 0.99.1 to 0.100.1 (#247)
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.99.1 to 0.100.1.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.99.1...0.100.1)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-28 08:24:51 +00:00
dependabot[bot]
9c1378c96f Bump pyright from 1.1.317 to 1.1.318 (#244)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.317 to 1.1.318.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.317...v1.1.318)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-24 08:56:57 +00:00
dependabot[bot]
300c9f0ae4 Bump types-redis from 4.6.0.2 to 4.6.0.3 (#243)
Bumps [types-redis](https://github.com/python/typeshed) from 4.6.0.2 to 4.6.0.3.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-redis
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-21 08:53:34 +00:00
dependabot[bot]
5cace7ee4b Bump pyright from 1.1.316 to 1.1.317 (#239)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.316 to 1.1.317.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.316...v1.1.317)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-13 09:00:21 +00:00
dependabot[bot]
fcc7f75d8f Bump types-aiobotocore from 2.5.1 to 2.5.2 (#237)
Bumps [types-aiobotocore](https://github.com/youtype/mypy_boto3_builder) from 2.5.1 to 2.5.2.
- [Release notes](https://github.com/youtype/mypy_boto3_builder/releases)
- [Commits](https://github.com/youtype/mypy_boto3_builder/commits)

---
updated-dependencies:
- dependency-name: types-aiobotocore
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 08:21:53 +00:00
dependabot[bot]
3639f76d47 Bump tox from 4.6.3 to 4.6.4 (#234)
Bumps [tox](https://github.com/tox-dev/tox) from 4.6.3 to 4.6.4.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.6.3...4.6.4)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-07 08:24:04 +00:00
dependabot[bot]
7a25b77295 Bump types-redis from 4.5.5.2 to 4.6.0.2 (#233)
Bumps [types-redis](https://github.com/python/typeshed) from 4.5.5.2 to 4.6.0.2.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-redis
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-05 08:23:07 +00:00
dependabot[bot]
9ec731be61 Bump tox from 4.6.1 to 4.6.3 (#214)
Bumps [tox](https://github.com/tox-dev/tox) from 4.6.1 to 4.6.3.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.6.1...4.6.3)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-04 08:36:23 +00:00
dependabot[bot]
e881a2a4d7 Bump ruff from 0.0.274 to 0.0.276 (#230)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.274 to 0.0.276.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.274...v0.0.276)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-04 08:31:06 +00:00
dependabot[bot]
6ea7d01c6f Bump fastapi from 0.98.0 to 0.99.1 (#227)
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.98.0 to 0.99.1.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.98.0...0.99.1)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-03 09:10:23 +00:00
dependabot[bot]
c5aa99a17c Bump typing-extensions from 4.6.3 to 4.7.0 (#226)
Bumps [typing-extensions](https://github.com/python/typing_extensions) from 4.6.3 to 4.7.0.
- [Release notes](https://github.com/python/typing_extensions/releases)
- [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/python/typing_extensions/compare/4.6.3...4.7.0)

---
updated-dependencies:
- dependency-name: typing-extensions
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-29 08:13:39 +00:00
dependabot[bot]
b979a1f73c Bump types-aiobotocore from 2.5.0.post2 to 2.5.1 (#223)
Bumps [types-aiobotocore](https://github.com/youtype/mypy_boto3_builder) from 2.5.0.post2 to 2.5.1.
- [Release notes](https://github.com/youtype/mypy_boto3_builder/releases)
- [Commits](https://github.com/youtype/mypy_boto3_builder/commits)

---
updated-dependencies:
- dependency-name: types-aiobotocore
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-28 09:23:40 +00:00
dependabot[bot]
706048f0c3 Bump pyright from 1.1.315 to 1.1.316 (#222)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.315 to 1.1.316.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.315...v1.1.316)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-27 09:12:04 +00:00
dependabot[bot]
e775799d41 Bump redis from 4.5.5 to 4.6.0 (#219)
Bumps [redis](https://github.com/redis/redis-py) from 4.5.5 to 4.6.0.
- [Release notes](https://github.com/redis/redis-py/releases)
- [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES)
- [Commits](https://github.com/redis/redis-py/compare/v4.5.5...v4.6.0)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-26 09:11:36 +00:00
dependabot[bot]
d543c6f3c4 Bump fastapi from 0.96.0 to 0.98.0 (#216)
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.96.0 to 0.98.0.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.96.0...0.98.0)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-23 09:13:13 +00:00
dependabot[bot]
3c0e25d854 Bump pyright from 1.1.314 to 1.1.315 (#215)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.314 to 1.1.315.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.314...v1.1.315)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-22 09:12:00 +00:00
dependabot[bot]
fd2baba920 Bump mypy from 1.3.0 to 1.4.0 (#213)
Bumps [mypy](https://github.com/python/mypy) from 1.3.0 to 1.4.0.
- [Commits](https://github.com/python/mypy/compare/v1.3.0...v1.4.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-21 09:13:55 +00:00
dependabot[bot]
0df52ec740 Bump ruff from 0.0.272 to 0.0.274 (#212)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.272 to 0.0.274.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.272...v0.0.274)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-21 09:10:20 +00:00
dependabot[bot]
50c1f84f24 Bump importlib-metadata from 6.6.0 to 6.7.0 (#209)
Bumps [importlib-metadata](https://github.com/python/importlib_metadata) from 6.6.0 to 6.7.0.
- [Release notes](https://github.com/python/importlib_metadata/releases)
- [Changelog](https://github.com/python/importlib_metadata/blob/main/CHANGES.rst)
- [Commits](https://github.com/python/importlib_metadata/compare/v6.6.0...v6.7.0)

---
updated-dependencies:
- dependency-name: importlib-metadata
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-19 09:16:02 +00:00
dependabot[bot]
8bfe814c36 Bump tox from 4.6.0 to 4.6.1 (#207)
Bumps [tox](https://github.com/tox-dev/tox) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.6.0...4.6.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-16 09:14:31 +00:00
dependabot[bot]
c90291fccf Bump pyright from 1.1.311 to 1.1.314 (#206)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.311 to 1.1.314.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.311...v1.1.314)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-14 09:21:13 +00:00
dependabot[bot]
c4b0f47642 Bump pytest from 7.3.1 to 7.3.2 (#204)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.1 to 7.3.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.3.1...7.3.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-12 09:15:44 +00:00
dependabot[bot]
37baa6cd0d Bump ruff from 0.0.271 to 0.0.272 (#200)
Bumps [ruff](https://github.com/charliermarsh/ruff) from 0.0.271 to 0.0.272.
- [Release notes](https://github.com/charliermarsh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/charliermarsh/ruff/compare/v0.0.271...v0.0.272)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-08 09:12:43 +00:00
dependabot[bot]
e8cfa24c6e Bump ruff from 0.0.270 to 0.0.271 (#199)
Bumps [ruff](https://github.com/charliermarsh/ruff) from 0.0.270 to 0.0.271.
- [Release notes](https://github.com/charliermarsh/ruff/releases)
- [Changelog](https://github.com/charliermarsh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/charliermarsh/ruff/compare/v0.0.270...v0.0.271)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-07 09:14:17 +00:00
dependabot[bot]
8b99ddfeb1 Bump tox from 4.5.2 to 4.6.0 (#198)
Bumps [tox](https://github.com/tox-dev/tox) from 4.5.2 to 4.6.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.5.2...4.6.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-06 09:11:37 +00:00
dependabot[bot]
dfb747230c Bump fastapi from 0.95.2 to 0.96.0 (#197)
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.95.2 to 0.96.0.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.95.2...0.96.0)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 09:14:32 +00:00
dependabot[bot]
eb84b1457e Bump typing-extensions from 4.6.2 to 4.6.3 (#196)
Bumps [typing-extensions](https://github.com/python/typing_extensions) from 4.6.2 to 4.6.3.
- [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/python/typing_extensions/compare/4.6.2...4.6.3)

---
updated-dependencies:
- dependency-name: typing-extensions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-02 09:12:17 +00:00
dependabot[bot]
069a112e3b Bump pyright from 1.1.308 to 1.1.311 (#195)
Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.308 to 1.1.311.
- [Release notes](https://github.com/RobertCraigie/pyright-python/releases)
- [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.308...v1.1.311)

---
updated-dependencies:
- dependency-name: pyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-31 09:12:07 +00:00
dependabot[bot]
8c67de4b44 Bump coverage from 7.2.6 to 7.2.7 (#193)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.2.6 to 7.2.7.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.2.6...7.2.7)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-30 09:22:23 +00:00
dependabot[bot]
f218c9deb0 Bump tox from 4.5.1 to 4.5.2 (#190)
Bumps [tox](https://github.com/tox-dev/tox) from 4.5.1 to 4.5.2.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.5.1...4.5.2)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-29 09:17:01 +00:00
dependabot[bot]
bcbe201053 Bump typing-extensions from 4.6.1 to 4.6.2 (#187)
Bumps [typing-extensions](https://github.com/python/typing_extensions) from 4.6.1 to 4.6.2.
- [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/python/typing_extensions/compare/4.6.1...4.6.2)

---
updated-dependencies:
- dependency-name: typing-extensions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-26 09:21:34 +00:00
dependabot[bot]
d395a16595 Bump coverage from 7.2.5 to 7.2.6 (#181)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.2.5 to 7.2.6.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.2.5...7.2.6)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-25 09:17:20 +00:00
dependabot[bot]
09e0da20b0 Bump ruff from 0.0.267 to 0.0.270 (#184)
Bumps [ruff](https://github.com/charliermarsh/ruff) from 0.0.267 to 0.0.270.
- [Release notes](https://github.com/charliermarsh/ruff/releases)
- [Changelog](https://github.com/charliermarsh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/charliermarsh/ruff/compare/v0.0.267...v0.0.270)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-25 09:13:17 +00:00
dependabot[bot]
8945ac7a3a Bump typing-extensions from 4.5.0 to 4.6.1 (#180)
Bumps [typing-extensions](https://github.com/python/typing_extensions) from 4.5.0 to 4.6.1.
- [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/python/typing_extensions/compare/4.5.0...4.6.1)

---
updated-dependencies:
- dependency-name: typing-extensions
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-24 09:14:07 +00:00
dependabot[bot]
c1d2dd65fa Bump requests from 2.30.0 to 2.31.0 (#178)
Bumps [requests](https://github.com/psf/requests) from 2.30.0 to 2.31.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.30.0...v2.31.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-23 09:11:18 +00:00
dependabot[bot]
0b66fd7535 Bump httpx from 0.24.0 to 0.24.1 (#174)
Bumps [httpx](https://github.com/encode/httpx) from 0.24.0 to 0.24.1.
- [Release notes](https://github.com/encode/httpx/releases)
- [Changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/httpx/compare/0.24.0...0.24.1)

---
updated-dependencies:
- dependency-name: httpx
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-19 09:13:15 +00:00
Martijn Pieters
fbdaa62e24 💄 Pull version from installation metadata (#172)
The version string in the `__init__` module is needed to support
towncrier, but that does mean there are now two locations for the
project version: pyproject.toml and the `__init__.py` file. Use
`importlib.metadata` to pull the version from the installation metadata.
2023-05-17 17:09:52 +00:00
Martijn Pieters
70d8fef402 Merge pull request #171 from long2ice/towncrier
📣 Start managing the changelog with towncrier
2023-05-17 17:37:51 +01:00
Martijn Pieters
42cb99d4eb 📣 Start managing the changelog with towncrier
This PR includes a workflow that validates that future PRs include
a changelog entry (unless the `skip-changelog` label is present on the
PR, or the PR is a dependabot PR).

Use 'poetry run towncrier create` to create entries for the changelog.
2023-05-17 17:31:03 +01:00
Martijn Pieters
b287f21043 📖 Copy-edit README (#168)
- Update workflow shields to point to new CI/CD pipeline, and link
  all shields to somewhere appropriate.
- Use product names instead of code-markup names.
- Edit for English grammar and style.
- Expand decorator argument table to add defaults
- Add more meaningful `Coder` and key builder examples and expand
  on what the default key builder does.
2023-05-17 11:34:40 +00:00
Martijn Pieters
826e785522 Merge pull request #166 from long2ice/release_wf
📦 Move PyPI release workflow into the main workflow.
2023-05-17 11:38:11 +01:00
Martijn Pieters
282fcc7cea 📦 Move PyPI release workflow into the main workflow.
This ensures that any releases are fully tested before publication.

The workflow first builds the distribution files (sdist, wheel) before
using a deployment environment to publish these to PyPI, using the
GitHub actions OpenID support to authenticate with PyPI.
2023-05-17 11:33:25 +01:00
dependabot[bot]
645cd94ec1 Bump fastapi from 0.95.1 to 0.95.2 (#167)
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.95.1 to 0.95.2.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.95.1...0.95.2)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-17 09:11:54 +00:00
Martijn Pieters
4aa8060faa Merge pull request #163 from long2ice/3777dependabot/pip/main/coverage-7.2.5 2023-05-16 15:07:50 +01:00
dependabot[bot]
189f997228 Bump coverage from 6.5.0 to 7.2.5
Bumps [coverage](https://github.com/nedbat/coveragepy) from 6.5.0 to 7.2.5.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/6.5.0...7.2.5)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-16 13:46:35 +00:00
Martijn Pieters
3e7deea2ba Correct merge metadata step id, loosen version (#165) 2023-05-16 13:31:55 +00:00
Martijn Pieters
452eaedf5b 🤦 put dependabot wf in correct place (#164) 2023-05-16 13:24:45 +00:00
Martijn Pieters
24e1d8d40b Merge pull request #162 from long2ice/5db3dependabot/pip/main/aiobotocore-2.5.0
Bump aiobotocore from 1.4.2 to 2.5.0
2023-05-16 14:21:41 +01:00
dependabot[bot]
621103c2b8 Bump aiobotocore from 1.4.2 to 2.5.0
Bumps [aiobotocore](https://github.com/aio-libs/aiobotocore) from 1.4.2 to 2.5.0.
- [Release notes](https://github.com/aio-libs/aiobotocore/releases)
- [Changelog](https://github.com/aio-libs/aiobotocore/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiobotocore/compare/1.4.2...2.5.0)

---
updated-dependencies:
- dependency-name: aiobotocore
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-16 13:17:50 +00:00
Martijn Pieters
bb12a91b58 Create codeql scanning workflow (#161)
This helps find any security issues in PRs.
2023-05-16 13:14:58 +00:00
Martijn Pieters
4236b350d9 Configure dependabot and associated workflows (#160)
The auto-merge workflow automatically approves dependabot PRs that
update dependencies or github actions where the semver difference is
at most a minor upgrade. These PRs are then auto-merged once the CI
checks have passed.

The labeller workflow adds / removes the auto-merge label to make
auto-merging more visible in issue lists and to make it possible to
filter such PRs. This workflow is unfortunately not triggered
when the auto-approve workflow enables auto-merging due to GH anti-
recursion rules, so the auto-merge workflow adds the label explicitly.
2023-05-16 13:06:11 +00:00
Martijn Pieters
23bb4e9cd4 Add pytest-style linting (#159)
This does mean we need to tell pyright that the init_cache auto-use
fixture is still being used even though it now has a leading underscore.
2023-05-16 12:45:26 +00:00
Martijn Pieters
230c24a4e0 Give tests and examples separate ruff configs (#158)
This allows more fine-grained lint rule adjustments, and allows
the examples to treat fastapi_cache as a 'third party' module, just
like users of the library would use it.
2023-05-16 12:42:12 +00:00
Martijn Pieters
d938bbe4d2 Add flake8-comprehensions linter (#157) 2023-05-16 12:18:53 +00:00
Martijn Pieters
50e3f91a87 Add flake8-bandit linting (#156)
The linter has been used in the past, so most assertions for these were
already there but needed to be updated to use `noqa: S` instead of
`nosec: B` annotations.
2023-05-16 12:11:10 +00:00
Martijn Pieters
cbad93c970 Merge pull request #155 from long2ice/pyupgrade
Add pyupgrade linter
2023-05-16 13:07:56 +01:00
Martijn Pieters
f7db5cceb1 Add pyupgrade linter
This ensures we use the best syntax for the minimal Python version used.
The linter found one issue, fixed with the auto-fixer.
2023-05-16 13:00:18 +01:00
Martijn Pieters
daad4cf9a8 Merge pull request #154 from long2ice/simplify_pyproject.toml
Clean up pyproject.toml
2023-05-16 12:59:16 +01:00
Martijn Pieters
d2947d01b5 Clean up pyproject.toml
- remove usused dependency
- use poetry syntax to specify minimal python versions
- simplify path selections in mypy and pyright
- correct pytest section name
2023-05-16 12:47:47 +01:00
Martijn Pieters
637c825dc8 Merge pull request #153 from long2ice/simplify_wf
CI: tox now takes care of extras and groups
2023-05-16 12:35:22 +01:00
Martijn Pieters
1cf352bc9c CI: tox now takes care of extras and groups
The outer Poetry installation can be simplified as long as tox is part
of the dev group.
2023-05-16 12:29:18 +01:00
Martijn Pieters
f314d84d31 Merge pull request #152 from long2ice/ruff
Switch to ruff to handle linting and formatting
2023-05-16 12:24:02 +01:00
Martijn Pieters
1d9e126037 Switch to ruff to handle linting and formatting
Ruff handles black, flake8 and isort in one package, and is way faster.
The isort rules had not been enforced, so this commit includes a lot
of import resorting changes.

I switched to flake8-bugbear and the standard black-compatible line
length of 80 + 10% (so max 88 characters), so some line reflowing is
included too.

Finally, because bugbear rightly points out that `setattr()` is less
performant, I've switched the `__signature__` assigment back to using
a direct assignment with type ignore comment.
2023-05-16 12:18:24 +01:00
Martijn Pieters
707b4aec95 Merge pull request #151 from long2ice/poetry_core
Use poetry-core as the build system
2023-05-16 11:08:03 +01:00
Martijn Pieters
293a06467a Use poetry-core as the build system
Poetry core is the lighter-weight build system for poetry; this makes
installing the project faster.
2023-05-16 11:03:53 +01:00
Martijn Pieters
7767d2460d Merge pull request #146 from long2ice/tox
Add tox configuration
2023-05-16 10:52:25 +01:00
Martijn Pieters
0e9a8baeb2 Add tox configuration
Tox manages test environments for all supported Python versions, as
well as linting and formatting tools. On GitHub, the test and lint
steps are kept as close as possible to the Makefile equivalents.
2023-05-16 10:49:17 +01:00
Martijn Pieters
2eabc49d24 Merge pull request #150 from long2ice/cache_mypy_cache
CI: cache the mypy cache for faster runs
2023-05-15 17:49:38 +01:00
Martijn Pieters
b5aabaaf59 CI: cache the mypy cache for faster runs 2023-05-15 17:43:49 +01:00
Martijn Pieters
2da4701c1d Merge pull request #148 from long2ice/separate_linting
CI: use a separate step to run linters
2023-05-15 17:33:34 +01:00
Martijn Pieters
31d0b007cd CI: use a separate step to run linters
This makes it easier to separate linter dependencies from older Python
releases.
2023-05-15 17:30:06 +01:00
Martijn Pieters
e57cd59c98 Merge pull request #149 from long2ice/pr_is_not_push
CI: don't run this workflow twice on a PR
2023-05-15 17:25:29 +01:00
Martijn Pieters
4a012c7cae CI: don't run this workflow twice on a PR
If a maintainer pushes to a PR, the workflow push and pull_request events
both trigger. Limit the workflow to the main branch, explicitly.
2023-05-15 17:23:26 +01:00
Martijn Pieters
915f3dd8f2 Add a cache status header to the response
The header name is configurable, and defaults to `X-FastAPI-Cache`,
the value is either `HIT` or `MISS`.

Note that the header is not set at all when the cache is disabled.
2023-05-14 17:03:57 +01:00
Martijn Pieters
29426de95f Refactor decorator, consolidate miss / hit paths
Use just three code paths: uncacheable, cache miss and cache hit. This
makes it much easier to follow what happens for each case. the only
places where the inner function now exits early are when the call is
uncacheable, or when there is a cache hit and the request included a
matching If-Not-Modified header.

- Use a utility function to capture when a request should not use the
  cache
- Use the starlette.status constant for the 'not modified' status for
  code clarity.
- Use `setattr()` for the inner function signature, avoiding the need
  for a type checker override comment.
2023-05-14 17:03:57 +01:00
Martijn Pieters
d10f4af6d6 Import supported backends
This ensures that any syntax issues are caught early (by type checkers
and tests). Backends that are missing dependencies are skipped. By
importing, this exposed an issue where the redis type annotations
raised an exception, which has been fixed by using forward annotations.

To help avoid import dependency hell, the Backend ABC has been moved to
`fastapi_cache.types`. In the process, it has been made an actual ABC.
2023-05-14 17:02:30 +01:00
long2ice
9638d70dfe Merge pull request #133 from mjpieters/correct_json_decode_return
Clean up type annotations
2023-05-12 14:53:40 +08:00
Martijn Pieters
0763cd7b95 Add pyright strict type checking 2023-05-11 12:57:28 +01:00
Martijn Pieters
ad1eae2b4b Set python version for mypy
This lets you catch compatibility issues regardless of the current python version used for development.
2023-05-11 12:57:27 +01:00
Martijn Pieters
eda2a437a4 More type hint compatibility updates 2023-05-11 12:57:27 +01:00
Martijn Pieters
a892a21b92 Run mypy during CI checks 2023-05-11 12:34:08 +01:00
Martijn Pieters
941cd044c7 Full mypy --strict type checking pass 2023-05-11 12:34:08 +01:00
Martijn Pieters
e92604802e Remove non-existing argument to unicorn.run 2023-05-11 12:31:07 +01:00
Martijn Pieters
94a02733c8 Run tests on all supported Python versions. 2023-05-11 12:31:07 +01:00
Martijn Pieters
013be85f97 Typing cleanup
- Compatibility with older Python versions
  - use `Optional` and `Union` instead of `... | None` and `a | b`
  - use `typing_extensions.Protocol` instead of `typing.Protocol`
  - use `typing.Dict`, `typing.List`, etc. instead of the concrete types.

- Fix backend `.get()` annotations; not all were marked as `Optional[str]`
- Don't return anything from `Backend.set()` methods.
- The `Coder.decode_as_type()` type parameter must be a type to be
  compatible with `ModelField(..., type_=...)`.
- Clean up `Optional[]` use, remove where it is not needed.
- Clean up variable use in decorator, keeping the raw cached value
  separate from the return value from the wrapped endpoint.
- Annotate the wrapper as returning either the original type _or_ a
  Response (returning a 304 Not Modified response).
- Clean up small edge-case where `response` could be `None`.
- Correct type annotation on `JsonCoder.decode()` to match `Coder.decode()`.
2023-05-11 12:31:05 +01:00
long2ice
564026e189 Merge pull request #135 from mjpieters/refactor_prefix
Make decorator responsibe for applying the prefix
2023-05-11 09:25:57 +08:00
long2ice
fba7726280 Merge pull request #134 from mjpieters/backend_coder_bytes
Make backends store bytes instead of strings
2023-05-11 09:24:38 +08:00
Martijn Pieters
d9965a45e5 Make decorator responsibe for applying the prefix
The key builder should not have to fetch the prefix separately, as this
makes creating custom key builders that much harder.
2023-05-10 18:59:59 +01:00
Martijn Pieters
23d439f83a Make backends store bytes instead of strings
This is, for the majority of backends, the native format anyway, and so
we save encoding and decoding when using the PickleCodec or if (in future)
a orjson Coder was to be added.

For the JsonCodec, the only thing that changed is the location where the
JSON data is encoded to bytes and decoded back again to a string.
2023-05-10 17:35:15 +01:00
long2ice
5f2fcf3581 Merge pull request #132 from mjpieters/cache_pydantic_fields
Cache pydantic model fields for faster decoding
2023-05-10 21:21:43 +08:00
Martijn Pieters
7c30402907 Cache pydantic model fields for faster decoding
In `timeit` tests, 10.000 calls to `ModelField()` could take up to half
a second on my Macbook Pro M1, depending on the type annotation used.
Given that the method is called for every cache hit, this can really add
up. The number of different return types for endpoints is very much
finite however, so caching is a definite win here.
2023-05-09 17:25:32 +01:00
long2ice
4d67e0c464 Merge pull request #124 from mjpieters/type_refinement
key_builder type; args and kwargs are always given
2023-05-09 18:16:37 +08:00
long2ice
276c7c725f Merge pull request #125 from mjpieters/update_lock
Refresh poetry lock
2023-05-09 18:16:13 +08:00
long2ice
da9a03ede8 Merge pull request #127 from mjpieters/exported_names
Explicitly list what names are exported
2023-05-09 18:15:57 +08:00
long2ice
eece971f0a Merge pull request #128 from mjpieters/namespaced_injection
Inject dependencies using a namespace
2023-05-09 18:15:32 +08:00
Martijn Pieters
e09ede2e4c Inject dependencies using a namespace
Instead of assuming that the Request and Response injected keyword
arguments can be named `request` and `response`, use namespaced
keyword names starting with a double underscore.

By default the parameter names now start with `__fastapi_cache_` and so
are a) clearly marked as internal, and b) highly unlikely to clash with
existing keyword arguments. The prefix is configurable in the unlikely
event that the names would clash in specific cases.
2023-05-09 11:09:29 +01:00
long2ice
2788006b8c Merge pull request #130 from mjpieters/non_get_requests
Fix handling non-GET requests
2023-05-09 18:03:24 +08:00
Martijn Pieters
4cc946eb00 Fix handling non-GET requests
The `request` parameter being passed in was just a hold-over from an
earlier refactoring. Added tests to ensure that this edge case keeps
working.
2023-05-09 10:58:37 +01:00
long2ice
1ded0ed50e Merge pull request #131 from mjpieters/json_decoder_pydantic
Decode cache data to the correct endpoint type
2023-05-09 10:16:17 +08:00
Martijn Pieters
f78a599bbc Decode cache data to the correct endpoint type
Use the return annotation to decode cached data to the correct type.
This follows the same logic FastAPI uses to JSON request bodies.

For the PickleCoder, this is a no-op as pickle already stores type
information in the serialised data.
2023-05-08 16:55:05 +01:00
Martijn Pieters
b1dc05a89a key_builder type; args and kwargs are always given
These arguments are never set to None so don't need to be optional. They
are always a tuple and a dict but can be empty.
2023-04-28 16:50:30 +01:00
Martijn Pieters
416a4ec850 Explicitly list what names are exported
This signals to automated tools what names usually can be imported from
the package, as otherwise you'd get warnings like `"default_key_builder"
is not exported from module "fastapi_cache"`.
2023-04-28 14:40:31 +01:00
Martijn Pieters
3a3964db1b Refresh poetry lock
The older releases in the lock can cause the CI build to fail with
more recent Poetry versions, see python-poetry/poetry#7611. Updating
the lock should remedy this.
2023-04-28 12:27:52 +01:00
long2ice
550ba76df4 Merge pull request #123 from mjpieters/method_signature
Attach updated endpoint signature to inner
2023-04-28 14:29:06 +08:00
long2ice
b26059b654 Merge pull request #122 from mjpieters/type-hinting
Complete type hints
2023-04-28 14:28:49 +08:00
Martijn Pieters
832650347b Attach updated endpoint signature to inner
Not all endpoints accept a __signature__ attribute, nor should the
cache decorator modify the decorated endpoint. Attach the signature
to the returned inner function instead.

While here, refactor the signature updating code, and extract it to
a separate function.
2023-04-27 18:14:59 +01:00
Martijn Pieters
72c42325ab The backend needs an async redis client with a pipeline method
The Abstract* classes lack the pipeline method so are not sufficient.
2023-04-27 16:33:43 +01:00
Martijn Pieters
6af14be049 Provide annotation for the session attribute 2023-04-27 16:32:07 +01:00
Martijn Pieters
9c966286b4 Use complete type hints with all generic parameters filled
This makes the core fastapi_cache pass all strict type checker tests.
2023-04-27 16:31:42 +01:00
Martijn Pieters
a52f6b1406 Simplify key_builder calling
The keybuilder is either returning a string, or a coroutine or other
awaitable. If the latter, await on the return value to get the string.
2023-04-27 16:29:10 +01:00
Martijn Pieters
255f40117b Define keybuilder protocol
This lets others create key builders that are type checked fully.
2023-04-27 16:26:41 +01:00
Martijn Pieters
d4cd787527 JSONResponse.body is UTF-8 bytes and must be decoded 2023-04-27 16:20:12 +01:00
Martijn Pieters
059793d585 Remove a type: ignore comment
GIve the type checker more information about the converters instead.
2023-04-27 16:19:02 +01:00
Martijn Pieters
0d0fe1f0d0 Mark up the class variables as such
There is never an instance of this class, so these are not instance attributes.
2023-04-27 16:15:06 +01:00
Martijn Pieters
32acafa5e0 Correct type hint: namespace is not optional
The namespace argument is positional and will never be `None` so should
not be marked as Optional. It is always a string, and the default is
to pass in an empty string.
2023-04-27 16:11:59 +01:00
long2ice
ee58f979d4 ci: fix workflows 2023-02-15 10:53:24 +08:00
long2ice
38ddd063c3 test: add httpx for test 2023-02-15 10:49:35 +08:00
long2ice
27acce3160 ci: fix poetry 2023-02-15 10:45:19 +08:00
long2ice
d04be274e9 feat: upgrade deps 2023-02-15 10:43:01 +08:00
long2ice
80563fd6e7 Merge pull request #118 from naoki-jarvisml/var_keyword
Support functions with VAR_KEYWORD parameter
2023-02-15 10:30:12 +08:00
Naoki Shima
98cf8a78a1 adding test coverage 2023-02-15 10:35:41 +09:00
Naoki Shima
01c895dbbb Support functions with VAR_KEYWORD parameter
decorating function with **kwargs parameter with @cache causes ValueError.

ValueError: wrong parameter order: variadic keyword parameter before keyword-only parameter

We need to inject request / response parameters before VAR_KEYWORD parameter.
2023-02-09 15:14:20 +09:00
long2ice
e3b08dda2c Merge pull request #114 from hackjammer/feature/redisCluster
Add RedisCluster Support
2023-02-01 15:33:11 +08:00
long2ice
552a7695e8 Update fastapi_cache/decorator.py
Co-authored-by: mkdir700 <56359329+mkdir700@users.noreply.github.com>
2023-02-01 15:33:04 +08:00
hackjammer
ea1ffcd7b4 Add logging to decorator.py on backend failures 2023-01-17 12:15:53 +00:00
hackjammer
e8193b5c22 enabled redis in cluster mode 2023-01-15 21:54:16 +00:00
hackjammer
ab26fad604 passthrough for any type of backend exception 2023-01-15 17:07:37 +00:00
long2ice
7a89f28b54 Merge pull request #112 from schmocker/main
add cache-control and etag to header of fist response
2023-01-15 12:27:56 +08:00
Tobias Schmocker
334b829a80 Merge branch 'master'
# Conflicts:
#	fastapi_cache/decorator.py
2023-01-14 19:11:42 +01:00
long2ice
62ef8bed37 Merge pull request #109 from Mrreadiness/fix/piclke-coder
Fix Piclke Coder
2023-01-11 21:31:02 +08:00
Ivan Moiseev
9a39db7a73 Merge branch 'long2ice:main' into fix/piclke-coder 2023-01-11 16:26:05 +03:00
Ivan Moiseev
e23289fcbf Merge branch 'main' into fix/piclke-coder 2022-12-08 00:23:39 +04:00
Ivan Moiseev
cb9fe5c065 fix: PickleCoder and add tests for it. 2022-11-05 13:45:16 +04:00
Tobias Schmocker
e5250c7f58 remove private from cache-control 2022-02-04 16:41:42 +01:00
Tobias Schmocker
1795c048d1 add cache-control to response after setting the cache 2022-02-04 16:37:18 +01:00
34 changed files with 4015 additions and 1326 deletions

14
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
target-branch: main
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
target-branch: main

118
.github/workflows/ci-cd.yml vendored Normal file
View File

@@ -0,0 +1,118 @@
name: CI/CD
on:
push:
branches:
- main
tags:
- 'v*'
pull_request:
branches:
- main
jobs:
lint:
name: Linter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Poetry
run: pipx install poetry
- name: Setup Python
id: setup-python
uses: actions/setup-python@v5
with:
python-version: '3.x'
cache: poetry
- name: Cache mypy cache
uses: actions/cache@v3
with:
path: .mypy_cache
key: ${{ runner.os }}-mypy-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('poetry.lock') }}
restore-keys: |
${{ runner.os }}-mypy-${{ steps.setup-python.outputs.python-version }}-
${{ runner.os }}-mypy-
- name: Install linting requirements
run: poetry install --no-root
- name: Execute linters
run: make lint
test:
needs:
- lint
strategy:
matrix:
python: ["3.8", "3.9", "3.10", "3.11"]
fail-fast: false
name: "Test on Python ${{ matrix.python }}"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Poetry
run: pipx install poetry
- uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python }}"
cache: poetry
- name: Install testing requirements
run: |
poetry install --no-root
poetry run pip install tox-gh-actions
- name: Execute tests
run: poetry run tox
test-summary:
name: Test matrix status
runs-on: ubuntu-latest
needs: [test]
if: always()
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
build:
name: Build distributions
runs-on: ubuntu-latest
needs: [test-summary]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Poetry
run: pipx install poetry
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
cache: poetry
- name: Build distributions
run:
make build
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: dist
path: dist
publish:
name: Build and publish Python 🐍 distributions 📦 to PyPI
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
needs: [build]
runs-on: ubuntu-latest
environment:
name: pypi
# The URL is used in the 'successfully deployed' message as the link
# for the 'View deployment' button.
url: https://pypi.org/p/fastapi-cache2
permissions:
id-token: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: dist
path: dist
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

View File

@@ -1,15 +0,0 @@
name: ci
on: [ push, pull_request ]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- uses: abatilo/actions-poetry@v2.1.6
- name: Config poetry
run: poetry config experimental.new-installer false
- name: CI
run: make ci

34
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '29 15 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:python"

View File

@@ -0,0 +1,35 @@
name: Dependabot auto-merge
on: pull_request_target
permissions:
pull-requests: write
contents: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: github.event.pull_request.user.login == 'dependabot[bot]'
steps:
- name: Dependabot metadata
id: dependabot-metadata
uses: dependabot/fetch-metadata@v1
- uses: actions/checkout@v3
- name: Approve PR
# only auto-approve direct deps that are minor or patch updates
# dependency type is indirect, direct:development or direct:production
# version-update is semver-major, semver-minor or semver-patch
if: |
steps.dependabot-metadata.outputs.dependency-type != 'indirect'
&& steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major'
run: |
if [ "$(gh pr view "$PR_URL" --json reviewDecision -q .reviewDecision)" != "APPROVED" ]; then
gh pr review --approve "$PR_URL"
else
echo "PR already approved, skipping additional approvals to minimize emails/notification noise."
fi
gh pr merge --auto --squash "$PR_URL"
gh pr edit "$PR_URL" --add-label "auto-merge"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

30
.github/workflows/labeller.yaml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Labeller
on:
pull_request_target:
types:
- auto_merge_disabled
- auto_merge_enabled
permissions: {}
jobs:
add_remove_labels:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Add auto-merge label
if: github.event.action == 'auto_merge_enabled'
run: gh pr edit "$PR_URL" --add-label "auto-merge"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Remove auto-merge label
if: github.event.action == 'auto_merge_disabled'
run: gh pr edit "$PR_URL" --remove-label "auto-merge"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

View File

@@ -1,23 +0,0 @@
name: pypi
on:
release:
types:
- created
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
with:
python-version: '3.x'
- uses: abatilo/actions-poetry@v2.1.3
- name: Config poetry
run: poetry config experimental.new-installer false
- name: Build dists
run: make build
- name: Pypi Publish
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.pypi_password }}

54
.github/workflows/towncrier.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: Changelog
on:
pull_request:
types:
- opened
- reopened
- synchronize
- labeled
- unlabeled
branches:
- main
jobs:
towncrier:
name: Towncrier
runs-on: ubuntu-latest
# skip if this is a bot or the PR has the skip-changelog label
# note that the towncrier check command can recognize a release PR, by looking
# for changes to the CHANGELOG.md file.
if: |
!(
github.event.pull_request.user.login == 'dependabot[bot]'
|| contains(github.event.pull_request.labels.*.name, 'skip-changelog')
)
steps:
- uses: actions/checkout@v2
- name: Install Poetry
run: pipx install poetry
- name: Setup Python
id: setup-python
uses: actions/setup-python@v5
with:
python-version: '3.x'
cache: poetry
- name: Install development tools
run: poetry install --no-root --only=dev
- name: Check for a towncrier fragment
run: |
# fetch enough commits from this merge commit to the base sha to ensure
# towncrier can inspect what changed
while [[ -z $(git merge-base $PR_BASE_SHA HEAD 2> /dev/null) ]]; do
git fetch --quiet --deepen=50 --no-tags --no-recurse-submodules origin $PR_BASE_SHA HEAD
done
if poetry run towncrier check --compare-with $PR_BASE_SHA; then
gh pr edit "$PR_URL" --add-label "changelog-provided"
else
gh pr edit "$PR_URL" --remove-label "changelog-provided"
exit 1
fi
env:
PR_BASE_SHA: ${{github.event.pull_request.base.sha}}
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

3
.gitignore vendored
View File

@@ -28,6 +28,9 @@ share/python-wheels/
*.egg *.egg
MANIFEST MANIFEST
# Ruff
.ruff_cache/
# PyInstaller # PyInstaller
# Usually these files are written by a python script from a template # Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it. # before PyInstaller builds the exe, so as to inject date/other infos into it.

View File

@@ -1,7 +1,21 @@
# ChangeLog # ChangeLog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the changes for the upcoming release can be found in <https://github.com/long2ice/fastapi-cache/tree/main/changelog.d/>.
<!-- towncrier release notes start -->
## 0.2 ## 0.2
### 0.2.1
- Fix picklecoder
- Fix connection failure transparency and add logging
- Add Cache-Control and ETag on first response
- Support Async RedisCluster client from redis-py
### 0.2.0 ### 0.2.0
- Make `request` and `response` optional. - Make `request` and `response` optional.

View File

@@ -1,27 +1,28 @@
checkfiles = fastapi_cache/ examples/ tests/
py_warn = PYTHONDEVMODE=1
up: up:
@poetry update @poetry update
deps: deps:
@poetry install --no-root -E all @poetry install --no-root --with=linting --all-extras
style: deps format: deps
@poetry run isort -src $(checkfiles) @poetry run tox run -e format
@poetry run black $(checkfiles)
check: deps lint: deps
@poetry run black $(checkfiles) || (echo "Please run 'make style' to auto-fix style issues" && false) @poetry run tox run -e lint
@poetry run flake8 $(checkfiles)
test: deps test: deps
$(py_warn) poetry run pytest @poetry run tox
test-parallel: deps
@poetry run tox run-parallel
build: clean deps build: clean deps
@poetry build @poetry build
@poetry run tox run -e lint_distributions
clean: clean:
@rm -rf ./dist @rm -rf ./dist
ci: check test # aliases
check: lint
style: format

164
README.md
View File

@@ -1,27 +1,26 @@
# fastapi-cache # fastapi-cache
![pypi](https://img.shields.io/pypi/v/fastapi-cache2.svg?style=flat) [![pypi](https://img.shields.io/pypi/v/fastapi-cache2.svg?style=flat)](https://pypi.org/p/fastapi-cache2)
![license](https://img.shields.io/github/license/long2ice/fastapi-cache) [![license](https://img.shields.io/github/license/long2ice/fastapi-cache)](https://github.com/long2ice/fastapi-cache/blob/main/LICENSE)
![workflows](https://github.com/long2ice/fastapi-cache/workflows/pypi/badge.svg) [![CI/CD](https://github.com/long2ice/fastapi-cache/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/long2ice/fastapi-cache/actions/workflows/ci-cd.yml)
![workflows](https://github.com/long2ice/fastapi-cache/workflows/ci/badge.svg)
## Introduction ## Introduction
`fastapi-cache` is a tool to cache fastapi response and function result, with backends support `redis`, `memcache`, `fastapi-cache` is a tool to cache FastAPI endpoint and function results, with
and `dynamodb`. backends supporting Redis, Memcached, and Amazon DynamoDB.
## Features ## Features
- Support `redis`, `memcache`, `dynamodb`, and `in-memory` backends. - Supports `redis`, `memcache`, `dynamodb`, and `in-memory` backends.
- Easily integration with `fastapi`. - Easy integration with [FastAPI](https://fastapi.tiangolo.com/).
- Support http cache like `ETag` and `Cache-Control`. - Support for HTTP cache headers like `ETag` and `Cache-Control`, as well as conditional `If-Match-None` requests.
## Requirements ## Requirements
- `asyncio` environment. - FastAPI
- `redis` if use `RedisBackend`. - `redis` when using `RedisBackend`.
- `memcache` if use `MemcacheBackend`. - `memcache` when using `MemcacheBackend`.
- `aiobotocore` if use `DynamoBackend`. - `aiobotocore` when using `DynamoBackend`.
## Install ## Install
@@ -52,6 +51,9 @@ or
### Quick Start ### Quick Start
```python ```python
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from fastapi import FastAPI from fastapi import FastAPI
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import Response from starlette.responses import Response
@@ -62,7 +64,15 @@ from fastapi_cache.decorator import cache
from redis import asyncio as aioredis from redis import asyncio as aioredis
app = FastAPI()
@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
redis = aioredis.from_url("redis://localhost")
FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
yield
app = FastAPI(lifespan=lifespan)
@cache() @cache()
@@ -74,32 +84,63 @@ async def get_cache():
@cache(expire=60) @cache(expire=60)
async def index(): async def index():
return dict(hello="world") return dict(hello="world")
@app.on_event("startup")
async def startup():
redis = aioredis.from_url("redis://localhost", encoding="utf8", decode_responses=True)
FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
``` ```
### Initialization ### Initialization
Firstly you must call `FastAPICache.init` on startup event of `fastapi`, there are some global config you can pass in. First you must call `FastAPICache.init` during startup FastAPI startup; this is where you set global configuration.
### Use `cache` decorator ### Use the `@cache` decorator
If you want cache `fastapi` response transparently, you can use `cache` as decorator between router decorator and view If you want cache a FastAPI response transparently, you can use the `@cache`
function and must pass `request` as param of view function. decorator between the router decorator and the view function.
Parameter | type, description Parameter | type | default | description
------------ | ------------- ------------ | ----| --------- | --------
expire | int, states a caching time in seconds `expire` | `int` | | sets the caching time in seconds
namespace | str, namespace to use to store certain cache items `namespace` | `str` | `""` | namespace to use to store certain cache items
coder | which coder to use, e.g. JsonCoder `coder` | `Coder` | `JsonCoder` | which coder to use, e.g. `JsonCoder`
key_builder | which key builder to use, default to builtin `key_builder` | `KeyBuilder` callable | `default_key_builder` | which key builder to use
`injected_dependency_namespace` | `str` | `__fastapi_cache` | prefix for injected dependency keywords.
`cache_status_header` | `str` | `X-FastAPI-Cache` | Name for the header on the response indicating if the request was served from cache; either `HIT` or `MISS`.
You can also use `cache` as decorator like other cache tools to cache common function result. You can also use the `@cache` decorator on regular functions to cache their result.
### Injected Request and Response dependencies
The `cache` decorator injects dependencies for the `Request` and `Response`
objects, so that it can add cache control headers to the outgoing response, and
return a 304 Not Modified response when the incoming request has a matching
`If-Non-Match` header. This only happens if the decorated endpoint doesn't already
list these dependencies already.
The keyword arguments for these extra dependencies are named
`__fastapi_cache_request` and `__fastapi_cache_response` to minimize collisions.
Use the `injected_dependency_namespace` argument to `@cache` to change the
prefix used if those names would clash anyway.
### Supported data types
When using the (default) `JsonCoder`, the cache can store any data type that FastAPI can convert to JSON, including Pydantic models and dataclasses,
_provided_ that your endpoint has a correct return type annotation. An
annotation is not needed if the return type is a standard JSON-supported Python
type such as a dictionary or a list.
E.g. for an endpoint that returns a Pydantic model named `SomeModel`, the return annotation is used to ensure that the cached result is converted back to the correct class:
```python
from .models import SomeModel, create_some_model
@app.get("/foo")
@cache(expire=60)
async def foo() -> SomeModel:
return create_some_model()
```
It is not sufficient to configure a response model in the route decorator; the cache needs to know what the method itself returns. If no return type decorator is given, the primitive JSON type is returned instead.
For broader type support, use the `fastapi_cache.coder.PickleCoder` or implement a custom coder (see below).
### Custom coder ### Custom coder
@@ -107,41 +148,78 @@ By default use `JsonCoder`, you can write custom coder to encode and decode cach
inherit `fastapi_cache.coder.Coder`. inherit `fastapi_cache.coder.Coder`.
```python ```python
from typing import Any
import orjson
from fastapi.encoders import jsonable_encoder
from fastapi_cache import Coder
class ORJsonCoder(Coder):
@classmethod
def encode(cls, value: Any) -> bytes:
return orjson.dumps(
value,
default=jsonable_encoder,
option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY,
)
@classmethod
def decode(cls, value: bytes) -> Any:
return orjson.loads(value)
@app.get("/") @app.get("/")
@cache(expire=60, coder=JsonCoder) @cache(expire=60, coder=ORJsonCoder)
async def index(): async def index():
return dict(hello="world") return dict(hello="world")
``` ```
### Custom key builder ### Custom key builder
By default use builtin key builder, if you need, you can override this and pass in `cache` or `FastAPICache.init` to By default the `default_key_builder` builtin key builder is used; this creates a
take effect globally. cache key from the function module and name, and the positional and keyword
arguments converted to their `repr()` representations, encoded as a MD5 hash.
You can provide your own by passing a key builder in to `@cache()`, or to
`FastAPICache.init()` to apply globally.
For example, if you wanted to use the request method, URL and query string as a cache key instead of the function identifier you could use:
```python ```python
def my_key_builder( def request_key_builder(
func, func,
namespace: Optional[str] = "", namespace: str = "",
*,
request: Request = None, request: Request = None,
response: Response = None, response: Response = None,
*args, *args,
**kwargs, **kwargs,
): ):
prefix = FastAPICache.get_prefix() return ":".join([
cache_key = f"{prefix}:{namespace}:{func.__module__}:{func.__name__}:{args}:{kwargs}" namespace,
return cache_key request.method.lower(),
request.url.path,
repr(sorted(request.query_params.items()))
])
@app.get("/") @app.get("/")
@cache(expire=60, coder=JsonCoder, key_builder=my_key_builder) @cache(expire=60, key_builder=request_key_builder)
async def index(): async def index():
return dict(hello="world") return dict(hello="world")
``` ```
## Backend notes
### InMemoryBackend ### InMemoryBackend
`InMemoryBackend` store cache data in memory and use lazy delete, which mean if you don't access it after cached, it The `InMemoryBackend` stores cache data in memory and only deletes when an
will not delete automatically. expired key is accessed. This means that if you don't access a function after
data has been cached, the data will not be removed automatically.
### RedisBackend
When using the Redis backend, please make sure you pass in a redis client that does [_not_ decode responses][redis-decode] (`decode_responses` **must** be `False`, which is the default). Cached data is stored as `bytes` (binary), decoding these in the Redis client would break caching.
[redis-decode]: https://redis-py.readthedocs.io/en/latest/examples/connection_examples.html#by-default-Redis-return-binary-responses,-to-decode-them-use-decode_responses=True
## Tests and coverage ## Tests and coverage

View File

@@ -0,0 +1 @@
Use `importlib.metadata` to include project version string as `fastapi_cache.__version__`.

View File

@@ -0,0 +1,15 @@
{% if sections[""] -%}
{% for category, val in definitions.items() if category in sections[""] -%}
### {{ definitions[category]['name'] }}
{% for text, values in sections[""][category].items() %}
- {{ text }} {{ values|join(', ') }}
{% endfor %}
{% endfor -%}
{% else -%}
No significant changes.
{% endif -%}

View File

@@ -1,14 +1,25 @@
# pyright: reportGeneralTypeIssues=false
from contextlib import asynccontextmanager
from typing import AsyncIterator, Dict, Optional
import pendulum import pendulum
import uvicorn import uvicorn
from fastapi import FastAPI from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from fastapi_cache import FastAPICache from fastapi_cache import FastAPICache
from fastapi_cache.backends.inmemory import InMemoryBackend from fastapi_cache.backends.inmemory import InMemoryBackend
from fastapi_cache.decorator import cache from fastapi_cache.decorator import cache
from pydantic import BaseModel
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
app = FastAPI()
@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
FastAPICache.init(InMemoryBackend())
yield
app = FastAPI(lifespan=lifespan)
ret = 0 ret = 0
@@ -23,7 +34,7 @@ async def get_ret():
@app.get("/") @app.get("/")
@cache(namespace="test", expire=10) @cache(namespace="test", expire=10)
async def index(): async def index():
return dict(ret=await get_ret()) return {"ret": await get_ret()}
@app.get("/clear") @app.get("/clear")
@@ -43,8 +54,18 @@ async def get_datetime(request: Request, response: Response):
return {"now": pendulum.now()} return {"now": pendulum.now()}
@app.get("/sync-me")
@cache(namespace="test") @cache(namespace="test")
async def func_kwargs(*unused_args, **kwargs):
return kwargs
@app.get("/kwargs")
async def get_kwargs(name: str):
return await func_kwargs(name, name=name)
@app.get("/sync-me")
@cache(namespace="test") # pyright: ignore[reportArgumentType]
def sync_me(): def sync_me():
# as per the fastapi docs, this sync function is wrapped in a thread, # as per the fastapi docs, this sync function is wrapped in a thread,
# thereby converted to async. fastapi-cache does the same. # thereby converted to async. fastapi-cache does the same.
@@ -57,10 +78,63 @@ async def cache_response_obj():
return JSONResponse({"a": 1}) return JSONResponse({"a": 1})
@app.on_event("startup") class SomeClass:
async def startup(): def __init__(self, value):
FastAPICache.init(InMemoryBackend()) self.value = value
async def handler_method(self):
return self.value
# register an instance method as a handler
instance = SomeClass(17)
app.get("/method")(cache(namespace="test")(instance.handler_method))
# cache a Pydantic model instance; the return type annotation is required in this case
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
@app.get("/pydantic_instance")
@cache(namespace="test", expire=5)
async def pydantic_instance() -> Item:
return Item(name="Something", description="An instance of a Pydantic model", price=10.5)
put_ret = 0
@app.put("/uncached_put")
@cache(namespace="test", expire=5)
async def uncached_put():
global put_ret
put_ret = put_ret + 1
return {"value": put_ret}
put_ret2 = 0
@app.get("/cached_put")
@cache(namespace="test", expire=5)
async def cached_put():
global put_ret2
put_ret2 = put_ret2 + 1
return {"value": put_ret2}
@app.get("/namespaced_injection")
@cache(namespace="test", expire=5, injected_dependency_namespace="monty_python") # pyright: ignore[reportArgumentType]
def namespaced_injection(
__fastapi_cache_request: int = 42, __fastapi_cache_response: int = 17
) -> Dict[str, int]:
return {
"__fastapi_cache_request": __fastapi_cache_request,
"__fastapi_cache_response": __fastapi_cache_response,
}
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run("main:app", debug=True, reload=True) uvicorn.run("main:app", reload=True)

3
examples/pyproject.toml Normal file
View File

@@ -0,0 +1,3 @@
[tool.ruff]
extend = "../pyproject.toml"

View File

@@ -1,22 +1,34 @@
# pyright: reportGeneralTypeIssues=false
import time import time
from contextlib import asynccontextmanager
from typing import AsyncIterator
import pendulum import pendulum
import redis.asyncio as redis
import uvicorn import uvicorn
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from redis.asyncio.connection import ConnectionPool
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from fastapi_cache import FastAPICache from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend from fastapi_cache.backends.redis import RedisBackend
from fastapi_cache.coder import PickleCoder from fastapi_cache.coder import PickleCoder
from fastapi_cache.decorator import cache from fastapi_cache.decorator import cache
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
app = FastAPI() import redis.asyncio as redis
from redis.asyncio.connection import ConnectionPool
@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
pool = ConnectionPool.from_url(url="redis://redis")
r = redis.Redis(connection_pool=pool)
FastAPICache.init(RedisBackend(r), prefix="fastapi-cache")
yield
app = FastAPI(lifespan=lifespan)
app.mount( app.mount(
path="/static", path="/static",
@@ -37,7 +49,7 @@ async def get_ret():
@app.get("/") @app.get("/")
@cache(namespace="test", expire=10) @cache(namespace="test", expire=10)
async def index(): async def index():
return dict(ret=await get_ret()) return {"ret": await get_ret()}
@app.get("/clear") @app.get("/clear")
@@ -54,10 +66,10 @@ async def get_data(request: Request, response: Response):
# Note: This function MUST be sync to demonstrate fastapi-cache's correct handling, # Note: This function MUST be sync to demonstrate fastapi-cache's correct handling,
# i.e. running cached sync functions in threadpool just like FastAPI itself! # i.e. running cached sync functions in threadpool just like FastAPI itself!
@app.get("/blocking") @app.get("/blocking")
@cache(namespace="test", expire=10) @cache(namespace="test", expire=10) # pyright: ignore[reportArgumentType]
def blocking(): def blocking():
time.sleep(2) time.sleep(2)
return dict(ret=42) return {"ret": 42}
@app.get("/datetime") @app.get("/datetime")
@@ -79,12 +91,5 @@ async def cache_response_obj():
return JSONResponse({"a": 1}) return JSONResponse({"a": 1})
@app.on_event("startup")
async def startup():
pool = ConnectionPool.from_url(url="redis://redis")
r = redis.Redis(connection_pool=pool)
FastAPICache.init(RedisBackend(r), prefix="fastapi-cache")
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run("main:app", debug=True, reload=True) uvicorn.run("main:app", reload=True)

View File

@@ -1,18 +1,30 @@
from typing import Callable, Optional, Type from importlib.metadata import version
from typing import ClassVar, Optional, Type
from fastapi_cache.backends import Backend
from fastapi_cache.coder import Coder, JsonCoder from fastapi_cache.coder import Coder, JsonCoder
from fastapi_cache.key_builder import default_key_builder from fastapi_cache.key_builder import default_key_builder
from fastapi_cache.types import Backend, KeyBuilder
__version__ = version("fastapi-cache2") # pyright: ignore[reportUnknownVariableType]
__all__ = [
"Backend",
"Coder",
"FastAPICache",
"JsonCoder",
"KeyBuilder",
"default_key_builder",
]
class FastAPICache: class FastAPICache:
_backend: Optional[Backend] = None _backend: ClassVar[Optional[Backend]] = None
_prefix: Optional[str] = None _prefix: ClassVar[Optional[str]] = None
_expire: Optional[int] = None _expire: ClassVar[Optional[int]] = None
_init = False _init: ClassVar[bool] = False
_coder: Optional[Type[Coder]] = None _coder: ClassVar[Optional[Type[Coder]]] = None
_key_builder: Optional[Callable] = None _key_builder: ClassVar[Optional[KeyBuilder]] = None
_enable = True _cache_status_header: ClassVar[Optional[str]] = None
_enable: ClassVar[bool] = True
@classmethod @classmethod
def init( def init(
@@ -21,7 +33,8 @@ class FastAPICache:
prefix: str = "", prefix: str = "",
expire: Optional[int] = None, expire: Optional[int] = None,
coder: Type[Coder] = JsonCoder, coder: Type[Coder] = JsonCoder,
key_builder: Callable = default_key_builder, key_builder: KeyBuilder = default_key_builder,
cache_status_header: str = "X-FastAPI-Cache",
enable: bool = True, enable: bool = True,
) -> None: ) -> None:
if cls._init: if cls._init:
@@ -32,6 +45,7 @@ class FastAPICache:
cls._expire = expire cls._expire = expire
cls._coder = coder cls._coder = coder
cls._key_builder = key_builder cls._key_builder = key_builder
cls._cache_status_header = cache_status_header
cls._enable = enable cls._enable = enable
@classmethod @classmethod
@@ -42,16 +56,17 @@ class FastAPICache:
cls._expire = None cls._expire = None
cls._coder = None cls._coder = None
cls._key_builder = None cls._key_builder = None
cls._cache_status_header = None
cls._enable = True cls._enable = True
@classmethod @classmethod
def get_backend(cls) -> Backend: def get_backend(cls) -> Backend:
assert cls._backend, "You must call init first!" # nosec: B101 assert cls._backend, "You must call init first!" # noqa: S101
return cls._backend return cls._backend
@classmethod @classmethod
def get_prefix(cls) -> str: def get_prefix(cls) -> str:
assert cls._prefix is not None, "You must call init first!" # nosec: B101 assert cls._prefix is not None, "You must call init first!" # noqa: S101
return cls._prefix return cls._prefix
@classmethod @classmethod
@@ -60,20 +75,29 @@ class FastAPICache:
@classmethod @classmethod
def get_coder(cls) -> Type[Coder]: def get_coder(cls) -> Type[Coder]:
assert cls._coder, "You must call init first!" # nosec: B101 assert cls._coder, "You must call init first!" # noqa: S101
return cls._coder return cls._coder
@classmethod @classmethod
def get_key_builder(cls) -> Callable: def get_key_builder(cls) -> KeyBuilder:
assert cls._key_builder, "You must call init first!" # nosec: B101 assert cls._key_builder, "You must call init first!" # noqa: S101
return cls._key_builder return cls._key_builder
@classmethod
def get_cache_status_header(cls) -> str:
assert cls._cache_status_header, "You must call init first!" # noqa: S101
return cls._cache_status_header
@classmethod @classmethod
def get_enable(cls) -> bool: def get_enable(cls) -> bool:
return cls._enable return cls._enable
@classmethod @classmethod
async def clear(cls, namespace: Optional[str] = None, key: Optional[str] = None) -> int: async def clear(
assert cls._backend and cls._prefix is not None, "You must call init first!" # nosec: B101 cls, namespace: Optional[str] = None, key: Optional[str] = None
) -> int:
assert ( # noqa: S101
cls._backend and cls._prefix is not None
), "You must call init first!"
namespace = cls._prefix + (":" + namespace if namespace else "") namespace = cls._prefix + (":" + namespace if namespace else "")
return await cls._backend.clear(namespace, key) return await cls._backend.clear(namespace, key)

View File

@@ -1,20 +1,28 @@
import abc from fastapi_cache.backends import inmemory
from typing import Optional, Tuple from fastapi_cache.types import Backend
__all__ = ["Backend", "inmemory"]
class Backend: # import each backend in turn and add to __all__. This syntax
@abc.abstractmethod # is explicitly supported by type checkers, while more dynamic
async def get_with_ttl(self, key: str) -> Tuple[int, Optional[str]]: # syntax would not be recognised.
raise NotImplementedError try:
from fastapi_cache.backends import dynamodb
except ImportError:
pass
else:
__all__ += ["dynamodb"]
@abc.abstractmethod try:
async def get(self, key: str) -> Optional[str]: from fastapi_cache.backends import memcached
raise NotImplementedError except ImportError:
pass
else:
__all__ += ["memcached"]
@abc.abstractmethod try:
async def set(self, key: str, value: str, expire: Optional[int] = None) -> None: from fastapi_cache.backends import redis
raise NotImplementedError except ImportError:
pass
@abc.abstractmethod else:
async def clear(self, namespace: Optional[str] = None, key: Optional[str] = None) -> int: __all__ += ["redis"]
raise NotImplementedError

View File

@@ -1,10 +1,15 @@
import datetime import datetime
from typing import Optional, Tuple from typing import TYPE_CHECKING, Optional, Tuple
from aiobotocore.client import AioBaseClient from aiobotocore.client import AioBaseClient
from aiobotocore.session import get_session from aiobotocore.session import AioSession, get_session
from fastapi_cache.backends import Backend from fastapi_cache.types import Backend
if TYPE_CHECKING:
from types_aiobotocore_dynamodb import DynamoDBClient
else:
DynamoDBClient = AioBaseClient
class DynamoBackend(Backend): class DynamoBackend(Backend):
@@ -25,25 +30,29 @@ class DynamoBackend(Backend):
>> FastAPICache.init(dynamodb) >> FastAPICache.init(dynamodb)
""" """
client: DynamoDBClient
session: AioSession
table_name: str
region: Optional[str]
def __init__(self, table_name: str, region: Optional[str] = None) -> None: def __init__(self, table_name: str, region: Optional[str] = None) -> None:
self.session = get_session() self.session: AioSession = get_session()
self.client: Optional[AioBaseClient] = None # Needs async init
self.table_name = table_name self.table_name = table_name
self.region = region self.region = region
async def init(self) -> None: async def init(self) -> None:
self.client = await self.session.create_client( self.client = await self.session.create_client( # pyright: ignore[reportUnknownMemberType]
"dynamodb", region_name=self.region "dynamodb", region_name=self.region
).__aenter__() ).__aenter__()
async def close(self) -> None: async def close(self) -> None:
self.client = await self.client.__aexit__(None, None, None) self.client = await self.client.__aexit__(None, None, None)
async def get_with_ttl(self, key: str) -> Tuple[int, str]: async def get_with_ttl(self, key: str) -> Tuple[int, Optional[bytes]]:
response = await self.client.get_item(TableName=self.table_name, Key={"key": {"S": key}}) response = await self.client.get_item(TableName=self.table_name, Key={"key": {"S": key}})
if "Item" in response: if "Item" in response:
value = response["Item"].get("value", {}).get("S") value = response["Item"].get("value", {}).get("B")
ttl = response["Item"].get("ttl", {}).get("N") ttl = response["Item"].get("ttl", {}).get("N")
if not ttl: if not ttl:
@@ -56,12 +65,13 @@ class DynamoBackend(Backend):
return 0, None return 0, None
async def get(self, key: str) -> str: async def get(self, key: str) -> Optional[bytes]:
response = await self.client.get_item(TableName=self.table_name, Key={"key": {"S": key}}) response = await self.client.get_item(TableName=self.table_name, Key={"key": {"S": key}})
if "Item" in response: if "Item" in response:
return response["Item"].get("value", {}).get("S") return response["Item"].get("value", {}).get("B")
return None
async def set(self, key: str, value: str, expire: Optional[int] = None) -> None: async def set(self, key: str, value: bytes, expire: Optional[int] = None) -> None:
ttl = ( ttl = (
{ {
"ttl": { "ttl": {
@@ -83,7 +93,7 @@ class DynamoBackend(Backend):
Item={ Item={
**{ **{
"key": {"S": key}, "key": {"S": key},
"value": {"S": value}, "value": {"B": value},
}, },
**ttl, **ttl,
}, },

View File

@@ -3,12 +3,12 @@ from asyncio import Lock
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, Optional, Tuple from typing import Dict, Optional, Tuple
from fastapi_cache.backends import Backend from fastapi_cache.types import Backend
@dataclass @dataclass
class Value: class Value:
data: str data: bytes
ttl_ts: int ttl_ts: int
@@ -29,21 +29,21 @@ class InMemoryBackend(Backend):
return v return v
return None return None
async def get_with_ttl(self, key: str) -> Tuple[int, Optional[str]]: async def get_with_ttl(self, key: str) -> Tuple[int, Optional[bytes]]:
async with self._lock: async with self._lock:
v = self._get(key) v = self._get(key)
if v: if v:
return v.ttl_ts - self._now, v.data return v.ttl_ts - self._now, v.data
return 0, None return 0, None
async def get(self, key: str) -> Optional[str]: async def get(self, key: str) -> Optional[bytes]:
async with self._lock: async with self._lock:
v = self._get(key) v = self._get(key)
if v: if v:
return v.data return v.data
return None return None
async def set(self, key: str, value: str, expire: Optional[int] = None) -> None: async def set(self, key: str, value: bytes, expire: Optional[int] = None) -> None:
async with self._lock: async with self._lock:
self._store[key] = Value(value, self._now + (expire or 0)) self._store[key] = Value(value, self._now + (expire or 0))

View File

@@ -2,21 +2,21 @@ from typing import Optional, Tuple
from aiomcache import Client from aiomcache import Client
from fastapi_cache.backends import Backend from fastapi_cache.types import Backend
class MemcachedBackend(Backend): class MemcachedBackend(Backend):
def __init__(self, mcache: Client): def __init__(self, mcache: Client):
self.mcache = mcache self.mcache = mcache
async def get_with_ttl(self, key: str) -> Tuple[int, Optional[str]]: async def get_with_ttl(self, key: str) -> Tuple[int, Optional[bytes]]:
return 3600, await self.mcache.get(key.encode()) return 3600, await self.get(key)
async def get(self, key: str) -> Optional[str]: async def get(self, key: str) -> Optional[bytes]:
return await self.mcache.get(key, key.encode()) return await self.mcache.get(key.encode())
async def set(self, key: str, value: str, expire: Optional[int] = None) -> None: async def set(self, key: str, value: bytes, expire: Optional[int] = None) -> None:
await self.mcache.set(key.encode(), value.encode(), exptime=expire or 0) await self.mcache.set(key.encode(), value, exptime=expire or 0)
async def clear(self, namespace: Optional[str] = None, key: Optional[str] = None) -> int: async def clear(self, namespace: Optional[str] = None, key: Optional[str] = None) -> int:
raise NotImplementedError raise NotImplementedError

View File

@@ -1,28 +1,30 @@
from typing import Optional, Tuple from typing import Optional, Tuple, Union
from redis.asyncio.client import Redis from redis.asyncio.client import Redis
from redis.asyncio.cluster import RedisCluster
from fastapi_cache.backends import Backend from fastapi_cache.types import Backend
class RedisBackend(Backend): class RedisBackend(Backend):
def __init__(self, redis: Redis): def __init__(self, redis: Union["Redis[bytes]", "RedisCluster[bytes]"]):
self.redis = redis self.redis = redis
self.is_cluster: bool = isinstance(redis, RedisCluster)
async def get_with_ttl(self, key: str) -> Tuple[int, str]: async def get_with_ttl(self, key: str) -> Tuple[int, Optional[bytes]]:
async with self.redis.pipeline(transaction=True) as pipe: async with self.redis.pipeline(transaction=not self.is_cluster) as pipe:
return await (pipe.ttl(key).get(key).execute()) return await pipe.ttl(key).get(key).execute() # type: ignore[union-attr,no-any-return]
async def get(self, key: str) -> Optional[str]: async def get(self, key: str) -> Optional[bytes]:
return await self.redis.get(key) return await self.redis.get(key) # type: ignore[union-attr]
async def set(self, key: str, value: str, expire: Optional[int] = None) -> None: async def set(self, key: str, value: bytes, expire: Optional[int] = None) -> None:
return await self.redis.set(key, value, ex=expire) await self.redis.set(key, value, ex=expire) # type: ignore[union-attr]
async def clear(self, namespace: Optional[str] = None, key: Optional[str] = None) -> int: async def clear(self, namespace: Optional[str] = None, key: Optional[str] = None) -> int:
if namespace: if namespace:
lua = f"for i, name in ipairs(redis.call('KEYS', '{namespace}:*')) do redis.call('DEL', name); end" lua = f"for i, name in ipairs(redis.call('KEYS', '{namespace}:*')) do redis.call('DEL', name); end"
return await self.redis.eval(lua, numkeys=0) return await self.redis.eval(lua, numkeys=0) # type: ignore[union-attr,no-any-return]
elif key: elif key:
return await self.redis.delete(key) return await self.redis.delete(key) # type: ignore[union-attr]
return 0 return 0

View File

@@ -2,14 +2,33 @@ import datetime
import json import json
import pickle # nosec:B403 import pickle # nosec:B403
from decimal import Decimal from decimal import Decimal
from typing import Any from typing import (
Any,
Callable,
ClassVar,
Dict,
Optional,
TypeVar,
Union,
overload,
)
import pendulum import pendulum
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from starlette.templating import _TemplateResponse as TemplateResponse from starlette.templating import (
_TemplateResponse as TemplateResponse, # pyright: ignore[reportPrivateUsage]
)
CONVERTERS = {
class ModelField:
pass
_T = TypeVar("_T", bound=type)
CONVERTERS: Dict[str, Callable[[str], Any]] = {
# Pendulum 3.0.0 adds parse to __all__, at which point these ignores can be removed
"date": lambda x: pendulum.parse(x, exact=True), "date": lambda x: pendulum.parse(x, exact=True),
"datetime": lambda x: pendulum.parse(x, exact=True), "datetime": lambda x: pendulum.parse(x, exact=True),
"decimal": Decimal, "decimal": Decimal,
@@ -17,15 +36,15 @@ CONVERTERS = {
class JsonEncoder(json.JSONEncoder): class JsonEncoder(json.JSONEncoder):
def default(self, obj: Any) -> Any: def default(self, o: Any) -> Any:
if isinstance(obj, datetime.datetime): if isinstance(o, datetime.datetime):
return {"val": str(obj), "_spec_type": "datetime"} return {"val": str(o), "_spec_type": "datetime"}
elif isinstance(obj, datetime.date): elif isinstance(o, datetime.date):
return {"val": str(obj), "_spec_type": "date"} return {"val": str(o), "_spec_type": "date"}
elif isinstance(obj, Decimal): elif isinstance(o, Decimal):
return {"val": str(obj), "_spec_type": "decimal"} return {"val": str(o), "_spec_type": "decimal"}
else: else:
return jsonable_encoder(obj) return jsonable_encoder(o)
def object_hook(obj: Any) -> Any: def object_hook(obj: Any) -> Any:
@@ -34,40 +53,77 @@ def object_hook(obj: Any) -> Any:
return obj return obj
if _spec_type in CONVERTERS: if _spec_type in CONVERTERS:
return CONVERTERS[_spec_type](obj["val"]) # type: ignore return CONVERTERS[_spec_type](obj["val"])
else: else:
raise TypeError("Unknown {}".format(_spec_type)) raise TypeError(f"Unknown {_spec_type}")
class Coder: class Coder:
@classmethod @classmethod
def encode(cls, value: Any) -> str: def encode(cls, value: Any) -> bytes:
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
def decode(cls, value: Any) -> Any: def decode(cls, value: bytes) -> Any:
raise NotImplementedError raise NotImplementedError
# (Shared) cache for endpoint return types to Pydantic model fields.
# Note that subclasses share this cache! If a subclass overrides the
# decode_as_type method and then stores a different kind of field for a
# given type, do make sure that the subclass provides its own class
# attribute for this cache.
_type_field_cache: ClassVar[Dict[Any, ModelField]] = {}
@overload
@classmethod
def decode_as_type(cls, value: bytes, *, type_: _T) -> _T:
...
@overload
@classmethod
def decode_as_type(cls, value: bytes, *, type_: None) -> Any:
...
@classmethod
def decode_as_type(cls, value: bytes, *, type_: Optional[_T]) -> Union[_T, Any]:
"""Decode value to the specific given type
The default implementation uses the Pydantic model system to convert the value.
"""
result = cls.decode(value)
return result
class JsonCoder(Coder): class JsonCoder(Coder):
@classmethod @classmethod
def encode(cls, value: Any) -> str: def encode(cls, value: Any) -> Any:
if isinstance(value, JSONResponse): if isinstance(value, JSONResponse):
return value.body return value.body
return json.dumps(value, cls=JsonEncoder) return json.dumps(value, cls=JsonEncoder).encode()
@classmethod @classmethod
def decode(cls, value: Any) -> str: def decode(cls, value: bytes) -> Any:
return json.loads(value, object_hook=object_hook) # explicitly decode from UTF-8 bytes first, as otherwise
# json.loads() will first have to detect the correct UTF-
# encoding used.
return json.loads(value.decode(), object_hook=object_hook)
class PickleCoder(Coder): class PickleCoder(Coder):
@classmethod @classmethod
def encode(cls, value: Any) -> str: def encode(cls, value: Any) -> bytes:
if isinstance(value, TemplateResponse): if isinstance(value, TemplateResponse):
value = value.body value = value.body
return str(pickle.dumps(value)) return pickle.dumps(value)
@classmethod @classmethod
def decode(cls, value: Any) -> Any: def decode(cls, value: bytes) -> Any:
return pickle.loads(bytes(value)) # nosec:B403,B301 return pickle.loads(value) # noqa: S301
@classmethod
def decode_as_type(cls, value: bytes, *, type_: Optional[_T]) -> Any:
# Pickle already produces the correct type on decoding, no point
# in paying an extra performance penalty for pydantic to discover
# the same.
return cls.decode(value)

View File

@@ -1,7 +1,17 @@
import inspect import logging
import sys import sys
from functools import wraps from functools import wraps
from typing import Any, Awaitable, Callable, Optional, Type, TypeVar from inspect import Parameter, Signature, isawaitable, iscoroutinefunction
from typing import (
Awaitable,
Callable,
List,
Optional,
Type,
TypeVar,
Union,
cast,
)
if sys.version_info >= (3, 10): if sys.version_info >= (3, 10):
from typing import ParamSpec from typing import ParamSpec
@@ -9,24 +19,81 @@ else:
from typing_extensions import ParamSpec from typing_extensions import ParamSpec
from fastapi.concurrency import run_in_threadpool from fastapi.concurrency import run_in_threadpool
from fastapi.dependencies.utils import (
get_typed_return_annotation,
get_typed_signature,
)
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import Response from starlette.responses import Response
from starlette.status import HTTP_304_NOT_MODIFIED
from fastapi_cache import FastAPICache from fastapi_cache import FastAPICache
from fastapi_cache.coder import Coder from fastapi_cache.coder import Coder
from fastapi_cache.types import KeyBuilder
logger: logging.Logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
P = ParamSpec("P") P = ParamSpec("P")
R = TypeVar("R") R = TypeVar("R")
def _augment_signature(signature: Signature, *extra: Parameter) -> Signature:
if not extra:
return signature
parameters = list(signature.parameters.values())
variadic_keyword_params: List[Parameter] = []
while parameters and parameters[-1].kind is Parameter.VAR_KEYWORD:
variadic_keyword_params.append(parameters.pop())
return signature.replace(parameters=[*parameters, *extra, *variadic_keyword_params])
def _locate_param(
sig: Signature, dep: Parameter, to_inject: List[Parameter]
) -> Parameter:
"""Locate an existing parameter in the decorated endpoint
If not found, returns the injectable parameter, and adds it to the to_inject list.
"""
param = next(
(p for p in sig.parameters.values() if p.annotation is dep.annotation), None
)
if param is None:
to_inject.append(dep)
param = dep
return param
def _uncacheable(request: Optional[Request]) -> bool:
"""Determine if this request should not be cached
Returns true if:
- Caching has been disabled globally
- This is not a GET request
- The request has a Cache-Control header with a value of "no-store"
"""
if not FastAPICache.get_enable():
return True
if request is None:
return False
if request.method != "GET":
return True
return request.headers.get("Cache-Control") == "no-store"
def cache( def cache(
expire: Optional[int] = None, expire: Optional[int] = None,
coder: Optional[Type[Coder]] = None, coder: Optional[Type[Coder]] = None,
key_builder: Optional[Callable[..., Any]] = None, key_builder: Optional[KeyBuilder] = None,
namespace: Optional[str] = "", namespace: str = "",
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]: injected_dependency_namespace: str = "__fastapi_cache",
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[Union[R, Response]]]]:
""" """
cache all function cache all function
:param injected_dependency_namespace:
:param namespace: :param namespace:
:param expire: :param expire:
:param coder: :param coder:
@@ -35,53 +102,42 @@ def cache(
:return: :return:
""" """
def wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]: injected_request = Parameter(
signature = inspect.signature(func) name=f"{injected_dependency_namespace}_request",
request_param = next(
(param for param in signature.parameters.values() if param.annotation is Request),
None,
)
response_param = next(
(param for param in signature.parameters.values() if param.annotation is Response),
None,
)
parameters = [*signature.parameters.values()]
if not request_param:
parameters.append(
inspect.Parameter(
name="request",
annotation=Request, annotation=Request,
kind=inspect.Parameter.KEYWORD_ONLY, kind=Parameter.KEYWORD_ONLY,
),
) )
if not response_param: injected_response = Parameter(
parameters.append( name=f"{injected_dependency_namespace}_response",
inspect.Parameter(
name="response",
annotation=Response, annotation=Response,
kind=inspect.Parameter.KEYWORD_ONLY, kind=Parameter.KEYWORD_ONLY,
),
) )
if parameters:
signature = signature.replace(parameters=parameters) def wrapper(
func.__signature__ = signature func: Callable[P, Awaitable[R]]
) -> Callable[P, Awaitable[Union[R, Response]]]:
# get_typed_signature ensures that any forward references are resolved first
wrapped_signature = get_typed_signature(func)
to_inject: List[Parameter] = []
request_param = _locate_param(wrapped_signature, injected_request, to_inject)
response_param = _locate_param(wrapped_signature, injected_response, to_inject)
return_type = get_typed_return_annotation(func)
@wraps(func) @wraps(func)
async def inner(*args: P.args, **kwargs: P.kwargs) -> R: async def inner(*args: P.args, **kwargs: P.kwargs) -> Union[R, Response]:
nonlocal coder nonlocal coder
nonlocal expire nonlocal expire
nonlocal key_builder nonlocal key_builder
async def ensure_async_func(*args: P.args, **kwargs: P.kwargs) -> R: async def ensure_async_func(*args: P.args, **kwargs: P.kwargs) -> R:
"""Run cached sync functions in thread pool just like FastAPI.""" """Run cached sync functions in thread pool just like FastAPI."""
# if the wrapped function does NOT have request or response in its function signature, # if the wrapped function does NOT have request or response in
# make sure we don't pass them in as keyword arguments # its function signature, make sure we don't pass them in as
if not request_param: # keyword arguments
kwargs.pop("request", None) kwargs.pop(injected_request.name, None)
if not response_param: kwargs.pop(injected_response.name, None)
kwargs.pop("response", None)
if inspect.iscoroutinefunction(func): if iscoroutinefunction(func):
# async, return as is. # async, return as is.
# unintuitively, we have to await once here, so that caller # unintuitively, we have to await once here, so that caller
# does not have to await twice. See # does not have to await twice. See
@@ -90,76 +146,85 @@ def cache(
else: else:
# sync, wrap in thread and return async # sync, wrap in thread and return async
# see above why we have to await even although caller also awaits. # see above why we have to await even although caller also awaits.
return await run_in_threadpool(func, *args, **kwargs) return await run_in_threadpool(func, *args, **kwargs) # type: ignore[arg-type]
copy_kwargs = kwargs.copy() copy_kwargs = kwargs.copy()
request: Optional[Request] = copy_kwargs.pop("request", None) request: Optional[Request] = copy_kwargs.pop(request_param.name, None) # type: ignore[assignment]
response: Optional[Response] = copy_kwargs.pop("response", None) response: Optional[Response] = copy_kwargs.pop(response_param.name, None) # type: ignore[assignment]
if (
request and request.headers.get("Cache-Control") in ("no-store", "no-cache") if _uncacheable(request):
) or not FastAPICache.get_enable():
return await ensure_async_func(*args, **kwargs) return await ensure_async_func(*args, **kwargs)
prefix = FastAPICache.get_prefix()
coder = coder or FastAPICache.get_coder() coder = coder or FastAPICache.get_coder()
expire = expire or FastAPICache.get_expire() expire = expire or FastAPICache.get_expire()
key_builder = key_builder or FastAPICache.get_key_builder() key_builder = key_builder or FastAPICache.get_key_builder()
backend = FastAPICache.get_backend() backend = FastAPICache.get_backend()
cache_status_header = FastAPICache.get_cache_status_header()
if inspect.iscoroutinefunction(key_builder):
cache_key = await key_builder(
func,
namespace,
request=request,
response=response,
args=args,
kwargs=copy_kwargs,
)
else:
cache_key = key_builder( cache_key = key_builder(
func, func,
namespace, f"{prefix}:{namespace}",
request=request, request=request,
response=response, response=response,
args=args, args=args,
kwargs=copy_kwargs, kwargs=copy_kwargs,
) )
if isawaitable(cache_key):
cache_key = await cache_key
assert isinstance(cache_key, str) # noqa: S101 # assertion is a type guard
try: try:
ttl, ret = await backend.get_with_ttl(cache_key) ttl, cached = await backend.get_with_ttl(cache_key)
except ConnectionError: except Exception:
ttl, ret = 0, None logger.warning(
if not request: f"Error retrieving cache key '{cache_key}' from backend:",
if ret is not None: exc_info=True,
return coder.decode(ret)
ret = await ensure_async_func(*args, **kwargs)
try:
await backend.set(
cache_key, coder.encode(ret), expire or FastAPICache.get_expire()
) )
except ConnectionError: ttl, cached = 0, None
pass
return ret
if request.method != "GET": if cached is None or (request is not None and request.headers.get("Cache-Control") == "no-cache") : # cache miss
return await ensure_async_func(request, *args, **kwargs) result = await ensure_async_func(*args, **kwargs)
to_cache = coder.encode(result)
if_none_match = request.headers.get("if-none-match")
if ret is not None:
if response:
response.headers["Cache-Control"] = f"max-age={ttl}"
etag = f"W/{hash(ret)}"
if if_none_match == etag:
response.status_code = 304
return response
response.headers["ETag"] = etag
return coder.decode(ret)
ret = await ensure_async_func(*args, **kwargs)
try: try:
await backend.set(cache_key, coder.encode(ret), expire or FastAPICache.get_expire()) await backend.set(cache_key, to_cache, expire)
except ConnectionError: except Exception:
pass logger.warning(
return ret f"Error setting cache key '{cache_key}' in backend:",
exc_info=True,
)
if response:
response.headers.update(
{
"Cache-Control": f"max-age={expire}",
"ETag": f"W/{hash(to_cache)}",
cache_status_header: "MISS",
}
)
else: # cache hit
if response:
etag = f"W/{hash(cached)}"
response.headers.update(
{
"Cache-Control": f"max-age={ttl}",
"ETag": etag,
cache_status_header: "HIT",
}
)
if_none_match = request and request.headers.get("if-none-match")
if if_none_match == etag:
response.status_code = HTTP_304_NOT_MODIFIED
return response
result = cast(R, coder.decode_as_type(cached, type_=return_type))
return result
inner.__signature__ = _augment_signature(wrapped_signature, *to_inject) # type: ignore[attr-defined]
return inner return inner

View File

@@ -1,25 +1,20 @@
import hashlib import hashlib
from typing import Callable, Optional from typing import Any, Callable, Dict, Optional, Tuple
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import Response from starlette.responses import Response
def default_key_builder( def default_key_builder(
func: Callable, func: Callable[..., Any],
namespace: Optional[str] = "", namespace: str = "",
*,
request: Optional[Request] = None, request: Optional[Request] = None,
response: Optional[Response] = None, response: Optional[Response] = None,
args: Optional[tuple] = None, args: Tuple[Any, ...],
kwargs: Optional[dict] = None, kwargs: Dict[str, Any],
) -> str: ) -> str:
from fastapi_cache import FastAPICache cache_key = hashlib.md5( # noqa: S324
prefix = f"{FastAPICache.get_prefix()}:{namespace}:"
cache_key = (
prefix
+ hashlib.md5( # nosec:B303
f"{func.__module__}:{func.__name__}:{args}:{kwargs}".encode() f"{func.__module__}:{func.__name__}:{args}:{kwargs}".encode()
).hexdigest() ).hexdigest()
) return f"{namespace}:{cache_key}"
return cache_key

40
fastapi_cache/types.py Normal file
View File

@@ -0,0 +1,40 @@
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Tuple, Union
from starlette.requests import Request
from starlette.responses import Response
from typing_extensions import Protocol
_Func = Callable[..., Any]
class KeyBuilder(Protocol):
def __call__(
self,
__function: _Func,
__namespace: str = ...,
*,
request: Optional[Request] = ...,
response: Optional[Response] = ...,
args: Tuple[Any, ...],
kwargs: Dict[str, Any],
) -> Union[Awaitable[str], str]:
...
class Backend(abc.ABC):
@abc.abstractmethod
async def get_with_ttl(self, key: str) -> Tuple[int, Optional[bytes]]:
raise NotImplementedError
@abc.abstractmethod
async def get(self, key: str) -> Optional[bytes]:
raise NotImplementedError
@abc.abstractmethod
async def set(self, key: str, value: bytes, expire: Optional[int] = None) -> None:
raise NotImplementedError
@abc.abstractmethod
async def clear(self, namespace: Optional[str] = None, key: Optional[str] = None) -> int:
raise NotImplementedError

3721
poetry.lock generated

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "fastapi-cache2" name = "fastapi-cache2"
version = "0.2.0" version = "0.2.2"
description = "Cache for FastAPI" description = "Cache for FastAPI"
authors = ["long2ice <long2ice@gmail.com>"] authors = ["long2ice <long2ice@gmail.com>"]
license = "Apache-2.0" license = "Apache-2.0"
@@ -15,27 +15,39 @@ packages = [
include = ["LICENSE", "README.md"] include = ["LICENSE", "README.md"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.7" python = "^3.8"
fastapi = "*" fastapi = "*"
uvicorn = "*" uvicorn = "*"
redis = { version = "^4.2.0rc1", optional = true } typing-extensions = { version = ">=4.1.0" }
aiomcache = { version = "*", optional = true } importlib-metadata = { version = "^6.6.0", python = "<3.8" }
pendulum = "*" pendulum = "^3.0.0"
aiobotocore = { version = "^1.4.1", optional = true } aiomcache = { version = "^0.8.2", optional = true }
typing-extensions = { version = ">=4.1.0", markers = "python_version < \"3.10\"" } aiobotocore = {version = "^2.13.1", optional = true}
aiohttp = { version = ">=3.8.3", markers = "python_version >= \"3.11\"" } redis = {version = "^5.0.8", extras = ["redis"]}
[tool.poetry.dev-dependencies] [tool.poetry.group.linting]
flake8 = "*" optional = true
isort = "*"
black = "*" [tool.poetry.group.linting.dependencies]
mypy = { version = "^1.2.0", python = "^3.10" }
pyright = { version = "^1.1.373", python = "^3.10" }
types-aiobotocore = { extras = ["dynamodb"], version = "^2.5.0.post2", python = "^3.10" }
types-redis = { version = "^4.5.4.2", python = "^3.10" }
ruff = { version = ">=0.0.267,<0.1.2", python = "^3.10" }
[tool.poetry.group.dev.dependencies]
pytest = "*" pytest = "*"
requests = "*" requests = "*"
coverage = "^6.5.0" coverage = ">=6.5,<8.0"
httpx = "*"
tox = "^4.5.1"
towncrier = "^22.12.0"
[build-system] [tool.poetry.group.distributing]
requires = ["poetry>=0.12"] optional = true
build-backend = "poetry.masonry.api"
[tool.poetry.group.distributing.dependencies]
twine = { version = "^4.0.2", python = "^3.10" }
[tool.poetry.extras] [tool.poetry.extras]
redis = ["redis"] redis = ["redis"]
@@ -43,6 +55,61 @@ memcache = ["aiomcache"]
dynamodb = ["aiobotocore"] dynamodb = ["aiobotocore"]
all = ["redis", "aiomcache", "aiobotocore"] all = ["redis", "aiomcache", "aiobotocore"]
[tool.black] [tool.mypy]
line-length = 100 files = ["."]
target-version = ['py36', 'py37', 'py38', 'py39'] python_version = "3.8"
# equivalent of --strict
warn_unused_configs = true
disallow_any_generics = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_return_any = true
no_implicit_reexport = true
strict_equality = true
extra_checks = true
[[tool.mypy.overrides]]
module = "examples.*.main"
ignore_errors = true
[tool.towncrier]
directory = "changelog.d"
filename = "CHANGELOG.md"
package = "fastapi_cache"
start_string = "<!-- towncrier release notes start -->\n"
underlines = ["", "", ""]
template = "changelog.d/changelog_template.jinja"
title_format = "## [{version}](https://github.com/long2ice/fastapi-cache/tree/{version}) - {project_date}"
issue_format = "[#{issue}](https://github.com/long2ice/fastapi-cache/issues/{issue})"
[tool.pyright]
strict = ["fastapi_cache", "tests"]
pythonVersion = "3.8"
[tool.pytest.ini_options]
addopts = "-p no:warnings"
[tool.ruff]
ignore = ["E501"]
line-length = 80
select = [
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"E", # pycodestyle errors
"F", # pyflakes
"I", # isort
"S", # flake8-bandit
"W", # pycodestyle warnings
"UP", # pyupgrade
]
target-version = "py38"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,5 +0,0 @@
[flake8]
ignore = E501,W503
[tool:pytest]
addopts = -p no:warnings

10
tests/pyproject.toml Normal file
View File

@@ -0,0 +1,10 @@
[tool.ruff]
extend = "../pyproject.toml"
extend-select = [
"PT", # flake8-pytest-style
]
ignore = ["S101"]
[tool.ruff.isort]
known-first-party = ["examples", "fastapi_cache"]

66
tests/test_codecs.py Normal file
View File

@@ -0,0 +1,66 @@
from dataclasses import dataclass
from typing import Any, Optional, Tuple, Type
import pytest
from pydantic import BaseModel, ValidationError
from fastapi_cache.coder import JsonCoder, PickleCoder
@dataclass
class DCItem:
name: str
price: float
description: Optional[str] = None
tax: Optional[float] = None
class PDItem(BaseModel):
name: str
price: float
description: Optional[str] = None
tax: Optional[float] = None
@pytest.mark.parametrize(
"value",
[
1,
"some_string",
(1, 2),
[1, 2, 3],
{"some_key": 1, "other_key": 2},
DCItem(name="foo", price=42.0, description="some dataclass item", tax=0.2),
PDItem(name="foo", price=42.0, description="some pydantic item", tax=0.2),
],
)
def test_pickle_coder(value: Any) -> None:
encoded_value = PickleCoder.encode(value)
assert isinstance(encoded_value, bytes)
decoded_value = PickleCoder.decode(encoded_value)
assert decoded_value == value
@pytest.mark.parametrize(
("value", "return_type"),
[
(1, None),
("some_string", None),
((1, 2), Tuple[int, int]),
([1, 2, 3], None),
({"some_key": 1, "other_key": 2}, None),
(DCItem(name="foo", price=42.0, description="some dataclass item", tax=0.2), DCItem),
(PDItem(name="foo", price=42.0, description="some pydantic item", tax=0.2), PDItem),
],
)
def test_json_coder(value: Any, return_type: Type[Any]) -> None:
encoded_value = JsonCoder.encode(value)
assert isinstance(encoded_value, bytes)
decoded_value = JsonCoder.decode_as_type(encoded_value, type_=return_type)
assert decoded_value == value
def test_json_coder_validation_error() -> None:
invalid = b'{"name": "incomplete"}'
with pytest.raises(ValidationError):
JsonCoder.decode_as_type(invalid, type_=PDItem)

View File

@@ -1,5 +1,5 @@
import time import time
from typing import Generator from typing import Any, Generator
import pendulum import pendulum
import pytest import pytest
@@ -11,7 +11,7 @@ from fastapi_cache.backends.inmemory import InMemoryBackend
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init_cache() -> Generator: def _init_cache() -> Generator[Any, Any, None]: # pyright: ignore[reportUnusedFunction]
FastAPICache.init(InMemoryBackend()) FastAPICache.init(InMemoryBackend())
yield yield
FastAPICache.reset() FastAPICache.reset()
@@ -20,36 +20,41 @@ def init_cache() -> Generator:
def test_datetime() -> None: def test_datetime() -> None:
with TestClient(app) as client: with TestClient(app) as client:
response = client.get("/datetime") response = client.get("/datetime")
assert response.headers.get("X-FastAPI-Cache") == "MISS"
now = response.json().get("now") now = response.json().get("now")
now_ = pendulum.now().replace(microsecond=0) now_ = pendulum.now()
assert pendulum.parse(now).replace(microsecond=0) == now_ assert pendulum.parse(now) == now_
response = client.get("/datetime") response = client.get("/datetime")
assert response.headers.get("X-FastAPI-Cache") == "HIT"
now = response.json().get("now") now = response.json().get("now")
assert pendulum.parse(now).replace(microsecond=0) == now_ assert pendulum.parse(now) == now_
time.sleep(3) time.sleep(3)
response = client.get("/datetime") response = client.get("/datetime")
now = response.json().get("now") now = response.json().get("now")
now = pendulum.parse(now).replace(microsecond=0) assert response.headers.get("X-FastAPI-Cache") == "MISS"
now = pendulum.parse(now)
assert now != now_ assert now != now_
assert now == pendulum.now().replace(microsecond=0) assert now == pendulum.now()
def test_date() -> None: def test_date() -> None:
"""Test path function without request or response arguments.""" """Test path function without request or response arguments."""
with TestClient(app) as client: with TestClient(app) as client:
response = client.get("/date") response = client.get("/date")
assert response.headers.get("X-FastAPI-Cache") == "MISS"
assert pendulum.parse(response.json()) == pendulum.today() assert pendulum.parse(response.json()) == pendulum.today()
# do it again to test cache # do it again to test cache
response = client.get("/date") response = client.get("/date")
assert response.headers.get("X-FastAPI-Cache") == "HIT"
assert pendulum.parse(response.json()) == pendulum.today() assert pendulum.parse(response.json()) == pendulum.today()
# now test with cache disabled, as that's a separate code path # now test with cache disabled, as that's a separate code path
FastAPICache._enable = False FastAPICache._enable = False # pyright: ignore[reportPrivateUsage]
response = client.get("/date") response = client.get("/date")
assert "X-FastAPI-Cache" not in response.headers
assert pendulum.parse(response.json()) == pendulum.today() assert pendulum.parse(response.json()) == pendulum.today()
FastAPICache._enable = True FastAPICache._enable = True # pyright: ignore[reportPrivateUsage]
def test_sync() -> None: def test_sync() -> None:
@@ -67,3 +72,66 @@ def test_cache_response_obj() -> None:
assert get_cache_response.json() == {"a": 1} assert get_cache_response.json() == {"a": 1}
assert get_cache_response.headers.get("cache-control") assert get_cache_response.headers.get("cache-control")
assert get_cache_response.headers.get("etag") assert get_cache_response.headers.get("etag")
def test_kwargs() -> None:
with TestClient(app) as client:
name = "Jon"
response = client.get("/kwargs", params={"name": name})
assert "X-FastAPI-Cache" not in response.headers
assert response.json() == {"name": name}
def test_method() -> None:
with TestClient(app) as client:
response = client.get("/method")
assert response.json() == 17
def test_pydantic_model() -> None:
with TestClient(app) as client:
r1 = client.get("/pydantic_instance")
assert r1.headers.get("X-FastAPI-Cache") == "MISS"
r2 = client.get("/pydantic_instance")
assert r2.headers.get("X-FastAPI-Cache") == "HIT"
assert r1.json() == r2.json()
def test_non_get() -> None:
with TestClient(app) as client:
response = client.put("/cached_put")
assert "X-FastAPI-Cache" not in response.headers
assert response.json() == {"value": 1}
response = client.put("/cached_put")
assert "X-FastAPI-Cache" not in response.headers
assert response.json() == {"value": 2}
def test_alternate_injected_namespace() -> None:
with TestClient(app) as client:
response = client.get("/namespaced_injection")
assert response.headers.get("X-FastAPI-Cache") == "MISS"
assert response.json() == {"__fastapi_cache_request": 42, "__fastapi_cache_response": 17}
def test_cache_control() -> None:
with TestClient(app) as client:
response = client.get("/cached_put")
assert response.json() == {"value": 1}
# HIT
response = client.get("/cached_put")
assert response.json() == {"value": 1}
# no-cache
response = client.get("/cached_put", headers={"Cache-Control": "no-cache"})
assert response.json() == {"value": 2}
response = client.get("/cached_put")
assert response.json() == {"value": 2}
# no-store
response = client.get("/cached_put", headers={"Cache-Control": "no-store"})
assert response.json() == {"value": 3}
response = client.get("/cached_put")
assert response.json() == {"value": 2}

51
tox.ini Normal file
View File

@@ -0,0 +1,51 @@
[tox]
env_list = py38,py39,py310,py311
minversion = 4.5.1
[gh-actions]
# Map Github Actions Python version to environment factors
# Requires tox-gh-actions 3.x is installed in the GitHub action
python =
3.8: py38
3.9: py39
3.10: py310
3.11: py311
[testenv]
description = Run the tests with pytest
package = wheel
extras = all
set_env =
# trick poetry into adopting the tox virtualenv
POETRY_VIRTUALENVS_PATH = {[tox]work_dir}
allowlist_externals = poetry
commands_pre =
poetry install --no-root --sync --all-extras
commands =
python -X dev -m pytest {tty:--color=yes} {posargs}
[testenv:lint]
description = Run the linters
skip_install = true
commands_pre =
poetry install --no-root --with=linting --sync --all-extras
commands =
ruff check --show-source .
mypy
pyright
[testenv:format]
description = Format the code
skip_install = true
commands_pre =
poetry install --no-root --sync --with=linting
commands =
ruff check --fix .
[testenv:lint_distributions]
description = Lint distribution files with Twine
skip_install = true
commands_pre =
poetry install --no-root --sync --only=distributing
commands =
twine check dist/*