Skip to content

Add Python 3.14 RC1 to CI, bump tool versions #159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# applying pre-commit hooks to the project
e91101b37f82558db84a6b8ee9a6dba1fd2ae0bb
e91101b37f82558db84a6b8ee9a6dba1fd2ae0bb
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
pip install -U pip
pip install -U tox
tox

pre-commit:
runs-on: ubuntu-latest
steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14.0-rc.1"]

steps:
- uses: actions/checkout@v4
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy3.10", "pypy3.11"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14.0-rc.1", "pypy3.10", "pypy3.11"]

steps:
- uses: actions/checkout@v4

- name: Install system libraries
if: contains(matrix.python-version, 'pypy')
if: contains(matrix.python-version, 'pypy') || contains(matrix.python-version, '3.14.0-rc')
run: |
sudo apt-get update
sudo apt-get install libxml2-dev libxslt-dev
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14.0-rc.1"]

steps:
- uses: actions/checkout@v4
Expand Down
19 changes: 17 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.2
rev: v0.12.5
hooks:
- id: ruff
- id: ruff-check
args: [ --fix ]
- id: ruff-format
- repo: https://github.com/adamchainz/blacken-docs
rev: 1.19.1
hooks:
- id: blacken-docs
additional_dependencies:
- black==25.1.0
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/sphinx-contrib/sphinx-lint
rev: v1.0.0
hooks:
- id: sphinx-lint
98 changes: 49 additions & 49 deletions cssselect/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,9 +441,9 @@ class Hash:
Represents selector#id
"""

def __init__(self, selector: Tree, id: str) -> None:
def __init__(self, selector: Tree, id_: str) -> None:
self.selector = selector
self.id = id
self.id = id_

def __repr__(self) -> str:
return f"{self.__class__.__name__}[{self.selector!r}#{self.id}]"
Expand Down Expand Up @@ -660,13 +660,13 @@ def parse_simple_selector(
argument, argument_pseudo_element = parse_simple_selector(
stream, inside_negation=True
)
next = stream.next()
next_ = stream.next()
if argument_pseudo_element:
raise SelectorSyntaxError(
f"Got pseudo-element ::{argument_pseudo_element} inside :not() at {next.pos}"
f"Got pseudo-element ::{argument_pseudo_element} inside :not() at {next_.pos}"
)
if next != ("DELIM", ")"):
raise SelectorSyntaxError(f"Expected ')', got {next}")
if next_ != ("DELIM", ")"):
raise SelectorSyntaxError(f"Expected ')', got {next_}")
result = Negation(result, argument)
elif ident.lower() == "has":
combinator, arguments = parse_relative_selector(stream)
Expand All @@ -687,46 +687,46 @@ def parse_simple_selector(
return result, pseudo_element


def parse_arguments(stream: TokenStream) -> list[Token]:
def parse_arguments(stream: TokenStream) -> list[Token]: # noqa: RET503
arguments: list[Token] = []
while 1: # noqa: RET503
while 1:
stream.skip_whitespace()
next = stream.next()
if next.type in ("IDENT", "STRING", "NUMBER") or next in [
next_ = stream.next()
if next_.type in ("IDENT", "STRING", "NUMBER") or next_ in [
("DELIM", "+"),
("DELIM", "-"),
]:
arguments.append(next)
elif next == ("DELIM", ")"):
arguments.append(next_)
elif next_ == ("DELIM", ")"):
return arguments
else:
raise SelectorSyntaxError(f"Expected an argument, got {next}")
raise SelectorSyntaxError(f"Expected an argument, got {next_}")


def parse_relative_selector(stream: TokenStream) -> tuple[Token, Selector]:
def parse_relative_selector(stream: TokenStream) -> tuple[Token, Selector]: # noqa: RET503
stream.skip_whitespace()
subselector = ""
next = stream.next()
next_ = stream.next()

if next in [("DELIM", "+"), ("DELIM", "-"), ("DELIM", ">"), ("DELIM", "~")]:
combinator = next
if next_ in [("DELIM", "+"), ("DELIM", "-"), ("DELIM", ">"), ("DELIM", "~")]:
combinator = next_
stream.skip_whitespace()
next = stream.next()
next_ = stream.next()
else:
combinator = Token("DELIM", " ", pos=0)

while 1: # noqa: RET503
if next.type in ("IDENT", "STRING", "NUMBER") or next in [
while 1:
if next_.type in ("IDENT", "STRING", "NUMBER") or next_ in [
("DELIM", "."),
("DELIM", "*"),
]:
subselector += cast("str", next.value)
elif next == ("DELIM", ")"):
subselector += cast("str", next_.value)
elif next_ == ("DELIM", ")"):
result = parse(subselector)
return combinator, result[0]
else:
raise SelectorSyntaxError(f"Expected an argument, got {next}")
next = stream.next()
raise SelectorSyntaxError(f"Expected an argument, got {next_}")
next_ = stream.next()


def parse_simple_selector_arguments(stream: TokenStream) -> list[Tree]:
Expand All @@ -738,16 +738,16 @@ def parse_simple_selector_arguments(stream: TokenStream) -> list[Tree]:
f"Got pseudo-element ::{pseudo_element} inside function"
)
stream.skip_whitespace()
next = stream.next()
if next in (("EOF", None), ("DELIM", ",")):
next_ = stream.next()
if next_ in (("EOF", None), ("DELIM", ",")):
stream.next()
stream.skip_whitespace()
arguments.append(result)
elif next == ("DELIM", ")"):
elif next_ == ("DELIM", ")"):
arguments.append(result)
break
else:
raise SelectorSyntaxError(f"Expected an argument, got {next}")
raise SelectorSyntaxError(f"Expected an argument, got {next_}")
return arguments


Expand All @@ -772,26 +772,26 @@ def parse_attrib(selector: Tree, stream: TokenStream) -> Attrib:
namespace = op = None
if op is None:
stream.skip_whitespace()
next = stream.next()
if next == ("DELIM", "]"):
next_ = stream.next()
if next_ == ("DELIM", "]"):
return Attrib(selector, namespace, cast("str", attrib), "exists", None)
if next == ("DELIM", "="):
if next_ == ("DELIM", "="):
op = "="
elif next.is_delim("^", "$", "*", "~", "|", "!") and (
elif next_.is_delim("^", "$", "*", "~", "|", "!") and (
stream.peek() == ("DELIM", "=")
):
op = cast("str", next.value) + "="
op = cast("str", next_.value) + "="
stream.next()
else:
raise SelectorSyntaxError(f"Operator expected, got {next}")
raise SelectorSyntaxError(f"Operator expected, got {next_}")
stream.skip_whitespace()
value = stream.next()
if value.type not in ("IDENT", "STRING"):
raise SelectorSyntaxError(f"Expected string or ident, got {value}")
stream.skip_whitespace()
next = stream.next()
if next != ("DELIM", "]"):
raise SelectorSyntaxError(f"Expected ']', got {next}")
next_ = stream.next()
if next_ != ("DELIM", "]"):
raise SelectorSyntaxError(f"Expected ']', got {next_}")
return Attrib(selector, namespace, cast("str", attrib), op, value)


Expand Down Expand Up @@ -1015,9 +1015,9 @@ def next(self) -> Token:
assert self.peeked is not None
self.used.append(self.peeked)
return self.peeked
next = self.next_token()
self.used.append(next)
return next
next_ = self.next_token()
self.used.append(next_)
return next_

def peek(self) -> Token:
if not self._peeking:
Expand All @@ -1027,18 +1027,18 @@ def peek(self) -> Token:
return self.peeked

def next_ident(self) -> str:
next = self.next()
if next.type != "IDENT":
raise SelectorSyntaxError(f"Expected ident, got {next}")
return cast("str", next.value)
next_ = self.next()
if next_.type != "IDENT":
raise SelectorSyntaxError(f"Expected ident, got {next_}")
return cast("str", next_.value)

def next_ident_or_star(self) -> str | None:
next = self.next()
if next.type == "IDENT":
return next.value
if next == ("DELIM", "*"):
next_ = self.next()
if next_.type == "IDENT":
return next_.value
if next_ == ("DELIM", "*"):
return None
raise SelectorSyntaxError(f"Expected ident or '*', got {next}")
raise SelectorSyntaxError(f"Expected ident or '*', got {next_}")

def skip_whitespace(self) -> None:
peek = self.peek()
Expand Down
8 changes: 4 additions & 4 deletions cssselect/xpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,7 @@ def __init__(self, xhtml: bool = False) -> None:
self.lower_case_element_names = True
self.lower_case_attribute_names = True

def xpath_checked_pseudo(self, xpath: XPathExpr) -> XPathExpr: # type: ignore[override]
def xpath_checked_pseudo(self, xpath: XPathExpr) -> XPathExpr:
# FIXME: is this really all the elements?
return xpath.add_condition(
"(@selected and name(.) = 'option') or "
Expand All @@ -850,15 +850,15 @@ def xpath_lang_function(self, xpath: XPathExpr, function: Function) -> XPathExpr
f"'-'), {arg})]"
)

def xpath_link_pseudo(self, xpath: XPathExpr) -> XPathExpr: # type: ignore[override]
def xpath_link_pseudo(self, xpath: XPathExpr) -> XPathExpr:
return xpath.add_condition(
"@href and (name(.) = 'a' or name(.) = 'link' or name(.) = 'area')"
)

# Links are never visited, the implementation for :visited is the same
# as in GenericTranslator

def xpath_disabled_pseudo(self, xpath: XPathExpr) -> XPathExpr: # type: ignore[override]
def xpath_disabled_pseudo(self, xpath: XPathExpr) -> XPathExpr:
# http://www.w3.org/TR/html5/section-index.html#attributes-1
return xpath.add_condition(
"""
Expand Down Expand Up @@ -888,7 +888,7 @@ def xpath_disabled_pseudo(self, xpath: XPathExpr) -> XPathExpr: # type: ignore[
# FIXME: in the second half, add "and is not a descendant of that
# fieldset element's first legend element child, if any."

def xpath_enabled_pseudo(self, xpath: XPathExpr) -> XPathExpr: # type: ignore[override]
def xpath_enabled_pseudo(self, xpath: XPathExpr) -> XPathExpr:
# http://www.w3.org/TR/html5/section-index.html#attributes-1
return xpath.add_condition(
"""
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

# General information about the project.
project = "cssselect"
copyright = "2012-2017, Simon Sapin, Scrapy developers"
project_copyright = "2012-2017, Simon Sapin, Scrapy developers"

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
Expand Down
16 changes: 14 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
Expand Down Expand Up @@ -104,10 +105,16 @@ testpaths = ["tests"]

[tool.ruff.lint]
extend-select = [
# flake8-builtins
"A",
# flake8-async
"ASYNC",
# flake8-bugbear
"B",
# flake8-comprehensions
"C4",
# flake8-commas
"COM",
# pydocstyle
"D",
# flake8-future-annotations
Expand All @@ -130,6 +137,8 @@ extend-select = [
"PIE",
# pylint
"PL",
# flake8-pytest-style
"PT",
# flake8-use-pathlib
"PTH",
# flake8-pyi
Expand Down Expand Up @@ -160,6 +169,8 @@ extend-select = [
"YTT",
]
ignore = [
# Trailing comma missing
"COM812",
# Missing docstring in public module
"D100",
# Missing docstring in public class
Expand Down Expand Up @@ -212,9 +223,10 @@ ignore = [
"RUF012",
# Use of `assert` detected
"S101",
# Using lxml to parse untrusted data is known to be vulnerable to XML attacks
"S320",
]

[tool.ruff.lint.isort]
split-on-trailing-comma = false

[tool.ruff.lint.pydocstyle]
convention = "pep257"
Loading
Loading