From: DongHun Kwak Date: Mon, 14 Jan 2019 01:42:53 +0000 (+0900) Subject: Imported Upstream version 40.1.0 X-Git-Tag: upstream/40.1.0^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=792f15111d3a3a517868c03e5c202a57c8497b6a;p=platform%2Fupstream%2Fpython-setuptools.git Imported Upstream version 40.1.0 --- diff --git a/CHANGES.rst b/CHANGES.rst index 2f96af9..cc43156 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,21 @@ +v40.1.0 +------- + +* #1410: Deprecated ``upload`` and ``register`` commands. +* #1312: Introduced find_namespace_packages() to find PEP 420 namespace packages. +* #1420: Added find_namespace: directive to config parser. +* #1418: Solved race in when creating egg cache directories. +* #1450: Upgraded vendored PyParsing from 2.1.10 to 2.2.0. +* #1451: Upgraded vendored appdirs from 1.4.0 to 1.4.3. +* #1388: Fixed "Microsoft Visual C++ Build Tools" link in exception when Visual C++ not found. +* #1389: Added support for scripts which have unicode content. +* #1416: Moved several Python version checks over to using ``six.PY2`` and ``six.PY3``. +* #1441: Removed spurious executable permissions from files that don't need them. + + v40.0.0 ------- + * #1342: Drop support for Python 3.3. * #1366: In package_index, fixed handling of encoded entities in URLs. * #1383: In pkg_resources VendorImporter, avoid removing packages imported from the root. diff --git a/README.rst b/README.rst old mode 100755 new mode 100644 diff --git a/changelog.d/.gitignore b/changelog.d/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/docs/setuptools.txt b/docs/setuptools.txt index c82dc51..89f45bd 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -48,8 +48,6 @@ Feature Highlights: * Command aliases - create project-specific, per-user, or site-wide shortcut names for commonly used commands and options -* PyPI upload support - upload your source distributions and eggs to PyPI - * Deploy your project in "development mode", such that it's available on ``sys.path``, yet can still be edited directly from its source checkout. @@ -59,6 +57,9 @@ Feature Highlights: * Create extensible applications and frameworks that automatically discover extensions, using simple "entry points" declared in a project's setup script. +* Full support for PEP 420 via ``find_namespace_packages()``, which is also backwards + compatible to the existing ``find_packages()`` for Python >= 3.3. + .. contents:: **Table of Contents** .. _ez_setup.py: `bootstrap module`_ @@ -107,11 +108,10 @@ As you can see, it doesn't take much to use setuptools in a project. Run that script in your project folder, alongside the Python packages you have developed. -Invoke that script to produce eggs, upload to -PyPI, and automatically include all packages in the directory where the -setup.py lives. See the `Command Reference`_ section below to see what -commands you can give to this setup script. For example, -to produce a source distribution, simply invoke:: +Invoke that script to produce distributions and automatically include all +packages in the directory where the setup.py lives. See the `Command +Reference`_ section below to see what commands you can give to this setup +script. For example, to produce a source distribution, simply invoke:: python setup.py sdist @@ -138,7 +138,7 @@ dependencies, and perhaps some data files and scripts:: 'hello': ['*.msg'], }, - # metadata for upload to PyPI + # metadata to display on PyPI author="Me", author_email="me@example.com", description="This is an Example Package", @@ -462,6 +462,67 @@ argument in your setup script. Especially since it frees you from having to remember to modify your setup script whenever your project grows additional top-level packages or subpackages. +``find_namespace_packages()`` +----------------------------- +In Python 3.3+, ``setuptools`` also provides the ``find_namespace_packages`` variant +of ``find_packages``, which has the same function signature as +``find_packages``, but works with `PEP 420`_ compliant implicit namespace +packages. Here is a minimal setup script using ``find_namespace_packages``:: + + from setuptools import setup, find_namespace_packages + setup( + name="HelloWorld", + version="0.1", + packages=find_namespace_packages(), + ) + + +Keep in mind that according to PEP 420, you may have to either re-organize your +codebase a bit or define a few exclusions, as the definition of an implicit +namespace package is quite lenient, so for a project organized like so:: + + + ├── namespace + │   └── mypackage + │   ├── __init__.py + │   └── mod1.py + ├── setup.py + └── tests + └── test_mod1.py + +A naive ``find_namespace_packages()`` would install both ``namespace.mypackage`` and a +top-level package called ``tests``! One way to avoid this problem is to use the +``include`` keyword to whitelist the packages to include, like so:: + + from setuptools import setup, find_namespace_packages + + setup( + name="namespace.mypackage", + version="0.1", + packages=find_namespace_packages(include=['namespace.*']) + ) + +Another option is to use the "src" layout, where all package code is placed in +the ``src`` directory, like so:: + + + ├── setup.py + ├── src + │   └── namespace + │   └── mypackage + │   ├── __init__.py + │   └── mod1.py + └── tests + └── test_mod1.py + +With this layout, the package directory is specified as ``src``, as such:: + + setup(name="namespace.mypackage", + version="0.1", + package_dir={'': 'src'}, + packages=find_namespace_packages(where='src')) + +.. _PEP 420: https://www.python.org/dev/peps/pep-0420/ Automatic Script Creation ========================= @@ -618,9 +679,8 @@ using ``setup.py develop``.) Dependencies that aren't in PyPI -------------------------------- -If your project depends on packages that aren't registered in PyPI, you may -still be able to depend on them, as long as they are available for download -as: +If your project depends on packages that don't exist on PyPI, you may still be +able to depend on them, as long as they are available for download as: - an egg, in the standard distutils ``sdist`` format, - a single ``.py`` file, or @@ -1192,15 +1252,6 @@ Whenever you install an updated version of setuptools, you should also update your projects' ``ez_setup.py`` files, so that a matching version gets installed on the target machine(s). -By the way, setuptools supports the new PyPI "upload" command, so you can use -``setup.py sdist upload`` or ``setup.py bdist_egg upload`` to upload your -source or egg distributions respectively. Your project's current version must -be registered with PyPI first, of course; you can use ``setup.py register`` to -do that. Or you can do it all in one step, e.g. ``setup.py register sdist -bdist_egg upload`` will register the package, build source and egg -distributions, and then upload them both to PyPI, where they'll be easily -found by other projects that depend on them. - (By the way, if you need to distribute a specific version of ``setuptools``, you can specify the exact version and base download URL as parameters to the ``use_setuptools()`` function. See the function's docstring for details.) @@ -1517,22 +1568,11 @@ you use any option at all. Making your package available for EasyInstall --------------------------------------------- -If you use the ``register`` command (``setup.py register``) to register your -package with PyPI, that's most of the battle right there. (See the -`docs for the register command`_ for more details.) - -.. _docs for the register command: http://docs.python.org/dist/package-index.html - -If you also use the `upload`_ command to upload actual distributions of your -package, that's even better, because EasyInstall will be able to find and -download them directly from your project's PyPI page. - -However, there may be reasons why you don't want to upload distributions to +There may be reasons why you don't want to upload distributions to PyPI, and just want your existing distributions (or perhaps a Subversion checkout) to be used instead. -So here's what you need to do before running the ``register`` command. There -are three ``setup()`` arguments that affect EasyInstall: +There are three ``setup()`` arguments that affect EasyInstall: ``url`` and ``download_url`` These become links on your project's PyPI page. EasyInstall will examine @@ -1590,13 +1630,12 @@ tagging the release, so the trunk will still produce development snapshots. Alternately, if you are not branching for releases, you can override the default version options on the command line, using something like:: - python setup.py egg_info -Db "" sdist bdist_egg register upload + python setup.py egg_info -Db "" sdist bdist_egg The first part of this command (``egg_info -Db ""``) will override the -configured tag information, before creating source and binary eggs, registering -the project with PyPI, and uploading the files. Thus, these commands will use -the plain version from your ``setup.py``, without adding the build designation -string. +configured tag information, before creating source and binary eggs. Thus, these +commands will use the plain version from your ``setup.py``, without adding the +build designation string. Of course, if you will be doing this a lot, you may wish to create a personal alias for this operation, e.g.:: @@ -1605,7 +1644,7 @@ alias for this operation, e.g.:: You can then use it like this:: - python setup.py release sdist bdist_egg register upload + python setup.py release sdist bdist_egg Or of course you can create more elaborate aliases that do all of the above. See the sections below on the `egg_info`_ and `alias`_ commands for more ideas. @@ -1921,11 +1960,11 @@ This command performs two operations: it updates a project's ``.egg-info`` metadata directory (used by the ``bdist_egg``, ``develop``, and ``test`` commands), and it allows you to temporarily change a project's version string, to support "daily builds" or "snapshot" releases. It is run automatically by -the ``sdist``, ``bdist_egg``, ``develop``, ``register``, and ``test`` commands -in order to update the project's metadata, but you can also specify it -explicitly in order to temporarily change the project's version string while -executing other commands. (It also generates the``.egg-info/SOURCES.txt`` -manifest file, which is used when you are building source distributions.) +the ``sdist``, ``bdist_egg``, ``develop``, and ``test`` commands in order to +update the project's metadata, but you can also specify it explicitly in order +to temporarily change the project's version string while executing other +commands. (It also generates the``.egg-info/SOURCES.txt`` manifest file, which +is used when you are building source distributions.) In addition to writing the core egg metadata defined by ``setuptools`` and required by ``pkg_resources``, this command can be extended to write other @@ -1999,10 +2038,10 @@ Creating a dated "nightly build" snapshot egg:: python setup.py egg_info --tag-date --tag-build=DEV bdist_egg -Creating and uploading a release with no version tags, even if some default -tags are specified in ``setup.cfg``:: +Creating a release with no version tags, even if some default tags are +specified in ``setup.cfg``:: - python setup.py egg_info -RDb "" sdist bdist_egg register upload + python setup.py egg_info -RDb "" sdist bdist_egg (Notice that ``egg_info`` must always appear on the command line *before* any commands that you want the version changes to apply to.) @@ -2251,25 +2290,16 @@ available: ``upload`` - Upload source and/or egg distributions to PyPI =========================================================== +.. warning:: + **upload** is deprecated in favor of using `twine + `_ + The ``upload`` command is implemented and `documented `_ in distutils. -Setuptools augments the ``upload`` command with support -for `keyring `_, -allowing the password to be stored in a secure -location and not in plaintext in the .pypirc file. To use -keyring, first install keyring and set the password for -the relevant repository, e.g.:: - - python -m keyring set - Password for '' in '': ******** - -Then, in .pypirc, set the repository configuration as normal, -but omit the password. Thereafter, uploads will use the -password from the keyring. - New in 20.1: Added keyring support. +New in 40.0: Deprecated the upload command. ----------------------------------------- @@ -2359,8 +2389,8 @@ Metadata and options are set in the config sections of the same name. * In some cases, complex values can be provided in dedicated subsections for clarity. -* Some keys allow ``file:``, ``attr:``, and ``find:`` directives in order to - cover common usecases. +* Some keys allow ``file:``, ``attr:``, and ``find:`` and ``find_namespace:`` directives in + order to cover common usecases. * Unknown keys are ignored. @@ -2449,7 +2479,7 @@ eager_resources list-comma dependency_links list-comma tests_require list-semi include_package_data bool -packages find:, list-comma +packages find:, find_namespace:, list-comma package_dir dict package_data section exclude_package_data section @@ -2459,10 +2489,13 @@ py_modules list-comma .. note:: - **packages** - The ``find:`` directive can be further configured + **packages** - The ``find:`` and ``find_namespace:`` directive can be further configured in a dedicated subsection ``options.packages.find``. This subsection - accepts the same keys as the `setuptools.find` function: + accepts the same keys as the `setuptools.find_packages` and the + `setuptools.find_namespace_packages` function: ``where``, ``include``, and ``exclude``. + + **find_namespace directive** - The ``find_namespace:`` directive is supported since Python >=3.3. Configuration API diff --git a/easy_install.py b/easy_install.py old mode 100755 new mode 100644 diff --git a/launcher.c b/launcher.c old mode 100755 new mode 100644 diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 6701540..86ec341 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -47,6 +47,11 @@ except ImportError: # Python 3.2 compatibility import imp as _imp +try: + FileExistsError +except NameError: + FileExistsError = OSError + from pkg_resources.extern import six from pkg_resources.extern.six.moves import urllib, map, filter @@ -640,13 +645,12 @@ class WorkingSet: distributions in the working set, otherwise only ones matching both `group` and `name` are yielded (in distribution order). """ - for dist in self: - entries = dist.get_entry_map(group) - if name is None: - for ep in entries.values(): - yield ep - elif name in entries: - yield entries[name] + return ( + entry + for dist in self + for entry in dist.get_entry_map(group).values() + if name is None or name == entry.name + ) def run_script(self, requires, script_name): """Locate distribution for `requires` and run `script_name` script""" @@ -3030,7 +3034,10 @@ def _bypass_ensure_directory(path): dirname, filename = split(path) if dirname and filename and not isdir(dirname): _bypass_ensure_directory(dirname) - mkdir(dirname, 0o755) + try: + mkdir(dirname, 0o755) + except FileExistsError: + pass def split_sections(s): diff --git a/pkg_resources/_vendor/appdirs.py b/pkg_resources/_vendor/appdirs.py index f4dba09..ae67001 100644 --- a/pkg_resources/_vendor/appdirs.py +++ b/pkg_resources/_vendor/appdirs.py @@ -13,7 +13,7 @@ See for details and usage. # - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html # - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html -__version_info__ = (1, 4, 0) +__version_info__ = (1, 4, 3) __version__ = '.'.join(map(str, __version_info__)) @@ -98,7 +98,7 @@ def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): - """Return full path to the user-shared data dir for this application. + r"""Return full path to the user-shared data dir for this application. "appname" is the name of application. If None, just the system directory is returned. @@ -117,7 +117,7 @@ def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): returned, or '/usr/local/share/', if XDG_DATA_DIRS is not set - Typical user data directories are: + Typical site data directories are: Mac OS X: /Library/Application Support/ Unix: /usr/local/share/ or /usr/share/ Win XP: C:\Documents and Settings\All Users\Application Data\\ @@ -184,13 +184,13 @@ def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): for a discussion of issues. - Typical user data directories are: + Typical user config directories are: Mac OS X: same as user_data_dir Unix: ~/.config/ # or in $XDG_CONFIG_HOME, if defined Win *: same as user_data_dir For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. - That means, by deafult "~/.config/". + That means, by default "~/.config/". """ if system in ["win32", "darwin"]: path = user_data_dir(appname, appauthor, None, roaming) @@ -204,7 +204,7 @@ def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): - """Return full path to the user-shared data dir for this application. + r"""Return full path to the user-shared data dir for this application. "appname" is the name of application. If None, just the system directory is returned. @@ -222,7 +222,7 @@ def site_config_dir(appname=None, appauthor=None, version=None, multipath=False) returned. By default, the first item from XDG_CONFIG_DIRS is returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set - Typical user data directories are: + Typical site config directories are: Mac OS X: same as site_data_dir Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in $XDG_CONFIG_DIRS @@ -311,6 +311,48 @@ def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): return path +def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific state dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user state directories are: + Mac OS X: same as user_data_dir + Unix: ~/.local/state/ # or in $XDG_STATE_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow this Debian proposal + to extend the XDG spec and support $XDG_STATE_HOME. + + That means, by default "~/.local/state/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): r"""Return full path to the user-specific log dir for this application. @@ -329,7 +371,7 @@ def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): "Logs" to the base app data dir for Windows, and "log" to the base cache dir for Unix. See discussion below. - Typical user cache directories are: + Typical user log directories are: Mac OS X: ~/Library/Logs/ Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs @@ -364,8 +406,8 @@ def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): class AppDirs(object): """Convenience wrapper for getting application dirs.""" - def __init__(self, appname, appauthor=None, version=None, roaming=False, - multipath=False): + def __init__(self, appname=None, appauthor=None, version=None, + roaming=False, multipath=False): self.appname = appname self.appauthor = appauthor self.version = version @@ -397,6 +439,11 @@ class AppDirs(object): return user_cache_dir(self.appname, self.appauthor, version=self.version) + @property + def user_state_dir(self): + return user_state_dir(self.appname, self.appauthor, + version=self.version) + @property def user_log_dir(self): return user_log_dir(self.appname, self.appauthor, @@ -410,7 +457,10 @@ def _get_win_folder_from_registry(csidl_name): registry for this guarantees us the correct answer for all CSIDL_* names. """ - import _winreg + if PY3: + import winreg as _winreg + else: + import _winreg shell_folder_name = { "CSIDL_APPDATA": "AppData", @@ -500,7 +550,7 @@ def _get_win_folder_with_jna(csidl_name): if has_high_char: buf = array.zeros('c', buf_size) kernel = win32.Kernel32.INSTANCE - if kernal.GetShortPathName(dir, buf, buf_size): + if kernel.GetShortPathName(dir, buf, buf_size): dir = jna.Native.toString(buf.tostring()).rstrip("\0") return dir @@ -527,9 +577,15 @@ if __name__ == "__main__": appname = "MyApp" appauthor = "MyCompany" - props = ("user_data_dir", "site_data_dir", - "user_config_dir", "site_config_dir", - "user_cache_dir", "user_log_dir") + props = ("user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "site_data_dir", + "site_config_dir") + + print("-- app dirs %s --" % __version__) print("-- app dirs (with optional 'version')") dirs = AppDirs(appname, appauthor, version="1.0") diff --git a/pkg_resources/_vendor/pyparsing.py b/pkg_resources/_vendor/pyparsing.py index a212243..e8aefc8 100644 --- a/pkg_resources/_vendor/pyparsing.py +++ b/pkg_resources/_vendor/pyparsing.py @@ -60,8 +60,8 @@ The pyparsing module handles some of the problems that are typically vexing when - embedded comments """ -__version__ = "2.1.10" -__versionTime__ = "07 Oct 2016 01:31 UTC" +__version__ = "2.2.0" +__versionTime__ = "06 Mar 2017 02:06 UTC" __author__ = "Paul McGuire " import string @@ -144,7 +144,7 @@ else: except UnicodeEncodeError: # Else encode it ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace') - xmlcharref = Regex('&#\d+;') + xmlcharref = Regex(r'&#\d+;') xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:]) return xmlcharref.transformString(ret) @@ -809,7 +809,7 @@ class ParseResults(object): return None def getName(self): - """ + r""" Returns the results name for this token expression. Useful when several different expressions might match at a particular location. @@ -1226,7 +1226,7 @@ class ParserElement(object): def setParseAction( self, *fns, **kwargs ): """ - Define action to perform when successfully matching parse element definition. + Define one or more actions to perform when successfully matching parse element definition. Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)}, C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where: - s = the original string being parsed (see note below) @@ -1264,7 +1264,7 @@ class ParserElement(object): def addParseAction( self, *fns, **kwargs ): """ - Add parse action to expression's list of parse actions. See L{I{setParseAction}}. + Add one or more parse actions to expression's list of parse actions. See L{I{setParseAction}}. See examples in L{I{copy}}. """ @@ -1443,10 +1443,14 @@ class ParserElement(object): def clear(self): cache.clear() + + def cache_len(self): + return len(cache) self.get = types.MethodType(get, self) self.set = types.MethodType(set, self) self.clear = types.MethodType(clear, self) + self.__len__ = types.MethodType(cache_len, self) if _OrderedDict is not None: class _FifoCache(object): @@ -1460,15 +1464,22 @@ class ParserElement(object): def set(self, key, value): cache[key] = value - if len(cache) > size: - cache.popitem(False) + while len(cache) > size: + try: + cache.popitem(False) + except KeyError: + pass def clear(self): cache.clear() + def cache_len(self): + return len(cache) + self.get = types.MethodType(get, self) self.set = types.MethodType(set, self) self.clear = types.MethodType(clear, self) + self.__len__ = types.MethodType(cache_len, self) else: class _FifoCache(object): @@ -1483,7 +1494,7 @@ class ParserElement(object): def set(self, key, value): cache[key] = value - if len(cache) > size: + while len(key_fifo) > size: cache.pop(key_fifo.popleft(), None) key_fifo.append(key) @@ -1491,9 +1502,13 @@ class ParserElement(object): cache.clear() key_fifo.clear() + def cache_len(self): + return len(cache) + self.get = types.MethodType(get, self) self.set = types.MethodType(set, self) self.clear = types.MethodType(clear, self) + self.__len__ = types.MethodType(cache_len, self) # argument cache for optimizing repeated calls when backtracking through recursive expressions packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail @@ -1743,8 +1758,12 @@ class ParserElement(object): cap_word = Word(alphas.upper(), alphas.lower()) print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")) + + # the sum() builtin can be used to merge results into a single ParseResults object + print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))) prints:: - ['More', 'Iron', 'Lead', 'Gold', 'I'] + [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']] + ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity'] """ try: return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) @@ -1819,7 +1838,7 @@ class ParserElement(object): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), SyntaxWarning, stacklevel=2) return None - return And( [ self, And._ErrorStop(), other ] ) + return self + And._ErrorStop() + other def __rsub__(self, other ): """ @@ -2722,7 +2741,7 @@ class Word(Token): class Regex(Token): - """ + r""" Token for matching strings that match a given regular expression. Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module. If the given regex contains named groups (defined using C{(?P...)}), these will be preserved as @@ -2911,7 +2930,7 @@ class QuotedString(Token): # replace escaped characters if self.escChar: - ret = re.sub(self.escCharReplacePattern,"\g<1>",ret) + ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret) # replace escaped quotes if self.escQuote: @@ -5020,7 +5039,9 @@ def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. - parseAction is the parse action to be associated with expressions matching this operator expression (the - parse action tuple member may be omitted) + parse action tuple member may be omitted); if the parse action + is passed a tuple or list of functions, this is equivalent to + calling C{setParseAction(*fn)} (L{ParserElement.setParseAction}) - lpar - expression for matching left-parentheses (default=C{Suppress('(')}) - rpar - expression for matching right-parentheses (default=C{Suppress(')')}) @@ -5093,7 +5114,10 @@ def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): else: raise ValueError("operator must indicate right or left associativity") if pa: - matchExpr.setParseAction( pa ) + if isinstance(pa, (tuple, list)): + matchExpr.setParseAction(*pa) + else: + matchExpr.setParseAction(pa) thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) lastExpr = thisExpr ret <<= lastExpr diff --git a/pkg_resources/_vendor/vendored.txt b/pkg_resources/_vendor/vendored.txt index 9a94c5b..e3d564f 100644 --- a/pkg_resources/_vendor/vendored.txt +++ b/pkg_resources/_vendor/vendored.txt @@ -1,4 +1,4 @@ packaging==16.8 -pyparsing==2.1.10 +pyparsing==2.2.0 six==1.10.0 -appdirs==1.4.0 +appdirs==1.4.3 diff --git a/pkg_resources/py31compat.py b/pkg_resources/py31compat.py index fd4b6fd..a381c42 100644 --- a/pkg_resources/py31compat.py +++ b/pkg_resources/py31compat.py @@ -2,6 +2,8 @@ import os import errno import sys +from .extern import six + def _makedirs_31(path, exist_ok=False): try: @@ -15,7 +17,7 @@ def _makedirs_31(path, exist_ok=False): # and exists_ok considerations are disentangled. # See https://github.com/pypa/setuptools/pull/1083#issuecomment-315168663 needs_makedirs = ( - sys.version_info.major == 2 or + six.PY2 or (3, 4) <= sys.version_info < (3, 4, 1) ) makedirs = _makedirs_31 if needs_makedirs else os.makedirs diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py index 4e2cac9..62a39b8 100644 --- a/pkg_resources/tests/test_pkg_resources.py +++ b/pkg_resources/tests/test_pkg_resources.py @@ -12,6 +12,11 @@ import stat import distutils.dist import distutils.command.install_egg_info +try: + from unittest import mock +except ImportError: + import mock + from pkg_resources.extern.six.moves import map from pkg_resources.extern.six import text_type, string_types @@ -138,8 +143,34 @@ class TestResourceManager: message = "Unexpected type from get_cache_path: " + type_ assert isinstance(path, string_types), message + def test_get_cache_path_race(self, tmpdir): + # Patch to os.path.isdir to create a race condition + def patched_isdir(dirname, unpatched_isdir=pkg_resources.isdir): + patched_isdir.dirnames.append(dirname) + + was_dir = unpatched_isdir(dirname) + if not was_dir: + os.makedirs(dirname) + return was_dir + + patched_isdir.dirnames = [] + + # Get a cache path with a "race condition" + mgr = pkg_resources.ResourceManager() + mgr.set_extraction_path(str(tmpdir)) + + archive_name = os.sep.join(('foo', 'bar', 'baz')) + with mock.patch.object(pkg_resources, 'isdir', new=patched_isdir): + mgr.get_cache_path(archive_name) + + # Because this test relies on the implementation details of this + # function, these assertions are a sentinel to ensure that the + # test suite will not fail silently if the implementation changes. + called_dirnames = patched_isdir.dirnames + assert len(called_dirnames) == 2 + assert called_dirnames[0].split(os.sep)[-2:] == ['foo', 'bar'] + assert called_dirnames[1].split(os.sep)[-1:] == ['foo'] -class TestIndependence: """ Tests to ensure that pkg_resources runs independently from setuptools. """ diff --git a/pytest.ini b/pytest.ini old mode 100755 new mode 100644 diff --git a/setup.cfg b/setup.cfg old mode 100755 new mode 100644 index 2caeca9..870d629 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 40.0.0 +current_version = 40.1.0 commit = True tag = True diff --git a/setup.py b/setup.py index 27a0e30..526a7ba 100755 --- a/setup.py +++ b/setup.py @@ -89,7 +89,7 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="40.0.0", + version="40.1.0", description=( "Easily download, build, install, upgrade, and uninstall " "Python packages" diff --git a/setuptools/__init__.py b/setuptools/__init__.py index ce55ec3..54309b5 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -1,12 +1,14 @@ """Extensions to the 'distutils' for large or complex distributions""" import os +import sys import functools import distutils.core import distutils.filelist from distutils.util import convert_path from fnmatch import fnmatchcase +from setuptools.extern.six import PY3 from setuptools.extern.six.moves import filter, map import setuptools.version @@ -17,11 +19,15 @@ from . import monkey __metaclass__ = type + __all__ = [ 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', - 'find_packages', + 'find_packages' ] +if PY3: + __all__.append('find_namespace_packages') + __version__ = setuptools.version.__version__ bootstrap_install_from = None @@ -111,6 +117,9 @@ class PEP420PackageFinder(PackageFinder): find_packages = PackageFinder.find +if PY3: + find_namespace_packages = PEP420PackageFinder.find + def _install_setup_requires(attrs): # Note: do not use `setuptools.Distribution` directly, as diff --git a/setuptools/_vendor/pyparsing.py b/setuptools/_vendor/pyparsing.py index a212243..e8aefc8 100644 --- a/setuptools/_vendor/pyparsing.py +++ b/setuptools/_vendor/pyparsing.py @@ -60,8 +60,8 @@ The pyparsing module handles some of the problems that are typically vexing when - embedded comments """ -__version__ = "2.1.10" -__versionTime__ = "07 Oct 2016 01:31 UTC" +__version__ = "2.2.0" +__versionTime__ = "06 Mar 2017 02:06 UTC" __author__ = "Paul McGuire " import string @@ -144,7 +144,7 @@ else: except UnicodeEncodeError: # Else encode it ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace') - xmlcharref = Regex('&#\d+;') + xmlcharref = Regex(r'&#\d+;') xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:]) return xmlcharref.transformString(ret) @@ -809,7 +809,7 @@ class ParseResults(object): return None def getName(self): - """ + r""" Returns the results name for this token expression. Useful when several different expressions might match at a particular location. @@ -1226,7 +1226,7 @@ class ParserElement(object): def setParseAction( self, *fns, **kwargs ): """ - Define action to perform when successfully matching parse element definition. + Define one or more actions to perform when successfully matching parse element definition. Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)}, C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where: - s = the original string being parsed (see note below) @@ -1264,7 +1264,7 @@ class ParserElement(object): def addParseAction( self, *fns, **kwargs ): """ - Add parse action to expression's list of parse actions. See L{I{setParseAction}}. + Add one or more parse actions to expression's list of parse actions. See L{I{setParseAction}}. See examples in L{I{copy}}. """ @@ -1443,10 +1443,14 @@ class ParserElement(object): def clear(self): cache.clear() + + def cache_len(self): + return len(cache) self.get = types.MethodType(get, self) self.set = types.MethodType(set, self) self.clear = types.MethodType(clear, self) + self.__len__ = types.MethodType(cache_len, self) if _OrderedDict is not None: class _FifoCache(object): @@ -1460,15 +1464,22 @@ class ParserElement(object): def set(self, key, value): cache[key] = value - if len(cache) > size: - cache.popitem(False) + while len(cache) > size: + try: + cache.popitem(False) + except KeyError: + pass def clear(self): cache.clear() + def cache_len(self): + return len(cache) + self.get = types.MethodType(get, self) self.set = types.MethodType(set, self) self.clear = types.MethodType(clear, self) + self.__len__ = types.MethodType(cache_len, self) else: class _FifoCache(object): @@ -1483,7 +1494,7 @@ class ParserElement(object): def set(self, key, value): cache[key] = value - if len(cache) > size: + while len(key_fifo) > size: cache.pop(key_fifo.popleft(), None) key_fifo.append(key) @@ -1491,9 +1502,13 @@ class ParserElement(object): cache.clear() key_fifo.clear() + def cache_len(self): + return len(cache) + self.get = types.MethodType(get, self) self.set = types.MethodType(set, self) self.clear = types.MethodType(clear, self) + self.__len__ = types.MethodType(cache_len, self) # argument cache for optimizing repeated calls when backtracking through recursive expressions packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail @@ -1743,8 +1758,12 @@ class ParserElement(object): cap_word = Word(alphas.upper(), alphas.lower()) print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")) + + # the sum() builtin can be used to merge results into a single ParseResults object + print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))) prints:: - ['More', 'Iron', 'Lead', 'Gold', 'I'] + [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']] + ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity'] """ try: return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) @@ -1819,7 +1838,7 @@ class ParserElement(object): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), SyntaxWarning, stacklevel=2) return None - return And( [ self, And._ErrorStop(), other ] ) + return self + And._ErrorStop() + other def __rsub__(self, other ): """ @@ -2722,7 +2741,7 @@ class Word(Token): class Regex(Token): - """ + r""" Token for matching strings that match a given regular expression. Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module. If the given regex contains named groups (defined using C{(?P...)}), these will be preserved as @@ -2911,7 +2930,7 @@ class QuotedString(Token): # replace escaped characters if self.escChar: - ret = re.sub(self.escCharReplacePattern,"\g<1>",ret) + ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret) # replace escaped quotes if self.escQuote: @@ -5020,7 +5039,9 @@ def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. - parseAction is the parse action to be associated with expressions matching this operator expression (the - parse action tuple member may be omitted) + parse action tuple member may be omitted); if the parse action + is passed a tuple or list of functions, this is equivalent to + calling C{setParseAction(*fn)} (L{ParserElement.setParseAction}) - lpar - expression for matching left-parentheses (default=C{Suppress('(')}) - rpar - expression for matching right-parentheses (default=C{Suppress(')')}) @@ -5093,7 +5114,10 @@ def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): else: raise ValueError("operator must indicate right or left associativity") if pa: - matchExpr.setParseAction( pa ) + if isinstance(pa, (tuple, list)): + matchExpr.setParseAction(*pa) + else: + matchExpr.setParseAction(pa) thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) lastExpr = thisExpr ret <<= lastExpr diff --git a/setuptools/_vendor/vendored.txt b/setuptools/_vendor/vendored.txt index be3e72e..ca0d5ce 100644 --- a/setuptools/_vendor/vendored.txt +++ b/setuptools/_vendor/vendored.txt @@ -1,3 +1,3 @@ packaging==16.8 -pyparsing==2.1.10 +pyparsing==2.2.0 six==1.10.0 diff --git a/setuptools/archive_util.py b/setuptools/archive_util.py old mode 100755 new mode 100644 diff --git a/setuptools/command/alias.py b/setuptools/command/alias.py old mode 100755 new mode 100644 diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py index 1453072..9f8df91 100644 --- a/setuptools/command/bdist_egg.py +++ b/setuptools/command/bdist_egg.py @@ -411,7 +411,7 @@ def scan_module(egg_dir, base, name, stubs): return True # Extension module pkg = base[len(egg_dir) + 1:].replace(os.sep, '.') module = pkg + (pkg and '.' or '') + os.path.splitext(name)[0] - if sys.version_info.major == 2: + if six.PY2: skip = 8 # skip magic & date elif sys.version_info < (3, 7): skip = 12 # skip magic & date & file size diff --git a/setuptools/command/bdist_rpm.py b/setuptools/command/bdist_rpm.py old mode 100755 new mode 100644 diff --git a/setuptools/command/bdist_wininst.py b/setuptools/command/bdist_wininst.py old mode 100755 new mode 100644 diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py old mode 100755 new mode 100644 diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 05508ce..dd17cc1 100755 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -96,7 +96,7 @@ def samefile(p1, p2): if six.PY2: - def _to_ascii(s): + def _to_bytes(s): return s def isascii(s): @@ -107,8 +107,8 @@ if six.PY2: return False else: - def _to_ascii(s): - return s.encode('ascii') + def _to_bytes(s): + return s.encode('utf8') def isascii(s): try: @@ -805,7 +805,7 @@ class easy_install(Command): if is_script: body = self._load_template(dev_path) % locals() script_text = ScriptWriter.get_header(script_text) + body - self.write_script(script_name, _to_ascii(script_text), 'b') + self.write_script(script_name, _to_bytes(script_text), 'b') @staticmethod def _load_template(dev_path): diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py old mode 100755 new mode 100644 index f3e604d..5fd6c88 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -116,7 +116,33 @@ def translate_pattern(glob): return re.compile(pat, flags=re.MULTILINE|re.DOTALL) -class egg_info(Command): +class InfoCommon: + tag_build = None + tag_date = None + + @property + def name(self): + return safe_name(self.distribution.get_name()) + + def tagged_version(self): + version = self.distribution.get_version() + # egg_info may be called more than once for a distribution, + # in which case the version string already contains all tags. + if self.vtags and version.endswith(self.vtags): + return safe_version(version) + return safe_version(version + self.vtags) + + def tags(self): + version = '' + if self.tag_build: + version += self.tag_build + if self.tag_date: + version += time.strftime("-%Y%m%d") + return version + vtags = property(tags) + + +class egg_info(InfoCommon, Command): description = "create a distribution's .egg-info directory" user_options = [ @@ -133,14 +159,9 @@ class egg_info(Command): } def initialize_options(self): - self.egg_name = None - self.egg_version = None self.egg_base = None self.egg_info = None - self.tag_build = None - self.tag_date = 0 self.broken_egg_info = False - self.vtags = None #################################### # allow the 'tag_svn_revision' to be detected and @@ -167,11 +188,15 @@ class egg_info(Command): egg_info['tag_date'] = 0 edit_config(filename, dict(egg_info=egg_info)) - def finalize_options(self): - self.egg_name = safe_name(self.distribution.get_name()) - self.vtags = self.tags() - self.egg_version = self.tagged_version() + @property + def egg_name(self): + return self.name + + @property + def egg_version(self): + return self.tagged_version() + def finalize_options(self): parsed_version = parse_version(self.egg_version) try: @@ -254,14 +279,6 @@ class egg_info(Command): if not self.dry_run: os.unlink(filename) - def tagged_version(self): - version = self.distribution.get_version() - # egg_info may be called more than once for a distribution, - # in which case the version string already contains all tags. - if self.vtags and version.endswith(self.vtags): - return safe_version(version) - return safe_version(version + self.vtags) - def run(self): self.mkpath(self.egg_info) installer = self.distribution.fetch_build_egg @@ -277,14 +294,6 @@ class egg_info(Command): self.find_sources() - def tags(self): - version = '' - if self.tag_build: - version += self.tag_build - if self.tag_date: - version += time.strftime("-%Y%m%d") - return version - def find_sources(self): """Generate SOURCES.txt manifest file""" manifest_filename = os.path.join(self.egg_info, "SOURCES.txt") diff --git a/setuptools/command/install_egg_info.py b/setuptools/command/install_egg_info.py old mode 100755 new mode 100644 diff --git a/setuptools/command/install_scripts.py b/setuptools/command/install_scripts.py old mode 100755 new mode 100644 diff --git a/setuptools/command/register.py b/setuptools/command/register.py old mode 100755 new mode 100644 index 8d6336a..98bc015 --- a/setuptools/command/register.py +++ b/setuptools/command/register.py @@ -1,3 +1,4 @@ +from distutils import log import distutils.command.register as orig @@ -5,6 +6,13 @@ class register(orig.register): __doc__ = orig.register.__doc__ def run(self): - # Make sure that we are using valid current name/version info - self.run_command('egg_info') - orig.register.run(self) + try: + # Make sure that we are using valid current name/version info + self.run_command('egg_info') + orig.register.run(self) + finally: + self.announce( + "WARNING: Registering is deprecated, use twine to " + "upload instead (https://pypi.org/p/twine/)", + log.WARN + ) diff --git a/setuptools/command/rotate.py b/setuptools/command/rotate.py old mode 100755 new mode 100644 diff --git a/setuptools/command/saveopts.py b/setuptools/command/saveopts.py old mode 100755 new mode 100644 diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py old mode 100755 new mode 100644 diff --git a/setuptools/command/setopt.py b/setuptools/command/setopt.py old mode 100755 new mode 100644 diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index a44173a..72f24d8 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -1,4 +1,5 @@ import getpass +from distutils import log from distutils.command import upload as orig @@ -8,6 +9,16 @@ class upload(orig.upload): in a variety of different ways. """ + def run(self): + try: + orig.upload.run(self) + finally: + self.announce( + "WARNING: Uploading via this command is deprecated, use twine " + "to upload instead (https://pypi.org/p/twine/)", + log.WARN + ) + def finalize_options(self): orig.upload.finalize_options(self) self.username = ( diff --git a/setuptools/config.py b/setuptools/config.py index 5f908cf..0da3dbc 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -8,7 +8,7 @@ from importlib import import_module from distutils.errors import DistutilsOptionError, DistutilsFileError from setuptools.extern.packaging.version import LegacyVersion, parse -from setuptools.extern.six import string_types +from setuptools.extern.six import string_types, PY3 __metaclass__ = type @@ -515,16 +515,24 @@ class ConfigOptionsHandler(ConfigHandler): :param value: :rtype: list """ - find_directive = 'find:' + find_directives = ['find:', 'find_namespace:'] + trimmed_value = value.strip() - if not value.startswith(find_directive): + if not trimmed_value in find_directives: return self._parse_list(value) + findns = trimmed_value == find_directives[1] + if findns and not PY3: + raise DistutilsOptionError('find_namespace: directive is unsupported on Python < 3.3') + # Read function arguments from a dedicated section. find_kwargs = self.parse_section_packages__find( self.sections.get('packages.find', {})) - from setuptools import find_packages + if findns: + from setuptools import find_namespace_packages as find_packages + else: + from setuptools import find_packages return find_packages(**find_kwargs) diff --git a/setuptools/extern/__init__.py b/setuptools/extern/__init__.py index 52785a0..cb2fa32 100644 --- a/setuptools/extern/__init__.py +++ b/setuptools/extern/__init__.py @@ -48,7 +48,7 @@ class VendorImporter: # on later Python versions to cause relative imports # in the vendor package to resolve the same modules # as those going through this importer. - if sys.version_info.major >= 3: + if sys.version_info >= (3, ): del sys.modules[extant] return mod except ImportError: diff --git a/setuptools/msvc.py b/setuptools/msvc.py index 5e20b3f..b9c472f 100644 --- a/setuptools/msvc.py +++ b/setuptools/msvc.py @@ -232,8 +232,7 @@ def _augment_exception(exc, version, arch=''): elif version >= 14.0: # For VC++ 14.0 Redirect user to Visual C++ Build Tools message += (' Get it with "Microsoft Visual C++ Build Tools": ' - r'http://landinghub.visualstudio.com/' - 'visual-cpp-build-tools') + r'https://visualstudio.microsoft.com/downloads/') exc.args = (message, ) diff --git a/setuptools/namespaces.py b/setuptools/namespaces.py old mode 100755 new mode 100644 diff --git a/setuptools/package_index.py b/setuptools/package_index.py old mode 100755 new mode 100644 diff --git a/setuptools/pep425tags.py b/setuptools/pep425tags.py index a86a0d1..8bf4277 100644 --- a/setuptools/pep425tags.py +++ b/setuptools/pep425tags.py @@ -12,6 +12,8 @@ import sysconfig import warnings from collections import OrderedDict +from .extern import six + from . import glibc _osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)') @@ -97,8 +99,8 @@ def get_abi_tag(): lambda: sys.maxunicode == 0x10ffff, expected=4, warn=(impl == 'cp' and - sys.version_info.major == 2)) \ - and sys.version_info.major == 2: + six.PY2)) \ + and six.PY2: u = 'u' abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u) elif soabi and soabi.startswith('cpython-'): diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py old mode 100755 new mode 100644 diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py index 54dd7d2..5f4a1c2 100644 --- a/setuptools/tests/__init__.py +++ b/setuptools/tests/__init__.py @@ -2,5 +2,17 @@ import locale import pytest +from setuptools.extern.six import PY2, PY3 + + +__all__ = [ + 'fail_on_ascii', 'py2_only', 'py3_only' +] + + is_ascii = locale.getpreferredencoding() == 'ANSI_X3.4-1968' fail_on_ascii = pytest.mark.xfail(is_ascii, reason="Test fails in this locale") + + +py2_only = pytest.mark.skipif(not PY2, reason="Test runs on Python 2 only") +py3_only = pytest.mark.skipif(not PY3, reason="Test runs on Python 3 only") diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 19b3763..acf2215 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -4,18 +4,20 @@ from distutils.errors import DistutilsOptionError, DistutilsFileError from mock import patch from setuptools.dist import Distribution, _Distribution from setuptools.config import ConfigHandler, read_configuration - +from . import py2_only, py3_only class ErrConfigHandler(ConfigHandler): """Erroneous handler. Fails to implement required methods.""" -def make_package_dir(name, base_dir): +def make_package_dir(name, base_dir, ns=False): dir_package = base_dir for dir_name in name.split('/'): dir_package = dir_package.mkdir(dir_name) - init_file = dir_package.join('__init__.py') - init_file.write('') + init_file = None + if not ns: + init_file = dir_package.join('__init__.py') + init_file.write('') return dir_package, init_file @@ -596,6 +598,60 @@ class TestOptions: assert set(dist.packages) == set( ['fake_package', 'fake_package.sub_two']) + @py2_only + def test_find_namespace_directive_fails_on_py2(self, tmpdir): + dir_package, config = fake_env( + tmpdir, + '[options]\n' + 'packages = find_namespace:\n' + ) + + with pytest.raises(DistutilsOptionError): + with get_dist(tmpdir) as dist: + dist.parse_config_files() + + @py3_only + def test_find_namespace_directive(self, tmpdir): + dir_package, config = fake_env( + tmpdir, + '[options]\n' + 'packages = find_namespace:\n' + ) + + dir_sub_one, _ = make_package_dir('sub_one', dir_package) + dir_sub_two, _ = make_package_dir('sub_two', dir_package, ns=True) + + with get_dist(tmpdir) as dist: + assert set(dist.packages) == { + 'fake_package', 'fake_package.sub_two', 'fake_package.sub_one' + } + + config.write( + '[options]\n' + 'packages = find_namespace:\n' + '\n' + '[options.packages.find]\n' + 'where = .\n' + 'include =\n' + ' fake_package.sub_one\n' + ' two\n' + ) + with get_dist(tmpdir) as dist: + assert dist.packages == ['fake_package.sub_one'] + + config.write( + '[options]\n' + 'packages = find_namespace:\n' + '\n' + '[options.packages.find]\n' + 'exclude =\n' + ' fake_package.sub_one\n' + ) + with get_dist(tmpdir) as dist: + assert set(dist.packages) == { + 'fake_package', 'fake_package.sub_two' + } + def test_extras_require(self, tmpdir): fake_env( tmpdir, diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 345d283..80a6549 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -185,6 +185,58 @@ class TestEasyInstallTest: cmd.ensure_finalized() cmd.easy_install(sdist_unicode) + @pytest.fixture + def sdist_unicode_in_script(self, tmpdir): + files = [ + ( + "setup.py", + DALS(""" + import setuptools + setuptools.setup( + name="setuptools-test-unicode", + version="1.0", + packages=["mypkg"], + include_package_data=True, + scripts=['mypkg/unicode_in_script'], + ) + """), + ), + ("mypkg/__init__.py", ""), + ( + "mypkg/unicode_in_script", + DALS( + """ + #!/bin/sh + # \xc3\xa1 + + non_python_fn() { + } + """), + ), + ] + sdist_name = "setuptools-test-unicode-script-1.0.zip" + sdist = tmpdir / sdist_name + # can't use make_sdist, because the issue only occurs + # with zip sdists. + sdist_zip = zipfile.ZipFile(str(sdist), "w") + for filename, content in files: + sdist_zip.writestr(filename, content) + sdist_zip.close() + return str(sdist) + + @fail_on_ascii + def test_unicode_content_in_sdist(self, sdist_unicode_in_script, tmpdir, monkeypatch): + """ + The install command should execute correctly even if + the package has unicode in scripts. + """ + dist = Distribution({"script_args": ["easy_install"]}) + target = (tmpdir / "target").ensure_dir() + cmd = ei.easy_install(dist, install_dir=str(target), args=["x"]) + monkeypatch.setitem(os.environ, "PYTHONPATH", str(target)) + cmd.ensure_finalized() + cmd.easy_install(sdist_unicode_in_script) + @pytest.fixture def sdist_script(self, tmpdir): files = [ diff --git a/setuptools/tests/test_find_packages.py b/setuptools/tests/test_find_packages.py index a6023de..b08f91c 100644 --- a/setuptools/tests/test_find_packages.py +++ b/setuptools/tests/test_find_packages.py @@ -7,14 +7,15 @@ import platform import pytest -import setuptools -from setuptools import find_packages +from . import py3_only -find_420_packages = setuptools.PEP420PackageFinder.find +from setuptools.extern.six import PY3 +from setuptools import find_packages +if PY3: + from setuptools import find_namespace_packages # modeled after CPython's test.support.can_symlink - def can_symlink(): TESTFN = tempfile.mktemp() symlink_path = TESTFN + "can_symlink" @@ -153,30 +154,35 @@ class TestFindPackages: def _assert_packages(self, actual, expected): assert set(actual) == set(expected) + @py3_only def test_pep420_ns_package(self): - packages = find_420_packages( + packages = find_namespace_packages( self.dist_dir, include=['pkg*'], exclude=['pkg.subpkg.assets']) self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg']) + @py3_only def test_pep420_ns_package_no_includes(self): - packages = find_420_packages( + packages = find_namespace_packages( self.dist_dir, exclude=['pkg.subpkg.assets']) self._assert_packages(packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg']) + @py3_only def test_pep420_ns_package_no_includes_or_excludes(self): - packages = find_420_packages(self.dist_dir) - expected = [ - 'docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets'] + packages = find_namespace_packages(self.dist_dir) + expected = ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets'] self._assert_packages(packages, expected) + @py3_only def test_regular_package_with_nested_pep420_ns_packages(self): self._touch('__init__.py', self.pkg_dir) - packages = find_420_packages( + packages = find_namespace_packages( self.dist_dir, exclude=['docs', 'pkg.subpkg.assets']) self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg']) + @py3_only def test_pep420_ns_package_no_non_package_dirs(self): shutil.rmtree(self.docs_dir) shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets')) - packages = find_420_packages(self.dist_dir) + packages = find_namespace_packages(self.dist_dir) self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg']) + diff --git a/setuptools/tests/test_register.py b/setuptools/tests/test_register.py new file mode 100644 index 0000000..9611459 --- /dev/null +++ b/setuptools/tests/test_register.py @@ -0,0 +1,43 @@ +import mock +from distutils import log + +import pytest + +from setuptools.command.register import register +from setuptools.dist import Distribution + + +class TestRegisterTest: + def test_warns_deprecation(self): + dist = Distribution() + + cmd = register(dist) + cmd.run_command = mock.Mock() + cmd.send_metadata = mock.Mock() + cmd.announce = mock.Mock() + + cmd.run() + + cmd.announce.assert_called_with( + "WARNING: Registering is deprecated, use twine to upload instead " + "(https://pypi.org/p/twine/)", + log.WARN + ) + + def test_warns_deprecation_when_raising(self): + dist = Distribution() + + cmd = register(dist) + cmd.run_command = mock.Mock() + cmd.send_metadata = mock.Mock() + cmd.send_metadata.side_effect = Exception + cmd.announce = mock.Mock() + + with pytest.raises(Exception): + cmd.run() + + cmd.announce.assert_called_with( + "WARNING: Registering is deprecated, use twine to upload instead " + "(https://pypi.org/p/twine/)", + log.WARN + ) diff --git a/setuptools/tests/test_upload.py b/setuptools/tests/test_upload.py new file mode 100644 index 0000000..95a8d16 --- /dev/null +++ b/setuptools/tests/test_upload.py @@ -0,0 +1,43 @@ +import mock +from distutils import log + +import pytest + +from setuptools.command.upload import upload +from setuptools.dist import Distribution + + +class TestUploadTest: + def test_warns_deprecation(self): + dist = Distribution() + dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())] + + cmd = upload(dist) + cmd.upload_file = mock.Mock() + cmd.announce = mock.Mock() + + cmd.run() + + cmd.announce.assert_called_once_with( + "WARNING: Uploading via this command is deprecated, use twine to " + "upload instead (https://pypi.org/p/twine/)", + log.WARN + ) + + def test_warns_deprecation_when_raising(self): + dist = Distribution() + dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())] + + cmd = upload(dist) + cmd.upload_file = mock.Mock() + cmd.upload_file.side_effect = Exception + cmd.announce = mock.Mock() + + with pytest.raises(Exception): + cmd.run() + + cmd.announce.assert_called_once_with( + "WARNING: Uploading via this command is deprecated, use twine to " + "upload instead (https://pypi.org/p/twine/)", + log.WARN + ) diff --git a/towncrier_template.rst b/towncrier_template.rst index 9c23b97..fbc5ef0 100644 --- a/towncrier_template.rst +++ b/towncrier_template.rst @@ -2,6 +2,7 @@ {% set underline = underlines[0] %}{% if section %}{{section}} {{ underline * section|length }} {% endif %} + {% if sections[section] %} {% for category, val in definitions.items() if category in sections[section]%} {% if definitions[category]['showcontent'] %} diff --git a/tox.ini b/tox.ini index 1a36965..3b03b7d 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,12 @@ envlist=python [testenv] deps=-rtests/requirements.txt +# Changed from default (`python -m pip ...`) +# to prevent the current working directory +# from being added to `sys.path`. +install_command={envbindir}/pip install {opts} {packages} +# Same as above. +list_dependencies_command={envbindir}/pip freeze setenv=COVERAGE_FILE={toxworkdir}/.coverage.{envname} # TODO: The passed environment variables came from copying other tox.ini files # These should probably be individually annotated to explain what needs them.