mirror of
https://codeberg.org/Freedium-cfd/web.git
synced 2026-03-11 09:04:37 +00:00
feat(services): integrate pre-commit hooks and enhance Medium API configuration
This commit is contained in:
parent
411095ceb0
commit
76bb8b3c1d
20 changed files with 757 additions and 19 deletions
15
freedium-library/.pre-commit-config.yaml
Normal file
15
freedium-library/.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: debug-statements
|
||||
- id: check-toml
|
||||
- id: check-json
|
||||
- id: check-xml
|
||||
- id: check-added-large-files
|
||||
- id: check-ast
|
||||
- id: check-merge-conflict
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
groups = ["default", "dev"]
|
||||
strategy = ["inherit_metadata"]
|
||||
lock_version = "4.5.0"
|
||||
content_hash = "sha256:cbe7c4d9cd8848458b3af09be1a95005fbc4adf178dd7a96f83810a4b45bff66"
|
||||
content_hash = "sha256:f33cda53180165f833ddded2b624db8f9a24af2e825c9e137db33c5813de495d"
|
||||
|
||||
[[metadata.targets]]
|
||||
requires_python = "==3.12.*"
|
||||
|
|
@ -38,6 +38,17 @@ files = [
|
|||
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfgv"
|
||||
version = "3.4.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Validate configuration and produce human readable error messages."
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
|
||||
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.7"
|
||||
|
|
@ -65,6 +76,29 @@ files = [
|
|||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deprecation"
|
||||
version = "2.1.0"
|
||||
summary = "A library to handle automated deprecations"
|
||||
groups = ["default"]
|
||||
dependencies = [
|
||||
"packaging",
|
||||
]
|
||||
files = [
|
||||
{file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"},
|
||||
{file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.3.9"
|
||||
summary = "Distribution utilities"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
|
||||
{file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "execnet"
|
||||
version = "2.1.1"
|
||||
|
|
@ -91,6 +125,17 @@ files = [
|
|||
{file = "faker-30.8.1.tar.gz", hash = "sha256:93e8b70813f76d05d98951154681180cb795cfbcff3eced7680d963bcc0da2a9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.16.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "A platform independent file lock."
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
|
||||
{file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
|
|
@ -138,6 +183,17 @@ files = [
|
|||
{file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.6.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "File identification library for Python"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"},
|
||||
{file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
|
|
@ -257,17 +313,86 @@ files = [
|
|||
{file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.9.1"
|
||||
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
summary = "Node.js virtual environment builder"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
|
||||
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "orjson"
|
||||
version = "3.10.11"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
||||
groups = ["default", "dev"]
|
||||
files = [
|
||||
{file = "orjson-3.10.11-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:360a4e2c0943da7c21505e47cf6bd725588962ff1d739b99b14e2f7f3545ba51"},
|
||||
{file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:496e2cb45de21c369079ef2d662670a4892c81573bcc143c4205cae98282ba97"},
|
||||
{file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7dfa8db55c9792d53c5952900c6a919cfa377b4f4534c7a786484a6a4a350c19"},
|
||||
{file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51f3382415747e0dbda9dade6f1e1a01a9d37f630d8c9049a8ed0e385b7a90c0"},
|
||||
{file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f35a1b9f50a219f470e0e497ca30b285c9f34948d3c8160d5ad3a755d9299433"},
|
||||
{file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f3b7c5803138e67028dde33450e054c87e0703afbe730c105f1fcd873496d5"},
|
||||
{file = "orjson-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f91d9eb554310472bd09f5347950b24442600594c2edc1421403d7610a0998fd"},
|
||||
{file = "orjson-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dfbb2d460a855c9744bbc8e36f9c3a997c4b27d842f3d5559ed54326e6911f9b"},
|
||||
{file = "orjson-3.10.11-cp312-none-win32.whl", hash = "sha256:d4a62c49c506d4d73f59514986cadebb7e8d186ad510c518f439176cf8d5359d"},
|
||||
{file = "orjson-3.10.11-cp312-none-win_amd64.whl", hash = "sha256:f1eec3421a558ff7a9b010a6c7effcfa0ade65327a71bb9b02a1c3b77a247284"},
|
||||
{file = "orjson-3.10.11.tar.gz", hash = "sha256:e35b6d730de6384d5b2dab5fd23f0d76fae8bbc8c353c2f78210aa5fa4beb3ef"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Core utilities for Python packages"
|
||||
groups = ["dev"]
|
||||
groups = ["default", "dev"]
|
||||
files = [
|
||||
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
||||
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pendulum"
|
||||
version = "3.0.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Python datetimes made easy"
|
||||
groups = ["default"]
|
||||
dependencies = [
|
||||
"backports-zoneinfo>=0.2.1; python_version < \"3.9\"",
|
||||
"importlib-resources>=5.9.0; python_version < \"3.9\"",
|
||||
"python-dateutil>=2.6",
|
||||
"time-machine>=2.6.0; implementation_name != \"pypy\"",
|
||||
"tzdata>=2020.1",
|
||||
]
|
||||
files = [
|
||||
{file = "pendulum-3.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:409e64e41418c49f973d43a28afe5df1df4f1dd87c41c7c90f1a63f61ae0f1f7"},
|
||||
{file = "pendulum-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a38ad2121c5ec7c4c190c7334e789c3b4624798859156b138fcc4d92295835dc"},
|
||||
{file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fde4d0b2024b9785f66b7f30ed59281bd60d63d9213cda0eb0910ead777f6d37"},
|
||||
{file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2c5675769fb6d4c11238132962939b960fcb365436b6d623c5864287faa319"},
|
||||
{file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8af95e03e066826f0f4c65811cbee1b3123d4a45a1c3a2b4fc23c4b0dff893b5"},
|
||||
{file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2165a8f33cb15e06c67070b8afc87a62b85c5a273e3aaa6bc9d15c93a4920d6f"},
|
||||
{file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ad5e65b874b5e56bd942546ea7ba9dd1d6a25121db1c517700f1c9de91b28518"},
|
||||
{file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17fe4b2c844bbf5f0ece69cfd959fa02957c61317b2161763950d88fed8e13b9"},
|
||||
{file = "pendulum-3.0.0-cp312-none-win_amd64.whl", hash = "sha256:78f8f4e7efe5066aca24a7a57511b9c2119f5c2b5eb81c46ff9222ce11e0a7a5"},
|
||||
{file = "pendulum-3.0.0-cp312-none-win_arm64.whl", hash = "sha256:28f49d8d1e32aae9c284a90b6bb3873eee15ec6e1d9042edd611b22a94ac462f"},
|
||||
{file = "pendulum-3.0.0.tar.gz", hash = "sha256:5d034998dea404ec31fae27af6b22cff1708f830a1ed7353be4d1019bb9f584e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.3.6"
|
||||
requires_python = ">=3.8"
|
||||
summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
|
||||
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.5.0"
|
||||
|
|
@ -294,6 +419,24 @@ files = [
|
|||
{file = "polyfactory-2.17.0.tar.gz", hash = "sha256:099d86f7c79c51a2caaf7c8598cc56e7b0a57c11b5918ddf699e82380735b6b7"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "4.0.1"
|
||||
requires_python = ">=3.9"
|
||||
summary = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"cfgv>=2.0.0",
|
||||
"identify>=1.0.0",
|
||||
"nodeenv>=0.11.1",
|
||||
"pyyaml>=5.1",
|
||||
"virtualenv>=20.10.0",
|
||||
]
|
||||
files = [
|
||||
{file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"},
|
||||
{file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psutil"
|
||||
version = "6.1.0"
|
||||
|
|
@ -370,6 +513,17 @@ files = [
|
|||
{file = "pytest_httpx-0.33.0.tar.gz", hash = "sha256:4af9ab0dae5e9c14cb1e27d18af3db1f627b2cf3b11c02b34ddf26aff6b0a24c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-integration"
|
||||
version = "0.2.3"
|
||||
requires_python = ">=3.6"
|
||||
summary = "Organizing pytests by integration or not"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pytest_integration-0.2.3-py3-none-any.whl", hash = "sha256:7f59ed1fa1cc8cb240f9495b68bc02c0421cce48589f78e49b7b842231604b12"},
|
||||
{file = "pytest_integration-0.2.3.tar.gz", hash = "sha256:b00988a5de8a6826af82d4c7a3485b43fbf32c11235e9f4a8b7225eef5fbcf65"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-xdist"
|
||||
version = "3.6.1"
|
||||
|
|
@ -420,7 +574,7 @@ name = "pyyaml"
|
|||
version = "6.0.2"
|
||||
requires_python = ">=3.8"
|
||||
summary = "YAML parser and emitter for Python"
|
||||
groups = ["default"]
|
||||
groups = ["default", "dev"]
|
||||
files = [
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
|
||||
|
|
@ -489,6 +643,31 @@ files = [
|
|||
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-machine"
|
||||
version = "2.16.0"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Travel through time in your tests."
|
||||
groups = ["default"]
|
||||
marker = "implementation_name != \"pypy\""
|
||||
dependencies = [
|
||||
"python-dateutil",
|
||||
]
|
||||
files = [
|
||||
{file = "time_machine-2.16.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:84788f4d62a8b1bf5e499bb9b0e23ceceea21c415ad6030be6267ce3d639842f"},
|
||||
{file = "time_machine-2.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:15ec236b6571730236a193d9d6c11d472432fc6ab54e85eac1c16d98ddcd71bf"},
|
||||
{file = "time_machine-2.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cedc989717c8b44a3881ac3d68ab5a95820448796c550de6a2149ed1525157f0"},
|
||||
{file = "time_machine-2.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d26d79de1c63a8c6586c75967e09b0ff306aa7e944a1eaddb74595c9b1839ca"},
|
||||
{file = "time_machine-2.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:317b68b56a9c3731e0cf8886e0f94230727159e375988b36c60edce0ddbcb44a"},
|
||||
{file = "time_machine-2.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:43e1e18279759897be3293a255d53e6b1cb0364b69d9591d0b80c51e461c94b0"},
|
||||
{file = "time_machine-2.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e43adb22def972a29d2b147999b56897116085777a0fea182fd93ee45730611e"},
|
||||
{file = "time_machine-2.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0c766bea27a0600e36806d628ebc4b47178b12fcdfb6c24dc0a566a9c06bfe7f"},
|
||||
{file = "time_machine-2.16.0-cp312-cp312-win32.whl", hash = "sha256:6dae82ab647d107817e013db82223e20a9853fa88543fec853ae326382d03c2e"},
|
||||
{file = "time_machine-2.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:265462c77dc9576267c3c7f20707780a171a9fdbac93ac22e608c309efd68c33"},
|
||||
{file = "time_machine-2.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:ef768e14768eebe3bb1196c0dece8e14c1c6991605721214a0c3c68cf77eb216"},
|
||||
{file = "time_machine-2.16.0.tar.gz", hash = "sha256:4a99acc273d2f98add23a89b94d4dd9e14969c01214c8514bfa78e4e9364c7e2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
|
|
@ -499,3 +678,51 @@ files = [
|
|||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2024.2"
|
||||
requires_python = ">=2"
|
||||
summary = "Provider of IANA time zone data"
|
||||
groups = ["default"]
|
||||
files = [
|
||||
{file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"},
|
||||
{file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ujson"
|
||||
version = "5.10.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Ultra fast JSON encoder and decoder for Python"
|
||||
groups = ["default"]
|
||||
files = [
|
||||
{file = "ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc"},
|
||||
{file = "ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.27.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Virtual Python Environment builder"
|
||||
groups = ["dev"]
|
||||
dependencies = [
|
||||
"distlib<1,>=0.3.7",
|
||||
"filelock<4,>=3.12.2",
|
||||
"importlib-metadata>=6.6; python_version < \"3.8\"",
|
||||
"platformdirs<5,>=3.9.1",
|
||||
]
|
||||
files = [
|
||||
{file = "virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4"},
|
||||
{file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ authors = [
|
|||
]
|
||||
dependencies = [
|
||||
"litestar>=2.12.1",
|
||||
"deprecation>=2.1.0",
|
||||
"orjson>=3.10.11",
|
||||
"pendulum>=3.0.0",
|
||||
"ujson>=5.10.0",
|
||||
]
|
||||
requires-python = "==3.12.*"
|
||||
readme = "README.md"
|
||||
|
|
@ -21,4 +25,7 @@ dev = [
|
|||
"pytest-asyncio>=0.24.0",
|
||||
"pytest-httpx>=0.33.0",
|
||||
"pytest-xdist[psutil]>=3.6.1",
|
||||
"orjson>=3.10.11",
|
||||
"pytest-integration>=0.2.3",
|
||||
"pre-commit>=4.0.1",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from loguru import logger
|
||||
|
||||
from freedium_library.container import Container
|
||||
from freedium_library.utils.http import Request
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from freedium_library.utils.http import Request
|
||||
|
||||
|
||||
class BaseService(ABC):
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class MediumConfig(BaseSettings):
|
||||
cookies: Optional[str] = Field(default=None)
|
||||
|
|
@ -1,9 +1,18 @@
|
|||
from dependency_injector import containers, providers
|
||||
|
||||
from freedium_library.utils.http import Request
|
||||
|
||||
from .api import MediumApiService
|
||||
from .config import MediumConfig
|
||||
from .validators import MediumServicePathValidator
|
||||
|
||||
|
||||
class MediumContainer(containers.DeclarativeContainer):
|
||||
medium_api_service = providers.Singleton(MediumApiService)
|
||||
medium_path_validator = providers.Singleton(MediumServicePathValidator)
|
||||
config = providers.Singleton(MediumConfig)
|
||||
request = providers.Singleton(Request)
|
||||
api_service = providers.Singleton(
|
||||
MediumApiService,
|
||||
request=request,
|
||||
config=config,
|
||||
)
|
||||
validator = providers.Singleton(MediumServicePathValidator)
|
||||
|
|
|
|||
|
|
@ -21,10 +21,8 @@ class MediumService(BaseService):
|
|||
def __init__(
|
||||
self,
|
||||
request: Request = Provide[Container.request],
|
||||
api_service: MediumApiService = Provide[MediumContainer.medium_api_service],
|
||||
path_validator: MediumServicePathValidator = Provide[
|
||||
MediumContainer.medium_path_validator
|
||||
],
|
||||
api_service: MediumApiService = Provide[MediumContainer.api_service],
|
||||
path_validator: MediumServicePathValidator = Provide[MediumContainer.validator],
|
||||
):
|
||||
self.request = request
|
||||
self.api_service = api_service
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
medium.com
|
||||
uxplanet.org
|
||||
osintteam.blog
|
||||
ahmedelfakharany.com
|
||||
drlee.io
|
||||
generativeai.pub
|
||||
productcoalition.com
|
||||
towardsdev.com
|
||||
infosecwriteups.com
|
||||
towardsdatascience.com
|
||||
thetaoist.online
|
||||
devopsquare.com
|
||||
bettermarketing.pub
|
||||
itnext.io
|
||||
betterprogramming.pub
|
||||
curiouse.co
|
||||
betterhumans.pub
|
||||
uxdesign.cc
|
||||
thebolditalic.com
|
||||
codeburst.io
|
||||
writingcooperative.com
|
||||
entrepreneurshandbook.co
|
||||
storiusmag.com
|
||||
javascript.plainenglish.io
|
||||
code.likeagirl.io
|
||||
medium.datadriveninvestor.com
|
||||
blog.det.life
|
||||
python.plainenglish.io
|
||||
blog.stackademic.com
|
||||
ai.gopubby.com
|
||||
blog.devops.dev
|
||||
levelup.gitconnected.com
|
||||
betterhumans.coach.me
|
||||
ai.plainenglish.io
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import os
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from bs4 import BeautifulSoup
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def check_medium_meta_tag(domain: str) -> Optional[bool]:
|
||||
url = f"https://{domain}"
|
||||
logger.info(f"Checking Medium meta tag for domain: {domain}")
|
||||
|
||||
try:
|
||||
with httpx.Client(timeout=10.0) as client:
|
||||
logger.debug(f"Sending GET request to {url}")
|
||||
response = client.get(url, follow_redirects=True)
|
||||
response.raise_for_status()
|
||||
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
meta_tag = soup.find(
|
||||
"meta",
|
||||
{"property": "al:android:package", "content": "com.medium.reader"},
|
||||
)
|
||||
|
||||
if meta_tag is not None:
|
||||
logger.success(f"Medium meta tag found for domain: {domain}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"No Medium meta tag found for domain: {domain}")
|
||||
return False
|
||||
|
||||
except (httpx.RequestError, httpx.HTTPStatusError) as e:
|
||||
logger.error(f"Error checking {domain}: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
def get_domains_from_file(filepath: str) -> list[str]:
|
||||
logger.info(f"Reading domains from file: {filepath}")
|
||||
with open(filepath, "r") as file:
|
||||
domains = [line.strip() for line in file if line.strip()]
|
||||
logger.debug(f"Loaded {len(domains)} domains from file")
|
||||
return domains
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.parametrize(
|
||||
"domain",
|
||||
get_domains_from_file(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "medium_domains.txt")
|
||||
),
|
||||
)
|
||||
def test_medium_domain_meta_tag(domain: str):
|
||||
logger.info(f"Starting test for domain: {domain}")
|
||||
result = check_medium_meta_tag(domain)
|
||||
assert result is not None, f"Failed to check domain {domain}"
|
||||
assert result is True, f"Domain {domain} does not have Medium meta tag"
|
||||
logger.success(f"Test passed for domain: {domain}")
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import string
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from deprecation import deprecated
|
||||
from loguru import logger
|
||||
|
||||
from freedium_library.container import Container
|
||||
|
|
@ -65,7 +67,7 @@ class _MediumServiceURLValidator:
|
|||
parsed_url = urlparse(url)
|
||||
parsed_netloc = URLProcessor.un_wwwify(parsed_url.netloc)
|
||||
|
||||
if parsed_url.path.startswith("/p/"):
|
||||
if parsed_url.path.startswith("/p/"): # TODO: add more information
|
||||
logger.debug("URL is Medium 'mobile' link")
|
||||
post_id = parsed_url.path.rsplit("/p/")[1]
|
||||
|
||||
|
|
@ -83,7 +85,7 @@ class _MediumServiceURLValidator:
|
|||
elif (
|
||||
parsed_netloc == "webcache.googleusercontent.com"
|
||||
and parsed_url.path.startswith("/search")
|
||||
):
|
||||
): # TODO: is'n it deprecated? https://www.seozoom.com/google-cache/
|
||||
logger.debug("URL seems like is Google Web Archive page link")
|
||||
|
||||
parsed_query = parse_qs(parsed_url.query)
|
||||
|
|
@ -174,8 +176,31 @@ class _MediumServiceURLValidator:
|
|||
|
||||
|
||||
class _MediumServiceHashesValidator:
|
||||
def is_valid(self, hash: str) -> bool:
|
||||
return bool(self.extract_hashes(hash))
|
||||
VALID_ID_CHARS = set(string.ascii_letters + string.digits)
|
||||
|
||||
def is_valid(self, path: str) -> bool:
|
||||
return bool(self.extract_hashes(path))
|
||||
|
||||
@deprecated(details="Use is_valid instead")
|
||||
def is_valid_old(self, hex_string: str) -> bool:
|
||||
# Check if the string is a valid hexadecimal string
|
||||
for char in hex_string:
|
||||
if char not in self.VALID_ID_CHARS:
|
||||
return False
|
||||
|
||||
# Unfortunately, this logic doesn't works correctly sometimes, because
|
||||
# there is some unique URLs that are has only digits, like this:
|
||||
# https://valeman.medium.com/python-vs-r-for-time-series-forecasting-395390432598
|
||||
|
||||
# Check if the string contains only lowercase hexadecimal characters
|
||||
# if not hex_string.islower():
|
||||
# return False
|
||||
|
||||
# Check if the length of the string is correct for a hexadecimal string (e.g., 10, 11 or 12 characters)
|
||||
if len(hex_string) not in range(8, 12 + 1):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def extract_hashes(self, path: str) -> list[str]:
|
||||
logger.debug(f"Extracting hashes from path: {path}")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from unittest.mock import AsyncMock, Mock
|
|||
import pytest
|
||||
|
||||
from freedium_library.services.medium.api import MediumApiService
|
||||
from freedium_library.services.medium.config import MediumConfig
|
||||
from freedium_library.services.medium.validators import (
|
||||
_MediumServiceHashesValidator, # type: ignore
|
||||
_MediumServiceURLValidator, # type: ignore
|
||||
|
|
@ -117,7 +118,8 @@ async def test_resolve_medium_url_with_real_short_link(
|
|||
@pytest.mark.asyncio
|
||||
async def test_resolve_medium_url_with_real_short_link_integration() -> None:
|
||||
request = Request()
|
||||
api_service = MediumApiService(request=request)
|
||||
config = MediumConfig()
|
||||
api_service = MediumApiService(request=request, config=config)
|
||||
hash_validator = _MediumServiceHashesValidator()
|
||||
url_validator = _MediumServiceURLValidator(api_service, hash_validator, request)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
from .hash import HashLib
|
||||
from .json import JSON, json_instance
|
||||
|
||||
__all__ = ["json_instance", "JSON", "HashLib"]
|
||||
14
freedium-library/src/freedium_library/utils/hash.py
Normal file
14
freedium-library/src/freedium_library/utils/hash.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import hashlib
|
||||
import secrets
|
||||
|
||||
|
||||
class HashLib:
|
||||
@staticmethod
|
||||
def sha256(data: bytes) -> str:
|
||||
sha256_hash = hashlib.sha256()
|
||||
sha256_hash.update(data)
|
||||
return sha256_hash.hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def random_sha256() -> str:
|
||||
return HashLib.sha256(secrets.token_bytes())
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import asyncio
|
||||
import warnings
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, Optional
|
||||
|
|
@ -68,7 +67,7 @@ class Request:
|
|||
|
||||
def __del__(self):
|
||||
self._client.close()
|
||||
asyncio.run(self._async_client.aclose())
|
||||
# asyncio.run(self._async_client.aclose()) # TODO: doesnt works
|
||||
|
||||
def _check_context_manager(self):
|
||||
if not self._in_context_manager:
|
||||
|
|
|
|||
130
freedium-library/src/freedium_library/utils/json.py
Normal file
130
freedium-library/src/freedium_library/utils/json.py
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Optional, Type
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class JSONBackend(ABC):
|
||||
@abstractmethod
|
||||
def dumps(self, obj: Any, pretty: bool = False) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def loads(self, string: str) -> Any:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
pass
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(backend='{self.name}')"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"JSON Backend: {self.name}"
|
||||
|
||||
|
||||
class OrjsonBackend(JSONBackend):
|
||||
def __init__(self):
|
||||
import orjson
|
||||
|
||||
self._orjson = orjson
|
||||
|
||||
def dumps(self, obj: Any, pretty: bool = False) -> str:
|
||||
opts = self._orjson.OPT_INDENT_2 if pretty else None
|
||||
return self._orjson.dumps(obj, option=opts).decode("utf-8")
|
||||
|
||||
def loads(self, string: str) -> Any:
|
||||
return self._orjson.loads(string)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "orjson"
|
||||
|
||||
|
||||
class UjsonBackend(JSONBackend):
|
||||
def __init__(self):
|
||||
import ujson
|
||||
|
||||
self._ujson = ujson
|
||||
|
||||
def dumps(self, obj: Any, pretty: bool = False) -> str:
|
||||
return self._ujson.dumps(obj, indent=2 if pretty else 0)
|
||||
|
||||
def loads(self, string: str) -> Any:
|
||||
return self._ujson.loads(string)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "ujson"
|
||||
|
||||
|
||||
class StandardJSONBackend(JSONBackend):
|
||||
def __init__(self):
|
||||
import json
|
||||
|
||||
self._json = json
|
||||
|
||||
def dumps(self, obj: Any, pretty: bool = False) -> str:
|
||||
return self._json.dumps(obj, indent=2 if pretty else None)
|
||||
|
||||
def loads(self, string: str) -> Any:
|
||||
return self._json.loads(string)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "json"
|
||||
|
||||
|
||||
class JSON:
|
||||
_instance: Optional[Type["JSON"]] = None
|
||||
_backend: Optional[JSONBackend] = None
|
||||
|
||||
def __init__(self, backend: Optional[JSONBackend] = None) -> None:
|
||||
if backend:
|
||||
JSON._backend = backend
|
||||
elif not JSON._backend:
|
||||
JSON._backend = JSON._get_best_backend()
|
||||
|
||||
@classmethod
|
||||
def _ensure_backend(cls) -> None:
|
||||
if cls._backend is None:
|
||||
cls._backend = cls._get_best_backend()
|
||||
|
||||
@classmethod
|
||||
def dumps(cls, obj: Any, pretty: bool = False) -> str:
|
||||
cls._ensure_backend()
|
||||
return cls._backend.dumps(obj, pretty) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def loads(cls, string: str) -> Any:
|
||||
cls._ensure_backend()
|
||||
return cls._backend.loads(string) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def backend(cls) -> str:
|
||||
cls._ensure_backend()
|
||||
return cls._backend.name # type: ignore
|
||||
|
||||
@classmethod
|
||||
def _get_best_backend(cls) -> JSONBackend:
|
||||
backends = [
|
||||
(OrjsonBackend, "orjson"),
|
||||
(UjsonBackend, "ujson"),
|
||||
(StandardJSONBackend, "json"),
|
||||
]
|
||||
|
||||
for backend_class, module_name in backends:
|
||||
try:
|
||||
__import__(module_name)
|
||||
logger.debug(f"Using {module_name} as JSON backend")
|
||||
return backend_class()
|
||||
except ImportError:
|
||||
continue
|
||||
|
||||
logger.warning("No JSON backend found, using standard JSON backend")
|
||||
return StandardJSONBackend()
|
||||
|
||||
|
||||
json_instance = JSON()
|
||||
117
freedium-library/src/freedium_library/utils/json_test.py
Normal file
117
freedium-library/src/freedium_library/utils/json_test.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
from typing import Any, Dict
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from freedium_library.utils.json import (
|
||||
JSON,
|
||||
JSONBackend,
|
||||
OrjsonBackend,
|
||||
StandardJSONBackend,
|
||||
UjsonBackend,
|
||||
)
|
||||
|
||||
# Test data
|
||||
SIMPLE_DICT: Dict[str, Any] = {"key": "value", "number": 42}
|
||||
COMPLEX_DICT: Dict[str, Any] = {
|
||||
"string": "hello",
|
||||
"number": 42,
|
||||
"float": 3.14,
|
||||
"bool": True,
|
||||
"null": None,
|
||||
"array": [1, 2, 3],
|
||||
"nested": {"a": 1, "b": 2},
|
||||
}
|
||||
UNICODE_DICT: Dict[str, str] = {"unicode": "Hello 世界"}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def json_backends() -> list[JSONBackend]:
|
||||
return [OrjsonBackend(), UjsonBackend(), StandardJSONBackend()]
|
||||
|
||||
|
||||
def test_backend_interface() -> None:
|
||||
"""Test that all backends implement the required interface"""
|
||||
for backend in [OrjsonBackend(), UjsonBackend(), StandardJSONBackend()]:
|
||||
assert isinstance(backend, JSONBackend)
|
||||
assert callable(backend.dumps)
|
||||
assert callable(backend.loads)
|
||||
assert isinstance(backend.name, str)
|
||||
assert str(backend).startswith("JSON Backend:")
|
||||
assert repr(backend).endswith(f"backend='{backend.name}')")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"backend", [OrjsonBackend(), UjsonBackend(), StandardJSONBackend()]
|
||||
)
|
||||
class TestBackendBehavior:
|
||||
def test_simple_roundtrip(self, backend: JSONBackend) -> None:
|
||||
"""Test serialization and deserialization of simple data"""
|
||||
serialized: str = backend.dumps(SIMPLE_DICT)
|
||||
deserialized: Dict[str, Any] = backend.loads(serialized)
|
||||
assert deserialized == SIMPLE_DICT
|
||||
|
||||
def test_complex_roundtrip(self, backend: JSONBackend) -> None:
|
||||
"""Test serialization and deserialization of complex data"""
|
||||
serialized: str = backend.dumps(COMPLEX_DICT)
|
||||
deserialized: Dict[str, Any] = backend.loads(serialized)
|
||||
assert deserialized == COMPLEX_DICT
|
||||
|
||||
def test_unicode_handling(self, backend: JSONBackend) -> None:
|
||||
"""Test proper Unicode handling"""
|
||||
serialized: str = backend.dumps(UNICODE_DICT)
|
||||
deserialized: Dict[str, str] = backend.loads(serialized)
|
||||
assert deserialized == UNICODE_DICT
|
||||
|
||||
def test_pretty_printing(self, backend: JSONBackend) -> None:
|
||||
"""Test pretty printing functionality"""
|
||||
ugly: str = backend.dumps(COMPLEX_DICT, pretty=False)
|
||||
pretty: str = backend.dumps(COMPLEX_DICT, pretty=True)
|
||||
assert len(pretty) > len(ugly)
|
||||
assert backend.loads(pretty) == backend.loads(ugly)
|
||||
|
||||
|
||||
class TestJSONClass:
|
||||
def test_backend_selection(self) -> None:
|
||||
"""Test automatic backend selection"""
|
||||
json = JSON()
|
||||
assert json.backend() in ["orjson", "ujson", "json"]
|
||||
|
||||
def test_backend_fallback(self) -> None:
|
||||
"""Test fallback behavior when preferred backends are unavailable"""
|
||||
JSON._instance = None
|
||||
JSON._backend = None
|
||||
with patch.dict("sys.modules", {"orjson": None, "ujson": None}):
|
||||
json = JSON()
|
||||
assert json.backend() == "json"
|
||||
|
||||
def test_explicit_backend(self) -> None:
|
||||
"""Test using an explicitly provided backend"""
|
||||
backend = StandardJSONBackend()
|
||||
json = JSON(backend=backend)
|
||||
assert json.backend() == "json"
|
||||
|
||||
def test_singleton_behavior(self) -> None:
|
||||
"""Test that JSON class maintains singleton behavior"""
|
||||
json1 = JSON()
|
||||
json2 = JSON()
|
||||
assert json1._backend is json2._backend
|
||||
|
||||
def test_class_methods(self) -> None:
|
||||
"""Test class-level convenience methods"""
|
||||
data: Dict[str, Any] = COMPLEX_DICT
|
||||
serialized: str = JSON.dumps(data)
|
||||
deserialized: Dict[str, Any] = JSON.loads(serialized)
|
||||
assert deserialized == data
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"backend", [OrjsonBackend(), UjsonBackend(), StandardJSONBackend()]
|
||||
)
|
||||
def test_error_handling(backend: JSONBackend) -> None:
|
||||
"""Test error handling for invalid JSON"""
|
||||
with pytest.raises(Exception):
|
||||
backend.loads("invalid json")
|
||||
|
||||
with pytest.raises(Exception):
|
||||
backend.dumps(object())
|
||||
0
freedium-library/src/freedium_library/utils/utils.py
Normal file
0
freedium-library/src/freedium_library/utils/utils.py
Normal file
1
src/freedium_library/utils/json_test.py
Normal file
1
src/freedium_library/utils/json_test.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
Loading…
Reference in a new issue