commit python-pyquery for openSUSE:Factory
Hello community, here is the log from the commit of package python-pyquery for openSUSE:Factory checked in at 2019-12-02 11:32:31 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyquery (Old) and /work/SRC/openSUSE:Factory/.python-pyquery.new.4691 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-pyquery" Mon Dec 2 11:32:31 2019 rev:17 rq:751376 version:1.4.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyquery/python-pyquery.changes 2019-02-26 22:15:34.518207749 +0100 +++ /work/SRC/openSUSE:Factory/.python-pyquery.new.4691/python-pyquery.changes 2019-12-02 11:37:47.750453794 +0100 @@ -1,0 +2,14 @@ +Sun Nov 24 17:21:55 UTC 2019 - Arun Persaud <arun@gmx.de> + +- specfile: + * be more specific in %files section + +- update to version 1.4.1: + * This is the latest release with py2 support + * Remove py33, py34 support + * web scraping improvements: default timeout and session support + * Add API methods to serialize form-related elements according to + spec + * Include HTML markup when querying textarea text/value + +------------------------------------------------------------------- Old: ---- pyquery-1.4.0.tar.gz New: ---- pyquery-1.4.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyquery.spec ++++++ --- /var/tmp/diff_new_pack.AyjsWC/_old 2019-12-02 11:37:48.890454034 +0100 +++ /var/tmp/diff_new_pack.AyjsWC/_new 2019-12-02 11:37:48.894454035 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-pyquery # -# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -26,12 +26,12 @@ %bcond_with test %endif Name: python-pyquery%{psuffix} -Version: 1.4.0 +Version: 1.4.1 Release: 0 Summary: A jQuery-like library for python License: BSD-3-Clause Group: Development/Languages/Python -URL: http://pypi.python.org/pypi/pyquery +URL: https://pypi.python.org/pypi/pyquery Source: https://files.pythonhosted.org/packages/source/p/pyquery/pyquery-%{version}.tar.gz BuildRequires: %{python_module coverage} BuildRequires: %{python_module cssselect > 0.7.9} @@ -77,7 +77,8 @@ %files %{python_files} %license LICENSE.txt %doc CHANGES.rst README.rst -%{python_sitelib}/* +%{python_sitelib}/pyquery/ +%{python_sitelib}/pyquery-%{version}-py*.egg-info %endif %changelog ++++++ pyquery-1.4.0.tar.gz -> pyquery-1.4.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/CHANGES.rst new/pyquery-1.4.1/CHANGES.rst --- old/pyquery-1.4.0/CHANGES.rst 2018-01-11 20:50:55.000000000 +0100 +++ new/pyquery-1.4.1/CHANGES.rst 2019-10-26 10:47:47.000000000 +0200 @@ -1,3 +1,17 @@ +1.4.1 (2019-10-26) +------------------ + +- This is the latest release with py2 support + +- Remove py33, py34 support + +- web scraping improvements: default timeout and session support + +- Add API methods to serialize form-related elements according to spec + +- Include HTML markup when querying textarea text/value + + 1.4.0 (2018-01-11) ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/PKG-INFO new/pyquery-1.4.1/PKG-INFO --- old/pyquery-1.4.0/PKG-INFO 2018-01-11 20:50:56.000000000 +0100 +++ new/pyquery-1.4.1/PKG-INFO 2019-10-26 10:47:47.000000000 +0200 @@ -1,12 +1,13 @@ -Metadata-Version: 1.1 +Metadata-Version: 1.2 Name: pyquery -Version: 1.4.0 +Version: 1.4.1 Summary: A jquery-like library for python Home-page: https://github.com/gawel/pyquery -Author: Gael Pasgrimaud -Author-email: gael@gawel.org +Author: Olivier Lauzanne +Author-email: olauzanne@gmail.com +Maintainer: Gael Pasgrimaud +Maintainer-email: gael@gawel.org License: BSD -Description-Content-Type: UNKNOWN Description: pyquery: a jquery-like library for python ========================================= @@ -25,7 +26,7 @@ The `project`_ is being actively developped on a git repository on Github. I have the policy of giving push access to anyone who wants it and then to review - what he does. So if you want to contribute just email me. + what they do. So if you want to contribute just email me. Please report bugs on the `github <https://github.com/gawel/pyquery/issues>`_ issue @@ -88,6 +89,20 @@ News ==== + 1.4.1 (2019-10-26) + ------------------ + + - This is the latest release with py2 support + + - Remove py33, py34 support + + - web scraping improvements: default timeout and session support + + - Add API methods to serialize form-related elements according to spec + + - Include HTML markup when querying textarea text/value + + 1.4.0 (2018-01-11) ------------------ @@ -335,7 +350,6 @@ Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/README.rst new/pyquery-1.4.1/README.rst --- old/pyquery-1.4.0/README.rst 2018-01-11 20:50:55.000000000 +0100 +++ new/pyquery-1.4.1/README.rst 2019-10-26 10:47:47.000000000 +0200 @@ -15,7 +15,7 @@ The `project`_ is being actively developped on a git repository on Github. I have the policy of giving push access to anyone who wants it and then to review -what he does. So if you want to contribute just email me. +what they do. So if you want to contribute just email me. Please report bugs on the `github <https://github.com/gawel/pyquery/issues>`_ issue diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/docs/scrap.rst new/pyquery-1.4.1/docs/scrap.rst --- old/pyquery-1.4.0/docs/scrap.rst 2018-01-11 20:50:55.000000000 +0100 +++ new/pyquery-1.4.1/docs/scrap.rst 2019-10-26 10:47:47.000000000 +0200 @@ -19,4 +19,15 @@
pq(your_url, {'q': 'foo'}, method='post', verify=True) [<html>]
+ +Timeout +------- + +The default timeout is 60 seconds, you can change it by setting the timeout parameter which is forwarded to the underlying urllib or requests library. + +Session +------- + +When using the requests library you can instantiate a Session object which keeps state between http calls (for example - to keep cookies). You can set the session parameter to use this session object. + .. _requests: http://docs.python-requests.org/en/latest/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/pyquery/cssselectpatch.py new/pyquery-1.4.1/pyquery/cssselectpatch.py --- old/pyquery-1.4.0/pyquery/cssselectpatch.py 2018-01-11 20:50:55.000000000 +0100 +++ new/pyquery-1.4.1/pyquery/cssselectpatch.py 2019-10-26 10:47:47.000000000 +0200 @@ -127,6 +127,26 @@ xpath.add_condition("@selected and name(.) = 'option'") return xpath + def _format_disabled_xpath(self, disabled=True): + """Format XPath condition for :disabled or :enabled pseudo-classes + according to the WHATWG spec. See: https://html.spec.whatwg.org + /multipage/semantics-other.html#concept-element-disabled + """ + bool_op = '' if disabled else 'not' + return '''( + ((name(.) = 'button' or name(.) = 'input' or name(.) = 'select' + or name(.) = 'textarea' or name(.) = 'fieldset') + and %s(@disabled or (ancestor::fieldset[@disabled] + and not(ancestor::legend[not(preceding-sibling::legend)]))) + ) + or + ((name(.) = 'option' + and %s(@disabled or ancestor::optgroup[@disabled])) + ) + or + ((name(.) = 'optgroup' and %s(@disabled))) + )''' % (bool_op, bool_op, bool_op) + def xpath_disabled_pseudo(self, xpath): """Matches all elements that are disabled:: @@ -137,7 +157,7 @@ .. """ - xpath.add_condition("@disabled") + xpath.add_condition(self._format_disabled_xpath()) return xpath def xpath_enabled_pseudo(self, xpath): @@ -150,7 +170,7 @@ .. """ - xpath.add_condition("not(@disabled) and name(.) = 'input'") + xpath.add_condition(self._format_disabled_xpath(disabled=False)) return xpath def xpath_file_pseudo(self, xpath): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/pyquery/openers.py new/pyquery-1.4.1/pyquery/openers.py --- old/pyquery-1.4.0/pyquery/openers.py 2018-01-11 20:50:55.000000000 +0100 +++ new/pyquery-1.4.1/pyquery/openers.py 2019-10-26 10:47:47.000000000 +0200 @@ -19,6 +19,7 @@ except ImportError: HAS_REQUEST = False +DEFAULT_TIMEOUT = 60 allowed_args = ( 'auth', 'data', 'headers', 'verify', @@ -48,16 +49,21 @@ def _requests(url, kwargs): + encoding = kwargs.get('encoding') method = kwargs.get('method', 'get').lower() - meth = getattr(requests, str(method)) + session = kwargs.get('session') + if session: + meth = getattr(session, str(method)) + else: + meth = getattr(requests, str(method)) if method == 'get': url, data = _query(url, method, kwargs) kw = {} for k in allowed_args: if k in kwargs: kw[k] = kwargs[k] - resp = meth(url=url, **kw) + resp = meth(url=url, timeout=kwargs.get('timeout', DEFAULT_TIMEOUT), **kw) if not (200 <= resp.status_code < 300): raise HTTPError(resp.url, resp.status_code, resp.reason, resp.headers, None) @@ -70,7 +76,7 @@ def _urllib(url, kwargs): method = kwargs.get('method') url, data = _query(url, method, kwargs) - return urlopen(url, data) + return urlopen(url, data, timeout=kwargs.get('timeout', DEFAULT_TIMEOUT)) def url_opener(url, kwargs): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/pyquery/pyquery.py new/pyquery-1.4.1/pyquery/pyquery.py --- old/pyquery-1.4.0/pyquery/pyquery.py 2018-01-11 20:50:55.000000000 +0100 +++ new/pyquery-1.4.1/pyquery/pyquery.py 2019-10-26 10:47:47.000000000 +0200 @@ -4,6 +4,7 @@ # # Distributed under the BSD license, see LICENSE.txt from .cssselectpatch import JQueryTranslator +from collections import OrderedDict from .openers import url_opener from .text import extract_text from copy import deepcopy @@ -424,7 +425,7 @@ """return the xml root element """ if self._parent is not no_default: - return self._parent.getroottree() + return self._parent[0].getroottree() return self[0].getroottree() @property @@ -996,13 +997,37 @@ >>> d.val() 'Youhou' + Set the selected values for a `select` element with the `multiple` + attribute:: + + >>> d = PyQuery(''' + ... <select multiple> + ... <option value="you"><option value="hou"> + ... </select> + ... ''') + >>> d.val(['you', 'hou']) + [<select>] + + Get the selected values for a `select` element with the `multiple` + attribute:: + + >>> d.val() + ['you', 'hou'] + """ def _get_value(tag): # <textarea> if tag.tag == 'textarea': - return self._copy(tag).text() + return self._copy(tag).html() # <select> elif tag.tag == 'select': + if 'multiple' in tag.attrib: + # Only extract value if selected + selected = self._copy(tag)('option[selected]') + # Rebuild list to avoid serialization error + return list(selected.map( + lambda _, o: self._copy(o).attr('value') + )) selected_option = self._copy(tag)('option[selected]:last') if selected_option: return selected_option.attr('value') @@ -1015,25 +1040,36 @@ return 'on' else: return val - # <input> and everything else. + # <input> + elif tag.tag == 'input': + val = self._copy(tag).attr('value') + return val.replace('\n', '') if val else '' + # everything else. return self._copy(tag).attr('value') or '' def _set_value(pq, value): for tag in pq: - # <textarea> - if tag.tag == 'textarea': - self._copy(tag).text(value) - continue # <select> if tag.tag == 'select': + if not isinstance(value, list): + value = [value] def _make_option_selected(_, elem): pq = self._copy(elem) - if pq.attr('value') == value: + if pq.attr('value') in value: pq.attr('selected', 'selected') + if 'multiple' not in tag.attrib: + del value[:] # Ensure it toggles first match else: pq.removeAttr('selected') self._copy(tag)('option').each(_make_option_selected) continue + # Stringify array + if isinstance(value, list): + value = ','.join(value) + # <textarea> + if tag.tag == 'textarea': + self._copy(tag).text(value) + continue # <input> and everything else. self._copy(tag).attr('value', value) @@ -1101,7 +1137,6 @@ if children: tag.extend(children) tag.text = root.text - tag.tail = root.tail return self @with_camel_case_alias @@ -1144,6 +1179,14 @@ >>> print(doc.text()) toto tata + Get the text value, without squashing newlines:: + + >>> doc = PyQuery('''<div><span>toto</span> + ... <span>tata</span></div>''') + >>> print(doc.text(squash_space=False)) + toto + tata + Set the text value:: >>> doc.text('Youhou !') @@ -1156,7 +1199,10 @@ if value is no_default: if not self: return '' - return ' '.join(extract_text(tag, **kwargs) for tag in self) + return ' '.join( + self._copy(tag).html() if tag.tag == 'textarea' else + extract_text(tag, **kwargs) for tag in self + ) for tag in self: for child in tag.getchildren(): @@ -1477,10 +1523,138 @@ setattr(PyQuery, name, fn) fn = Fn() + + ######## + # AJAX # + ######## + + @with_camel_case_alias + def serialize_array(self): + """Serialize form elements as an array of dictionaries, whose structure + mirrors that produced by the jQuery API. Notably, it does not handle the + deprecated `keygen` form element. + + >>> d = PyQuery('<form><input name="order" value="spam"></form>') + >>> d.serialize_array() == [{'name': 'order', 'value': 'spam'}] + True + >>> d.serializeArray() == [{'name': 'order', 'value': 'spam'}] + True + """ + return list(map( + lambda p: {'name': p[0], 'value': p[1]}, + self.serialize_pairs() + )) + + def serialize(self): + """Serialize form elements as a URL-encoded string. + + >>> h = ( + ... '<form><input name="order" value="spam">' + ... '<input name="order2" value="baked beans"></form>' + ... ) + >>> d = PyQuery(h) + >>> d.serialize() + 'order=spam&order2=baked%20beans' + """ + return urlencode(self.serialize_pairs()).replace('+', '%20') + + ##################################################### # Additional methods that are not in the jQuery API # ##################################################### + @with_camel_case_alias + def serialize_pairs(self): + """Serialize form elements as an array of 2-tuples conventional for + typical URL-parsing operations in Python. + + >>> d = PyQuery('<form><input name="order" value="spam"></form>') + >>> d.serialize_pairs() + [('order', 'spam')] + >>> d.serializePairs() + [('order', 'spam')] + """ + # https://github.com/jquery/jquery/blob + # /2d4f53416e5f74fa98e0c1d66b6f3c285a12f0ce/src/serialize.js#L14 + _submitter_types = ['submit', 'button', 'image', 'reset', 'file'] + + controls = self._copy([]) + # Expand list of form controls + for el in self.items(): + if el[0].tag == 'form': + form_id = el.attr('id') + if form_id: + # 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))) + else: + controls.extend(el(':not([form]):input')) + elif el[0].tag == 'fieldset': + controls.extend(el(':input')) + else: + 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)) + controls = controls.filter(selector) + + def _filter_out_unchecked(_, el): + el = controls._copy(el) + return not el.is_(':checkbox:not(:checked)') \ + and not el.is_(':radio:not(:checked)') + controls = controls.filter(_filter_out_unchecked) + + # jQuery serializes inputs with the datalist element as an ancestor + # contrary to WHATWG spec as of August 2018 + # + # xpath = 'self::*[not(ancestor::datalist)]' + # results = [] + # for tag in controls: + # results.extend(tag.xpath(xpath, namespaces=controls.namespaces)) + # controls = controls._copy(results) + + # Serialize values + ret = [] + for field in controls: + val = self._copy(field).val() + if isinstance(val, list): + ret.extend(map( + lambda v: (field.attrib['name'], v.replace('\n', '\r\n')), + val + )) + else: + ret.append((field.attrib['name'], val.replace('\n', '\r\n'))) + return ret + + @with_camel_case_alias + def serialize_dict(self): + """Serialize form elements as an ordered dictionary. Multiple values + corresponding to the same input name are concatenated into one list. + + >>> d = PyQuery('''<form> + ... <input name="order" value="spam"> + ... <input name="order" value="eggs"> + ... <input name="order2" value="ham"> + ... </form>''') + >>> d.serialize_dict() + OrderedDict([('order', ['spam', 'eggs']), ('order2', 'ham')]) + >>> d.serializeDict() + OrderedDict([('order', ['spam', 'eggs']), ('order2', 'ham')]) + """ + ret = OrderedDict() + for name, val in self.serialize_pairs(): + if name not in ret: + ret[name] = val + elif not isinstance(ret[name], list): + ret[name] = [ret[name], val] + else: + ret[name].append(val) + return ret + @property def base_url(self): """Return the url of current html document or None if not available. @@ -1507,6 +1681,11 @@ if attr_value is None: return None + # skip specific "protocol" schemas + if any(attr_value.startswith(schema) + for schema in ('tel:', 'callto:', 'sms:')): + return None + return self(e).attr(attr, urljoin(base_url, attr_value.strip())) return rep diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/pyquery.egg-info/PKG-INFO new/pyquery-1.4.1/pyquery.egg-info/PKG-INFO --- old/pyquery-1.4.0/pyquery.egg-info/PKG-INFO 2018-01-11 20:50:56.000000000 +0100 +++ new/pyquery-1.4.1/pyquery.egg-info/PKG-INFO 2019-10-26 10:47:47.000000000 +0200 @@ -1,12 +1,13 @@ -Metadata-Version: 1.1 +Metadata-Version: 1.2 Name: pyquery -Version: 1.4.0 +Version: 1.4.1 Summary: A jquery-like library for python Home-page: https://github.com/gawel/pyquery -Author: Gael Pasgrimaud -Author-email: gael@gawel.org +Author: Olivier Lauzanne +Author-email: olauzanne@gmail.com +Maintainer: Gael Pasgrimaud +Maintainer-email: gael@gawel.org License: BSD -Description-Content-Type: UNKNOWN Description: pyquery: a jquery-like library for python ========================================= @@ -25,7 +26,7 @@ The `project`_ is being actively developped on a git repository on Github. I have the policy of giving push access to anyone who wants it and then to review - what he does. So if you want to contribute just email me. + what they do. So if you want to contribute just email me. Please report bugs on the `github <https://github.com/gawel/pyquery/issues>`_ issue @@ -88,6 +89,20 @@ News ==== + 1.4.1 (2019-10-26) + ------------------ + + - This is the latest release with py2 support + + - Remove py33, py34 support + + - web scraping improvements: default timeout and session support + + - Add API methods to serialize form-related elements according to spec + + - Include HTML markup when querying textarea text/value + + 1.4.0 (2018-01-11) ------------------ @@ -335,7 +350,6 @@ Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/setup.py new/pyquery-1.4.1/setup.py --- old/pyquery-1.4.0/setup.py 2018-01-11 20:50:55.000000000 +0100 +++ new/pyquery-1.4.1/setup.py 2019-10-26 10:47:47.000000000 +0200 @@ -40,7 +40,7 @@ """ % read('README', 'CHANGES') -version = '1.4.0' +version = '1.4.1' setup(name='pyquery', version=version, @@ -51,10 +51,9 @@ "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", ], keywords='jquery html xml scraping', author='Olivier Lauzanne', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/tests/geckodriver.sh new/pyquery-1.4.1/tests/geckodriver.sh --- old/pyquery-1.4.0/tests/geckodriver.sh 2018-01-11 20:50:55.000000000 +0100 +++ new/pyquery-1.4.1/tests/geckodriver.sh 2019-10-26 10:47:47.000000000 +0200 @@ -1,5 +1,5 @@ #!/bin/bash -driver="https://github.com/mozilla/geckodriver/releases/download/v0.19.1/geckodriver..." +driver="https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver..." [ -f geckodriver ] || wget -cqO- $driver | tar xvzf - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/tests/selenium.sh new/pyquery-1.4.1/tests/selenium.sh --- old/pyquery-1.4.0/tests/selenium.sh 2018-01-11 20:50:55.000000000 +0100 +++ new/pyquery-1.4.1/tests/selenium.sh 2019-10-26 10:47:47.000000000 +0200 @@ -4,5 +4,5 @@ # get geckodriver ./tests/geckodriver.sh -# run tox with py3.6 -MOZ_HEADLESS=1 PATH=$PATH:$PWD tox -e py36 tests/test_real_browser.py +# run tox with py3.7 +MOZ_HEADLESS=1 PATH=$PATH:$PWD tox -e py37 tests/test_real_browser.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/tests/test_pyquery.py new/pyquery-1.4.1/tests/test_pyquery.py --- old/pyquery-1.4.0/tests/test_pyquery.py 2018-01-11 20:50:55.000000000 +0100 +++ new/pyquery-1.4.1/tests/test_pyquery.py 2019-10-26 10:47:47.000000000 +0200 @@ -5,8 +5,10 @@ # Distributed under the BSD license, see LICENSE.txt import os import sys +import time from lxml import etree -from pyquery.pyquery import PyQuery as pq +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 .compat import PY3k @@ -94,8 +96,32 @@ <body> <form action="/"> <input name="enabled" type="text" value="test"/> + <b disabled>Not :disabled</b> <input name="disabled" type="text" value="disabled" disabled="disabled"/> + <fieldset> + <input name="fieldset-enabled"> + </fieldset> + <fieldset disabled> + <legend> + <input name="legend-enabled"> + </legend> + <input name="fieldset-disabled"> + <legend> + <input name="legend-disabled"> + </legend> + <select id="disabled-select"> + <optgroup> + <option></option> + </optgroup> + </select> + </fieldset> + <select> + <optgroup id="disabled-optgroup" disabled> + <option id="disabled-from-optgroup"></option> + <option id="disabled-option" disabled></option> + </optgroup> + </select> <input name="file" type="file" /> <select name="select"> <option value="">Choose something</option> @@ -137,6 +163,10 @@ self.assertEqual(isinstance(doc.root, etree._ElementTree), True) self.assertEqual(doc.encoding, 'UTF-8') + child = doc.children().eq(0) + self.assertNotEqual(child._parent, no_default) + self.assertTrue(isinstance(child.root, etree._ElementTree)) + def test_selector_from_doc(self): doc = etree.fromstring(self.html) assert len(self.klass(doc)) == 1 @@ -178,12 +208,23 @@ # test on the form e = self.klass(self.html4) - assert len(e(':disabled')) == 1 - assert len(e('input:enabled')) == 9 + disabled = e(':disabled') + self.assertIn(e('[name="disabled"]')[0], disabled) + self.assertIn(e('fieldset[disabled]')[0], disabled) + self.assertIn(e('[name="legend-disabled"]')[0], disabled) + self.assertIn(e('[name="fieldset-disabled"]')[0], disabled) + self.assertIn(e('#disabled-optgroup')[0], disabled) + self.assertIn(e('#disabled-from-optgroup')[0], disabled) + self.assertIn(e('#disabled-option')[0], disabled) + self.assertIn(e('#disabled-select')[0], disabled) + + assert len(disabled) == 8 + assert len(e('select:enabled')) == 2 + assert len(e('input:enabled')) == 11 assert len(e(':selected')) == 1 assert len(e(':checked')) == 2 assert len(e(':file')) == 1 - assert len(e(':input')) == 12 + assert len(e(':input')) == 18 assert len(e(':button')) == 2 assert len(e(':radio')) == 3 assert len(e(':checkbox')) == 3 @@ -349,8 +390,18 @@ <input type="radio" value="Ham"> ''' + html2_newline = ''' + <input id="newline-text" type="text" name="order" value="S +pam"> + <input id="newline-radio" type="radio" name="order" value="S +pam"> + ''' + html3 = ''' - <textarea>Spam</textarea> + <textarea id="textarea-single">Spam</textarea> + <textarea id="textarea-multi">Spam +<b>Eggs</b> +Bacon</textarea> ''' html4 = ''' @@ -365,6 +416,29 @@ </select> <select id="third"> </select> + <select id="fourth"> + <option value="spam">Spam</option> + <option value="spam">Eggs</option> + <option value="spam">Bacon</option> + </select> + ''' + + html6 = ''' + <select id="first" multiple> + <option value="spam" selected>Spam</option> + <option value="eggs" selected>Eggs</option> + <option value="bacon">Bacon</option> + </select> + <select id="second" multiple> + <option value="spam">Spam</option> + <option value="eggs">Eggs</option> + <option value="bacon">Bacon</option> + </select> + <select id="third" multiple> + <option value="spam">Spam</option> + <option value="spam">Eggs</option> + <option value="spam">Bacon</option> + </select> ''' html5 = ''' @@ -410,14 +484,27 @@ self.assertEqual(d('input:checkbox').val(), '44') self.assertEqual(d('input:radio').val(), '45') + def test_val_for_inputs_with_newline(self): + d = pq(self.html2_newline) + self.assertEqual(d('#newline-text').val(), 'Spam') + self.assertEqual(d('#newline-radio').val(), 'S\npam') + def test_val_for_textarea(self): d = pq(self.html3) - self.assertEqual(d('textarea').val(), 'Spam') - self.assertEqual(d('textarea').text(), 'Spam') - d('textarea').val('42') - self.assertEqual(d('textarea').val(), '42') + self.assertEqual(d('#textarea-single').val(), 'Spam') + self.assertEqual(d('#textarea-single').text(), 'Spam') + d('#textarea-single').val('42') + self.assertEqual(d('#textarea-single').val(), '42') # Note: jQuery still returns 'Spam' here. - self.assertEqual(d('textarea').text(), '42') + self.assertEqual(d('#textarea-single').text(), '42') + + multi_expected = '''Spam\n<b>Eggs</b>\nBacon''' + self.assertEqual(d('#textarea-multi').val(), multi_expected) + self.assertEqual(d('#textarea-multi').text(), multi_expected) + multi_new = '''Bacon\n<b>Eggs</b>\nSpam''' + d('#textarea-multi').val(multi_new) + 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) @@ -432,6 +519,36 @@ self.assertIsNone(d('#third').val()) d('#first').val('bacon') # Selecting non-existing option. self.assertEqual(d('#first').val(), 'spam') + # Value set based on option order, not value order + d('#second').val(['bacon', 'eggs']) + self.assertEqual(d('#second').val(), 'eggs') + d('#fourth').val(['spam']) + self.assertEqual(d('#fourth').val(), 'spam') + # Sets first option with matching value + self.assertEqual(d('#fourth option[selected]').length, 1) + self.assertEqual(d('#fourth option[selected]').text(), 'Spam') + + def test_val_for_select_multiple(self): + d = pq(self.html6) + self.assertEqual(d('#first').val(), ['spam', 'eggs']) + # Selecting non-existing option. + d('#first').val(['eggs', 'sausage', 'bacon']) + self.assertEqual(d('#first').val(), ['eggs', 'bacon']) + self.assertEqual(d('#second').val(), []) + d('#second').val('eggs') + self.assertEqual(d('#second').val(), ['eggs']) + d('#second').val(['not spam', 'not eggs']) + self.assertEqual(d('#second').val(), []) + d('#third').val(['spam']) + self.assertEqual(d('#third').val(), ['spam', 'spam', 'spam']) + + def test_val_for_input_and_textarea_given_array_value(self): + d = pq('<input type="text">') + d('input').val(['spam', 'eggs']) + self.assertEqual(d('input').val(), 'spam,eggs') + d = pq('<textarea></textarea>') + d('textarea').val(['spam', 'eggs']) + self.assertEqual(d('textarea').val(), 'spam,eggs') def test_val_for_multiple_elements(self): d = pq(self.html5) @@ -461,6 +578,152 @@ d = pq('<input>') self.assertEqual(d.val(), '') + def test_html_replacement(self): + html = '<div>Not Me<span>Replace Me</span>Not Me</div>' + replacement = 'New <em>Contents</em> New' + expected = html.replace('Replace Me', replacement) + + d = pq(html) + d.find('span').html(replacement) + + new_html = d.outerHtml() + self.assertEqual(new_html, expected) + self.assertIn(replacement, new_html) + + +class TestAjax(TestCase): + + html = ''' + <div id="div"> + <input form="dispersed" name="order" value="spam"> + </div> + <form id="dispersed"> + <div><input name="order" value="eggs"></div> + <input form="dispersed" name="order" value="ham"> + <input form="other-form" name="order" value="nothing"> + <input form="" name="order" value="nothing"> + </form> + <form id="other-form"> + <input form="dispersed" name="order" value="tomato"> + </form> + <form class="no-id"> + <input form="dispersed" name="order" value="baked beans"> + <input name="spam" value="Spam"> + </form> + ''' + + html2 = ''' + <form id="first"> + <input name="order" value="spam"> + <fieldset> + <input name="fieldset" value="eggs"> + <input id="input" name="fieldset" value="ham"> + </fieldset> + </form> + <form id="datalist"> + <datalist><div><input name="datalist" value="eggs"></div></datalist> + <input type="checkbox" name="checkbox" checked> + <input type="radio" name="radio" checked> + </form> + ''' + + html3 = ''' + <form> + <input name="order" value="spam"> + <input id="noname" value="sausage"> + <fieldset disabled> + <input name="order" value="sausage"> + </fieldset> + <input name="disabled" value="ham" disabled> + <input type="submit" name="submit" value="Submit"> + <input type="button" name="button" value=""> + <input type="image" name="image" value=""> + <input type="reset" name="reset" value="Reset"> + <input type="file" name="file" value=""> + <button type="submit" name="submit" value="submit"></button> + <input type="checkbox" name="spam"> + <input type="radio" name="eggs"> + </form> + ''' + + html4 = ''' + <form> + <input name="spam" value="Spam/ +spam"> + <select name="order" multiple> + <option value="baked +beans" selected> + <option value="tomato" selected> + <option value="spam"> + </select> + <textarea name="multiline">multiple +lines +of text</textarea> + </form> + ''' + + def test_serialize_pairs_form_id(self): + d = pq(self.html) + self.assertEqual(d('#div').serialize_pairs(), []) + self.assertEqual(d('#dispersed').serialize_pairs(), [ + ('order', 'spam'), ('order', 'eggs'), ('order', 'ham'), + ('order', 'tomato'), ('order', 'baked beans'), + ]) + self.assertEqual(d('.no-id').serialize_pairs(), [ + ('spam', 'Spam'), + ]) + + def test_serialize_pairs_form_controls(self): + d = pq(self.html2) + self.assertEqual(d('fieldset').serialize_pairs(), [ + ('fieldset', 'eggs'), ('fieldset', 'ham'), + ]) + self.assertEqual(d('#input, fieldset, #first').serialize_pairs(), [ + ('order', 'spam'), ('fieldset', 'eggs'), ('fieldset', 'ham'), + ('fieldset', 'eggs'), ('fieldset', 'ham'), ('fieldset', 'ham'), + ]) + self.assertEqual(d('#datalist').serialize_pairs(), [ + ('datalist', 'eggs'), ('checkbox', 'on'), ('radio', 'on'), + ]) + + def test_serialize_pairs_filter_controls(self): + d = pq(self.html3) + self.assertEqual(d('form').serialize_pairs(), [ + ('order', 'spam') + ]) + + def test_serialize_pairs_form_values(self): + d = pq(self.html4) + self.assertEqual(d('form').serialize_pairs(), [ + ('spam', 'Spam/spam'), ('order', 'baked\r\nbeans'), + ('order', 'tomato'), ('multiline', 'multiple\r\nlines\r\nof text'), + ]) + + def test_serialize_array(self): + d = pq(self.html4) + self.assertEqual(d('form').serialize_array(), [ + {'name': 'spam', 'value': 'Spam/spam'}, + {'name': 'order', 'value': 'baked\r\nbeans'}, + {'name': 'order', 'value': 'tomato'}, + {'name': 'multiline', 'value': 'multiple\r\nlines\r\nof text'}, + ]) + + def test_serialize(self): + d = pq(self.html4) + self.assertEqual( + d('form').serialize(), + 'spam=Spam%2Fspam&order=baked%0D%0Abeans&order=tomato&' + 'multiline=multiple%0D%0Alines%0D%0Aof%20text' + ) + + def test_serialize_dict(self): + d = pq(self.html4) + self.assertEqual(d('form').serialize_dict(), { + 'spam': 'Spam/spam', + 'order': ['baked\r\nbeans', 'tomato'], + 'multiline': 'multiple\r\nlines\r\nof text', + }) + class TestMakeLinks(TestCase): @@ -609,6 +872,17 @@ self.assertIn('REQUEST_METHOD: POST', d('p').text()) self.assertIn('q=foo', d('p').text()) + def test_session(self): + if HAS_REQUEST: + import requests + session = requests.Session() + session.headers.update({'X-FOO': 'bar'}) + d = pq(self.application_url, {'q': 'foo'}, + method='get', session=session) + self.assertIn('HTTP_X_FOO: bar', d('p').text()) + else: + self.skipTest('no requests library') + def tearDown(self): self.s.shutdown() @@ -620,3 +894,23 @@ method='get') print(d) self.assertEqual(d('#pt-login').text(), u'Войти') + + +class TestWebScrappingTimeouts(TestCase): + + def setUp(self): + def app(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + time.sleep(2) + return [b'foobar\n'] + self.s = http.StopableWSGIServer.create(app) + self.s.wait() + self.application_url = self.s.application_url.rstrip('/') + + def test_get(self): + pq(self.application_url) + with self.assertRaises(Exception): + pq(self.application_url, timeout=1) + + def tearDown(self): + self.s.shutdown() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyquery-1.4.0/tox.ini new/pyquery-1.4.1/tox.ini --- old/pyquery-1.4.0/tox.ini 2018-01-11 20:50:55.000000000 +0100 +++ new/pyquery-1.4.1/tox.ini 2019-10-26 10:47:47.000000000 +0200 @@ -1,5 +1,5 @@ [tox] -envlist=py27,py33,py34,py35,py36 +envlist=py27,py35,py36,py37 [testenv] whitelist_externals= @@ -10,7 +10,7 @@ rm -f .coverage {envbindir}/nosetests [] deps = - py36: selenium + py37: selenium requests WebOb>1.1.9 WebTest @@ -20,7 +20,7 @@ [testenv:flake8] skipsdist=true skip_install=true -basepython = python3.5 +basepython = python3.7 commands = flake8 pyquery tests deps = @@ -29,7 +29,7 @@ [testenv:docs] skip_install=false skipsdist=true -basepython = python3.5 +basepython = python3.7 changedir = docs deps = sphinx
participants (1)
-
root