From 805db8b8f4108613f2a0ff4c2dc842c9db037c29 Mon Sep 17 00:00:00 2001 From: Lefteris Chatzimparmpas Date: Tue, 20 Feb 2024 08:58:04 +0200 Subject: [PATCH] chore: Migrate to Ruff (#127) --- .pre-commit-config.yaml | 20 ++-- poetry.lock | 167 +++++----------------------- pyproject.toml | 136 +++++++++++++++++++++- todoist_api_python/http_requests.py | 2 +- todoist_api_python/models.py | 14 +-- 5 files changed, 176 insertions(+), 163 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 52d1e46..b67be5d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,20 +12,14 @@ repos: - id: check-case-conflict - id: debug-statements - - repo: https://github.com/psf/black - rev: 23.7.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.13 hooks: - - id: black - - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - - - repo: https://github.com/pycqa/flake8 - rev: 6.1.0 - hooks: - - id: flake8 + # Run the linter + - id: ruff + args: ['--fix'] + # Run the formatter + - id: ruff-format - repo: local hooks: diff --git a/poetry.lock b/poetry.lock index 64e936a..befdcfb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "atomicwrites" @@ -27,52 +27,6 @@ docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] -[[package]] -name = "black" -version = "23.7.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, - {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, - {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, - {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, - {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, - {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, - {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, - {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, - {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, - {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, - {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "certifi" version = "2023.7.22" @@ -109,20 +63,6 @@ files = [ [package.extras] unicode-backport = ["unicodedata2"] -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - [[package]] name = "colorama" version = "0.4.5" @@ -160,22 +100,6 @@ files = [ docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] -[[package]] -name = "flake8" -version = "6.1.0" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.8.1" -files = [ - {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, - {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.11.0,<2.12.0" -pyflakes = ">=3.1.0,<3.2.0" - [[package]] name = "identify" version = "2.5.3" @@ -212,34 +136,6 @@ files = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] -[[package]] -name = "isort" -version = "5.12.0" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, -] - -[package.extras] -colors = ["colorama (>=0.4.3)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - [[package]] name = "mypy" version = "0.990" @@ -326,17 +222,6 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] -[[package]] -name = "pathspec" -version = "0.9.0" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -files = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, -] - [[package]] name = "platformdirs" version = "2.5.2" @@ -397,28 +282,6 @@ files = [ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] -[[package]] -name = "pycodestyle" -version = "2.11.0" -description = "Python style guide checker" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycodestyle-2.11.0-py2.py3-none-any.whl", hash = "sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8"}, - {file = "pycodestyle-2.11.0.tar.gz", hash = "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0"}, -] - -[[package]] -name = "pyflakes" -version = "3.1.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, - {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, -] - [[package]] name = "pytest" version = "7.1.2" @@ -551,6 +414,32 @@ urllib3 = ">=1.25.10" [package.extras] tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "types-requests"] +[[package]] +name = "ruff" +version = "0.2.2" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"}, + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"}, + {file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"}, + {file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"}, + {file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"}, + {file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"}, +] + [[package]] name = "setuptools" version = "65.5.1" @@ -675,4 +564,4 @@ testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7 [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "3f9b34bd049c277f51cd87be4b85dea774c566a8f53f23d79304520241fe1bae" +content-hash = "4a2db0eed4883c7ea40f2b8eb2e4423d1ec6bab4c8d52e3e1dfb6568983700ed" diff --git a/pyproject.toml b/pyproject.toml index fd54e31..e687562 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,14 +23,12 @@ requests = "^2.26.0" [tool.poetry.group.dev.dependencies] pytest = "^7.1.1" -black = "^23.7.0" -flake8 = "^6.1.0" pre-commit = "^2.17.0" -isort = "^5.12.0" mypy = "^0.990" responses = "^0.22.0" pytest-asyncio = "^0.21.0" types-requests = "^2.31.0.2" +ruff = "^0.2.2" [tool.isort] profile = "black" @@ -38,3 +36,135 @@ profile = "black" [build-system] requires = ["poetry-core>=1.0.8"] build-backend = "poetry.core.masonry.api" + +[tool.ruff] +select = [ + "ASYNC", # flake8-async + "C4", # flake8-comprehensions + "D", # pydocstyle, + "E", "W", # pycodestyle + "F", # pyflakes + "I", # isort + "PL", # pylint + "RUF", # ruff + "S", # flake8-bandit + "SIM", # flake8-simplify + "UP", # pyupgrade +] + +# By default, always show source code snippets. +show-source = true + +ignore = [ + ## D - pydocstyle ## + # D1XX errors are OK. Don't force people into over-documenting. + "D100", "D101", "D102", "D103", "D104", "D105", "D107", + # These need to be fixed. + "D202", "D205", "D400", "D401", + + ## E / W - pycodestyle ## + "E501", # line too long + "E203", # whitespace-before-punctuation + "E741", # ambiguous variable name + + ## PL - pylint ## + # Commented-out rules are rules that we disable in pylint but are not supported by ruff yet. + + # Import order issues + # "PLC0411", # wrong-import-order + # "PLC0412", # wrong-import-position + "PLC0414", # ungrouped-imports + + # Documentation issues + # "C0114", # missing-module-docstring + + # Complexity issues + # "PLR0904", # too-many-public-methods + # "PLC0302", # too-many-lines + # "PLR1702", # too-many-nested-blocks + # "PLR0902", # too-many-instance-attributes + "PLR0911", # too-many-return-statements + "PLR0915", # too-many-statements + "PLR0912", # too-many-branches + # "PLR0903", # too-few-public-methods + # "PLR0914", # too-many-locals + # "PLC0301", # line-too-long + "PLR0913", # too-many-arguments + "PLR2004", # magic-value-comparison + "PLR5501", # collapsible-else-if + "PLW0603", # global-statement + "PLW2901", # redefined-loop-name + "PLC1901", # compare-to-empty-string + + ## RUF - ruff ## + "RUF001", # ambiguous-unicode-character-string + "RUF002", # ambiguous-unicode-character-docstring + "RUF003", # ambiguous-unicode-character-comment + "RUF012", # mutable-class-default + + # Enable when Poetry supports PEP 621 and we migrate our confguration to it. + # See: https://github.com/python-poetry/poetry-core/pull/567 + "RUF200", + + "S101", # assert + "S104", # hardcoded-bind-all-interfaces + "S105", # hardcoded-password-string + "S106", # hardcoded-password-func-arg + "S107", # hardcoded-password-default + "S110", # try-except-pass + "S301", # suspicious-pickle-usage + "S303", # suspicious-insecure-hash-usage + "S310", # suspicious-url-open-usage + "S311", # suspicious-non-cryptographic-random-usage + "S324", # hashlib-insecure-hash-function + "S603", # subprocess-without-shell-equals-true + "S607", # start-process-with-partial-path + "S608", # hardcoded-sql-expression + + ## SIM - flake8-simplify ## + "SIM102", # collapsible-if + "SIM105", # suppressible-exception + "SIM108", # if-else-block-instead-of-if-exp + "SIM114", # if-with-same-arms + "SIM116", # if-else-block-instead-of-dict-lookup + "SIM117", # multiple-with-statements + + # Enable when the rule is out of preview and false-positives are handled. + # See: https://docs.astral.sh/ruff/rules/in-dict-keys/ + "SIM118", # in-dict-keys +] + +extend-exclude = [ + "env", + "runtime", +] + +[tool.ruff.per-file-ignores] +# These files have only a bunch of imports in them to force code loading. +"tests/**" = ["S101"] # Allow assert statement in tests. + +# To import all fixtures from other conftests. +"conftest.py" = ["F401", "F403"] +# To import select fixtures from non-local conftests. +# Importing and using the fixture makes it be shadowed. +"test_*.py" = ["F401", "F811"] + +[tool.ruff.isort] +section-order = [ + "future", + "standard-library", + "third-party", + "parts", + "first-party", + "td-models", + "td-apps", + "local-folder", +] + +[tool.ruff.pydocstyle] +convention = "pep257" + +[tool.ruff.lint.pyupgrade] +# Required by tools like Pydantic that use type information at runtime. +# https://github.com/asottile/pyupgrade/issues/622#issuecomment-1088766572 +keep-runtime-typing = true diff --git a/todoist_api_python/http_requests.py b/todoist_api_python/http_requests.py index c39a138..31cb8e3 100644 --- a/todoist_api_python/http_requests.py +++ b/todoist_api_python/http_requests.py @@ -32,7 +32,7 @@ def post( request_id = data.pop("request_id", None) if data else None headers = create_headers( - token=token, with_content=True if data else False, request_id=request_id + token=token, with_content=bool(data), request_id=request_id ) response = session.post( diff --git a/todoist_api_python/models.py b/todoist_api_python/models.py index 7a78719..ab9d61e 100644 --- a/todoist_api_python/models.py +++ b/todoist_api_python/models.py @@ -9,7 +9,7 @@ @dataclass -class Project(object): +class Project: color: str comment_count: int id: str @@ -42,7 +42,7 @@ def from_dict(cls, obj): @dataclass -class Section(object): +class Section: id: str name: str order: int @@ -59,7 +59,7 @@ def from_dict(cls, obj): @dataclass -class Due(object): +class Due: date: str is_recurring: bool string: str @@ -110,7 +110,7 @@ def from_quick_add_response(cls, obj): @dataclass -class Task(object): +class Task: assignee_id: str | None assigner_id: str | None comment_count: int @@ -252,7 +252,7 @@ def from_quick_add_response(cls, obj): @dataclass -class Collaborator(object): +class Collaborator: id: str email: str name: str @@ -267,7 +267,7 @@ def from_dict(cls, obj): @dataclass -class Attachment(object): +class Attachment: resource_type: str | None = None file_name: str | None = None @@ -302,7 +302,7 @@ def from_dict(cls, obj): @dataclass -class Comment(object): +class Comment: attachment: Attachment | None content: str id: str