# Custom Component pyproject.toml based on Home Assistant Core
# https://github.com/home-assistant/core/blob/dev/pyproject.toml
#
# Sections not included (HA Core specific):
# - [build-system] - Not published to PyPI (installed via HACS)
# - [tool.pylint.*] - Optional additional linting (Ruff is sufficient)

[project]
name = "tibber_prices"
version = "0.0.0"            # Version is managed in manifest.json only
requires-python = ">=3.14.2"

[tool.setuptools]
packages = ["custom_components.tibber_prices"]

[tool.pyright]
include = ["custom_components/tibber_prices"]
exclude = [
    "**/.*",
    "**/__pycache__",
    "**/config",
    "**/docs",
    "**/node_modules",
    "**/venv",
]
venvPath = "."
venv = ".venv"
typeCheckingMode = "basic"
reportUnusedImport = "none"
reportUnusedVariable = "none"
reportUnusedCoroutine = "none"
reportMissingTypeStubs = "none"

[tool.pytest.ini_options]
testpaths = ["tests"]
norecursedirs = [".git", "testing_config"]
log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s"
log_date_format = "%Y-%m-%d %H:%M:%S"
asyncio_debug = true
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
addopts = "-ra -q --strict-markers"
markers = [
    "unit: Unit tests (fast, no external dependencies)",
    "integration: Integration tests (may use coordinator/time service)",
]
filterwarnings = [
    # Treat warnings as errors to catch issues early
    "error",
    # Ignore specific warnings from third-party libraries as needed
    # "ignore:.*custom_components.* is using deprecated.*:DeprecationWarning",
    # AsyncMock cleanup noise when mixing sync/async mocks
    "ignore::pytest.PytestUnraisableExceptionWarning",
]

[tool.coverage.run]
source = ["custom_components/tibber_prices"]
omit = ["tests/*"]

[tool.coverage.report]
exclude_lines = [
    "pragma: no cover",
    "def __repr__",
    "raise AssertionError",
    "raise NotImplementedError",
    "if TYPE_CHECKING:",
    "@overload",
]

[tool.ruff]
# Based on https://github.com/home-assistant/core/blob/dev/pyproject.toml
required-version = ">=0.15.1"
target-version = "py314"
line-length = 120

[tool.ruff.lint]
select = [
    "A001",   # Variable {name} is shadowing a Python builtin
    "ASYNC",  # flake8-async
    "B002",   # Python does not support the unary prefix increment
    "B005",   # Using .strip() with multi-character strings is misleading
    "B007",   # Loop control variable {name} not used within loop body
    "B009",   # Do not call getattr with a constant attribute value. It is not any safer than normal property access.
    "B014",   # Exception handler with duplicate exception
    "B015",   # Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it.
    "B017",   # pytest.raises(BaseException) should be considered evil
    "B018",   # Found useless attribute access. Either assign it to a variable or remove it.
    "B023",   # Function definition does not bind loop variable {name}
    "B024",   # `{name}` is an abstract base class, but it has no abstract methods or properties
    "B025",   # try-except* block with duplicate exception {name}
    "B026",   # Star-arg unpacking after a keyword argument is strongly discouraged
    "B032",   # Possible unintentional type annotation (using :). Did you mean to assign (using =)?
    "B035",   # Dictionary comprehension uses static key
    "B904",   # Use raise from to specify exception cause
    "B905",   # zip() without an explicit strict= parameter
    "BLE",
    "C",      # complexity
    "COM818", # Trailing comma on bare tuple prohibited
    "D",      # docstrings
    "DTZ003", # Use datetime.now(tz=) instead of datetime.utcnow()
    "DTZ004", # Use datetime.fromtimestamp(ts, tz=) instead of datetime.utcfromtimestamp(ts)
    "E",      # pycodestyle
    "F",      # pyflakes/autoflake
    "F541",   # f-string without any placeholders
    "FLY",    # flynt
    "FURB",   # refurb
    "G",      # flake8-logging-format
    "I",      # isort
    "INP",    # flake8-no-pep420
    "ISC",    # flake8-implicit-str-concat
    "ICN001", # import concentions; {name} should be imported as {asname}
    "LOG",    # flake8-logging
    "N804",   # First argument of a class method should be named cls
    "N805",   # First argument of a method should be named self
    "N815",   # Variable {name} in class scope should not be mixedCase
    "PERF",   # Perflint
    "PGH",    # pygrep-hooks
    "PIE",    # flake8-pie
    "PL",     # pylint
    "PT",     # flake8-pytest-style
    "PTH",    # flake8-pathlib
    "PYI",    # flake8-pyi
    "RET",    # flake8-return
    "RSE",    # flake8-raise
    "RUF005", # Consider iterable unpacking instead of concatenation
    "RUF006", # Store a reference to the return value of asyncio.create_task
    "RUF007", # Prefer itertools.pairwise() over zip() when iterating over successive pairs
    "RUF008", # Do not use mutable default values for dataclass attributes
    "RUF010", # Use explicit conversion flag
    "RUF013", # PEP 484 prohibits implicit Optional
    "RUF016", # Slice in indexed access to type {value_type} uses type {index_type} instead of an integer
    "RUF017", # Avoid quadratic list summation
    "RUF018", # Avoid assignment expressions in assert statements
    "RUF019", # Unnecessary key check before dictionary access
    "RUF020", # {never_like} | T is equivalent to T
    "RUF021", # Parenthesize a and b expressions when chaining and and or together, to make the precedence clear
    "RUF022", # Sort __all__
    "RUF023", # Sort __slots__
    "RUF024", # Do not pass mutable objects as values to dict.fromkeys
    "RUF026", # default_factory is a positional-only argument to defaultdict
    "RUF030", # print() call in assert statement is likely unintentional
    "RUF032", # Decimal() called with float literal argument
    "RUF033", # __post_init__ method with argument defaults
    "RUF034", # Useless if-else condition
    "RUF059", # unused-unpacked-variable
    "RUF100", # Unused `noqa` directive
    "RUF101", # noqa directives that use redirected rule codes
    "RUF200", # Failed to parse pyproject.toml: {message}
    "S102",   # Use of exec detected
    "S103",   # bad-file-permissions
    "S108",   # hardcoded-temp-file
    "S306",   # suspicious-mktemp-usage
    "S307",   # suspicious-eval-usage
    "S313",   # suspicious-xmlc-element-tree-usage
    "S314",   # suspicious-xml-element-tree-usage
    "S315",   # suspicious-xml-expat-reader-usage
    "S316",   # suspicious-xml-expat-builder-usage
    "S317",   # suspicious-xml-sax-usage
    "S318",   # suspicious-xml-mini-dom-usage
    "S319",   # suspicious-xml-pull-dom-usage
    "S601",   # paramiko-call
    "S602",   # subprocess-popen-with-shell-equals-true
    "S604",   # call-with-shell-equals-true
    "S608",   # hardcoded-sql-expression
    "S609",   # unix-command-wildcard-injection
    "SIM",    # flake8-simplify
    "SLF",    # flake8-self
    "SLOT",   # flake8-slots
    "T100",   # Trace found: {name} used
    "T20",    # flake8-print
    "TC",     # flake8-type-checking
    "TID",    # Tidy imports
    "TRY",    # tryceratops
    "UP",     # pyupgrade
    "UP031",  # Use format specifiers instead of percent format
    "UP032",  # Use f-string instead of `format` call
    "W",      # pycodestyle
]

ignore = [
    "ANN401",   # Dynamically typed expressions (typing.Any) are disallowed
    "ASYNC109", # Async function definition with a `timeout` parameter Use `asyncio.timeout` instead
    "ASYNC110", # Use `asyncio.Event` instead of awaiting `asyncio.sleep` in a `while` loop
    "ASYNC240", # Use an async function for entering the file system
    "D202",     # No blank lines allowed after function docstring
    "D203",     # 1 blank line required before class docstring
    "D213",     # Multi-line docstring summary should start at the second line
    "D406",     # Section name should end with a newline
    "D407",     # Section name underlining
    "D417",     # Missing argument descriptions in docstring - to allow documenting only non-obvious parameters
    "E501",     # line too long

    "PLC1901", # {existing} can be simplified to {replacement} as an empty string is falsey; too many false positives
    "PLR0911", # Too many return statements ({returns} > {max_returns})
    "PLR0912", # Too many branches ({branches} > {max_branches})
    "PLR0913", # Too many arguments to function call ({c_args} > {max_args})
    "PLR0915", # Too many statements ({statements} > {max_statements})
    "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
    "PLW0108", # Unnecessary lambda wrapping a function call; can often be replaced by the function itself
    "PLW1641", # __eq__ without __hash__
    "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target
    "PT011",   # pytest.raises({exception}) is too broad, set the `match` parameter or use a more specific exception
    "PT018",   # Assertion should be broken down into multiple parts
    "RUF001",  # String contains ambiguous unicode character.
    "RUF002",  # Docstring contains ambiguous unicode character.
    "RUF003",  # Comment contains ambiguous unicode character.
    "RUF015",  # Prefer next(...) over single element slice
    "SIM102",  # Use a single if statement instead of nested if statements
    "SIM103",  # Return the condition {condition} directly
    "SIM108",  # Use ternary operator {contents} instead of if-else-block
    "SIM115",  # Use context handler for opening files

    # Moving imports into type-checking blocks can mess with pytest.patch()
    "TC001", # Move application import {} into a type-checking block
    "TC002", # Move third-party import {} into a type-checking block
    "TC003", # Move standard library import {} into a type-checking block
    # Quotes for typing.cast generally not necessary, only for performance critical paths
    "TC006", # Add quotes to type expression in typing.cast()

    "TRY003", # Avoid specifying long messages outside the exception class
    "TRY400", # Use `logging.exception` instead of `logging.error`

    "UP046", # Non PEP 695 generic class
    "UP047", # Non PEP 696 generic function
    "UP049", # Avoid private type parameter names

    # May conflict with the formatter, https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
    "W191",
    "E111",
    "E114",
    "E117",
    "D203",
    "D206",
    "D212",
    "D300",
    "Q",
    "COM812",
    "COM819",
    "ISC001",

    # Disabled because ruff does not understand type of __all__ generated by a function
    "PLE0605",

    "FURB116",
]

[tool.ruff.lint.flake8-import-conventions.extend-aliases]
# Commonly used Home Assistant imports
voluptuous = "vol"
"homeassistant.helpers.config_validation" = "cv"
"homeassistant.helpers.device_registry" = "dr"
"homeassistant.helpers.entity_registry" = "er"
"homeassistant.util.dt" = "dt_util"

[tool.ruff.lint.flake8-pytest-style]
fixture-parentheses = false
mark-parentheses = false

[tool.ruff.lint.flake8-tidy-imports.banned-api]
"async_timeout".msg = "use asyncio.timeout instead"
"pytz".msg = "use zoneinfo instead"
"tests".msg = "You should not import tests"

[tool.ruff.lint.isort]
force-sort-within-sections = true
known-first-party = ["custom_components", "homeassistant"]
combine-as-imports = true
split-on-trailing-comma = false

[tool.ruff.lint.per-file-ignores]
"script/*" = [
    "T20",    # print() allowed in scripts
    "INP001", # Implicit namespace package (scripts are not a package)
]
"tests/*" = [
    "S101",    # assert is fine in tests
    "PLR2004", # Magic values are fine in tests
    "D",       # Docstrings not required in tests
    "PTH",     # Use pathlib - temporary exemption for tests
]

[tool.ruff.lint.mccabe]
max-complexity = 25

[tool.ruff.lint.pydocstyle]
convention = "google"
property-decorators = ["propcache.api.cached_property"]

[tool.ruff.lint.pyupgrade]
keep-runtime-typing = true

[project.optional-dependencies]
test = ["pytest-homeassistant-custom-component>=0.13.324"]
