mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-03-12 10:31:50 +01:00
## ℹ️ Description Upgrade nodriver dependency from pinned version 0.39.0 to latest 0.47.0 to resolve browser startup issues and JavaScript evaluation problems that affected versions 0.40-0.44. - Link to the related issue(s): Resolves nodriver compatibility issues - This upgrade addresses browser startup problems and window.BelenConf evaluation failures that were blocking the use of newer nodriver versions. ## 📋 Changes Summary - Updated nodriver dependency from pinned 0.39.0 to >=0.47.0 in pyproject.toml - Fixed RemoteObject handling in web_execute method for nodriver 0.47 compatibility - Added comprehensive BelenConf test fixture with real production data structure - Added integration test to validate window.BelenConf evaluation works correctly - Added German translation for new error message - Replaced real user data with privacy-safe dummy data in test fixtures ### 🔧 Type Safety Improvements **Added explicit `str()` conversions to resolve type inference issues:** The comprehensive BelenConf test fixture contains deeply nested data structures that caused pyright's type checker to infer complex dictionary types throughout the codebase. To ensure type safety and prevent runtime errors, I added explicit `str()` conversions in key locations: - **CSRF tokens**: `str(csrf_token)` - Ensures CSRF tokens are treated as strings - **Special attributes**: `str(special_attribute_value)` - Converts special attribute values to strings - **DOM attributes**: `str(special_attr_elem.attrs.id)` - Ensures element IDs are strings - **URL handling**: `str(current_img_url)` and `str(href_attributes)` - Converts URLs and href attributes to strings - **Price values**: `str(ad_cfg.price)` - Ensures price values are strings These conversions are defensive programming measures that ensure backward compatibility and prevent type-related runtime errors, even if the underlying data structures change in the future. ### ⚙️ Type of Change - [x] ✨ New feature (adds new functionality without breaking existing usage) - [ ] 🐞 Bug fix (non-breaking change which fixes an issue) - [ ] 💥 Breaking change (changes that might break existing user setups, scripts, or configurations) ## ✅ Checklist Before requesting a review, confirm the following: - [x] I have reviewed my changes to ensure they meet the project's standards. - [x] I have tested my changes and ensured that all tests pass (`pdm run test`). - [x] I have formatted the code (`pdm run format`). - [x] I have verified that linting passes (`pdm run lint`). - [x] I have updated documentation where necessary. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
392 lines
14 KiB
TOML
392 lines
14 KiB
TOML
# https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/
|
|
#
|
|
# SPDX-FileCopyrightText: © Sebastian Thomschke and contributors
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
# SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
|
#
|
|
|
|
[build-system] # https://backend.pdm-project.org/
|
|
requires = ["pdm-backend"]
|
|
build-backend = "pdm.backend"
|
|
|
|
[project]
|
|
name = "kleinanzeigen-bot"
|
|
dynamic = ["version"]
|
|
description = "Command line tool to publish ads on kleinanzeigen.de"
|
|
readme = "README.md"
|
|
authors = [
|
|
{name = "sebthom", email = "sebthom@users.noreply.github.com"},
|
|
]
|
|
license = {text = "AGPL-3.0-or-later"}
|
|
|
|
classifiers = [ # https://pypi.org/classifiers/
|
|
"Private :: Do Not Upload",
|
|
|
|
"Development Status :: 5 - Production/Stable",
|
|
"Environment :: Console",
|
|
"Operating System :: OS Independent",
|
|
|
|
"Intended Audience :: End Users/Desktop",
|
|
"Topic :: Office/Business",
|
|
|
|
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.10"
|
|
]
|
|
requires-python = ">=3.10,<3.14"
|
|
dependencies = [
|
|
"certifi",
|
|
"colorama",
|
|
"jaraco.text", # required by pkg_resources during runtime
|
|
"nodriver>=0.47.0", # Updated from 0.39.0 - 0.40-0.44 had issues starting browsers and evaluating self.web_execute("window.BelenConf") fails
|
|
"pydantic>=2.0.0",
|
|
"ruamel.yaml",
|
|
"psutil",
|
|
"wcmatch",
|
|
"sanitize-filename>=1.2.0",
|
|
]
|
|
|
|
[dependency-groups] # https://peps.python.org/pep-0735/
|
|
dev = [
|
|
"pip-audit",
|
|
"pytest>=8.3.4",
|
|
"pytest-asyncio>=0.25.3",
|
|
"pytest-rerunfailures",
|
|
"pytest-cov>=6.0.0",
|
|
"ruff",
|
|
"mypy",
|
|
"basedpyright",
|
|
"autopep8",
|
|
"yamlfix",
|
|
"pyinstaller",
|
|
"platformdirs",
|
|
"types-requests>=2.32.0.20250515",
|
|
"pytest-mock>=3.14.0",
|
|
]
|
|
|
|
[project.urls]
|
|
Homepage = "https://github.com/Second-Hand-Friends/kleinanzeigen-bot"
|
|
Repository = "https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git"
|
|
Documentation = "https://github.com/Second-Hand-Friends/kleinanzeigen-bot/README.md"
|
|
Issues = "https://github.com/Second-Hand-Friends/kleinanzeigen-bot/issues"
|
|
CI = "https://github.com/Second-Hand-Friends/kleinanzeigen-bot/actions"
|
|
|
|
|
|
#####################
|
|
# pdm https://github.com/pdm-project/pdm/
|
|
#####################
|
|
[tool.pdm.version] # https://backend.pdm-project.org/metadata/#dynamic-project-version
|
|
source = "call"
|
|
getter = "version:get_version" # uses get_version() of <project_root>/version.py
|
|
write_to = "kleinanzeigen_bot/_version.py"
|
|
write_template = "__version__ = '{}'\n"
|
|
|
|
[tool.pdm.scripts] # https://pdm-project.org/latest/usage/scripts/
|
|
app = "python -m kleinanzeigen_bot"
|
|
debug = "python -m pdb -m kleinanzeigen_bot"
|
|
|
|
# build & packaging
|
|
generate-schemas = "python scripts/generate_schemas.py"
|
|
compile.cmd = "python -O -m PyInstaller pyinstaller.spec --clean --workpath .temp"
|
|
compile.env = {PYTHONHASHSEED = "1", SOURCE_DATE_EPOCH = "0"} # https://pyinstaller.org/en/stable/advanced-topics.html#creating-a-reproducible-build
|
|
|
|
deps = "pdm list --fields name,version,groups"
|
|
"deps:tree" = "pdm list --tree"
|
|
"deps:runtime" = "pdm list --fields name,version,groups --include default"
|
|
"deps:runtime:tree" = "pdm list --tree --include default"
|
|
|
|
# format & lint
|
|
format = { composite = ["format:py", "format:yaml"] }
|
|
"format:py" = { shell = "autopep8 --recursive --in-place scripts src tests --verbose && python scripts/post_autopep8.py scripts src tests" }
|
|
"format:yaml" = "yamlfix scripts/ src/ tests/"
|
|
|
|
lint = { composite = ["lint:ruff", "lint:mypy", "lint:pyright"] }
|
|
"lint:ruff" = "ruff check --preview"
|
|
"lint:mypy" = "mypy"
|
|
"lint:pyright" = "basedpyright"
|
|
"lint:fix" = {shell = "ruff check --preview --fix" }
|
|
|
|
# tests
|
|
# Run unit tests only (exclude smoke and itest)
|
|
utest = "python -m pytest --capture=tee-sys -m \"not itest and not smoke\""
|
|
# Run integration tests only (exclude smoke)
|
|
itest = "python -m pytest --capture=tee-sys -m \"itest and not smoke\""
|
|
# Run smoke tests only
|
|
smoke = "python -m pytest --capture=tee-sys -m smoke"
|
|
# Run all tests in order: unit, integration, smoke
|
|
# (for CI: run these three scripts in sequence)
|
|
test = { composite = ["utest", "itest", "smoke"] }
|
|
# Run all tests in a single invocation for unified summary (unit tests run first)
|
|
"test:unified" = "python -m pytest --capture=tee-sys"
|
|
#
|
|
# Coverage scripts:
|
|
# - Each group writes its own data file to .temp/.coverage.<group>.xml
|
|
#
|
|
"test:cov" = { composite = ["utest:cov", "itest:cov", "smoke:cov", "coverage:combine"] }
|
|
"utest:cov" = { shell = "python -m pytest --capture=tee-sys -m \"not itest and not smoke\" --cov=src/kleinanzeigen_bot --cov-report=xml:.temp/coverage-unit.xml --cov-append" }
|
|
"itest:cov" = { shell = "python -m pytest --capture=tee-sys -m \"itest and not smoke\" --cov=src/kleinanzeigen_bot --cov-report=xml:.temp/coverage-integration.xml --cov-append" }
|
|
"smoke:cov" = { shell = "python -m pytest --capture=tee-sys -m smoke --cov=src/kleinanzeigen_bot --cov-report=xml:.temp/coverage-smoke.xml --cov-append" }
|
|
"coverage:combine" = { shell = "coverage report -m" }
|
|
# Run all tests with coverage in a single invocation
|
|
"test:cov:unified" = "python -m pytest --capture=tee-sys --cov=src/kleinanzeigen_bot --cov-report=term-missing"
|
|
|
|
# Test script structure:
|
|
# - Composite test groups (unit, integration, smoke) are run in order to fail fast and surface critical errors early.
|
|
# - This prevents running all tests if a foundational component is broken, saving time.
|
|
# - Each group is covered and reported separately.
|
|
#
|
|
# See docs/TESTING.md for more details.
|
|
|
|
|
|
#####################
|
|
# autopep8
|
|
# https://pypi.org/project/autopep8/
|
|
# https://github.com/hhatto/autopep8
|
|
#####################
|
|
[tool.autopep8]
|
|
max_line_length = 160
|
|
ignore = [ # https://github.com/hhatto/autopep8#features
|
|
"E124", # Don't change indention of multi-line statements
|
|
"E128", # Don't change indention of multi-line statements
|
|
"E231", # Don't add whitespace after colon (:) on type declaration
|
|
"E251", # Don't remove whitespace around parameter '=' sign.
|
|
"E401" # Don't put imports on separate lines
|
|
]
|
|
aggressive = 3
|
|
|
|
|
|
#####################
|
|
# ruff
|
|
# https://pypi.org/project/ruff/
|
|
# https://docs.astral.sh/ruff/configuration/
|
|
#####################
|
|
[tool.ruff]
|
|
cache-dir = ".temp/cache_ruff"
|
|
include = ["pyproject.toml", "scripts/**/*.py", "src/**/*.py", "tests/**/*.py"]
|
|
line-length = 160
|
|
indent-width = 4
|
|
target-version = "py310"
|
|
|
|
[tool.ruff.lint]
|
|
# https://docs.astral.sh/ruff/rules/
|
|
select = [
|
|
"A", # flake8-builtins
|
|
"ARG", # flake8-unused-arguments
|
|
"ANN", # flake8-annotations
|
|
"ASYNC", # flake8-async
|
|
#"BLE", # flake8-blind-except
|
|
"B", # flake8-bugbear
|
|
"C4", # flake8-comprehensions
|
|
"COM", # flake8-commas
|
|
"CPY", # flake8-copyright
|
|
"DTZ", # flake8-datetimez
|
|
#"EM", # flake8-errmsg
|
|
#"ERA", # eradicate commented-out code
|
|
"EXE", # flake8-executable
|
|
"FA", # flake8-future-annotations
|
|
"FBT", # flake8-boolean-trap
|
|
"FIX", # flake8-fixme
|
|
"G", # flake8-logging-format
|
|
"ICN", # flake8-import-conventions
|
|
"ISC", # flake8-implicit-str-concat
|
|
"INP", # flake8-no-pep420
|
|
"INT", # flake8-gettext
|
|
"LOG", # flake8-logging
|
|
"PIE", # flake8-pie
|
|
"PT", # flake8-pytest-style
|
|
#"PTH", # flake8-use-pathlib
|
|
"PYI", # flake8-pyi
|
|
"Q", # flake8-quotes
|
|
"RET", # flake8-return
|
|
"RSE", # flake8-raise
|
|
"S", # flake8-bandit
|
|
"SIM", # flake8-simplify
|
|
"SLF", # flake8-self
|
|
"SLOT", # flake8-slots
|
|
"T10", # flake8-debugger
|
|
#"T20", # flake8-print
|
|
"TC", # flake8-type-checking
|
|
"TD", # flake8-todo
|
|
"TID", # flake8-flake8-tidy-import
|
|
"YTT", # flake8-2020
|
|
|
|
"E", # pycodestyle-errors
|
|
"W", # pycodestyle-warnings
|
|
|
|
#"C90", # mccabe
|
|
"D", # pydocstyle
|
|
"F", # pyflakes
|
|
"FLY", # flynt
|
|
"I", # isort
|
|
"PERF", # perflint
|
|
"PGH", # pygrep-hooks
|
|
"PL", # pylint
|
|
]
|
|
ignore = [
|
|
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed
|
|
"ASYNC210", # TODO Async functions should not call blocking HTTP methods
|
|
"ASYNC230", # TODO Async functions should not open files with blocking methods like `open`
|
|
"ASYNC240", # TODO Async functions should not use os.path methods, use trio.Path or anyio.path
|
|
"ASYNC250", # TODO Blocking call to input() in async context
|
|
"COM812", # Trailing comma missing
|
|
"D1", # Missing docstring in ...
|
|
"D200", # One-line docstring should fit on one line
|
|
"D202", # No blank lines allowed after function docstring (found 1)
|
|
"D203", # 1 blank line required before class docstring
|
|
"D204", # 1 blank line required after class docstring
|
|
"D205", # 1 blank line required between summary line and description
|
|
"D209", # Multi-line docstring closing quotes should be on a separate line"
|
|
"D212", # Multi-line docstring summary should start at the first line
|
|
"D213", # Multi-line docstring summary should start at the second line
|
|
"D400", # First line should end with a period
|
|
"D401", # First line of docstring should be in imperative mood
|
|
"D402", # First line should not be the function's signature
|
|
"D404", # First word of the docstring should not be "This"
|
|
"D413", # Missing blank line after last section ("Returns")"
|
|
"D415", # First line should end with a period, question mark, or exclamation point
|
|
"D417", # Missing argument description in the docstring for
|
|
#"E124", # Don't change indention of multi-line statements
|
|
#"E128", # Don't change indention of multi-line statements
|
|
"E231", # Don't add whitespace after colon (:) on type declaration
|
|
"E251", # Don't remove whitespace around parameter '=' sign.
|
|
"E401", # Don't put imports on separate lines
|
|
"FIX002", # Line contains TODO, consider resolving the issue
|
|
"PERF203", # `try`-`except` within a loop incurs performance overhead
|
|
"RET504", # Unnecessary assignment to `...` before `return` statement
|
|
"PLR6301", # Method `...` could be a function, class method, or static method
|
|
"PLR0913", # Too many arguments in function definition (needed to match parent signature)
|
|
"PYI041", # Use `float` instead of `int | float`
|
|
"SIM102", # Use a single `if` statement instead of nested `if` statements
|
|
"SIM105", # Use `contextlib.suppress(TimeoutError)` instead of `try`-`except`-`pass`
|
|
"SIM114", # Combine `if` branches using logical `or` operator
|
|
"TC006", # Add quotes to type expression in `typing.cast()`
|
|
"TD002", # Missing author in TODO
|
|
"TD003", # Missing issue link for this TODO
|
|
]
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
"scripts/**/*.py" = [
|
|
"INP001", # File `...` is part of an implicit namespace package. Add an `__init__.py`.
|
|
]
|
|
"tests/**/*.py" = [
|
|
"ARG",
|
|
"B",
|
|
"FBT",
|
|
"INP",
|
|
"SLF",
|
|
"S101", # Use of `assert` detected
|
|
"PLR0904", # Too many public methods (12 > 10)
|
|
"PLR2004", # Magic value used in comparison
|
|
]
|
|
|
|
[tool.ruff.lint.flake8-copyright]
|
|
notice-rgx = "SPDX-FileCopyrightText: .*"
|
|
min-file-size = 256
|
|
|
|
|
|
[tool.ruff.lint.isort]
|
|
# combine-straight-imports = true # not (yet) supported by ruff
|
|
|
|
[tool.ruff.lint.pylint]
|
|
# https://pylint.pycqa.org/en/latest/user_guide/configuration/all-options.html#design-checker
|
|
# https://pylint.pycqa.org/en/latest/user_guide/checkers/features.html#design-checker-messages
|
|
max-args = 6 # max. number of args for function / method (R0913)
|
|
# max-attributes = 15 # TODO max. number of instance attrs for a class (R0902)
|
|
max-branches = 45 # max. number of branch for function / method body (R0912)
|
|
max-locals = 30 # max. number of local vars for function / method body (R0914)
|
|
max-returns = 15 # max. number of return / yield for function / method body (R0911)
|
|
max-statements = 150 # max. number of statements in function / method body (R0915)
|
|
max-public-methods = 25 # max. number of public methods for a class (R0904)
|
|
# max-positional-arguments = 5 # max. number of positional args for function / method (R0917)
|
|
|
|
|
|
#####################
|
|
# mypy
|
|
# https://github.com/python/mypy
|
|
#####################
|
|
[tool.mypy]
|
|
# https://mypy.readthedocs.io/en/stable/config_file.html
|
|
#mypy_path = "$MYPY_CONFIG_FILE_DIR/tests/stubs"
|
|
cache_dir = ".temp/cache_mypy"
|
|
python_version = "3.10"
|
|
files = "scripts,src,tests"
|
|
strict = true
|
|
disallow_untyped_calls = false
|
|
disallow_untyped_defs = true
|
|
disallow_incomplete_defs = true
|
|
ignore_missing_imports = true
|
|
show_error_codes = true
|
|
warn_unused_ignores = true
|
|
verbosity = 0
|
|
|
|
|
|
#####################
|
|
# basedpyright
|
|
# https://github.com/detachhead/basedpyright
|
|
#####################
|
|
[tool.basedpyright]
|
|
# https://docs.basedpyright.com/latest/configuration/config-files/
|
|
include = ["scripts", "src", "tests"]
|
|
defineConstant = { DEBUG = false }
|
|
pythonVersion = "3.10"
|
|
typeCheckingMode = "standard"
|
|
|
|
|
|
#####################
|
|
# pytest
|
|
# https://pypi.org/project/pytest/
|
|
#####################
|
|
[tool.pytest.ini_options]
|
|
cache_dir = ".temp/cache_pytest"
|
|
testpaths = [
|
|
"src",
|
|
"tests"
|
|
]
|
|
# https://docs.pytest.org/en/stable/reference.html#confval-addopts
|
|
addopts = """
|
|
--strict-markers
|
|
--doctest-modules
|
|
--cov-report=term-missing
|
|
"""
|
|
markers = [
|
|
"smoke: marks a test as a high-level smoke test (critical path, no mocks)",
|
|
"itest: marks a test as an integration test (i.e. a test with external dependencies)",
|
|
"asyncio: mark test as async"
|
|
]
|
|
asyncio_mode = "auto"
|
|
asyncio_default_fixture_loop_scope = "function"
|
|
filterwarnings = [
|
|
"ignore:Exception ignored in:pytest.PytestUnraisableExceptionWarning",
|
|
"ignore::DeprecationWarning"
|
|
]
|
|
|
|
[tool.coverage.run]
|
|
# https://coverage.readthedocs.io/en/latest/config.html#run
|
|
data_file = ".temp/coverage.sqlite"
|
|
branch = true # track branch coverage
|
|
relative_files = true
|
|
|
|
[tool.coverage.report]
|
|
precision = 2
|
|
show_missing = true
|
|
skip_covered = false
|
|
|
|
#####################
|
|
# yamlfix
|
|
# https://lyz-code.github.io/yamlfix/
|
|
#####################
|
|
[tool.yamlfix]
|
|
allow_duplicate_keys = true
|
|
comments_min_spaces_from_content = 2
|
|
comments_require_starting_space = false # FIXME should be true but rule is buggy
|
|
comments_whitelines = 1
|
|
section_whitelines = 1
|
|
explicit_start = false
|
|
indentation = 2
|
|
line_length = 1024
|
|
preserve_quotes = true
|
|
quote_basic_values = false
|
|
quote_keys_and_basic_values = false
|
|
quote_representation = '"'
|
|
whitelines = 1
|