From 3639e1860c57d2391b3f373ba574a036e1c85cff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 28 Sep 2022 21:22:24 +0800 Subject: [PATCH] feat: async timer can change interval (#49) add more test, change to dependency group pyproject Co-authored-by: JamzumSum --- .pre-commit-config.yaml | 2 +- README.md | 2 +- poetry.lock | 238 +++++++++++++++++--------------- pyproject.toml | 48 ++++--- src/aioqzone_feed/api/feed.py | 36 ++--- src/aioqzone_feed/utils/task.py | 11 +- test/api/test_feed.py | 27 ++++ test/test_task.py | 24 ++++ 8 files changed, 234 insertions(+), 154 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9c06d92..1bf426e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: isort name: isort (python) - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.8.0 hooks: - id: black default_language_version: diff --git a/README.md b/README.md index e409bf3..83dd202 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ An [aioqzone][aioqzone] plugin for handling feeds, a high-level api for feed ope ## License - [AGPL-3.0](LICENSE) -- `aioqzone-feed` is a plugin of [aioqzone][aioqzone]. This repository inherits license, instructions and any other requirements from `aioqzone`. See also: [License fragment in aioqzone README file](https://github.com/aioqzone/aioqzone#license) +- `aioqzone-feed` is a plugin of [aioqzone][aioqzone]. This repository inherits license, instructions and any other disclaimers from `aioqzone`. See also: [License fragment in aioqzone README file](https://github.com/aioqzone/aioqzone#license) [aioqzone]: https://github.com/aioqzone/aioqzone "Python wrapper for Qzone web login and Qzone http api." diff --git a/poetry.lock b/poetry.lock index faccdb7..1876f44 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,7 +8,7 @@ python-versions = ">=3.7,<4.0" [[package]] name = "aioqzone" -version = "0.9.4a4.dev1" +version = "0.9.5.dev3" description = "Python wrapper for Qzone web login and Qzone http api." category = "main" optional = false @@ -27,10 +27,6 @@ pydantic = ">=1.9.0,<2.0.0" pytz = ">=2022.1,<2023.0" rsa = ">=4.8,<5.0" -[package.extras] -dev = ["black (>=22.1.0,<23.0.0)", "isort (>=5.10.1,<6.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] -doc = ["Sphinx (>=5.0.1,<6.0.0)", "autodoc-pydantic (>=1.7.1,<2.0.0)", "sphinx-autodoc-typehints (<1.16.0)", "sphinx-rtd-theme (>=1.0.0,<2.0.0)"] - [[package]] name = "aiosqlite" version = "0.17.0" @@ -46,8 +42,8 @@ typing_extensions = ">=3.7.2" name = "alabaster" version = "0.7.12" description = "A configurable sidebar-enabled Sphinx theme" -category = "main" -optional = true +category = "dev" +optional = false python-versions = "*" [[package]] @@ -108,8 +104,8 @@ tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy name = "autodoc-pydantic" version = "1.7.2" description = "Seamlessly integrate pydantic models in your Sphinx documentation." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6.1,<4.0.0" [package.dependencies] @@ -125,8 +121,8 @@ test = ["coverage (>=5,<6)", "pytest (>=6,<7)"] name = "Babel" version = "2.10.3" description = "Internationalization utilities" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [package.dependencies] @@ -136,8 +132,8 @@ pytz = ">=2015.7" name = "black" version = "22.8.0" description = "The uncompromising code formatter." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6.2" [package.dependencies] @@ -157,7 +153,7 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2022.9.14" +version = "2022.9.24" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -167,16 +163,16 @@ python-versions = ">=3.6" name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6.1" [[package]] name = "charset-normalizer" version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6.0" [package.extras] @@ -186,8 +182,8 @@ unicode_backport = ["unicodedata2"] name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.dependencies] @@ -198,7 +194,7 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} name = "colorama" version = "0.4.5" description = "Cross-platform colored terminal text." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -214,24 +210,24 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "distlib" version = "0.3.6" description = "Distribution utilities" -category = "main" -optional = true +category = "dev" +optional = false python-versions = "*" [[package]] name = "docutils" version = "0.17.1" description = "Docutils -- Python Documentation Utilities" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "filelock" version = "3.8.0" description = "A platform independent file lock." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.extras] @@ -299,8 +295,8 @@ socks = ["socksio (>=1.0.0,<2.0.0)"] name = "identify" version = "2.5.5" description = "File identification library for Python" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.extras] @@ -318,8 +314,8 @@ python-versions = ">=3.5" name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] @@ -351,8 +347,8 @@ python-versions = "*" name = "isort" version = "5.10.1" description = "A Python utility / library to sort Python imports." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6.1,<4.0" [package.extras] @@ -365,8 +361,8 @@ requirements_deprecated_finder = ["pip-api", "pipreqs"] name = "Jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.dependencies] @@ -393,24 +389,24 @@ source = ["Cython (>=0.29.7)"] name = "MarkupSafe" version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [[package]] name = "mypy-extensions" version = "0.4.3" description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "main" -optional = true +category = "dev" +optional = false python-versions = "*" [[package]] name = "nodeenv" version = "1.7.0" description = "Node.js virtual environment builder" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" [package.dependencies] @@ -463,16 +459,16 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" name = "pathspec" version = "0.10.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [[package]] name = "platformdirs" version = "2.5.2" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.extras] @@ -498,8 +494,8 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "2.20.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.dependencies] @@ -546,8 +542,8 @@ email = ["email-validator (>=1.0.3)"] name = "Pygments" version = "2.13.0" description = "Pygments is a syntax highlighting package written in Python." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [package.extras] @@ -600,6 +596,20 @@ typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} [package.extras] testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] +[[package]] +name = "pytest-mock" +version = "3.8.2" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "pytz" version = "2022.2.1" @@ -636,12 +646,13 @@ dev = ["black (>=22.1.0,<23.0.0)", "isort (>=5.10.1,<6.0.0)", "pre-commit (>=2.2 [package.source] type = "url" url = "https://github.com/aioqzone/QzEmoji/releases/download/3.3.3/qzemoji-3.3.3-py3-none-any.whl" + [[package]] name = "requests" version = "2.28.1" description = "Python HTTP for Humans." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7, <4" [package.dependencies] @@ -681,10 +692,10 @@ pyasn1 = ">=0.1.3" [[package]] name = "setuptools" -version = "65.3.0" +version = "65.4.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.extras] @@ -704,8 +715,8 @@ python-versions = ">=3.7" name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "main" -optional = true +category = "dev" +optional = false python-versions = "*" [[package]] @@ -725,24 +736,24 @@ resolved_reference = "9f992b32be9856ec5975d21f8296f5f1936d091a" [[package]] name = "Sphinx" -version = "5.1.1" +version = "5.2.2" description = "Python documentation generator" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [package.dependencies] alabaster = ">=0.7,<0.8" -babel = ">=1.3" -colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} docutils = ">=0.14,<0.20" -imagesize = "*" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} -Jinja2 = ">=2.3" -packaging = "*" -Pygments = ">=2.0" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.12" requests = ">=2.5.0" -snowballstemmer = ">=1.1" +snowballstemmer = ">=2.0" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" @@ -752,30 +763,31 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "isort", "mypy (>=0.971)", "sphinx-lint", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest (>=4.6)", "typed-ast"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] [[package]] name = "sphinx-autodoc-typehints" -version = "1.19.2" +version = "1.19.4" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.dependencies] -Sphinx = ">=5.1.1" +sphinx = ">=5.2.1" [package.extras] -testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "diff-cover (>=6.5.1)", "nptyping (>=2.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "sphobjinv (>=2.2.2)", "typing-extensions (>=4.3)"] -type_comments = ["typed-ast (>=1.5.4)"] +docs = ["furo (>=2022.9.15)", "sphinx (>=5.2.1)", "sphinx-autodoc-typehints (>=1.19.3)"] +testing = ["covdefaults (>=2.2)", "coverage (>=6.4.4)", "diff-cover (>=7.0.1)", "nptyping (>=2.3.1)", "pytest (>=7.1.3)", "pytest-cov (>=3)", "sphobjinv (>=2.2.2)", "typing-extensions (>=4.3)"] +type-comment = ["typed-ast (>=1.5.4)"] [[package]] name = "sphinx-rtd-theme" version = "1.0.0" description = "Read the Docs theme for Sphinx" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [package.dependencies] @@ -789,8 +801,8 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"] name = "sphinxcontrib-applehelp" version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.5" [package.extras] @@ -801,8 +813,8 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.5" [package.extras] @@ -813,8 +825,8 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [package.extras] @@ -825,8 +837,8 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.5" [package.extras] @@ -836,8 +848,8 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.5" [package.extras] @@ -848,8 +860,8 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.5" [package.extras] @@ -893,15 +905,15 @@ sqlcipher = ["sqlcipher3_binary"] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -909,8 +921,8 @@ python-versions = ">=3.7" name = "typed-ast" version = "1.5.4" description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [[package]] @@ -925,8 +937,8 @@ python-versions = ">=3.7" name = "urllib3" version = "1.26.12" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] @@ -938,8 +950,8 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "virtualenv" version = "20.16.5" description = "Virtual Python Environment builder" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [package.dependencies] @@ -964,14 +976,10 @@ python-versions = ">=3.7" docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] -[extras] -dev = ["pre-commit", "black", "isort"] -doc = ["Sphinx", "autodoc-pydantic", "sphinx-autodoc-typehints", "sphinx-rtd-theme"] - [metadata] lock-version = "1.1" python-versions = ">=3.7,<3.11" -content-hash = "9a704155282629c4a129fff4fac225883465012f42015cd1e6d6f4c331a89280" +content-hash = "12970d46185dfaef69bca548d6752f45a7e7d1fb0870b5a4fbbce028164b730a" [metadata.files] aiofiles = [ @@ -979,8 +987,8 @@ aiofiles = [ {file = "aiofiles-22.1.0.tar.gz", hash = "sha256:9107f1ca0b2a5553987a94a3c9959fe5b491fdf731389aa5b7b1bd0733e32de6"}, ] aioqzone = [ - {file = "aioqzone-0.9.4a4.dev1-py3-none-any.whl", hash = "sha256:8b485e661bb0d8ed884531c2be2db7427031007a870f15978081c57bdd6e501d"}, - {file = "aioqzone-0.9.4a4.dev1.tar.gz", hash = "sha256:5a0332833e33cd90e71130ab5e25d353e4882b5fb8da095139309b56ba9349d0"}, + {file = "aioqzone-0.9.5.dev3-py3-none-any.whl", hash = "sha256:c3534edb5323ae9712de0e05cbfc9ae7f2bab9e981caefca4b845f3def5412a3"}, + {file = "aioqzone-0.9.5.dev3.tar.gz", hash = "sha256:4d9c2f1f221fc0d142d21499f2d5730d8151003c82dc401c9b2111f0fcf322a6"}, ] aiosqlite = [ {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"}, @@ -1033,8 +1041,8 @@ black = [ {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"}, ] certifi = [ - {file = "certifi-2022.9.14-py3-none-any.whl", hash = "sha256:e232343de1ab72c2aa521b625c80f699e356830fd0e2c620b465b304b17b0516"}, - {file = "certifi-2022.9.14.tar.gz", hash = "sha256:36973885b9542e6bd01dea287b2b4b3b21236307c56324fcc3f1160f2d655ed5"}, + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, ] cfgv = [ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, @@ -1449,6 +1457,10 @@ pytest-asyncio = [ {file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"}, {file = "pytest_asyncio-0.19.0-py3-none-any.whl", hash = "sha256:7a97e37cfe1ed296e2e84941384bdd37c376453912d397ed39293e0916f521fa"}, ] +pytest-mock = [ + {file = "pytest-mock-3.8.2.tar.gz", hash = "sha256:77f03f4554392558700295e05aed0b1096a20d4a60a4f3ddcde58b0c31c8fca2"}, + {file = "pytest_mock-3.8.2-py3-none-any.whl", hash = "sha256:8a9e226d6c0ef09fcf20c94eb3405c388af438a90f3e39687f84166da82d5948"}, +] pytz = [ {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"}, {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"}, @@ -1502,8 +1514,8 @@ rsa = [ {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, ] setuptools = [ - {file = "setuptools-65.3.0-py3-none-any.whl", hash = "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82"}, - {file = "setuptools-65.3.0.tar.gz", hash = "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"}, + {file = "setuptools-65.4.0-py3-none-any.whl", hash = "sha256:c2d2709550f15aab6c9110196ea312f468f41cd546bceb24127a1be6fdcaeeb1"}, + {file = "setuptools-65.4.0.tar.gz", hash = "sha256:a8f6e213b4b0661f590ccf40de95d28a177cd747d098624ad3f69c40287297e9"}, ] sniffio = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, @@ -1515,12 +1527,12 @@ snowballstemmer = [ ] socksio = [] Sphinx = [ - {file = "Sphinx-5.1.1-py3-none-any.whl", hash = "sha256:309a8da80cb6da9f4713438e5b55861877d5d7976b69d87e336733637ea12693"}, - {file = "Sphinx-5.1.1.tar.gz", hash = "sha256:ba3224a4e206e1fbdecf98a4fae4992ef9b24b85ebf7b584bb340156eaf08d89"}, + {file = "Sphinx-5.2.2.tar.gz", hash = "sha256:7225c104dc06169eb73b061582c4bc84a9594042acae6c1582564de274b7df2f"}, + {file = "sphinx-5.2.2-py3-none-any.whl", hash = "sha256:9150a8ed2e98d70e778624373f183c5498bf429dd605cf7b63e80e2a166c35a5"}, ] sphinx-autodoc-typehints = [ - {file = "sphinx_autodoc_typehints-1.19.2-py3-none-any.whl", hash = "sha256:3d761de928d5a86901331133d6d4a2552afa2e798ebcfc0886791792aeb4dd9a"}, - {file = "sphinx_autodoc_typehints-1.19.2.tar.gz", hash = "sha256:872fb2d7b3d794826c28e36edf6739e93549491447dcabeb07c58855e9f914de"}, + {file = "sphinx_autodoc_typehints-1.19.4-py3-none-any.whl", hash = "sha256:e190d8ee8204c3de05a64f41cf10e592e987e4063c8ec0de7e4b11f6e036b2e2"}, + {file = "sphinx_autodoc_typehints-1.19.4.tar.gz", hash = "sha256:ffd8e710f6757471b5c831c7ece88f52a9ff15f27836f4ef1c8695a64f8dcca8"}, ] sphinx-rtd-theme = [ {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, diff --git a/pyproject.toml b/pyproject.toml index 121035f..016570b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aioqzone-feed" -version = "0.9.3a4.dev2" +version = "0.9.4a1.dev1" description = "aioqzone plugin providing higher level api for processing feed." authors = ["aioqzone "] license = "AGPL-3.0" @@ -14,30 +14,36 @@ documentation = "https://aioqzone.github.io/aioqzone-feed" [tool.poetry.dependencies] python = ">=3.7,<3.11" -aioqzone = { version = "^0.9.4a3.dev1", allow-prereleases = true } +aioqzone = { version = "^0.9.5.dev3", allow-prereleases = true } QzEmoji = { url = "https://github.com/aioqzone/QzEmoji/releases/download/3.3.3/qzemoji-3.3.3-py3-none-any.whl" } -# extras: dev -pre-commit = { version = "^2.17.0", optional = true } -black = { version = "^22.1.0", optional = true } -isort = { version = "^5.10.1", optional = true } -# extras: doc -Sphinx = { version = "^5.0.2", optional = true } -autodoc-pydantic = { version = "^1.6.1", optional = true } -sphinx-autodoc-typehints = { version = "^1.18.3", optional = true } -sphinx-rtd-theme = { version = "^1.0.0", optional = true } - -[tool.poetry.dev-dependencies] + + +# dependency groups +[tool.poetry.group.test] +optional = false + +[tool.poetry.group.test.dependencies] pytest = "^7.0.1" pytest-asyncio = "~0.19.0" +pytest-mock = "^3.8.2" + +[tool.poetry.group.dev] +optional = true + +[tool.poetry.group.dev.dependencies] +pre-commit = "^2.17.0" +black = "^22.1.0" +isort = "^5.10.1" + +[tool.poetry.group.docs] +optional = true + +[tool.poetry.group.docs.dependencies] +Sphinx = "^5.0.2" +autodoc-pydantic = "^1.6.1" +sphinx-autodoc-typehints = "^1.18.3" +sphinx-rtd-theme = "^1.0.0" -[tool.poetry.extras] -dev = ["pre-commit", "black", "isort"] -doc = [ - "Sphinx", - "autodoc-pydantic", - "sphinx-autodoc-typehints", - "sphinx-rtd-theme", -] [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/src/aioqzone_feed/api/feed.py b/src/aioqzone_feed/api/feed.py index c8754e1..d72f68f 100644 --- a/src/aioqzone_feed/api/feed.py +++ b/src/aioqzone_feed/api/feed.py @@ -305,46 +305,50 @@ def stop(self) -> None: super().clear(*self._tasks.keys()) def clear(self): - """Cancel all __dispatch__ tasks registered. + """Cancel all **dispatch** tasks registered. .. seealso:: :external:meth:`aioqzone.interface.hook.Emittable.clear`""" super().clear("dispatch") - def add_heartbeat(self, retry: int = 5): + def add_heartbeat(self, *, retry: int = 5, refresh_intv: float = 300): """create a heartbeat task and keep a ref of it. :param retry: max retry times when exception occurs, defaults to 5. + :param refresh_intv: refresh interval in seconds, defaults to 300. :return: the heartbeat task """ async def heartbeat_refresh(): - exc = None + excg = [] + emit = lambda c: self.add_hook_ref("hook", c) for i in range(retry): try: cnt = (await self.api.get_feeds_count()).friendFeeds_new_cnt log.debug("heartbeat: friendFeeds_new_cnt=%d", cnt) if cnt: - self.add_hook_ref("hook", self.hook.HeartbeatRefresh(cnt)) + emit(self.hook.HeartbeatRefresh(cnt)) return False # don't stop except qz_exc as e: - exc = e - log.warning("Error when heartbeat. retry=%d", i, exc_info=True) + log.warning("Error in heartbeat, retry at once %d", i, exc_info=e) + excg.append(e) continue # retry at once - except TimeoutException: - log.warning("Error in connector", exc_info=True) + except TimeoutException as e: + log.warning("Error in connector, retry on next trigger", exc_info=True) + emit(self.hook.HeartbeatFailed(e)) return False # retry in next trigger except login_exc as e: - log.info(f"Heartbeat stopped: {e}") - break + log.error(f"Heartbeat stopped, stop at once.", exc_info=e) + excg.append(e) + break # stop at once except BaseException as e: - exc = e - log.error("Uncaught error in heartbeat.", exc_info=True) - break + log.error("Uncaught error in heartbeat, stop at once.", exc_info=e) + excg.append(e) + break # stop at once - log.error("Max retry exceeds. Heartbeat stopped.") - self.add_hook_ref("hook", self.hook.HeartbeatFailed(exc)) + log.error("Max retry exceeds. Heartbeat stopped.", exc_info=excg[-1]) + emit(self.hook.HeartbeatFailed(excg[-1])) return True # stop at once - self.hb_timer = AsyncTimer(300, heartbeat_refresh, delay=300) + self.hb_timer = AsyncTimer(refresh_intv, heartbeat_refresh, delay=refresh_intv) return self.hb_timer() diff --git a/src/aioqzone_feed/utils/task.py b/src/aioqzone_feed/utils/task.py index efdd074..3731896 100644 --- a/src/aioqzone_feed/utils/task.py +++ b/src/aioqzone_feed/utils/task.py @@ -26,12 +26,12 @@ def __init__( async def _loop(self): try: await asyncio.sleep(self.delay) - stop = await self.func() self.last_call = time() + stop = await self.func() while not stop: await asyncio.sleep(self.itvl) - stop = await self.func() self.last_call = time() + stop = await self.func() except asyncio.CancelledError: logger.info("%s cancelled.", self.name) return @@ -53,3 +53,10 @@ def stop(self): def __repr__(self) -> str: return f"{self.name} ({self.state})" + + def change_interval(self, intv: float, stop=True): + self.itvl = intv + if stop and self.task and self.task._state == "PENDING": + self.stop() + self.delay = intv - (time() - self.last_call) + self() diff --git a/test/api/test_feed.py b/test/api/test_feed.py index 41855c5..7bc9e12 100644 --- a/test/api/test_feed.py +++ b/test/api/test_feed.py @@ -1,7 +1,12 @@ +from typing import Optional, Type +from unittest import mock + import pytest import pytest_asyncio from aioqzone.api.loginman import MixedLoginMan from aioqzone.exception import LoginError +from httpx import HTTPStatusError, TimeoutException +from qqqr.exception import UserBreak from qqqr.utils.net import ClientAdapter from aioqzone_feed.api.feed import FeedApi @@ -62,3 +67,25 @@ async def test_by_second(api: FeedApi): assert len(set(hook.batch)) == len(hook.batch) api.clear() hook.batch.clear() + + +@pytest.mark.parametrize( + "exc2r,exc2e", + [ + (LoginError("mock", "forbid"), LoginError), + (HTTPStatusError("mock", request=..., response=...), HTTPStatusError), # type: ignore + (TimeoutException("mock"), TimeoutException), + (UserBreak(), UserBreak), + (SystemExit(1), SystemExit), + ], +) +async def test_heartbeat_exc(api: FeedApi, exc2r: BaseException, exc2e: Type[BaseException]): + class coll_exc(FeedEvent): + async def HeartbeatFailed(self, exc: Optional[BaseException] = None): + assert isinstance(exc, exc2e) + if api.hb_timer: + api.hb_timer.stop() + + api.register_hook(coll_exc()) + with mock.patch("aioqzone.api.raw.QzoneApi.get_feeds_count", side_effect=exc2r): + await api.add_heartbeat(retry=2, refresh_intv=0.1) diff --git a/test/test_task.py b/test/test_task.py index b3d1d64..1be3c69 100644 --- a/test/test_task.py +++ b/test/test_task.py @@ -19,10 +19,12 @@ async def inc(): cnt = 0 timer = AsyncTimer(0.1, inc) assert timer.state == "INIT" + assert timer.last_call == 0 timer() assert timer.state == "PENDING" await asyncio.sleep(1) assert timer.state == "FINISHED" + assert timer.last_call > 0 assert cnt == 4 @@ -40,8 +42,10 @@ async def inc(): assert timer.state == "INIT" timer() assert timer.state == "PENDING" + assert timer.last_call == 0 await asyncio.sleep(1) assert timer.state == "FINISHED" + assert timer.last_call > 0 assert cnt == 4 @@ -66,3 +70,23 @@ async def inc(): assert timer.state == "FINISHED" assert cnt == 4 assert entr == 2 + + +async def test_timer_reschedule(): + async def inc(): + nonlocal cnt, timer + if cnt == 0: + cnt += 1 + timer.change_interval(0.5) + return False + else: + return True + + cnt = 0 + timer = AsyncTimer(0.1, inc) + timer() + assert timer.state == "PENDING" + await asyncio.sleep(1) + assert timer.state == "FINISHED" + assert cnt == 1 + assert timer.delay > 0.4