From 338cad687a1f49b021e81d33fd16847a4c5c7731 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 27 Jul 2019 12:01:10 -0400 Subject: [PATCH] Bump Python versions, remove conditional imports Mako 1.1 now supports Python versions: * 2.7 * 3.4 and higher This includes that setup.py no longer includes any conditionals, allowing for a pure Python wheel build, however this is not necessarily part of the Pypi release process as of yet. The test suite also raises for Python deprecation warnings. Fixes: #249 Replaced usage of ``inspect.getfullargspec()`` with the vendored version used by SQLAlchemy, Alembic to avoid future deprecation warnings. Also cleans up an additional version of the same function that's apparently been floating around for some time. Fixes: #295 Change-Id: I98274c16b6022289d1890f4daf532bab323ab112 --- doc/build/unreleased/249.rst | 14 ++++ doc/build/unreleased/295.rst | 9 +++ mako/codegen.py | 3 +- mako/compat.py | 120 +++++++++-------------------------- mako/ext/pygmentplugin.py | 2 +- mako/runtime.py | 19 +++--- mako/template.py | 3 +- setup.cfg | 3 +- setup.py | 25 ++------ test/__init__.py | 3 +- tox.ini | 4 +- 11 files changed, 78 insertions(+), 127 deletions(-) create mode 100644 doc/build/unreleased/249.rst create mode 100644 doc/build/unreleased/295.rst diff --git a/doc/build/unreleased/249.rst b/doc/build/unreleased/249.rst new file mode 100644 index 0000000..206a72a --- /dev/null +++ b/doc/build/unreleased/249.rst @@ -0,0 +1,14 @@ +.. change:: + :tags: change, py3k, installer + :tickets: 249 + + Mako 1.1 now supports Python versions: + + * 2.7 + * 3.4 and higher + + This includes that setup.py no longer includes any conditionals, allowing + for a pure Python wheel build, however this is not necessarily part of the + Pypi release process as of yet. The test suite also raises for Python + deprecation warnings. + diff --git a/doc/build/unreleased/295.rst b/doc/build/unreleased/295.rst new file mode 100644 index 0000000..147d23b --- /dev/null +++ b/doc/build/unreleased/295.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, py3k + :tickets: 295 + + Replaced usage of ``inspect.getfullargspec()`` with the vendored version + used by SQLAlchemy, Alembic to avoid future deprecation warnings. Also + cleans up an additional version of the same function that's apparently + been floating around for some time. + diff --git a/mako/codegen.py b/mako/codegen.py index 5ca7b04..8f9eef4 100644 --- a/mako/codegen.py +++ b/mako/codegen.py @@ -7,6 +7,7 @@ """provides functionality for rendering a parsetree constructing into module source code.""" +import json import re import time @@ -176,7 +177,7 @@ def write_metadata_struct(self): self.printer.writelines( '"""', "__M_BEGIN_METADATA", - compat.json.dumps(struct), + json.dumps(struct), "__M_END_METADATA\n" '"""', ) diff --git a/mako/compat.py b/mako/compat.py index 2444cad..4460fde 100644 --- a/mako/compat.py +++ b/mako/compat.py @@ -4,37 +4,49 @@ # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -import json # noqa +import collections +import inspect import sys py3k = sys.version_info >= (3, 0) -py33 = sys.version_info >= (3, 3) py2k = sys.version_info < (3,) py27 = sys.version_info >= (2, 7) jython = sys.platform.startswith("java") win32 = sys.platform.startswith("win") pypy = hasattr(sys, "pypy_version_info") -if py3k: - # create a "getargspec" from getfullargspec(), which is not deprecated - # in Py3K; getargspec() has started to emit warnings as of Py3.5. - # As of Py3.4, now they are trying to move from getfullargspec() - # to "signature()", but getfullargspec() is not deprecated, so stick - # with that for now. +ArgSpec = collections.namedtuple( + "ArgSpec", ["args", "varargs", "keywords", "defaults"] +) - import collections - ArgSpec = collections.namedtuple( - "ArgSpec", ["args", "varargs", "keywords", "defaults"] - ) - from inspect import getfullargspec as inspect_getfullargspec +def inspect_getargspec(func): + """getargspec based on fully vendored getfullargspec from Python 3.3.""" - def inspect_getargspec(func): - return ArgSpec(*inspect_getfullargspec(func)[0:4]) + if inspect.ismethod(func): + func = func.__func__ + if not inspect.isfunction(func): + raise TypeError("{!r} is not a Python function".format(func)) + co = func.__code__ + if not inspect.iscode(co): + raise TypeError("{!r} is not a code object".format(co)) -else: - from inspect import getargspec as inspect_getargspec # noqa + nargs = co.co_argcount + names = co.co_varnames + nkwargs = co.co_kwonlyargcount if py3k else 0 + args = list(names[:nargs]) + + nargs += nkwargs + varargs = None + if co.co_flags & inspect.CO_VARARGS: + varargs = co.co_varnames[nargs] + nargs = nargs + 1 + varkw = None + if co.co_flags & inspect.CO_VARKEYWORDS: + varkw = co.co_varnames[nargs] + + return ArgSpec(args, varargs, varkw, func.__defaults__) if py3k: @@ -86,7 +98,7 @@ def octal(lit): return eval("0" + lit) -if py33: +if py3k: from importlib import machinery def load_module(module_id, path): @@ -125,34 +137,6 @@ def exception_as(): return sys.exc_info()[1] -try: - import threading - - if py3k: - import _thread as thread - else: - import thread -except ImportError: - import dummy_threading as threading # noqa - - if py3k: - import _dummy_thread as thread - else: - import dummy_thread as thread # noqa - -try: - from functools import partial -except: - - def partial(func, *args, **keywords): - def newfunc(*fargs, **fkeywords): - newkeywords = keywords.copy() - newkeywords.update(fkeywords) - return func(*(args + fargs), **newkeywords) - - return newfunc - - all = all # noqa @@ -160,50 +144,6 @@ def exception_name(exc): return exc.__class__.__name__ -try: - from inspect import CO_VARKEYWORDS, CO_VARARGS - - def inspect_func_args(fn): - if py3k: - co = fn.__code__ - else: - co = fn.func_code - - nargs = co.co_argcount - names = co.co_varnames - args = list(names[:nargs]) - - varargs = None - if co.co_flags & CO_VARARGS: - varargs = co.co_varnames[nargs] - nargs = nargs + 1 - varkw = None - if co.co_flags & CO_VARKEYWORDS: - varkw = co.co_varnames[nargs] - - if py3k: - return args, varargs, varkw, fn.__defaults__ - else: - return args, varargs, varkw, fn.func_defaults - - -except ImportError: - import inspect - - def inspect_func_args(fn): - return inspect.getargspec(fn) - - -if py3k: - # TODO: this has been restored in py3k - def callable(fn): # noqa - return hasattr(fn, "__call__") - - -else: - callable = callable # noqa - - ################################################ # cross-compatible metaclass implementation # Copyright (c) 2010-2012 Benjamin Peterson diff --git a/mako/ext/pygmentplugin.py b/mako/ext/pygmentplugin.py index 2824693..1734ccd 100644 --- a/mako/ext/pygmentplugin.py +++ b/mako/ext/pygmentplugin.py @@ -59,7 +59,7 @@ class MakoLexer(RegexLexer): ), (r"<%(?=([\w\.\:]+))", Comment.Preproc, "ondeftags"), ( - r"(<%(?:!?))(.*?)(%>)(?s)", + r"(?s)(<%(?:!?))(.*?)(%>)", bygroups(Comment.Preproc, using(PythonLexer), Comment.Preproc), ), ( diff --git a/mako/runtime.py b/mako/runtime.py index 0956cd0..0e7149b 100644 --- a/mako/runtime.py +++ b/mako/runtime.py @@ -7,6 +7,7 @@ """provides runtime services for templates, including Context, Namespace, and various helper functions.""" +import functools import sys from mako import compat @@ -37,7 +38,7 @@ def __init__(self, buffer, **data): # "capture" function which proxies to the # generic "capture" function - self._data["capture"] = compat.partial(capture, self) + self._data["capture"] = functools.partial(capture, self) # "caller" stack used by def calls with content self.caller_stack = self._data["caller"] = CallerStack() @@ -625,7 +626,7 @@ def _get_star(self): def get(key): callable_ = self.template._get_def_callable(key) - return compat.partial(callable_, self.context) + return functools.partial(callable_, self.context) for k in self.template.module._exports: yield (k, get(k)) @@ -635,7 +636,7 @@ def __getattr__(self, key): val = self.callables[key] elif self.template.has_def(key): callable_ = self.template._get_def_callable(key) - val = compat.partial(callable_, self.context) + val = functools.partial(callable_, self.context) elif self.inherits: val = getattr(self.inherits, key) @@ -686,15 +687,15 @@ def _get_star(self): for key in dir(self.module): if key[0] != "_": callable_ = getattr(self.module, key) - if compat.callable(callable_): - yield key, compat.partial(callable_, self.context) + if callable(callable_): + yield key, functools.partial(callable_, self.context) def __getattr__(self, key): if key in self.callables: val = self.callables[key] elif hasattr(self.module, key): callable_ = getattr(self.module, key) - val = compat.partial(callable_, self.context) + val = functools.partial(callable_, self.context) elif self.inherits: val = getattr(self.inherits, key) else: @@ -731,7 +732,7 @@ def capture(context, callable_, *args, **kwargs): """ - if not compat.callable(callable_): + if not callable(callable_): raise exceptions.RuntimeException( "capture() function expects a callable as " "its argument (i.e. capture(func, *args, **kwargs))" @@ -885,7 +886,7 @@ def _render(template, callable_, args, data, as_unicode=False): def _kwargs_for_callable(callable_, data): - argspec = compat.inspect_func_args(callable_) + argspec = compat.inspect_getargspec(callable_) # for normal pages, **pageargs is usually present if argspec[2]: return data @@ -900,7 +901,7 @@ def _kwargs_for_callable(callable_, data): def _kwargs_for_include(callable_, data, **kwargs): - argspec = compat.inspect_func_args(callable_) + argspec = compat.inspect_getargspec(callable_) namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None] for arg in namedargs: if arg != "context" and arg in data and arg not in kwargs: diff --git a/mako/template.py b/mako/template.py index 8e87d50..937d15b 100644 --- a/mako/template.py +++ b/mako/template.py @@ -7,6 +7,7 @@ """Provides the Template class, a facade for parsing, generating and executing template strings, as well as template runtime operations.""" +import json import os import re import shutil @@ -659,7 +660,7 @@ def get_module_source_metadata(cls, module_source, full_line_map=False): source_map = re.search( r"__M_BEGIN_METADATA(.+?)__M_END_METADATA", module_source, re.S ).group(1) - source_map = compat.json.loads(source_map) + source_map = json.loads(source_map) source_map["line_map"] = dict( (int(k), int(v)) for k, v in source_map["line_map"].items() ) diff --git a/setup.cfg b/setup.cfg index 59bac7f..5e0292c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,7 +4,7 @@ tag_build = dev [tool:pytest] -addopts= --tb native -v -r fxX +addopts= --tb native -v -r fxX -W error python_files=test/*test_*.py @@ -13,6 +13,7 @@ sign = 1 identity = C4DAFEE1 [flake8] +show-source = true enable-extensions = G # E203 is due to https://github.com/PyCQA/pycodestyle/issues/373 ignore = diff --git a/setup.py b/setup.py index 17b6c70..68b46ef 100644 --- a/setup.py +++ b/setup.py @@ -14,24 +14,9 @@ ) v.close() -readme = open(os.path.join(os.path.dirname(__file__), "README.rst")).read() +readme = os.path.join(os.path.dirname(__file__), "README.rst") -if sys.version_info < (2, 6): - raise Exception("Mako requires Python 2.6 or higher.") - -markupsafe_installs = ( - sys.version_info >= (2, 6) and sys.version_info < (3, 0) -) or sys.version_info >= (3, 3) - -install_requires = [] - -if markupsafe_installs: - install_requires.append("MarkupSafe>=0.9.2") - -try: - import argparse # noqa -except ImportError: - install_requires.append("argparse") +install_requires = ["MarkupSafe>=0.9.2"] class PyTest(TestCommand): @@ -59,7 +44,8 @@ def run_tests(self): version=VERSION, description="A super-fast templating language that borrows the \ best ideas from the existing templating languages.", - long_description=readme, + long_description=open(readme).read(), + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", classifiers=[ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", @@ -77,14 +63,13 @@ def run_tests(self): url="https://www.makotemplates.org/", project_urls={ "Documentation": "https://docs.makotemplates.org", - "Issue Tracker": "https://github.com/sqlalchemy/mako" + "Issue Tracker": "https://github.com/sqlalchemy/mako", }, license="MIT", packages=find_packages(".", exclude=["examples*", "test*"]), tests_require=["pytest", "mock"], cmdclass={"test": PyTest}, zip_safe=False, - python_requires=">=2.6", install_requires=install_requires, extras_require={}, entry_points=""" diff --git a/test/__init__.py b/test/__init__.py index b91e709..c5c9e91 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -6,7 +6,6 @@ from mako import compat from mako.cache import CacheImpl from mako.cache import register_plugin -from mako.compat import py33 from mako.compat import py3k from mako.template import Template from mako.util import update_wrapper @@ -106,7 +105,7 @@ def teardown(): shutil.rmtree(module_base, True) -if py33: +if py3k: from unittest import mock # noqa else: import mock # noqa diff --git a/tox.ini b/tox.ini index 45b48d1..b1a1e81 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,10 @@ [tox] -envlist = py{27,35,36,37,38} +envlist = py{27,34,35,36,37,38} [testenv] cov_args=--cov=mako --cov-report term --cov-report xml -deps=pytest +deps=pytest>=3.1.0 mock beaker markupsafe