Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pyquery for openSUSE:Factory checked in at 2026-08-25 13:18:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyquery (Old) and /work/SRC/openSUSE:Factory/.python-pyquery.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyquery" Tue Aug 25 13:18:11 2026 rev:28 rq:1373132 version:2.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyquery/python-pyquery.changes 2025-09-25 18:44:15.651573281 +0200 +++ /work/SRC/openSUSE:Factory/.python-pyquery.new.1258/python-pyquery.changes 2026-08-25 13:18:17.824167979 +0200 @@ -1,0 +2,10 @@ +Sat Aug 22 19:00:58 UTC 2026 - Matej Cepl <[email protected]> + +- Update to 2.1.0: + * Drop support for Python 3.9 and 3.10, add support for Python 3.13. + * Drop unicode_literals and modernize formatting with f-strings. + * Empty/whitespace-only markup now returns empty document matching jQuery. + * Handle option value fallback to option text. +- Refresh make_webtest_optional.patch + +------------------------------------------------------------------- Old: ---- pyquery-2.0.1.tar.gz New: ---- pyquery-2.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyquery.spec ++++++ --- /var/tmp/diff_new_pack.IaXotM/_old 2026-08-25 13:18:18.575194438 +0200 +++ /var/tmp/diff_new_pack.IaXotM/_new 2026-08-25 13:18:18.576194474 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-pyquery # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -27,7 +27,7 @@ %endif %{?sle15_python_module_pythons} Name: python-pyquery%{psuffix} -Version: 2.0.1 +Version: 2.1.0 Release: 0 Summary: A jQuery-like library for python License: BSD-3-Clause @@ -36,14 +36,14 @@ # PATCH-FIX-OPENSUSE make_webtest_optional.patch [email protected] # Make it possible to run test suite (albeit partial) without WebTest module Patch0: make_webtest_optional.patch -BuildRequires: %{python_module cssselect >= 1.2.0} +BuildRequires: %{python_module cssselect >= 1.5.0} BuildRequires: %{python_module lxml >= 2.1} BuildRequires: %{python_module pip} BuildRequires: %{python_module setuptools} BuildRequires: %{python_module wheel} BuildRequires: fdupes BuildRequires: python-rpm-macros -Requires: python-cssselect >= 1.2.0 +Requires: python-cssselect >= 1.5.0 Requires: python-lxml >= 2.1 BuildArch: noarch %if %{with test} ++++++ make_webtest_optional.patch ++++++ --- /var/tmp/diff_new_pack.IaXotM/_old 2026-08-25 13:18:18.611195707 +0200 +++ /var/tmp/diff_new_pack.IaXotM/_new 2026-08-25 13:18:18.617195918 +0200 @@ -1,8 +1,10 @@ -diff -Nru pyquery-2.0.1.orig/conftest.py pyquery-2.0.1/conftest.py ---- pyquery-2.0.1.orig/conftest.py 2024-08-30 10:12:19.000000000 +0200 -+++ pyquery-2.0.1/conftest.py 2024-11-15 14:36:47.617602776 +0100 -@@ -1,7 +1,12 @@ - import os +Index: pyquery-2.1.0/conftest.py +=================================================================== +--- pyquery-2.1.0.orig/conftest.py 2026-07-27 21:00:02.000000000 +0200 ++++ pyquery-2.1.0/conftest.py 2026-08-22 20:58:58.803206680 +0200 +@@ -2,8 +2,12 @@ + from urllib.request import urlopen + import pytest -from webtest import http -from webtest.debugapp import debug_app @@ -12,30 +14,34 @@ + from webtest.debugapp import debug_app +except (ImportError, ModuleNotFoundError): + http = None -+ - from urllib.request import urlopen -diff -Nru pyquery-2.0.1.orig/tests/test_pyquery.py pyquery-2.0.1/tests/test_pyquery.py ---- pyquery-2.0.1.orig/tests/test_pyquery.py 2024-08-30 10:12:19.000000000 +0200 -+++ pyquery-2.0.1/tests/test_pyquery.py 2024-11-15 14:36:47.607602471 +0100 -@@ -7,9 +7,12 @@ + @pytest.fixture +Index: pyquery-2.1.0/tests/test_pyquery.py +=================================================================== +--- pyquery-2.1.0.orig/tests/test_pyquery.py 2026-07-27 21:00:02.000000000 +0200 ++++ pyquery-2.1.0/tests/test_pyquery.py 2026-08-22 20:59:04.549266706 +0200 +@@ -4,12 +4,16 @@ + import os + import sys + import time +-from unittest import TestCase ++from unittest import TestCase, skipIf + + import pytest from lxml import etree - from pyquery.pyquery import PyQuery as pq, no_default - from pyquery.openers import HAS_REQUEST -from webtest import http -from webtest.debugapp import debug_app --from unittest import TestCase -+from unittest import TestCase, skipIf ++ +try: + from webtest import http + from webtest.debugapp import debug_app +except (ImportError, ModuleNotFoundError): + http = None - sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) - -@@ -888,6 +891,7 @@ + from pyquery.openers import HAS_REQUEST + from pyquery.pyquery import PyQuery as pq +@@ -943,6 +947,7 @@ self.assertEqual(repr(val), repr('b')) @@ -43,7 +49,7 @@ class TestWebScrapping(TestCase): def setUp(self): -@@ -932,6 +936,7 @@ +@@ -988,6 +993,7 @@ self.assertEqual(d('#pt-login').text(), 'Войти') ++++++ pyquery-2.0.1.tar.gz -> pyquery-2.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/CHANGES.rst new/pyquery-2.1.0/CHANGES.rst --- old/pyquery-2.0.1/CHANGES.rst 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/CHANGES.rst 2026-07-27 21:00:02.000000000 +0200 @@ -1,3 +1,17 @@ +2.1.0 (2026-07-27) +------------------ + +- pseudo selector ``:has()`` no longer support strings as arguments + + +2.0.2 (2026-07-27) +------------------ + +- require cssselect 1.4.x. 1.5.x breaks ``has()`` pseudo selector + +- Drop support for python 3.9and 3.10 + + 2.0.1 (2024-08-30) ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/PKG-INFO new/pyquery-2.1.0/PKG-INFO --- old/pyquery-2.0.1/PKG-INFO 2024-08-30 10:12:20.397670300 +0200 +++ new/pyquery-2.1.0/PKG-INFO 2026-07-27 21:00:03.079367000 +0200 @@ -1,6 +1,6 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: pyquery -Version: 2.0.1 +Version: 2.1.0 Summary: A jquery-like library for python Home-page: https://github.com/gawel/pyquery Author: Olivier Lauzanne @@ -12,19 +12,31 @@ Classifier: Intended Audience :: Developers Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 License-File: LICENSE.txt Requires-Dist: lxml>=2.1 -Requires-Dist: cssselect>=1.2.0 +Requires-Dist: cssselect>=1.5.0 Provides-Extra: test Requires-Dist: requests; extra == "test" Requires-Dist: webob; extra == "test" Requires-Dist: webtest; extra == "test" Requires-Dist: pytest; extra == "test" Requires-Dist: pytest-cov; extra == "test" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: license-file +Dynamic: maintainer +Dynamic: maintainer-email +Dynamic: provides-extra +Dynamic: requires-dist +Dynamic: summary pyquery: a jquery-like library for python @@ -100,6 +112,20 @@ News ==== +2.1.0 (2026-07-27) +------------------ + +- pseudo selector ``:has()`` no longer support strings as arguments + + +2.0.2 (2026-07-27) +------------------ + +- require cssselect 1.4.x. 1.5.x breaks ``has()`` pseudo selector + +- Drop support for python 3.9and 3.10 + + 2.0.1 (2024-08-30) ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/README_fixt.py new/pyquery-2.1.0/README_fixt.py --- old/pyquery-2.0.1/README_fixt.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/README_fixt.py 2026-07-27 21:00:02.000000000 +0200 @@ -1,4 +1,5 @@ import os + from webtest import http from webtest.debugapp import debug_app diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/conftest.py new/pyquery-2.1.0/conftest.py --- old/pyquery-2.0.1/conftest.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/conftest.py 2026-07-27 21:00:02.000000000 +0200 @@ -1,8 +1,9 @@ import os +from urllib.request import urlopen + import pytest from webtest import http from webtest.debugapp import debug_app -from urllib.request import urlopen @pytest.fixture diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/docs/conf.py new/pyquery-2.1.0/docs/conf.py --- old/pyquery-2.0.1/docs/conf.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/docs/conf.py 2026-07-27 21:00:02.000000000 +0200 @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # # pyquery documentation build configuration file, created by # sphinx-quickstart on Thu Nov 1 21:48:09 2012. @@ -252,7 +253,7 @@ pkg_dir = path.abspath(__file__).split('/docs')[0] setup = path.join(pkg_dir, 'setup.py') if path.isfile(setup): - for line_ in open(setup): + for line_ in open(setup): # NOQA if line_.startswith("version"): version = line_.split('=')[-1] version = version.strip() @@ -285,6 +286,6 @@ continue if t == 'function': k += '()' - fd.write('\n\n:%s\n' % k) + fd.write(f'\n\n:{k}\n') fd.write('==================\n\n') - fd.write(doc.strip('..').replace(' ', ' ')) + fd.write(doc.strip('..').replace(' ', ' ')) # NOQA diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/docs/conftest.py new/pyquery-2.1.0/docs/conftest.py --- old/pyquery-2.0.1/docs/conftest.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/docs/conftest.py 2026-07-27 21:00:02.000000000 +0200 @@ -1,5 +1,6 @@ import os import sys + import pytest from webtest import http from webtest.debugapp import debug_app diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/docs/pseudo_classes.rst new/pyquery-2.1.0/docs/pseudo_classes.rst --- old/pyquery-2.0.1/docs/pseudo_classes.rst 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/docs/pseudo_classes.rst 2026-07-27 21:00:02.000000000 +0200 @@ -14,7 +14,7 @@ >>> d(':button') [<input>, <button>] - + :checkbox ================== @@ -26,7 +26,7 @@ >>> d('input:checkbox') [<input>] - + :checked ================== @@ -38,7 +38,7 @@ >>> d('input:checked') [<input>] - + :child ================== @@ -55,7 +55,7 @@ >>> d('h1:contains("title")') [<h1.title>] - + :descendant ================== @@ -72,7 +72,7 @@ >>> d('input:disabled') [<input>] - + :empty ================== @@ -84,7 +84,7 @@ >>> d(':empty') [<h2>] - + :enabled ================== @@ -96,7 +96,7 @@ >>> d('input:enabled') [<input>] - + :eq() ================== @@ -110,7 +110,7 @@ >>> d('h1:eq(1)') [<h1.last>] - + :even ================== @@ -122,7 +122,7 @@ >>> d('p:even') [<p>] - + :file ================== @@ -134,7 +134,7 @@ >>> d('input:file') [<input>] - + :first ================== @@ -146,7 +146,7 @@ >>> d('p:first') [<p.first>] - + :gt() ================== @@ -158,7 +158,7 @@ >>> d('h1:gt(0)') [<h1.last>] - + :has() ================== @@ -168,16 +168,16 @@ >>> from pyquery import PyQuery >>> d = PyQuery('<div class="foo"><div class="bar"></div></div>') - >>> d('.foo:has(".baz")') + >>> d('.foo:has(.baz)') [] - >>> d('.foo:has(".foo")') + >>> d('.foo:has(.foo)') [] - >>> d('.foo:has(".bar")') + >>> d('.foo:has(.bar)') [<div.foo>] >>> d('.foo:has(div)') [<div.foo>] - + :header ================== @@ -189,7 +189,7 @@ >>> d(':header') [<h1>] - + :hidden ================== @@ -201,7 +201,7 @@ >>> d('input:hidden') [<input>] - + :image ================== @@ -213,7 +213,7 @@ >>> d('input:image') [<input>] - + :input ================== @@ -226,7 +226,7 @@ >>> d(':input') [<input>, <textarea>] - + :last ================== @@ -238,7 +238,7 @@ >>> d('p:last') [<p.last>] - + :lt() ================== @@ -250,7 +250,7 @@ >>> d('h1:lt(1)') [<h1.first>] - + :odd ================== @@ -262,7 +262,7 @@ >>> d('p:odd') [<p.last>] - + :parent ================== @@ -274,7 +274,7 @@ >>> d('h1:parent') [<h1>] - + :password ================== @@ -286,7 +286,7 @@ >>> d('input:password') [<input>] - + :pseudo ================== @@ -306,7 +306,7 @@ >>> d('input:radio') [<input>] - + :reset ================== @@ -318,7 +318,7 @@ >>> d('input:reset') [<input>] - + :selected ================== @@ -330,7 +330,7 @@ >>> d('option:selected') [<option>] - + :submit ================== @@ -342,7 +342,7 @@ >>> d('input:submit') [<input>] - + :text ================== @@ -354,4 +354,4 @@ >>> d('input:text') [<input>] - \ No newline at end of file + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/pyquery/cssselectpatch.py new/pyquery-2.1.0/pyquery/cssselectpatch.py --- old/pyquery-2.0.1/pyquery/cssselectpatch.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/pyquery/cssselectpatch.py 2026-07-27 21:00:02.000000000 +0200 @@ -1,7 +1,6 @@ # Copyright (C) 2008 - Olivier Lauzanne <[email protected]> # # Distributed under the BSD license, see LICENSE.txt -from __future__ import unicode_literals from cssselect import xpath as cssselect_xpath from cssselect.xpath import ExpressionError @@ -18,22 +17,18 @@ def add_post_condition(self, post_condition): if self.post_condition: - self.post_condition = '%s and (%s)' % (self.post_condition, - post_condition) + self.post_condition = f'{self.post_condition} and ({post_condition})' else: self.post_condition = post_condition def __str__(self): path = XPathExprOrig.__str__(self) if self.post_condition: - path = '%s[%s]' % (path, self.post_condition) + path = f'{path}[{self.post_condition}]' return path - def join(self, combiner, other, - closing_combiner=None, has_inner_condition=False): - res = XPathExprOrig.join(self, combiner, other, - closing_combiner=closing_combiner, - has_inner_condition=has_inner_condition) + def join(self, combiner, other): + res = XPathExprOrig.join(self, combiner, other) self.post_condition = other.post_condition return res @@ -134,19 +129,19 @@ /multipage/semantics-other.html#concept-element-disabled """ bool_op = '' if disabled else 'not' - return '''( + return f'''( ((name(.) = 'button' or name(.) = 'input' or name(.) = 'select' or name(.) = 'textarea' or name(.) = 'fieldset') - and %s(@disabled or (ancestor::fieldset[@disabled] + and {bool_op}(@disabled or (ancestor::fieldset[@disabled] and not(ancestor::legend[not(preceding-sibling::legend)]))) ) or ((name(.) = 'option' - and %s(@disabled or ancestor::optgroup[@disabled])) + and {bool_op}(@disabled or ancestor::optgroup[@disabled])) ) or - ((name(.) = 'optgroup' and %s(@disabled))) - )''' % (bool_op, bool_op, bool_op) + ((name(.) = 'optgroup' and {bool_op}(@disabled))) + )''' def xpath_disabled_pseudo(self, xpath): """Matches all elements that are disabled:: @@ -198,9 +193,9 @@ .. """ - xpath.add_condition(( + xpath.add_condition( "(name(.) = 'input' or name(.) = 'select') " - "or (name(.) = 'textarea' or name(.) = 'button')")) + "or (name(.) = 'textarea' or name(.) = 'button')") return xpath def xpath_button_pseudo(self, xpath): @@ -214,9 +209,9 @@ .. """ - xpath.add_condition(( + xpath.add_condition( "(@type = 'button' and name(.) = 'input') " - "or name(.) = 'button'")) + "or name(.) = 'button'") return xpath def xpath_radio_pseudo(self, xpath): @@ -334,9 +329,9 @@ .. """ # this seems kind of brute-force, is there a better way? - xpath.add_condition(( + xpath.add_condition( "(name(.) = 'h1' or name(.) = 'h2' or name (.) = 'h3') " - "or (name(.) = 'h4' or name (.) = 'h5' or name(.) = 'h6')")) + "or (name(.) = 'h4' or name (.) = 'h5' or name(.) = 'h6')") return xpath def xpath_parent_pseudo(self, xpath): @@ -379,8 +374,7 @@ """ if function.argument_types() != ['NUMBER']: raise ExpressionError( - "Expected a single integer for :eq(), got %r" % ( - function.arguments,)) + f"Expected a single integer for :eq(), got {function.arguments!r}") value = int(function.arguments[0].value) xpath.add_post_condition('position() = %s' % (value + 1)) return xpath @@ -397,8 +391,7 @@ """ if function.argument_types() != ['NUMBER']: raise ExpressionError( - "Expected a single integer for :gt(), got %r" % ( - function.arguments,)) + f"Expected a single integer for :gt(), got {function.arguments!r}") value = int(function.arguments[0].value) xpath.add_post_condition('position() > %s' % (value + 1)) return xpath @@ -415,8 +408,7 @@ """ if function.argument_types() != ['NUMBER']: raise ExpressionError( - "Expected a single integer for :gt(), got %r" % ( - function.arguments,)) + f"Expected a single integer for :gt(), got {function.arguments!r}") value = int(function.arguments[0].value) xpath.add_post_condition('position() < %s' % (value + 1)) @@ -434,11 +426,10 @@ """ if function.argument_types() not in (['STRING'], ['IDENT']): raise ExpressionError( - "Expected a single string or ident for :contains(), got %r" % ( - function.arguments,)) + f"Expected a single string or ident for :contains(), got {function.arguments!r}") value = self.xpath_literal(function.arguments[0].value) - xpath.add_post_condition('contains(., %s)' % value) + xpath.add_post_condition(f'contains(., {value})') return xpath def xpath_has_function(self, xpath, function): @@ -447,11 +438,11 @@ >>> from pyquery import PyQuery >>> d = PyQuery('<div class="foo"><div class="bar"></div></div>') - >>> d('.foo:has(".baz")') + >>> d('.foo:has(.baz)') [] - >>> d('.foo:has(".foo")') + >>> d('.foo:has(.foo)') [] - >>> d('.foo:has(".bar")') + >>> d('.foo:has(.bar)') [<div.foo>] >>> d('.foo:has(div)') [<div.foo>] @@ -460,8 +451,7 @@ """ if function.argument_types() not in (['STRING'], ['IDENT']): raise ExpressionError( - "Expected a single string or ident for :has(), got %r" % ( - function.arguments,)) + f"Expected a single string or ident for :has(), got {function.arguments!r}") value = self.css_to_xpath( function.arguments[0].value, prefix='descendant::', ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/pyquery/openers.py new/pyquery-2.1.0/pyquery/openers.py --- old/pyquery-2.0.1/pyquery/openers.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/pyquery/openers.py 2026-07-27 21:00:02.000000000 +0200 @@ -1,6 +1,6 @@ -from urllib.request import urlopen -from urllib.parse import urlencode from urllib.error import HTTPError +from urllib.parse import urlencode +from urllib.request import urlopen try: import requests @@ -49,7 +49,7 @@ else: meth = getattr(requests, str(method)) if method == 'get': - url, data = _query(url, method, kwargs) + url, _data = _query(url, method, kwargs) kw = {} for k in allowed_args: if k in kwargs: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/pyquery/pyquery.py new/pyquery-2.1.0/pyquery/pyquery.py --- old/pyquery-2.0.1/pyquery/pyquery.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/pyquery/pyquery.py 2026-07-27 21:00:02.000000000 +0200 @@ -1,20 +1,21 @@ # Copyright (C) 2008 - Olivier Lauzanne <[email protected]> # # Distributed under the BSD license, see LICENSE.txt -from .cssselectpatch import JQueryTranslator -from reprlib import recursive_repr -from urllib.parse import urlencode -from urllib.parse import urljoin -from .openers import url_opener -from .text import extract_text -from copy import deepcopy -from html import escape -from lxml import etree -import lxml.html import inspect import itertools -import types import sys +import types +from copy import deepcopy +from html import escape +from reprlib import recursive_repr +from urllib.parse import urlencode, urljoin + +import lxml.html +from lxml import etree + +from .cssselectpatch import JQueryTranslator +from .openers import url_opener +from .text import extract_text if sys.version_info >= (3, 12, 0): from collections import OrderedDict @@ -30,8 +31,8 @@ def __repr__(self): 'od.__repr__() <==> repr(od)' if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, dict(self.items())) + return f'{self.__class__.__name__}()' + return f'{self.__class__.__name__}({dict(self.items())!r})' basestring = (str, bytes) @@ -60,14 +61,17 @@ f = types.FunctionType(func.__code__, func.__globals__, name, func.__defaults__) f.__doc__ = ( - 'Alias for :func:`~pyquery.pyquery.PyQuery.%s`') % func.__name__ + f'Alias for :func:`~pyquery.pyquery.PyQuery.{func.__name__}`') setattr(PyQuery, name, f.__get__(None, PyQuery)) def fromstring(context, parser=None, custom_parser=None): """use html parser if we don't have clean xml """ - if hasattr(context, 'read') and hasattr(context.read, '__call__'): + # Empty/whitespace-only markup → empty document (jQuery $('') parity). + if isinstance(context, basestring) and not context.strip(): + return [] + if hasattr(context, 'read') and callable(context.read): meth = 'parse' else: meth = 'fromstring' @@ -96,7 +100,7 @@ elif parser == 'html_fragments': custom_parser = lxml.html.fragments_fromstring else: - raise ValueError('No such parser: "%s"' % parser) + raise ValueError(f'No such parser: "{parser}"') result = custom_parser(context) if isinstance(result, list): @@ -113,7 +117,7 @@ return func(*args[:func.__code__.co_argcount]) -class NoDefault(object): +class NoDefault: def __repr__(self): """clean representation in Sphinx""" return '<NoDefault>' @@ -123,7 +127,7 @@ del NoDefault -class FlexibleElement(object): +class FlexibleElement: """property to allow a flexible api""" def __init__(self, pget, pset=no_default, pdel=no_default): self.pget = pget @@ -131,7 +135,7 @@ self.pdel = pdel def __get__(self, instance, klass): - class _element(object): + class _element: """real element to support set/get/del attr and item and js call style""" def __call__(prop, *args, **kwargs): @@ -146,7 +150,7 @@ __delattr__ = __delitem__ def __repr__(prop): - return '<flexible_element %s>' % self.pget.__name__ + return f'<flexible_element {self.pget.__name__}>' return _element() def __set__(self, instance, value): @@ -187,7 +191,7 @@ if kwargs: # specific case to get the dom if 'filename' in kwargs: - html = open(kwargs['filename'], + html = open(kwargs['filename'], # NOQA encoding=kwargs.get('encoding')) elif 'url' in kwargs: url = kwargs.pop('url') @@ -200,14 +204,14 @@ self.parser = 'html' self._base_url = url else: - raise ValueError('Invalid keyword arguments %s' % kwargs) + raise ValueError(f'Invalid keyword arguments {kwargs}') elements = fromstring(html, self.parser) # close open descriptor if possible if hasattr(html, 'close'): try: html.close() - except Exception: + except Exception: # NOQA pass else: @@ -228,7 +232,7 @@ if isinstance(context, basestring): try: elements = fromstring(context, self.parser) - except Exception: + except Exception: # NOQA raise elif isinstance(context, self.__class__): # copy @@ -344,6 +348,15 @@ el.tag = el.tag.split('}', 1)[1] return self + def _serialize_nodes(self, dumps, method): + # Text nodes from contents() cannot go through tostring. + has_text_nodes = any(isinstance(e, str) for e in self) + return ''.join( + e if isinstance(e, str) else dumps( + e, encoding=str, method=method, with_tail=not has_text_nodes) + for e in self + ) + def __str__(self): """xml representation of current nodes:: @@ -353,12 +366,11 @@ <script><![[CDATA[ ]></script> """ - return ''.join([etree.tostring(e, encoding=str) for e in self]) + return self._serialize_nodes(etree.tostring, method='xml') def __unicode__(self): """xml representation of current nodes""" - return u''.join([etree.tostring(e, encoding=str) - for e in self]) + return self._serialize_nodes(etree.tostring, method='xml') def __html__(self): """html representation of current nodes:: @@ -369,8 +381,7 @@ <script><![[CDATA[ ]></script> """ - return u''.join([lxml.html.tostring(e, encoding=str) - for e in self]) + return self._serialize_nodes(lxml.html.tostring, method='html') def __repr__(self): r = [] @@ -380,7 +391,7 @@ c = c and '.' + '.'.join(c.split(' ')) or '' id = el.get('id') id = id and '#' + id or '' - r.append('<%s%s%s>' % (el.tag, id, c)) + r.append(f'<{el.tag}{id}{c}>') return '[' + (', '.join(r)) + ']' except AttributeError: return list.__repr__(self) @@ -457,8 +468,7 @@ this_list.append(current) current = current.getparent() this_list.reverse() - for j in this_list: - yield j + yield from this_list def _next_all(self): return [e for e in self._traverse('getnext')] @@ -604,7 +614,7 @@ >>> d('p').filter(lambda i, this: PyQuery(this).text() == 'Hi') [<p.hello>] """ - if not hasattr(selector, '__call__'): + if not callable(selector): return self._filter_only(selector, self) else: elements = [] @@ -768,7 +778,8 @@ length = len(args) if length == 1: attr = args[0] - attr = mapping.get(attr, attr) + if not isinstance(attr, dict): + attr = mapping.get(attr, attr) elif length == 2: attr, value = args attr = mapping.get(attr, attr) @@ -777,14 +788,14 @@ for k, v in kwargs.items(): attr[mapping.get(k, k)] = v else: - raise ValueError('Invalid arguments %s %s' % (args, kwargs)) + raise ValueError(f'Invalid arguments {args} {kwargs}') if not self: return None elif isinstance(attr, dict): for tag in self: for key, value in attr.items(): - tag.set(key, value) + tag.set(mapping.get(key, key), value) elif value is no_default: return self[0].get(attr) elif value is None: @@ -840,7 +851,7 @@ .. """ - return self.is_('.%s' % name) + return self.is_(f'.{name}') @with_camel_case_alias def add_class(self, value): @@ -879,9 +890,7 @@ classes.difference_update(values) classes.difference_update(['']) classes = ' '.join(classes) - if classes.strip(): - tag.set('class', classes) - elif tag.get('class'): + if classes.strip() or tag.get('class'): tag.set('class', classes) return self @@ -919,19 +928,19 @@ elif kwargs: attr = kwargs else: - raise ValueError('Invalid arguments %s %s' % (args, kwargs)) + raise ValueError(f'Invalid arguments {args} {kwargs}') if isinstance(attr, dict): for tag in self: stripped_keys = [key.strip().replace('_', '-') - for key in attr.keys()] + for key in attr] current = [el.strip() for el in (tag.get('style') or '').split(';') if el.strip() and el.split(':')[0].strip() not in stripped_keys] for key, value in attr.items(): key = key.replace('_', '-') - current.append('%s: %s' % (key, value)) + current.append(f'{key}: {value}') tag.set('style', '; '.join(current)) elif isinstance(value, basestring): attr = attr.replace('_', '-') @@ -940,8 +949,8 @@ el.strip() for el in (tag.get('style') or '').split(';') if (el.strip() and - not el.split(':')[0].strip() == attr.strip())] - current.append('%s: %s' % (attr, value)) + el.split(':')[0].strip() != attr.strip())] + current.append(f'{attr}: {value}') tag.set('style', '; '.join(current)) return self @@ -1001,10 +1010,15 @@ ['you', 'hou'] """ + def _option_value(option): + # Missing value attr uses option text (HTML / jQuery). + val = option.attr('value') + return option.text() if val is None else val + def _get_value(tag): # <textarea> if tag.tag == 'textarea': - return self._copy(tag).html() + return self._copy(tag).html(escape=False) # <select> elif tag.tag == 'select': if 'multiple' in tag.attrib: @@ -1012,13 +1026,15 @@ selected = self._copy(tag)('option[selected]') # Rebuild list to avoid serialization error return list(selected.map( - lambda _, o: self._copy(o).attr('value') + lambda _, o: _option_value(self._copy(o)) )) selected_option = self._copy(tag)('option[selected]:last') if selected_option: - return selected_option.attr('value') - else: - return self._copy(tag)('option').attr('value') + return _option_value(selected_option) + first = self._copy(tag)('option:first') + if first: + return _option_value(first) + return None # <input type="checkbox"> or <input type="radio"> elif self.is_(':checkbox,:radio'): val = self._copy(tag).attr('value') @@ -1042,10 +1058,11 @@ def _make_option_selected(_, elem): pq = self._copy(elem) - if pq.attr('value') in value: + if pq.attr('value') in value: # NOQA pq.attr('selected', 'selected') - if 'multiple' not in tag.attrib: - del value[:] # Ensure it toggles first match + if 'multiple' not in tag.attrib: # NOQA + # Ensure it toggles first match + del value[:] # NOQA else: pq.removeAttr('selected') @@ -1097,12 +1114,14 @@ return None tag = self[0] children = tag.getchildren() - html = escape(tag.text or '', quote=False) + html = tag.text or '' + if kwargs.pop('escape', True): + html = escape(html, quote=False) if not children: return html if 'encoding' not in kwargs: kwargs['encoding'] = str - html += u''.join([etree.tostring(e, **kwargs) + html += ''.join([etree.tostring(e, **kwargs) for e in children]) return html else: @@ -1119,7 +1138,7 @@ for child in tag.getchildren(): tag.remove(child) root = fromstring( - u'<root>' + new_html + u'</root>', + '<root>' + new_html + '</root>', self.parser)[0] children = root.getchildren() if children: @@ -1188,7 +1207,7 @@ if not self: return '' return ' '.join( - self._copy(tag).html() if tag.tag == 'textarea' else + self._copy(tag).html(escape=False) if tag.tag == 'textarea' else extract_text(tag, **kwargs) for tag in self ) @@ -1204,7 +1223,7 @@ def _get_root(self, value): if isinstance(value, basestring): - root = fromstring(u'<root>' + value + u'</root>', + root = fromstring('<root>' + value + '</root>', self.parser)[0] elif isinstance(value, etree._Element): root = self._copy(value) @@ -1212,7 +1231,7 @@ root = value else: raise TypeError( - 'Value must be string, PyQuery or Element. Got %r' % value) + f'Value must be string, PyQuery or Element. Got {value!r}') if hasattr(root, 'text') and isinstance(root.text, basestring): root_text = root.text else: @@ -1422,7 +1441,7 @@ """ if isinstance(value, PyQuery): value = str(value) - if hasattr(value, '__call__'): + if callable(value): for i, element in enumerate(self): self._copy(element).before( value(i, element) + (element.tail or '')) @@ -1490,7 +1509,7 @@ results.remove() return self - class Fn(object): + class Fn: """Hook for defining custom function (like the jQuery.fn): .. sourcecode:: python @@ -1527,10 +1546,7 @@ >>> d.serializeArray() == [{'name': 'order', 'value': 'spam'}] True """ - return list(map( - lambda p: {'name': p[0], 'value': p[1]}, - self.serialize_pairs() - )) + return [{'name': p[0], 'value': p[1]} for p in self.serialize_pairs()] def serialize(self): """Serialize form elements as a URL-encoded string. @@ -1573,8 +1589,7 @@ # Include inputs outside of their form owner root = self._copy(el.root.getroot()) controls.extend(root( - '#%s :not([form]):input, [form="%s"]:input' - % (form_id, form_id))) + f'#{form_id} :not([form]):input, [form="{form_id}"]:input')) else: controls.extend(el(':not([form]):input')) elif el[0].tag == 'fieldset': @@ -1583,9 +1598,7 @@ controls.extend(el) # Filter controls selector = '[name]:enabled:not(button)' # Not serializing image button - selector += ''.join(map( - lambda s: ':not([type="%s"])' % s, - _submitter_types)) + selector += ''.join(f':not([type="{s}"])' for s in _submitter_types) controls = controls.filter(selector) def _filter_out_unchecked(_, el): @@ -1608,10 +1621,7 @@ for field in controls: val = self._copy(field).val() or '' if isinstance(val, list): - ret.extend(map( - lambda v: (field.attrib['name'], v.replace('\n', '\r\n')), - val - )) + ret.extend((field.attrib['name'], v.replace('\n', '\r\n')) for v in val) else: ret.append((field.attrib['name'], val.replace('\n', '\r\n'))) return ret @@ -1656,9 +1666,9 @@ if base_url is None: base_url = self.base_url if base_url is None: - raise ValueError(( + raise ValueError( 'You need a base URL to make your links' - 'absolute. It can be provided by the base_url parameter.')) + 'absolute. It can be provided by the base_url parameter.') def repl(attr): def rep(i, e): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/pyquery/text.py new/pyquery-2.1.0/pyquery/text.py --- old/pyquery-2.0.1/pyquery/text.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/pyquery/text.py 2026-07-27 21:00:02.000000000 +0200 @@ -1,6 +1,5 @@ import re - # https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements#Elements INLINE_TAGS = { 'a', 'abbr', 'acronym', 'b', 'bdo', 'big', 'br', 'button', 'cite', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/pyquery.egg-info/PKG-INFO new/pyquery-2.1.0/pyquery.egg-info/PKG-INFO --- old/pyquery-2.0.1/pyquery.egg-info/PKG-INFO 2024-08-30 10:12:20.000000000 +0200 +++ new/pyquery-2.1.0/pyquery.egg-info/PKG-INFO 2026-07-27 21:00:03.000000000 +0200 @@ -1,6 +1,6 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: pyquery -Version: 2.0.1 +Version: 2.1.0 Summary: A jquery-like library for python Home-page: https://github.com/gawel/pyquery Author: Olivier Lauzanne @@ -12,19 +12,31 @@ Classifier: Intended Audience :: Developers Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 License-File: LICENSE.txt Requires-Dist: lxml>=2.1 -Requires-Dist: cssselect>=1.2.0 +Requires-Dist: cssselect>=1.5.0 Provides-Extra: test Requires-Dist: requests; extra == "test" Requires-Dist: webob; extra == "test" Requires-Dist: webtest; extra == "test" Requires-Dist: pytest; extra == "test" Requires-Dist: pytest-cov; extra == "test" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: license-file +Dynamic: maintainer +Dynamic: maintainer-email +Dynamic: provides-extra +Dynamic: requires-dist +Dynamic: summary pyquery: a jquery-like library for python @@ -100,6 +112,20 @@ News ==== +2.1.0 (2026-07-27) +------------------ + +- pseudo selector ``:has()`` no longer support strings as arguments + + +2.0.2 (2026-07-27) +------------------ + +- require cssselect 1.4.x. 1.5.x breaks ``has()`` pseudo selector + +- Drop support for python 3.9and 3.10 + + 2.0.1 (2024-08-30) ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/pyquery.egg-info/requires.txt new/pyquery-2.1.0/pyquery.egg-info/requires.txt --- old/pyquery-2.0.1/pyquery.egg-info/requires.txt 2024-08-30 10:12:20.000000000 +0200 +++ new/pyquery-2.1.0/pyquery.egg-info/requires.txt 2026-07-27 21:00:03.000000000 +0200 @@ -1,5 +1,5 @@ lxml>=2.1 -cssselect>=1.2.0 +cssselect>=1.5.0 [test] requests diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/setup.py new/pyquery-2.1.0/setup.py --- old/pyquery-2.0.1/setup.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/setup.py 2026-07-27 21:00:02.000000000 +0200 @@ -3,22 +3,22 @@ # # Distributed under the BSD license, see LICENSE.txt -from setuptools import setup, find_packages import os +from setuptools import find_packages, setup install_requires = [ 'lxml>=2.1', - 'cssselect>=1.2.0', + 'cssselect>=1.5.0', ] def read(*names): - values = dict() + values = {} for name in names: filename = name + '.rst' if os.path.isfile(filename): - fd = open(filename) + fd = open(filename) # NOQA value = fd.read() fd.close() else: @@ -28,18 +28,18 @@ long_description = """ -%(README)s +{README} See http://pyquery.rtfd.org/ for the full documentation News ==== -%(CHANGES)s +{CHANGES} -""" % read('README', 'CHANGES') +""".format(**read('README', 'CHANGES')) -version = '2.0.1' +version = '2.1.0' setup(name='pyquery', version=version, @@ -49,10 +49,9 @@ "Intended Audience :: Developers", "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ], keywords='jquery html xml scraping', author='Olivier Lauzanne', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/tests/apps.py new/pyquery-2.1.0/tests/apps.py --- old/pyquery-2.0.1/tests/apps.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/tests/apps.py 2026-07-27 21:00:02.000000000 +0200 @@ -1,6 +1,4 @@ -from webob import Request -from webob import Response -from webob import exc +from webob import Request, Response, exc def input_app(environ, start_response): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/tests/browser_base.py new/pyquery-2.1.0/tests/browser_base.py --- old/pyquery-2.0.1/tests/browser_base.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/tests/browser_base.py 2026-07-27 21:00:02.000000000 +0200 @@ -1,4 +1,4 @@ -class TextExtractionMixin(): +class TextExtractionMixin: def _prepare_dom(self, html): self.last_html = '<html><body>' + html + '</body></html>' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/tests/test_browser.py new/pyquery-2.1.0/tests/test_browser.py --- old/pyquery-2.0.1/tests/test_browser.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/tests/test_browser.py 2026-07-27 21:00:02.000000000 +0200 @@ -1,6 +1,7 @@ import unittest from pyquery.pyquery import PyQuery + from .browser_base import TextExtractionMixin diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/tests/test_pyquery.py new/pyquery-2.1.0/tests/test_pyquery.py --- old/pyquery-2.0.1/tests/test_pyquery.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/tests/test_pyquery.py 2026-07-27 21:00:02.000000000 +0200 @@ -4,12 +4,16 @@ import os import sys import time +from unittest import TestCase + +import pytest from lxml import etree -from pyquery.pyquery import PyQuery as pq, no_default -from pyquery.openers import HAS_REQUEST from webtest import http from webtest.debugapp import debug_app -from unittest import TestCase + +from pyquery.openers import HAS_REQUEST +from pyquery.pyquery import PyQuery as pq +from pyquery.pyquery import no_default sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) @@ -349,6 +353,14 @@ def test_typeerror_on_invalid_value(self): self.assertRaises(TypeError, pq, object()) + def test_empty_string_returns_empty_doc(self): + """Empty/whitespace markup must not raise ParserError.""" + for markup in ('', ' ', '\n', '\t \n'): + doc = pq(markup) + self.assertEqual(len(doc), 0) + self.assertEqual(doc.html(), None) + self.assertEqual(list(doc), []) + class TestComment(TestCase): @@ -357,6 +369,19 @@ self.assertEqual(doc.text(), 'bar') +class TestContentsStr(TestCase): + + def test_str_contents_with_text_nodes(self): + doc = pq('hello <b>bold</b> world') + contents = doc.contents() + self.assertEqual(str(contents), 'hello <b>bold</b> world') + self.assertEqual(contents.__html__(), 'hello <b>bold</b> world') + + def test_str_contents_text_only(self): + doc = pq('<div>only text</div>') + self.assertEqual(str(doc.contents()), 'only text') + + class TestCallback(TestCase): html = """ <ol> @@ -487,6 +512,15 @@ self.assertEqual(d.outer_html(), '<div value=""></div>') self.assertEqual(d.outer_html(method="xml"), '<div value=""/>') + def test_attr_dict_mapping(self): + d = pq('<div>') + d.attr({'id': 'x', 'class_': 'y', 'data-a': '1'}) + self.assertEqual(d.attr('id'), 'x') + self.assertEqual(d.attr('class'), 'y') + self.assertEqual(d.attr('data-a'), '1') + self.assertEqual(d.attr(id='z', class_='w').attr('id'), 'z') + self.assertEqual(d.attr('class'), 'w') + def test_remove(self): d = pq(self.html) d('img').remove() @@ -534,10 +568,9 @@ self.assertEqual(d('#textarea-multi').val(), multi_expected) self.assertEqual(d('#textarea-multi').text(), multi_expected) multi_new = '''Bacon\n<b>Eggs</b>\nSpam''' - multi_new_expected = '''Bacon\n<b>Eggs</b>\nSpam''' d('#textarea-multi').val(multi_new) - self.assertEqual(d('#textarea-multi').val(), multi_new_expected) - self.assertEqual(d('#textarea-multi').text(), multi_new_expected) + self.assertEqual(d('#textarea-multi').val(), multi_new) + self.assertEqual(d('#textarea-multi').text(), multi_new) def test_val_for_select(self): d = pq(self.html4) @@ -593,6 +626,27 @@ self.assertEqual(d('#second').val(), '42') self.assertEqual(d('#third').val(), '42') + def test_val_select_option_without_value_attr(self): + d = pq('<select><option>a</option><option selected>b</option></select>') + self.assertEqual(d.val(), 'b') + d = pq('<select><option>first</option><option>second</option></select>') + self.assertEqual(d.val(), 'first') + d = pq('<select><option selected value="">empty</option></select>') + self.assertEqual(d.val(), '') + d = pq( + '<select multiple>' + '<option selected>a</option>' + '<option selected>b</option>' + '</select>' + ) + self.assertEqual(d.val(), ['a', 'b']) + form = pq( + '<form><select name="s">' + '<option>t1</option><option selected>t2</option>' + '</select></form>' + ) + self.assertEqual(form.serialize_pairs(), [('s', 't2')]) + def test_val_checkbox_no_value_attribute(self): d = pq('<input type="checkbox">') self.assertEqual(d.val(), 'on') @@ -784,6 +838,7 @@ 'http://example.com/path_info') [email protected](os.name == 'nt', reason='fail on windows') class TestHTMLParser(TestCase): xml = "<div>I'm valid XML</div>" html = '''<div class="portlet"> @@ -802,7 +857,7 @@ expected = '''<div class="portlet"> <a href="/toto">TestimageMy link text</a> <a href="/toto2">imageMy link text 2</a> - Behind you, a three-headed HTML&dash;Entity! + Behind you, a three-headed HTML‐Entity! </div>''' d = pq(self.html) d('img').replace_with('image') @@ -813,7 +868,7 @@ expected = '''<div class="portlet"> TestimageMy link text imageMy link text 2 - Behind you, a three-headed HTML&dash;Entity! + Behind you, a three-headed HTML‐Entity! </div>''' d = pq(self.html) d('a').replace_with(lambda i, e: pq(e).html()) @@ -838,7 +893,7 @@ </body> </html>''' - namespaces = {'bar': 'http://example.com/bar', + namespaces = {'bar': 'http://example.com/bar', # NOQA 'baz': 'http://example.com/baz'} def test_selector(self): @@ -899,14 +954,14 @@ d = pq(url=self.application_url, data={'q': 'foo'}, method='get') print(d) - self.assertIn('REQUEST_METHOD: GET', d('p').text()) - self.assertIn('q=foo', d('p').text()) + self.assertIn('REQUEST_METHOD: GET', d.text()) + self.assertIn('q=foo', d.text()) def test_post(self): d = pq(url=self.application_url, data={'q': 'foo'}, method='post') - self.assertIn('REQUEST_METHOD: POST', d('p').text()) - self.assertIn('q=foo', d('p').text()) + self.assertIn('REQUEST_METHOD: POST', d.text()) + self.assertIn('q=foo', d.text()) def test_session(self): if HAS_REQUEST: @@ -915,7 +970,7 @@ session.headers.update({'X-FOO': 'bar'}) d = pq(url=self.application_url, data={'q': 'foo'}, method='get', session=session) - self.assertIn('HTTP_X_FOO: bar', d('p').text()) + self.assertIn('HTTP_X_FOO: bar', d.text()) else: self.skipTest('no requests library') @@ -925,6 +980,7 @@ class TestWebScrappingEncoding(TestCase): + @pytest.mark.skip('No longer possible to query this url') def test_get(self): d = pq(url='http://ru.wikipedia.org/wiki/Заглавная_страница', method='get') @@ -945,7 +1001,7 @@ def test_get(self): pq(url=self.application_url) - with self.assertRaises(Exception): + with self.assertRaises(Exception): # NOQA pq(url=self.application_url, timeout=1) def tearDown(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/tests/test_real_browser.py new/pyquery-2.1.0/tests/test_real_browser.py --- old/pyquery-2.0.1/tests/test_real_browser.py 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/tests/test_real_browser.py 2026-07-27 21:00:02.000000000 +0200 @@ -14,9 +14,9 @@ SELENIUM = False if SELENIUM: - from urllib.parse import urlunsplit - from http.server import HTTPServer, BaseHTTPRequestHandler + from http.server import BaseHTTPRequestHandler, HTTPServer from queue import Queue + from urllib.parse import urlunsplit class BaseTestRequestHandler(BaseHTTPRequestHandler): _last_html = '' @@ -99,8 +99,7 @@ def open_url(self, path): self.driver.get(urlunsplit( - ('http', '{}:{}'.format( - self.LOCAL_IP, self.PORT), path, '', ''))) + ('http', f'{self.LOCAL_IP}:{self.PORT}', path, '', ''))) class TestInnerText(BaseBrowserTest, TextExtractionMixin): REQUEST_HANDLER_CLASS = HTMLSnippetSender diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-2.0.1/tox.ini new/pyquery-2.1.0/tox.ini --- old/pyquery-2.0.1/tox.ini 2024-08-30 10:12:19.000000000 +0200 +++ new/pyquery-2.1.0/tox.ini 2026-07-27 21:00:02.000000000 +0200 @@ -1,5 +1,5 @@ [tox] -envlist=py38,py39,py310,py311,py312 +envlist=py311,py312 [testenv] whitelist_externals= @@ -15,7 +15,7 @@ [testenv:lint] skipsdist=true skip_install=true -basepython = python3.11 +basepython = python3.12 commands = ruff check deps = @@ -24,7 +24,7 @@ [testenv:docs] skip_install=false skipsdist=true -basepython = python3.11 +basepython = python3.12 changedir = docs deps = sphinx
