Gael Pasgrimaud avatar Gael Pasgrimaud committed 1765cf6

Python 3 compatible

Add __unicode__ method

Add root and encoding attribute

fix issues #19, #20, #22, #23

Comments (0)

Files changed (13)

 develop-eggs/
 bin/
 dist/
+build/
+parts/
 docs/_build/
 .installed.cfg
 *.egg-info
+0.7
+---
+
+Python 3 compatible
+
+Add __unicode__ method
+
+Add root and encoding attribute
+
+fix issues 19, 20, 22, 23 
+
 0.6.1
 ------
 
     >>> d = pq("<html></html>")
     >>> d = pq(etree.fromstring("<html></html>"))
     >>> d = pq(url='http://google.com/')
-    >>> d = pq(url='http://google.com/', opener=lambda url: urllib.urlopen(url).read())
+    >>> # d = pq(url='http://google.com/', opener=lambda url: urllib.urlopen(url).read())
     >>> d = pq(filename=path_to_html_file)
 
 Now d is like the $ in jquery::
     >>> d("#hello")
     [<p#hello.hello>]
     >>> p = d("#hello")
-    >>> p.html()
-    'Hello world !'
+    >>> print(p.html())
+    Hello world !
     >>> p.html("you know <a href='http://python.org/'>Python</a> rocks")
     [<p#hello.hello>]
-    >>> p.html()
-    u'you know <a href="http://python.org/">Python</a> rocks'
-    >>> p.text()
-    'you know Python rocks'
+    >>> print(p.html())
+    you know <a href="http://python.org/">Python</a> rocks
+    >>> print(p.text())
+    you know Python rocks
 
 You can use some of the pseudo classes that are available in jQuery but that
 are not standard in css such as :first :last :even :odd :eq :lt :gt :checked

bootstrap-py3k.py

+##############################################################################
+#
+# Copyright (c) 2006 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+"""
+
+import os, shutil, sys, tempfile, textwrap
+try:
+    import urllib.request as urllib2
+except ImportError:
+    import urllib2
+import subprocess
+from optparse import OptionParser
+
+if sys.platform == 'win32':
+    def quote(c):
+        if ' ' in c:
+            return '"%s"' % c # work around spawn lamosity on windows
+        else:
+            return c
+else:
+    quote = str
+
+# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
+stdout, stderr = subprocess.Popen(
+    [sys.executable, '-S', '-c',
+     'try:\n'
+     '    import pickle\n'
+     'except ImportError:\n'
+     '    print(1)\n'
+     'else:\n'
+     '    print(0)\n'],
+    stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+has_broken_dash_S = bool(int(stdout.strip()))
+
+# In order to be more robust in the face of system Pythons, we want to
+# run without site-packages loaded.  This is somewhat tricky, in
+# particular because Python 2.6's distutils imports site, so starting
+# with the -S flag is not sufficient.  However, we'll start with that:
+if not has_broken_dash_S and 'site' in sys.modules:
+    # We will restart with python -S.
+    args = sys.argv[:]
+    args[0:0] = [sys.executable, '-S']
+    args = list(map(quote, args))
+    os.execv(sys.executable, args)
+
+# Now we are running with -S.  We'll get the clean sys.path, import site
+# because distutils will do it later, and then reset the path and clean
+# out any namespace packages from site-packages that might have been
+# loaded by .pth files.
+clean_path = sys.path[:]
+import site
+sys.path[:] = clean_path
+for k, v in list(sys.modules.items()):
+    if k in ('setuptools', 'pkg_resources') or (
+        hasattr(v, '__path__') and
+        len(v.__path__)==1 and
+        not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))):
+        # This is a namespace package.  Remove it.
+        sys.modules.pop(k)
+
+is_jython = sys.platform.startswith('java')
+
+setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
+distribute_source = 'http://python-distribute.org/distribute_setup.py'
+
+# parsing arguments
+def normalize_to_url(option, opt_str, value, parser):
+    if value:
+        if '://' not in value: # It doesn't smell like a URL.
+            value = 'file://%s' % (
+                urllib2.pathname2url(
+                    os.path.abspath(os.path.expanduser(value))),)
+        if opt_str == '--download-base' and not value.endswith('/'):
+            # Download base needs a trailing slash to make the world happy.
+            value += '/'
+    else:
+        value = None
+    name = opt_str[2:].replace('-', '_')
+    setattr(parser.values, name, value)
+
+usage = '''\
+[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
+
+Bootstraps a buildout-based project.
+
+Simply run this script in a directory containing a buildout.cfg, using the
+Python that you want bin/buildout to use.
+
+Note that by using --setup-source and --download-base to point to
+local resources, you can keep this script from going over the network.
+'''
+
+parser = OptionParser(usage=usage)
+parser.add_option("-v", "--version", dest="version",
+                          help="use a specific zc.buildout version")
+parser.add_option("--setup-version", dest="setup_version",
+                  help="The version of setuptools or distribute to use.")
+parser.add_option("-d", "--distribute",
+                   action="store_true", dest="use_distribute",
+                   default= sys.version_info[0] >= 3,
+                   help="Use Distribute rather than Setuptools.")
+parser.add_option("--setup-source", action="callback", dest="setup_source",
+                  callback=normalize_to_url, nargs=1, type="string",
+                  help=("Specify a URL or file location for the setup file. "
+                        "If you use Setuptools, this will default to " +
+                        setuptools_source + "; if you use Distribute, this "
+                        "will default to " + distribute_source +"."))
+parser.add_option("--download-base", action="callback", dest="download_base",
+                  callback=normalize_to_url, nargs=1, type="string",
+                  help=("Specify a URL or directory for downloading "
+                        "zc.buildout and either Setuptools or Distribute. "
+                        "Defaults to PyPI."))
+parser.add_option("--eggs",
+                  help=("Specify a directory for storing eggs.  Defaults to "
+                        "a temporary directory that is deleted when the "
+                        "bootstrap script completes."))
+parser.add_option("-t", "--accept-buildout-test-releases",
+                  dest='accept_buildout_test_releases',
+                  action="store_true",
+                  default=sys.version_info[0] > 2,
+                  help=("Normally, if you do not specify a --version, the "
+                        "bootstrap script and buildout gets the newest "
+                        "*final* versions of zc.buildout and its recipes and "
+                        "extensions for you.  If you use this flag, "
+                        "bootstrap and buildout will get the newest releases "
+                        "even if they are alphas or betas."))
+parser.add_option("-c", None, action="store", dest="config_file",
+                   help=("Specify the path to the buildout configuration "
+                         "file to be used."))
+
+options, args = parser.parse_args()
+
+# if -c was provided, we push it back into args for buildout's main function
+if options.config_file is not None:
+    args += ['-c', options.config_file]
+
+if options.eggs:
+    eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
+else:
+    eggs_dir = tempfile.mkdtemp()
+
+if options.setup_source is None:
+    if options.use_distribute:
+        options.setup_source = distribute_source
+    else:
+        options.setup_source = setuptools_source
+
+if options.accept_buildout_test_releases:
+    args.append('buildout:accept-buildout-test-releases=true')
+args.append('bootstrap')
+
+try:
+    import pkg_resources
+    import setuptools # A flag.  Sometimes pkg_resources is installed alone.
+    if not hasattr(pkg_resources, '_distribute'):
+        raise ImportError
+except ImportError:
+    ez_code = urllib2.urlopen(
+        options.setup_source).read().replace('\r\n'.encode(), '\n'.encode())
+    ez = {}
+    exec(ez_code, ez)
+    setup_args = dict(to_dir=eggs_dir, download_delay=0)
+    if options.download_base:
+        setup_args['download_base'] = options.download_base
+    if options.setup_version:
+        setup_args['version'] = options.setup_version
+    if options.use_distribute:
+        setup_args['no_fake'] = True
+    ez['use_setuptools'](**setup_args)
+    if 'pkg_resources' in sys.modules:
+        if sys.version_info[0] >= 3:
+            import imp
+            reload_ = imp.reload
+        else:
+            reload_ = reload
+
+        reload_(sys.modules['pkg_resources'])
+    import pkg_resources
+    # This does not (always?) update the default working set.  We will
+    # do it.
+    for path in sys.path:
+        if path not in pkg_resources.working_set.entries:
+            pkg_resources.working_set.add_entry(path)
+
+cmd = [quote(sys.executable),
+       '-c',
+       quote('from setuptools.command.easy_install import main; main()'),
+       '-mqNxd',
+       quote(eggs_dir)]
+
+if not has_broken_dash_S:
+    cmd.insert(1, '-S')
+
+find_links = options.download_base
+if not find_links:
+    find_links = os.environ.get('bootstrap-testing-find-links')
+if find_links:
+    cmd.extend(['-f', quote(find_links)])
+
+if options.use_distribute:
+    setup_requirement = 'distribute'
+else:
+    setup_requirement = 'setuptools'
+ws = pkg_resources.working_set
+setup_requirement_path = ws.find(
+    pkg_resources.Requirement.parse(setup_requirement)).location
+env = dict(
+    os.environ,
+    PYTHONPATH=setup_requirement_path)
+
+requirement = 'zc.buildout'
+version = options.version
+if version is None and not options.accept_buildout_test_releases:
+    # Figure out the most recent final version of zc.buildout.
+    import setuptools.package_index
+    _final_parts = '*final-', '*final'
+    def _final_version(parsed_version):
+        for part in parsed_version:
+            if (part[:1] == '*') and (part not in _final_parts):
+                return False
+        return True
+    index = setuptools.package_index.PackageIndex(
+        search_path=[setup_requirement_path])
+    if find_links:
+        index.add_find_links((find_links,))
+    req = pkg_resources.Requirement.parse(requirement)
+    if index.obtain(req) is not None:
+        best = []
+        bestv = None
+        for dist in index[req.project_name]:
+            distv = dist.parsed_version
+            if _final_version(distv):
+                if bestv is None or distv > bestv:
+                    best = [dist]
+                    bestv = distv
+                elif distv == bestv:
+                    best.append(dist)
+        if best:
+            best.sort()
+            version = best[-1].version
+if version:
+    requirement = '=='.join((requirement, version))
+cmd.append(requirement)
+
+if is_jython:
+    import subprocess
+    exitcode = subprocess.Popen(cmd, env=env).wait()
+else: # Windows prefers this, apparently; otherwise we would prefer subprocess
+    exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
+if exitcode != 0:
+    sys.stdout.flush()
+    sys.stderr.flush()
+    print("An error occurred when trying to install zc.buildout. "
+          "Look above this message for any errors that "
+          "were output by easy_install.")
+    sys.exit(exitcode)
+
+ws.add_entry(eggs_dir)
+ws.require(requirement)
+import zc.buildout.buildout
+zc.buildout.buildout.main(args)
+if not options.eggs: # clean up temporary egg directory
+    shutil.rmtree(eggs_dir)
 ##############################################################################
 #
-# Copyright (c) 2006 Zope Corporation and Contributors.
+# Copyright (c) 2006 Zope Foundation and Contributors.
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
 Simply run this script in a directory containing a buildout.cfg.
 The script accepts buildout command-line options, so you can
 use the -c option to specify an alternate configuration file.
-
-$Id$
 """
 
-import os, shutil, sys, tempfile, urllib2
-
-tmpeggs = tempfile.mkdtemp()
-
-is_jython = sys.platform.startswith('java')
-
-try:
-    import pkg_resources
-except ImportError:
-    ez = {}
-    exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
-                         ).read() in ez
-    ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
-
-    import pkg_resources
+import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess
+from optparse import OptionParser
 
 if sys.platform == 'win32':
     def quote(c):
         else:
             return c
 else:
-    def quote (c):
-        return c
+    quote = str
 
-cmd = 'from setuptools.command.easy_install import main; main()'
-ws  = pkg_resources.working_set
+# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
+stdout, stderr = subprocess.Popen(
+    [sys.executable, '-Sc',
+     'try:\n'
+     '    import ConfigParser\n'
+     'except ImportError:\n'
+     '    print 1\n'
+     'else:\n'
+     '    print 0\n'],
+    stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+has_broken_dash_S = bool(int(stdout.strip()))
+
+# In order to be more robust in the face of system Pythons, we want to
+# run without site-packages loaded.  This is somewhat tricky, in
+# particular because Python 2.6's distutils imports site, so starting
+# with the -S flag is not sufficient.  However, we'll start with that:
+if not has_broken_dash_S and 'site' in sys.modules:
+    # We will restart with python -S.
+    args = sys.argv[:]
+    args[0:0] = [sys.executable, '-S']
+    args = map(quote, args)
+    os.execv(sys.executable, args)
+# Now we are running with -S.  We'll get the clean sys.path, import site
+# because distutils will do it later, and then reset the path and clean
+# out any namespace packages from site-packages that might have been
+# loaded by .pth files.
+clean_path = sys.path[:]
+import site
+sys.path[:] = clean_path
+for k, v in sys.modules.items():
+    if k in ('setuptools', 'pkg_resources') or (
+        hasattr(v, '__path__') and
+        len(v.__path__)==1 and
+        not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))):
+        # This is a namespace package.  Remove it.
+        sys.modules.pop(k)
+
+is_jython = sys.platform.startswith('java')
+
+setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
+distribute_source = 'http://python-distribute.org/distribute_setup.py'
+
+# parsing arguments
+def normalize_to_url(option, opt_str, value, parser):
+    if value:
+        if '://' not in value: # It doesn't smell like a URL.
+            value = 'file://%s' % (
+                urllib.pathname2url(
+                    os.path.abspath(os.path.expanduser(value))),)
+        if opt_str == '--download-base' and not value.endswith('/'):
+            # Download base needs a trailing slash to make the world happy.
+            value += '/'
+    else:
+        value = None
+    name = opt_str[2:].replace('-', '_')
+    setattr(parser.values, name, value)
+
+usage = '''\
+[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
+
+Bootstraps a buildout-based project.
+
+Simply run this script in a directory containing a buildout.cfg, using the
+Python that you want bin/buildout to use.
+
+Note that by using --setup-source and --download-base to point to
+local resources, you can keep this script from going over the network.
+'''
+
+parser = OptionParser(usage=usage)
+parser.add_option("-v", "--version", dest="version",
+                          help="use a specific zc.buildout version")
+parser.add_option("-d", "--distribute",
+                   action="store_true", dest="use_distribute", default=False,
+                   help="Use Distribute rather than Setuptools.")
+parser.add_option("--setup-source", action="callback", dest="setup_source",
+                  callback=normalize_to_url, nargs=1, type="string",
+                  help=("Specify a URL or file location for the setup file. "
+                        "If you use Setuptools, this will default to " +
+                        setuptools_source + "; if you use Distribute, this "
+                        "will default to " + distribute_source +"."))
+parser.add_option("--download-base", action="callback", dest="download_base",
+                  callback=normalize_to_url, nargs=1, type="string",
+                  help=("Specify a URL or directory for downloading "
+                        "zc.buildout and either Setuptools or Distribute. "
+                        "Defaults to PyPI."))
+parser.add_option("--eggs",
+                  help=("Specify a directory for storing eggs.  Defaults to "
+                        "a temporary directory that is deleted when the "
+                        "bootstrap script completes."))
+parser.add_option("-t", "--accept-buildout-test-releases",
+                  dest='accept_buildout_test_releases',
+                  action="store_true", default=False,
+                  help=("Normally, if you do not specify a --version, the "
+                        "bootstrap script and buildout gets the newest "
+                        "*final* versions of zc.buildout and its recipes and "
+                        "extensions for you.  If you use this flag, "
+                        "bootstrap and buildout will get the newest releases "
+                        "even if they are alphas or betas."))
+parser.add_option("-c", None, action="store", dest="config_file",
+                   help=("Specify the path to the buildout configuration "
+                         "file to be used."))
+
+options, args = parser.parse_args()
+
+# if -c was provided, we push it back into args for buildout's main function
+if options.config_file is not None:
+    args += ['-c', options.config_file]
+
+if options.eggs:
+    eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
+else:
+    eggs_dir = tempfile.mkdtemp()
+
+if options.setup_source is None:
+    if options.use_distribute:
+        options.setup_source = distribute_source
+    else:
+        options.setup_source = setuptools_source
+
+if options.accept_buildout_test_releases:
+    args.append('buildout:accept-buildout-test-releases=true')
+args.append('bootstrap')
+
+try:
+    import pkg_resources
+    import setuptools # A flag.  Sometimes pkg_resources is installed alone.
+    if not hasattr(pkg_resources, '_distribute'):
+        raise ImportError
+except ImportError:
+    ez_code = urllib2.urlopen(
+        options.setup_source).read().replace('\r\n', '\n')
+    ez = {}
+    exec ez_code in ez
+    setup_args = dict(to_dir=eggs_dir, download_delay=0)
+    if options.download_base:
+        setup_args['download_base'] = options.download_base
+    if options.use_distribute:
+        setup_args['no_fake'] = True
+    ez['use_setuptools'](**setup_args)
+    if 'pkg_resources' in sys.modules:
+        reload(sys.modules['pkg_resources'])
+    import pkg_resources
+    # This does not (always?) update the default working set.  We will
+    # do it.
+    for path in sys.path:
+        if path not in pkg_resources.working_set.entries:
+            pkg_resources.working_set.add_entry(path)
+
+cmd = [quote(sys.executable),
+       '-c',
+       quote('from setuptools.command.easy_install import main; main()'),
+       '-mqNxd',
+       quote(eggs_dir)]
+
+if not has_broken_dash_S:
+    cmd.insert(1, '-S')
+
+find_links = options.download_base
+if not find_links:
+    find_links = os.environ.get('bootstrap-testing-find-links')
+if find_links:
+    cmd.extend(['-f', quote(find_links)])
+
+if options.use_distribute:
+    setup_requirement = 'distribute'
+else:
+    setup_requirement = 'setuptools'
+ws = pkg_resources.working_set
+setup_requirement_path = ws.find(
+    pkg_resources.Requirement.parse(setup_requirement)).location
+env = dict(
+    os.environ,
+    PYTHONPATH=setup_requirement_path)
+
+requirement = 'zc.buildout'
+version = options.version
+if version is None and not options.accept_buildout_test_releases:
+    # Figure out the most recent final version of zc.buildout.
+    import setuptools.package_index
+    _final_parts = '*final-', '*final'
+    def _final_version(parsed_version):
+        for part in parsed_version:
+            if (part[:1] == '*') and (part not in _final_parts):
+                return False
+        return True
+    index = setuptools.package_index.PackageIndex(
+        search_path=[setup_requirement_path])
+    if find_links:
+        index.add_find_links((find_links,))
+    req = pkg_resources.Requirement.parse(requirement)
+    if index.obtain(req) is not None:
+        best = []
+        bestv = None
+        for dist in index[req.project_name]:
+            distv = dist.parsed_version
+            if _final_version(distv):
+                if bestv is None or distv > bestv:
+                    best = [dist]
+                    bestv = distv
+                elif distv == bestv:
+                    best.append(dist)
+        if best:
+            best.sort()
+            version = best[-1].version
+if version:
+    requirement = '=='.join((requirement, version))
+cmd.append(requirement)
 
 if is_jython:
     import subprocess
-    
-    assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', 
-           quote(tmpeggs), 'zc.buildout'], 
-           env=dict(os.environ,
-               PYTHONPATH=
-               ws.find(pkg_resources.Requirement.parse('setuptools')).location
-               ),
-           ).wait() == 0
+    exitcode = subprocess.Popen(cmd, env=env).wait()
+else: # Windows prefers this, apparently; otherwise we would prefer subprocess
+    exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
+if exitcode != 0:
+    sys.stdout.flush()
+    sys.stderr.flush()
+    print ("An error occurred when trying to install zc.buildout. "
+           "Look above this message for any errors that "
+           "were output by easy_install.")
+    sys.exit(exitcode)
 
-else:
-    assert os.spawnle(
-        os.P_WAIT, sys.executable, quote (sys.executable),
-        '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout',
-        dict(os.environ,
-            PYTHONPATH=
-            ws.find(pkg_resources.Requirement.parse('setuptools')).location
-            ),
-        ) == 0
-
-ws.add_entry(tmpeggs)
-ws.require('zc.buildout')
+ws.add_entry(eggs_dir)
+ws.require(requirement)
 import zc.buildout.buildout
-zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
-shutil.rmtree(tmpeggs)
+zc.buildout.buildout.main(args)
+if not options.eggs: # clean up temporary egg directory
+    shutil.rmtree(eggs_dir)

buildout-py3k.cfg

+[buildout]
+newest = false
+parts = eggs
+develop = .
+
+[eggs]
+recipe = zc.recipe.egg
+eggs =
+    pyquery
+    nose
+scripts =
+    nosetests=test
+interpreter = python
+    
     Sphinx
     sphinx-pypi-upload
 interpreter = python
+scripts =
 
-

pyquery/__init__.py

 #
 # Distributed under the BSD license, see LICENSE.txt
 
+import sys
+
 try:
     import webob
 except ImportError:
-    from pyquery import PyQuery
+    from .pyquery import PyQuery
 else:
-    from ajax import PyQuery
+    from .ajax import PyQuery
 
 # -*- coding: utf-8 -*-
-from webob import Request, Response
-from pyquery import PyQuery as Base
-from pyquery import no_default
+import sys
+from .pyquery import PyQuery as Base
+from .pyquery import no_default
+
+if sys.version_info < (3,):
+    from webob import Request, Response
 
 try:
     from paste.proxy import Proxy

pyquery/pyquery.py

 # Copyright (C) 2008 - Olivier Lauzanne <olauzanne@gmail.com>
 #
 # Distributed under the BSD license, see LICENSE.txt
-from cssselectpatch import selector_to_xpath
+from .cssselectpatch import selector_to_xpath
+from copy import deepcopy
 from lxml import etree
 import lxml.html
-from copy import deepcopy
-from urlparse import urljoin
+import sys
+
+PY3k = sys.version_info >= (3,)
+
+if PY3k:
+    from urllib.request import urlopen
+    from urllib.parse import urlencode
+    from urllib.parse import urljoin
+    basestring = (str, bytes)
+    unicode = str
+else:
+    from urllib2 import urlopen
+    from urllib import urlencode
+    from urlparse import urljoin
+
+def func_globals(f):
+    return f.__globals__ if PY3k else f.func_globals
+
+def func_code(f):
+    return f.__code__ if PY3k else f.func_code
 
 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__'):
+        meth = 'parse'
+    else:
+        meth = 'fromstring'
     if custom_parser is None:
         if parser is None:
             try:
-                return [etree.fromstring(context)]
+                result = getattr(etree, meth)(context)
             except etree.XMLSyntaxError:
-                return [lxml.html.fromstring(context)]
-
+                result = getattr(lxml.html, meth)(context)
+            if isinstance(result, etree._ElementTree):
+                return [result.getroot()]
+            else:
+                return [result]
         elif parser == 'xml':
-            custom_parser = etree.fromstring
+            custom_parser = getattr(etree, meth)
         elif parser == 'html':
-            custom_parser = lxml.html.fromstring
+            custom_parser = getattr(lxml.html, meth)
         elif parser == 'soup':
             from  lxml.html import soupparser
-            custom_parser = lxml.html.soupparser.fromstring
+            custom_parser = getattr(lxml.html.soupparser, meth)
         elif parser == 'html_fragments':
             custom_parser = lxml.html.fragments_fromstring
         else:
     result = custom_parser(context)
     if type(result) is list:
         return result
+    elif isinstance(result, etree._ElementTree):
+        return [result.getroot()]
     else:
         return [result]
 
 def callback(func, *args):
-    return func(*args[:func.func_code.co_argcount])
+    return func(*args[:func_code(func).co_argcount])
 
 class NoDefault(object):
     def __repr__(self):
                     raise NotImplementedError()
             __delattr__ = __delitem__
             def __repr__(prop):
-                return '<flexible_element %s>' % self.pget.func_name
+                return '<flexible_element %s>' % self.pget.__name__
         return _element()
     def __set__(self, instance, value):
         if self.pset is not no_default:
         if kwargs:
             # specific case to get the dom
             if 'filename' in kwargs:
-                html = file(kwargs['filename']).read()
+                html = open(kwargs['filename'])
             elif 'url' in kwargs:
                 url = kwargs.pop('url')
                 if 'opener' in kwargs:
                     opener = kwargs.pop('opener')
                     html = opener(url)
                 else:
-                    from urllib2 import urlopen
-                    from urllib import urlencode
                     method = kwargs.get('method')
                     data = kwargs.get('data')
                     if type(data) in (dict, list, tuple):
                         url += data
                         data = None
 
-                    html = urlopen(url, data).read()
+                    if data and PY3k:
+                        data = data.encode('utf-8')
+
+                    html = urlopen(url, data)
+                    if not self.parser:
+                        self.parser = 'html'
                 self._base_url = url
             else:
                 raise ValueError('Invalid keyword arguments %s' % kwargs)
             if isinstance(context, basestring):
                 try:
                     elements = fromstring(context, self.parser)
-                except Exception, e:
-                    raise ValueError('%r, %s' % (e, context))
+                except Exception:
+                    raise ValueError(context)
             elif isinstance(context, self.__class__):
                 # copy
                 elements = context[:]
         """xml representation of current nodes::
 
             >>> xml = PyQuery('<script><![[CDATA[ ]></script>', parser='html_fragments')
-            >>> print str(xml)
+            >>> print(str(xml))
             <script>&lt;![[CDATA[ ]&gt;</script>
 
         """
-        return ''.join([etree.tostring(e) for e in self])
+        if PY3k:
+            return ''.join([etree.tostring(e, encoding=str) for e in self])
+        else:
+            return ''.join([etree.tostring(e) for e in self])
+
+    def __unicode__(self):
+        """xml representation of current nodes"""
+        return unicode('').join([etree.tostring(e, encoding=unicode) for e in self])
 
     def __html__(self):
         """html representation of current nodes::
 
             >>> html = PyQuery('<script><![[CDATA[ ]></script>', parser='html_fragments')
-            >>> print html.__html__()
+            >>> print(html.__html__())
             <script><![[CDATA[ ]></script>
 
         """
-        return ''.join([lxml.html.tostring(e) for e in self])
+        return unicode('').join([lxml.html.tostring(e, encoding=unicode) for e in self])
 
     def __repr__(self):
         r = []
                 r.append('<%s%s%s>' % (el.tag, id, c))
             return '[' + (', '.join(r)) + ']'
         except AttributeError:
-            return list.__repr__(self)
+            if PY3k:
+                return list.__repr__(self)
+            else:
+                for el in self:
+                    if isinstance(el, unicode):
+                        r.append(el.encode('utf-8'))
+                    else:
+                        r.append(el)
+                return repr(r)
 
 
+    @property
+    def root(self):
+        """return the xml root element
+        """
+        return self[0].getroottree()
+
+    @property
+    def encoding(self):
+        """return the xml encoding of the root element
+        """
+        return self.root.docinfo.encoding
+
     ##############
     # Traversing #
     ##############
             >>> d('strong').closest('form')
             []
         """
-        try:
-            current = self[0]
-        except IndexError:
-            current = None
-        while current is not None and not self.__class__(current).is_(selector):
-            current = current.getparent()
-        return self.__class__(current, **dict(parent=self))
+        result = []
+        for current in self:
+            while current is not None and not self.__class__(current).is_(selector):
+                current = current.getparent()
+            if current is not None:
+                result.append(current)
+        return self.__class__(result, **dict(parent=self))
 
     def filter(self, selector):
         """Filter elements in self using selector (string or function).
             >>> d('p').filter(lambda i: PyQuery(this).text() == 'Hi')
             [<p.hello>]
         """
-        if not callable(selector):
+        if not hasattr(selector, '__call__'):
             return self._filter_only(selector, self)
         else:
             elements = []
             try:
                 for i, this in enumerate(self):
-                    selector.func_globals['this'] = this
+                    func_globals(selector)['this'] = this
                     if callback(selector, i):
                         elements.append(this)
             finally:
-                if 'this' in selector.func_globals:
-                    del selector.func_globals['this']
+                f_globals = func_globals(selector)
+                if 'this' in f_globals:
+                    del f_globals['this']
             return self.__class__(elements, **dict(parent=self))
 
     def not_(self, selector):
         """
         try:
             for i, element in enumerate(self):
-                func.func_globals['this'] = element
+                func_globals(func)['this'] = element
                 if callback(func, i, element) == False:
                     break
         finally:
-            if 'this' in func.func_globals:
-                del func.func_globals['this']
+            f_globals = func_globals(func)
+            if 'this' in f_globals:
+                del f_globals['this']
         return self
 
     def map(self, func):
         items = []
         try:
             for i, element in enumerate(self):
-                func.func_globals['this'] = element
+                func_globals(func)['this'] = element
                 result = callback(func, i, element)
                 if result is not None:
                     if not isinstance(result, list):
                     else:
                         items.extend(result)
         finally:
-            if 'this' in func.func_globals:
-                del func.func_globals['this']
+            f_globals = func_globals(func)
+            if 'this' in f_globals:
+                del f_globals['this']
         return self.__class__(items, **dict(parent=self))
 
     @property
     def hide(self):
         """remove display:none to elements style
 
-            >>> print PyQuery('<div style="display:none;"/>').hide()
+            >>> print(PyQuery('<div style="display:none;"/>').hide())
             <div style="display: none"/>
 
         """
     def show(self):
         """add display:block to elements style
 
-            >>> print PyQuery('<div />').show()
+            >>> print(PyQuery('<div />').show())
             <div style="display: block"/>
 
         """
         Get the text value::
 
             >>> d = PyQuery('<div><span>toto</span></div>')
-            >>> print d.html()
+            >>> print(d.html())
             <span>toto</span>
 
         Set the text value::
 
             >>> d.html('<span>Youhou !</span>')
             [<div>]
-            >>> print d
+            >>> print(d)
             <div><span>Youhou !</span></div>
         """
         if value is no_default:
             if not children:
                 return tag.text
             html = tag.text or ''
-            html += ''.join(map(lambda x: etree.tostring(x, encoding=unicode), children))
+            html += unicode('').join([etree.tostring(e, encoding=unicode) for e in children])
             return html
         else:
             if isinstance(value, self.__class__):
-                new_html = str(value)
+                new_html = unicode(value)
             elif isinstance(value, basestring):
                 new_html = value
+            elif not value:
+                new_html = ''
+            else:
+                raise ValueError(type(value))
 
             for tag in self:
                 for child in tag.getchildren():
                     tag.remove(child)
-                root = fromstring('<root>' + new_html + '</root>', self.parser)[0]
+                root = fromstring(unicode('<root>') + new_html + unicode('</root>'), self.parser)[0]
                 children = root.getchildren()
                 if children:
                     tag.extend(children)
         """Get the html representation of the first selected element::
 
             >>> d = PyQuery('<div><span class="red">toto</span> rocks</div>')
-            >>> print d('span')
+            >>> print(d('span'))
             <span class="red">toto</span> rocks
-            >>> print d('span').outerHtml()
+            >>> print(d('span').outerHtml())
             <span class="red">toto</span>
 
             >>> S = PyQuery('<p>Only <b>me</b> & myself</p>')
-            >>> S('b').outerHtml()
-            '<b>me</b>'
+            >>> print(S('b').outerHtml())
+            <b>me</b>
 
         ..
         """
         if e0.tail:
             e0 = deepcopy(e0)
             e0.tail = ''
-        return lxml.html.tostring(e0)
+        return lxml.html.tostring(e0, encoding=unicode)
 
     def text(self, value=no_default):
         """Get or set the text representation of sub nodes.
         Get the text value::
 
             >>> doc = PyQuery('<div><span>toto</span><span>tata</span></div>')
-            >>> print doc.text()
+            >>> print(doc.text())
             toto tata
 
         Set the text value::
 
             >>> doc.text('Youhou !')
             [<div>]
-            >>> print doc
+            >>> print(doc)
             <div>Youhou !</div>
 
         """
 
     def _get_root(self, value):
         if  isinstance(value, basestring):
-            root = fromstring('<root>' + value + '</root>', self.parser)[0]
+            root = fromstring(unicode('<root>') + value + unicode('</root>'), self.parser)[0]
         elif isinstance(value, etree._Element):
             root = self.__class__(value)
         elif isinstance(value, PyQuery):
             >>> d = PyQuery('<span>youhou</span>')
             >>> d.wrap('<div></div>')
             [<div>]
-            >>> print d
+            >>> print(d)
             <div><span>youhou</span></div>
 
         """
         """Wrap all the elements in the matched set into a single wrapper element::
 
             >>> d = PyQuery('<div><span>Hey</span><span>you !</span></div>')
-            >>> print d('span').wrapAll('<div id="wrapper"></div>')
+            >>> print(d('span').wrapAll('<div id="wrapper"></div>'))
             <div id="wrapper"><span>Hey</span><span>you !</span></div>
 
         ..
     def replaceWith(self, value):
         """replace nodes by value
         """
-        if callable(value):
+        if hasattr(value, '__call__'):
             for i, element in enumerate(self):
                 self.__class__(element).before(value(i, element) + (element.tail or ''))
                 parent = element.getparent()
         >>> d = PyQuery('<div>Maybe <em>she</em> does <strong>NOT</strong> know</div>')
         >>> d('strong').remove()
         [<strong>]
-        >>> print d
+        >>> print(d)
         <div>Maybe <em>she</em> does   know</div>
         """
         if expr is no_default:
         """
         def __setattr__(self, name, func):
             def fn(self, *args):
-                func.func_globals['this'] = self
+                func_globals(func)['this'] = self
                 return func(*args)
             fn.__name__ = name
             setattr(PyQuery, name, fn)
 # Copyright (C) 2008 - Olivier Lauzanne <olauzanne@gmail.com>
 #
 # Distributed under the BSD license, see LICENSE.txt
-from webob import Request, Response, exc
 from lxml import etree
 import unittest
 import doctest
-import httplib
 import socket
+import sys
 import os
 
-import pyquery
-from pyquery import PyQuery as pq
-from ajax import PyQuery as pqa
+PY3k = sys.version_info >= (3,)
+
+if PY3k:
+    import pyquery
+    from pyquery.pyquery import PyQuery as pq
+    from http.client import HTTPConnection
+    pqa = pq
+else:
+    import pyquery
+    from httplib import HTTPConnection
+    from webob import Request, Response, exc
+    from pyquery import PyQuery as pq
+    from ajax import PyQuery as pqa
 
 socket.setdefaulttimeout(1)
 
 try:
-    conn = httplib.HTTPConnection("pyquery.org:80")
+    conn = HTTPConnection("pyquery.org:80")
     conn.request("GET", "/")
     response = conn.getresponse()
 except (socket.timeout, socket.error):
 else:
     GOT_NET=True
 
+
 def with_net(func):
     if GOT_NET:
         return func
 
+def not_py3k(func):
+    if not PY3k:
+        return func
+
 dirname = os.path.dirname(os.path.abspath(pyquery.__file__))
 docs = os.path.join(os.path.dirname(dirname), 'docs')
 path_to_html_file = os.path.join(dirname, 'test.html')
     if filename.endswith('.txt'):
         if not GOT_NET and filename in ('ajax.txt', 'tips.txt'):
             continue
+        if PY3k and filename in ('ajax.txt',):
+            continue
         klass_name = 'Test%s' % filename.replace('.txt', '').title()
         path = os.path.join(docs, filename)
-        exec '%s = type("%s", (TestReadme,), dict(path=path))' % (klass_name, klass_name)
+        exec('%s = type("%s", (TestReadme,), dict(path=path))' % (klass_name, klass_name))
 
 class TestTests(doctest.DocFileCase):
     path = os.path.join(dirname, 'tests.txt')
         test = parser.get_doctest(doc, globals(), '', self.path, 0)
         doctest.DocFileCase.__init__(self, test, optionflags=doctest.ELLIPSIS)
 
+class TestUnicode(unittest.TestCase):
+
+    @not_py3k
+    def test_unicode(self):
+        xml = pq(unicode("<p>é</p>", 'utf-8'))
+        self.assertEqual(unicode(xml), unicode("<p>é</p>", 'utf-8'))
+        self.assertEqual(type(xml.html()), unicode)
+        self.assertEqual(str(xml), '<p>&#233;</p>')
+
+
 class TestSelector(unittest.TestCase):
     klass = pq
     html = """
            </html>
            """
 
+    def test_get_root(self):
+        doc = pq('<?xml version="1.0" encoding="UTF-8"?><root/>')
+        self.assertEqual(doc.encoding, 'UTF-8')
+
     def test_selector_from_doc(self):
         doc = etree.fromstring(self.html)
         assert len(self.klass(doc)) == 1
         assert len(e(":parent")) == 2
         assert len(e(":empty")) == 6
         assert len(e(":contains('Heading')")) == 6
-        
+
     def test_on_the_fly_dom_creation(self):
         e = self.klass(self.html)
         assert e('<p>Hello world</p>').text() == 'Hello world'
         assert len(self.klass('div', self.html).filter('.node3')) == 1
         assert len(self.klass('div', self.html).filter('#node2')) == 1
         assert len(self.klass('div', self.html).filter(lambda i: i == 0)) == 1
-        
+
         d = pq('<p>Hello <b>warming</b> world</p>')
         self.assertEqual(d('strong').filter(lambda el: True), [])
 
         def ids_minus_one(i, elem):
             return int(self.klass(elem).attr('id')[-1]) - 1
         assert self.klass('div', self.html).map(ids_minus_one) == [0, 1]
-        
+
         d = pq('<p>Hello <b>warming</b> world</p>')
         self.assertEqual(d('strong').map(lambda i,el: pq(this).text()), [])
 
             <li>Milk</li>
         </ol>
     """
-    
+
     def test_S_this_inside_callback(self):
         S = pq(self.html)
         self.assertEqual(S('li').map(lambda i, el: S(this).html()), ['Coffee', 'Tea', 'Milk'])
-        
+
     def test_parameterless_callback(self):
         S = pq(self.html)
         self.assertEqual(S('li').map(lambda: S(this).html()), ['Coffee', 'Tea', 'Milk'])
-        
+
 def application(environ, start_response):
     req = Request(environ)
     response = Response()
 class TestAjaxSelector(TestSelector):
     klass = pqa
 
+    @not_py3k
     @with_net
     def test_proxy(self):
         e = self.klass([])
         val = e.get('http://pyquery.org/')
         assert len(val('body')) == 1, (str(val.response), val)
 
+    @not_py3k
     def test_get(self):
         e = self.klass(app=application)
         val = e.get('/')
         assert len(val('pre')) == 1, val
 
+    @not_py3k
     def test_secure_get(self):
         e = self.klass(app=secure_application)
         val = e.get('/', environ=dict(REMOTE_USER='gawii'))
         val = e.get('/', REMOTE_USER='gawii')
         assert len(val('pre')) == 1, val
 
+    @not_py3k
     def test_secure_get_not_authorized(self):
         e = self.klass(app=secure_application)
         val = e.get('/')
         assert len(val('pre')) == 0, val
 
+    @not_py3k
     def test_post(self):
         e = self.klass(app=application)
         val = e.post('/')
         assert len(val('a')) == 1, val
 
+    @not_py3k
     def test_subquery(self):
         e = self.klass(app=application)
         n = e('div')
         self.assertRaises(etree.XMLSyntaxError, lambda: d.after(self.html))
         d = pq(self.xml, parser='html')
         d.after(self.html) # this should not fail
-        
+
+
+    @not_py3k
+    def test_soup_parser(self):
         d = pq('<meta><head><title>Hello</head><body onload=crash()>Hi all<p>', parser='soup')
         self.assertEqual(str(d), '<html><meta/><head><title>Hello</title></head><body onload="crash()">Hi all<p/></body></html>')
 
         d = pq('http://www.theonion.com/search/', {'q': 'inconsistency'}, method='get')
         self.assertEqual(d('input[name=q]:last').val(), 'inconsistency')
         self.assertEqual(d('.news-in-brief h3').text(), 'Slight Inconsistency Found In Bible')
-    
+
     @with_net
     def test_post(self):
         d = pq('http://www.theonion.com/search/', {'q': 'inconsistency'}, method='post')
 if __name__ == '__main__':
     fails, total = unittest.main()
     if fails == 0:
-        print 'OK'
+        print('OK')

pyquery/tests.txt

     >>> pq('<ul> <li>  </li> </ul>').text()
     ''
 
-    >>> print pq('<ul> <li> toto </li> <li> tata </li> </ul>').text()
+    >>> print(pq('<ul> <li> toto </li> <li> tata </li> </ul>').text())
     toto tata
 
 Complex wrapping::
 
 We get the original doc with new node::
 
-    >>> print d
+    >>> print(d)
     <div id="bouh"><div><div id="wrapper"><span>youhou</span></div></div></div>
 
 Complex wrapAll::
     >>> s.wrapAll('<div id="wrapper"></div>')
     [<div#wrapper>]
 
-    >>> print doc
+    >>> print(doc)
     <div><div id="wrapper"><span>Hey</span><span>you !</span></div></div>
           'lxml>=2.1'
           # -*- Extra requirements: -*-
       ],
+      test_requires=['nose'],
+      test_suite='nose.collector',
       entry_points="""
       # -*- Entry points: -*-
       """,
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.