From 8eee01ee202a40d33ac02aa3c4378ccea9efe89e Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Fri, 14 Jun 2024 08:02:29 +0200 Subject: [PATCH 1/4] Bumped version for breaking release. --- CHANGES.rst | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2f1acd6..484827d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,7 +2,7 @@ Changes ========= -5.1 (unreleased) +6.0 (unreleased) ================ - Nothing changed yet. diff --git a/setup.py b/setup.py index 32d1a92..c906e44 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ def read(*rnames): setup( name='zope.browserpage', - version='5.1.dev0', + version='6.0.dev0', url='https://github.com/zopefoundation/zope.browserpage', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', From d75dac59ebc5e55092aaffd294044c17faf9e477 Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Fri, 14 Jun 2024 08:02:30 +0200 Subject: [PATCH 2/4] Drop support for Python 3.7. --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 484827d..f3103e2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,7 @@ 6.0 (unreleased) ================ -- Nothing changed yet. +- Drop support for Python 3.7. 5.0 (2023-01-19) From f23933d55d977cc6fc2f5befeed475cba9541ffd Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Fri, 14 Jun 2024 08:03:05 +0200 Subject: [PATCH 3/4] Configuring for pure-python --- .github/workflows/tests.yml | 21 ++++++++++++-------- .meta.toml | 3 ++- setup.cfg | 2 -- tox.ini | 39 +++++++++++++++++++++++++++---------- 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0e45a78..0474d6a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,29 +17,30 @@ jobs: fail-fast: false matrix: os: - - ["ubuntu", "ubuntu-20.04"] + - ["ubuntu", "ubuntu-latest"] config: # [Python version, tox env] - - ["3.9", "lint"] - - ["3.7", "py37"] + - ["3.11", "release-check"] + - ["3.11", "lint"] - ["3.8", "py38"] - ["3.9", "py39"] - ["3.10", "py310"] - ["3.11", "py311"] - - ["pypy-3.9", "pypy3"] - - ["3.9", "coverage"] + - ["3.12", "py312"] + - ["pypy-3.10", "pypy3"] + - ["3.11", "coverage"] runs-on: ${{ matrix.os[1] }} if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: ${{ matrix.config[1] }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.config[0] }} - name: Pip cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }} @@ -51,7 +52,11 @@ jobs: python -m pip install --upgrade pip pip install tox - name: Test + if: ${{ !startsWith(runner.os, 'Mac') }} run: tox -e ${{ matrix.config[1] }} + - name: Test (macOS) + if: ${{ startsWith(runner.os, 'Mac') }} + run: tox -e ${{ matrix.config[1] }}-universal2 - name: Coverage if: matrix.config[1] == 'coverage' run: | diff --git a/.meta.toml b/.meta.toml index 32492b6..aed9641 100644 --- a/.meta.toml +++ b/.meta.toml @@ -2,7 +2,7 @@ # https://github.com/zopefoundation/meta/tree/master/config/pure-python [meta] template = "pure-python" -commit-id = "fd874ae4" +commit-id = "b1221c3c" [python] with-windows = false @@ -10,6 +10,7 @@ with-pypy = true with-future-python = false with-sphinx-doctests = false with-macos = false +with-docs = false [tox] use-flake8 = true diff --git a/setup.cfg b/setup.cfg index d18b07e..064a6fe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,5 @@ # Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python -[bdist_wheel] -universal = 0 [flake8] doctests = 1 diff --git a/tox.ini b/tox.ini index cef2f9c..7cc875d 100644 --- a/tox.ini +++ b/tox.ini @@ -3,37 +3,55 @@ [tox] minversion = 3.18 envlist = + release-check lint - py37 py38 py39 py310 py311 + py312 pypy3 coverage [testenv] usedevelop = true +package = wheel +wheel_build_env = .pkg deps = + setuptools < 69 +setenv = + py312: VIRTUALENV_PIP=23.1.2 + py312: PIP_REQUIRE_VIRTUALENV=0 commands = zope-testrunner --test-path=src {posargs:-vc} extras = test +[testenv:release-check] +description = ensure that the distribution is ready to release +basepython = python3 +skip_install = true +deps = + twine + build + check-manifest + check-python-versions >= 0.20.0 + wheel +commands_pre = +commands = + check-manifest + check-python-versions --only setup.py,tox.ini,.github/workflows/tests.yml + python -m build --sdist --no-isolation + twine check dist/* [testenv:lint] basepython = python3 skip_install = true +deps = + isort + flake8 commands = isort --check-only --diff {toxinidir}/src {toxinidir}/setup.py flake8 src setup.py - check-manifest - check-python-versions -deps = - check-manifest - check-python-versions >= 0.19.1 - wheel - flake8 - isort [testenv:isort-apply] basepython = python3 @@ -54,7 +72,7 @@ commands = mkdir -p {toxinidir}/parts/htmlcov coverage run -m zope.testrunner --test-path=src {posargs:-vc} coverage html --ignore-errors - coverage report --ignore-errors --show-missing --fail-under=99.47 + coverage report --show-missing --fail-under=99.47 [coverage:run] branch = True @@ -62,6 +80,7 @@ source = zope.browserpage [coverage:report] precision = 2 +ignore_errors = True exclude_lines = pragma: no cover pragma: nocover From 0f28061ec75e4a96eeaf07f3f8075d4091d1d863 Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Fri, 14 Jun 2024 08:04:31 +0200 Subject: [PATCH 4/4] Drop support for Python 3.7. --- CHANGES.rst | 2 + setup.py | 4 +- src/zope/browserpage/metaconfigure.py | 173 +++++++++++++++---------- src/zope/browserpage/metadirectives.py | 92 +++++-------- 4 files changed, 142 insertions(+), 129 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f3103e2..ad74480 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,8 @@ 6.0 (unreleased) ================ +- Add support for Python 3.12. + - Drop support for Python 3.7. diff --git a/setup.py b/setup.py index c906e44..823553d 100644 --- a/setup.py +++ b/setup.py @@ -45,11 +45,11 @@ def read(*rnames): 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Operating System :: OS Independent', @@ -62,7 +62,7 @@ def read(*rnames): packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope'], - python_requires='>=3.7', + python_requires='>=3.8', install_requires=[ 'setuptools', 'zope.tal >= 4.2.0', diff --git a/src/zope/browserpage/metaconfigure.py b/src/zope/browserpage/metaconfigure.py index b87c1aa..c031f67 100644 --- a/src/zope/browserpage/metaconfigure.py +++ b/src/zope/browserpage/metaconfigure.py @@ -43,15 +43,14 @@ def _fallbackMenuItemDirective(_context, *args, **kwargs): import warnings warnings.warn_explicit( 'Page directive used with "menu" argument, while "zope.browsermenu" ' - 'package is not installed. Doing nothing.', - UserWarning, + 'package is not installed. Doing nothing.', UserWarning, _context.info.file, _context.info.line) return [] try: from zope.browsermenu.metaconfigure import menuItemDirective -except ImportError: # pragma: no cover +except ModuleNotFoundError: # pragma: no cover menuItemDirective = _fallbackMenuItemDirective # There are three cases we want to suport: @@ -113,10 +112,18 @@ def _norm_template(_context, template): return template -def page(_context, name, permission, for_=Interface, - layer=IDefaultBrowserLayer, template=None, class_=None, - allowed_interface=None, allowed_attributes=None, - attribute='__call__', menu=None, title=None): +def page(_context, + name, + permission, + for_=Interface, + layer=IDefaultBrowserLayer, + template=None, + class_=None, + allowed_interface=None, + allowed_attributes=None, + attribute='__call__', + menu=None, + title=None): _handle_menu(_context, menu, title, [for_], name, permission, layer) required = {} @@ -141,8 +148,7 @@ def page(_context, name, permission, for_=Interface, if attribute != '__call__': if not hasattr(class_, attribute): raise ConfigurationError( - "The provided class doesn't have the specified attribute " - ) + "The provided class doesn't have the specified attribute ") if template: # class and template new_class = SimpleViewClass(template, bases=(class_, ), name=name) @@ -150,7 +156,10 @@ def page(_context, name, permission, for_=Interface, cdict = {} cdict['__name__'] = name cdict['__page_attribute__'] = attribute - new_class = type(class_.__name__, (class_, simple,), cdict) + new_class = type(class_.__name__, ( + class_, + simple, + ), cdict) if hasattr(class_, '__implements__'): classImplements(new_class, IBrowserPublisher) @@ -169,16 +178,16 @@ def page(_context, name, permission, for_=Interface, _handle_for(_context, for_) new_class._simple__whitelist = ( - set(required) - - {attribute, 'browserDefault', '__call__', 'publishTraverse'}) + set(required) - + {attribute, 'browserDefault', '__call__', 'publishTraverse'}) defineChecker(new_class, Checker(required)) _context.action( discriminator=('view', (for_, layer), name, IBrowserRequest), callable=handler, - args=('registerAdapter', - new_class, (for_, layer), Interface, name, _context.info), + args=('registerAdapter', new_class, (for_, layer), Interface, name, + _context.info), ) @@ -187,30 +196,45 @@ def page(_context, name, permission, for_=Interface, # Note that a class might want to access one of the defined # templates. If it does though, it should use getMultiAdapter. + class pages: - def __init__(self, _context, permission, for_=Interface, - layer=IDefaultBrowserLayer, class_=None, - allowed_interface=None, allowed_attributes=None): + def __init__(self, + _context, + permission, + for_=Interface, + layer=IDefaultBrowserLayer, + class_=None, + allowed_interface=None, + allowed_attributes=None): self.opts = dict( - for_=for_, permission=permission, - layer=layer, class_=class_, + for_=for_, + permission=permission, + layer=layer, + class_=class_, allowed_interface=allowed_interface, allowed_attributes=allowed_attributes, ) - def page(self, _context, name, attribute='__call__', template=None, - menu=None, title=None): + def page(self, + _context, + name, + attribute='__call__', + template=None, + menu=None, + title=None): return page(_context, name=name, attribute=attribute, template=template, - menu=menu, title=title, + menu=menu, + title=title, **(self.opts)) def __call__(self): return () + # view (named view with pages) # This is a different case. We actually build a class with attributes @@ -221,10 +245,18 @@ class view: default = None - def __init__(self, _context, permission, for_=Interface, - name='', layer=IDefaultBrowserLayer, class_=None, - allowed_interface=None, allowed_attributes=None, - menu=None, title=None, provides=Interface): + def __init__(self, + _context, + permission, + for_=Interface, + name='', + layer=IDefaultBrowserLayer, + class_=None, + allowed_interface=None, + allowed_attributes=None, + menu=None, + title=None, + provides=Interface): _handle_menu(_context, menu, title, [for_], name, permission, layer) @@ -253,8 +285,8 @@ def defaultPage(self, _context, name): return () def __call__(self): - (_context, name, (for_, layer), permission, class_, - allowed_interface, allowed_attributes) = self.args + (_context, name, (for_, layer), permission, class_, allowed_interface, + allowed_attributes) = self.args required = {} @@ -269,8 +301,7 @@ def __call__(self): cdict[attribute] = cdict[pname] else: if not hasattr(class_, attribute): - raise ConfigurationError("Undefined attribute", - attribute) + raise ConfigurationError("Undefined attribute", attribute) attribute = attribute or pname required[pname] = permission @@ -280,8 +311,11 @@ def __call__(self): # This should go away, but noone seems to remember what to do. :-( if hasattr(class_, 'publishTraverse'): - def publishTraverse(self, request, name, - pages=pages, getattr=getattr): + def publishTraverse(self, + request, + name, + pages=pages, + getattr=getattr): if name in pages: return getattr(self, pages[name]) @@ -293,8 +327,12 @@ def publishTraverse(self, request, name, return m(request, name) else: - def publishTraverse(self, request, name, - pages=pages, getattr=getattr): + + def publishTraverse(self, + request, + name, + pages=pages, + getattr=getattr): if name in pages: return getattr(self, pages[name]) @@ -310,15 +348,12 @@ def publishTraverse(self, request, name, if self.default or self.pages: _default = self.default or self.pages[0][0] cdict['browserDefault'] = ( - lambda self, request, default=_default: - (self, (default, )) - ) + lambda self, request, default=_default: (self, + (default, ))) elif providesCallable(class_): - cdict['browserDefault'] = ( - lambda self, request: (self, ()) - ) + cdict['browserDefault'] = (lambda self, request: (self, ())) - bases = (simple,) if class_ is None else (class_, simple) + bases = (simple, ) if class_ is None else (class_, simple) try: cname = str(name) @@ -340,22 +375,24 @@ def publishTraverse(self, request, name, defineChecker(newclass, Checker(required)) if self.provides is not None: - _context.action( - discriminator=None, - callable=provideInterface, - args=('', self.provides) - ) + _context.action(discriminator=None, + callable=provideInterface, + args=('', self.provides)) _context.action( discriminator=('view', (for_, layer), name, self.provides), callable=handler, - args=('registerAdapter', - newclass, (for_, layer), self.provides, name, - _context.info), + args=('registerAdapter', newclass, (for_, layer), self.provides, + name, _context.info), ) -def _handle_menu(_context, menu, title, for_, name, permission, +def _handle_menu(_context, + menu, + title, + for_, + name, + permission, layer=IDefaultBrowserLayer): if not menu and not title: # Neither of them @@ -372,9 +409,13 @@ def _handle_menu(_context, menu, title, for_, name, permission, "Menus can be specified only for single-view, not for " "multi-views.") - return menuItemDirective( - _context, menu, for_[0], '@@' + name, title, - permission=permission, layer=layer) + return menuItemDirective(_context, + menu, + for_[0], + '@@' + name, + title, + permission=permission, + layer=layer) def _handle_permission(_context, permission): @@ -389,11 +430,9 @@ def _handle_allowed_interface(_context, allowed_interface, permission, # Allow access for all names defined by named interfaces if allowed_interface: for i in allowed_interface: - _context.action( - discriminator=None, - callable=provideInterface, - args=(None, i) - ) + _context.action(discriminator=None, + callable=provideInterface, + args=(None, i)) for name in i: required[name] = permission @@ -409,11 +448,9 @@ def _handle_allowed_attributes(_context, allowed_attributes, permission, def _handle_for(_context, for_): if for_ is not None: - _context.action( - discriminator=None, - callable=provideInterface, - args=('', for_) - ) + _context.action(discriminator=None, + callable=provideInterface, + args=('', for_)) @implementer(IBrowserPublisher) @@ -459,11 +496,9 @@ def providesCallable(class_): def expressiontype(_context, name, handler): - _context.action( - discriminator=("tales:expressiontype", name), - callable=registerType, - args=(name, handler) - ) + _context.action(discriminator=("tales:expressiontype", name), + callable=registerType, + args=(name, handler)) def registerType(name, handler): @@ -480,7 +515,7 @@ def clear(): try: from zope.testing.cleanup import addCleanUp -except ImportError: # pragma: no cover +except ModuleNotFoundError: # pragma: no cover pass else: addCleanUp(clear) diff --git a/src/zope/browserpage/metadirectives.py b/src/zope/browserpage/metadirectives.py index fb30cf1..16d3b06 100644 --- a/src/zope/browserpage/metadirectives.py +++ b/src/zope/browserpage/metadirectives.py @@ -26,7 +26,7 @@ try: from zope.browsermenu.field import MenuField -except ImportError: # pragma: no cover +except ModuleNotFoundError: # pragma: no cover # avoid hard dependency on zope.browsermenu MenuField = TextLine @@ -40,23 +40,19 @@ class IPagesDirective(IBasicViewInformation): 'allowed_attributes', and 'allowed_interface' attributes. """ - for_ = GlobalObject( - title="The interface or class this view is for.", - required=False - ) + for_ = GlobalObject(title="The interface or class this view is for.", + required=False) layer = GlobalObject( title="The request interface or class this view is for.", description="Defaults to " - "zope.publisher.interfaces.browser.IDefaultBrowserLayer.", - required=False - ) + "zope.publisher.interfaces.browser.IDefaultBrowserLayer.", + required=False) permission = Permission( title="Permission", description="The permission needed to use the view.", - required=True - ) + required=True) class IViewDirective(IPagesDirective): @@ -67,10 +63,8 @@ class IViewDirective(IPagesDirective): traversing to the view name and then traversing to the page name. """ - for_ = GlobalInterface( - title="The interface this view is for.", - required=False - ) + for_ = GlobalInterface(title="The interface this view is for.", + required=False) name = TextLine( title="The name of the view.", @@ -79,9 +73,8 @@ class IViewDirective(IPagesDirective): default='', ) - menu = MenuField( - title="The browser menu to include the page (view) in.", - description=""" + menu = MenuField(title="The browser menu to include the page (view) in.", + description=""" Many views are included in menus. It's convenient to name the menu in the page directive, rather than having to give a separate menuItem directive. 'zmi_views' is the menu most often @@ -89,19 +82,16 @@ class IViewDirective(IPagesDirective): This attribute will only work if zope.browsermenu is installed. """, - required=False - ) + required=False) - title = MessageID( - title="The browser menu label for the page (view)", - description=""" + title = MessageID(title="The browser menu label for the page (view)", + description=""" This attribute must be supplied if a menu attribute is supplied. This attribute will only work if zope.browsermenu is installed. """, - required=False - ) + required=False) provides = GlobalInterface( title="The interface this view provides.", @@ -118,33 +108,28 @@ class IViewPageSubdirective(Interface): Subdirective to IViewDirective. """ - name = TextLine( - title="The name of the page (view)", - description=""" + name = TextLine(title="The name of the page (view)", + description=""" The name shows up in URLs/paths. For example 'foo' or 'foo.html'. This attribute is required unless you use the subdirective 'page' to create sub views. If you do not have sub pages, it is common to use an extension for the view name such as '.html'. If you do have sub pages and you want to provide a view name, you shouldn't use extensions.""", - required=True - ) + required=True) attribute = PythonIdentifier( title="The name of the view attribute implementing the page.", description=""" This refers to the attribute (method) on the view that is implementing a specific sub page.""", - required=False - ) + required=False) - template = Path( - title="The name of a template that implements the page.", - description=""" + template = Path(title="The name of a template that implements the page.", + description=""" Refers to a file containing a page template (should end in extension '.pt' or '.html').""", - required=False - ) + required=False) class IViewDefaultPageSubdirective(Interface): @@ -152,15 +137,13 @@ class IViewDefaultPageSubdirective(Interface): Subdirective to IViewDirective. """ - name = TextLine( - title="The name of the page that is the default.", - description=""" + name = TextLine(title="The name of the page that is the default.", + description=""" The named page will be used as the default if no name is specified explicitly in the path. If no defaultPage directive is supplied, the default page will be the first page listed.""", - required=True - ) + required=True) class IPagesPageSubdirective(IViewPageSubdirective): @@ -168,28 +151,24 @@ class IPagesPageSubdirective(IViewPageSubdirective): Subdirective to IPagesDirective """ - menu = MenuField( - title="The browser menu to include the page (view) in.", - description=""" + menu = MenuField(title="The browser menu to include the page (view) in.", + description=""" Many views are included in menus. It's convenient to name the menu in the page directive, rather than having to give a separate menuItem directive. This attribute will only work if zope.browsermenu is installed. """, - required=False - ) + required=False) - title = MessageID( - title="The browser menu label for the page (view)", - description=""" + title = MessageID(title="The browser menu label for the page (view)", + description=""" This attribute must be supplied if a menu attribute is supplied. This attribute will only work if zope.browsermenu is installed. """, - required=False - ) + required=False) class IPageDirective(IPagesDirective, IPagesPageSubdirective): @@ -209,12 +188,9 @@ class IExpressionTypeDirective(Interface): title="Name", description="""Name of the expression. This will also be used as the prefix in actual TALES expressions.""", - required=True - ) + required=True) - handler = GlobalObject( - title="Handler", - description="""Handler is class that implements + handler = GlobalObject(title="Handler", + description="""Handler is class that implements zope.tales.interfaces.ITALESExpression.""", - required=True - ) + required=True)