Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for Python 3.12 and drop Python 2 Support #18

Merged
merged 3 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Changelog

Breaking changes:

- *add item here*
- Add support for Python 3.12. Drop support for Python 2
[pbauer]

New features:

Expand Down
35 changes: 17 additions & 18 deletions plone/reload/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,21 @@

@implementer(IReload)
class Reload(BrowserView):
"""Reload view.
"""
"""Reload view."""

def __init__(self, context, request):
BrowserView.__init__(self, context, request)
self.message = None

def __call__(self):
action = self.request.form.get('action')
action = self.request.form.get("action")
if action is not None:
if self.available():
if action == 'code':
if action == "code":
self.message = self.code_reload()
elif action == 'zcml':
elif action == "zcml":
self.message = self.zcml_reload()
if action == 'template':
if action == "template":
self.message = self.template_reload()
return self.index()

Expand All @@ -49,22 +48,22 @@ def template_reload(self):
if HAS_CMF:
reloaded = reload_template(self.context)
if reloaded > 0:
return '%s templates reloaded.' % reloaded
return 'No templates reloaded.'
return 'CMF is not installed. Templates cannot be reloaded.'
return "%s templates reloaded." % reloaded
return "No templates reloaded."
return "CMF is not installed. Templates cannot be reloaded."

def code_reload(self):
if not self.available():
return

reloaded = reload_code()

result = ''
result = ""
if reloaded:
result += 'Code reloaded:\n\n'
result += '\n'.join(reloaded)
result += "Code reloaded:\n\n"
result += "\n".join(reloaded)
else:
result = 'No code reloaded!'
result = "No code reloaded!"
return result

def zcml_reload(self):
Expand All @@ -79,11 +78,11 @@ def zcml_reload(self):
# TODO Minimize all caches, we only really want to invalidate the
# local site manager from all caches
self.context._p_jar.db().cacheMinimize()
result = ''
result = ""
if reloaded:
result += 'Code reloaded:\n\n'
result += '\n'.join(reloaded)
result += "Code reloaded:\n\n"
result += "\n".join(reloaded)
else:
result = 'No code reloaded!'
result += '\n\nGlobal ZCML reloaded.'
result = "No code reloaded!"
result += "\n\nGlobal ZCML reloaded."
return result
58 changes: 17 additions & 41 deletions plone/reload/code.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,43 @@
import os
import sys

from os.path import abspath
from os.path import isfile

from importlib.util import cache_from_source, source_from_cache
from plone.reload import config
from plone.reload.xreload import Reloader

import os
import sys

_marker = object()
MOD_TIMES = dict()


try:
# Py3
from imp import cache_from_source, source_from_cache

def _cache_from_source(path):
if '__pycache__' in path:
return path
return cache_from_source(path)

def _source_from_cache(path):
if '__pycache__' in path:
return source_from_cache(path)
def _cache_from_source(path):
if "__pycache__" in path:
return path
return cache_from_source(path)

except ImportError:
# Py2
def _cache_from_source(path):
if path.endswith('pyc') or path.endswith('pyo'):
cache = path
else:
cache = path + 'c'
if os.path.isfile(cache):
path = cache
return path

def _source_from_cache(path):
source = path
if path.endswith('pyc') or path.endswith('pyo'):
source = path[:-1]
if os.path.isfile(source):
path = source
return path
def _source_from_cache(path):
if "__pycache__" in path:
return source_from_cache(path)
return path


def in_search_path(path):
if 'site-packages' in path:
if "site-packages" in path:
return False
elif '.egg' in path:
elif ".egg" in path:
return False
return True


def search_modules():
modules = []
for name, module in sys.modules.items():
for _, module in sys.modules.items():
if module is not None:
f = getattr(module, '__file__', None)
f = getattr(module, "__file__", None)
# Standard library modules don't have a __file__
if f is None:
continue
f = abspath(_source_from_cache(f))
f = os.path.abspath(_source_from_cache(f))
if config.EXCLUDE_SITE_PACKAGES:
if in_search_path(f):
modules.append((f, module))
Expand All @@ -75,7 +51,7 @@ def get_mod_time(path):
# If we have the compiled source, look for the source code change date
path = _source_from_cache(path)
# protect against missing and unaccessible files
if isfile(path):
if os.path.isfile(path):
mtime = os.stat(path)[8]
return mtime

Expand Down
3 changes: 1 addition & 2 deletions plone/reload/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@


class IReload(Interface):
"""Interface for the ZCML reload view.
"""
"""Interface for the ZCML reload view."""

def status():
"""Return a status text."""
Expand Down
4 changes: 2 additions & 2 deletions plone/reload/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def reload_skins(tool):
for folder in tool.objectValues():
for obj in folder.objectValues():
if isinstance(obj, FSObject):
parsed = getattr(obj, '_parsed', 0)
parsed = getattr(obj, "_parsed", 0)
if parsed:
obj._parsed = 0
counter += 1
Expand All @@ -19,7 +19,7 @@ def reload_template(root):
counter = 0
for obj in root.objectValues():
if ISiteRoot.providedBy(obj):
tool = getToolByName(obj, 'portal_skins', None)
tool = getToolByName(obj, "portal_skins", None)
if tool is not None:
counter = reload_skins(tool)
return counter
76 changes: 31 additions & 45 deletions plone/reload/xreload.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,13 @@

"""

import marshal
import imp
from importlib import reload
from zope.interface.interface import Specification

import importlib
import inspect
import sys
import types
import inspect

import six
from six.moves import reload_module
import zope.component


CLASS_STATICS = frozenset(["__dict__", "__doc__", "__module__", "__weakref__"])

Expand Down Expand Up @@ -53,45 +50,37 @@ def reload(self):
# 'whatever'
i = modname.rfind(".")
if i >= 0:
pkgname, modname = modname[:i], modname[i + 1:]
pkgname, modname = modname[:i], modname[i + 1 :]
else:
pkgname = None
# Compute the search path
if pkgname:
# We're not reloading the package, only the module in it
pkg = sys.modules[pkgname]
path = pkg.__path__ # Search inside the package
else:
# Search the top-level module path
pkg = None
path = None # Make find_module() uses the default search path
# Find the module; may raise ImportError
(stream, filename, (suffix, mode, kind)) = imp.find_module(
modname, path)
# Turn it into a code object
try:
# Is it Python source code or byte code read from a file?
# XXX Could handle frozen modules, zip-import modules
if kind not in (imp.PY_COMPILED, imp.PY_SOURCE):
# Fall back to built-in reload()
return reload_module(self.mod)
if kind == imp.PY_SOURCE:
package_name = pkg.__name__ if pkg else None
specs = importlib.util.find_spec(self.mod.__name__, package=package_name)
filename = specs.origin
if specs.has_location:
with open(filename, "rb") as stream:
source = stream.read()
# PeterB: if we don't strip the source code and add newline we
# get a SyntaxError even if `python $filename` is perfectly
# happy.
source = source.strip() + '\n'
source = source.strip() + b"\n"
code = compile(source, filename, "exec")
else:
# I have no idea how to test this one
code = marshal.load(stream) # pragma NO COVER
finally:
if stream:
stream.close()
else:
# Fall back to built-in reload()
return reload(self.mod)

# Execute the code im a temporary namespace; if this fails, no changes
tmpns = {'__name__': '%s.%s' % (pkgname, modname),
'__file__': filename,
'__doc__': modns['__doc__']}
tmpns = {
"__name__": "%s.%s" % (pkgname, modname),
"__file__": filename,
"__doc__": modns["__doc__"],
}
exec(code, tmpns)
# Now we get to the hard part
_update_scope(modns, tmpns)
Expand All @@ -117,13 +106,13 @@ def _update(self, oldobj, newobj):
# Cop-out: if the type changed, give up
return newobj

new_module = getattr(newobj, '__module__', None)
new_module = getattr(newobj, "__module__", None)
if new_module != self.mod.__name__:
# Do not update objects in-place that have been imported.
# Just update their references.
return newobj

if isinstance(newobj, zope.interface.interface.Specification):
if isinstance(newobj, Specification):
# XXX we can't update interfaces because their internal
# data structures break. We'll have to implement the reload method
# for those and patch it in.
Expand Down Expand Up @@ -162,28 +151,25 @@ def _update_scope(oldscope, newscope):
oldscope[name] = newscope[name]
# Delete names that are no longer current
for name in oldnames - newnames:
if not name.startswith('__'):
if not name.startswith("__"):
del oldscope[name]


def _update_function(oldfunc, newfunc):
"""Update a function object."""
if _closure_changed(six.get_function_closure(oldfunc),
six.get_function_closure(newfunc)):
if _closure_changed(oldfunc.__closure__, newfunc.__closure__):
raise ClosureChanged()
setattr(oldfunc, six._func_code, six.get_function_code(newfunc))
setattr(oldfunc, six._func_defaults, six.get_function_defaults(newfunc))
_update_scope(six.get_function_globals(oldfunc),
six.get_function_globals(newfunc))
setattr(oldfunc, "__code__", newfunc.__code__)
setattr(oldfunc, "__defaults__", newfunc.__defaults__)
_update_scope(oldfunc.__globals__, newfunc.__globals__)
# XXX What else?
return oldfunc


def _update_method(oldmeth, newmeth):
"""Update a method object."""
# XXX What if im_func is not a function?
_update_function(six.get_unbound_function(oldmeth),
six.get_unbound_function(newmeth))
_update_function(oldmeth, newmeth)
return oldmeth


Expand Down Expand Up @@ -212,7 +198,7 @@ def _update_class(oldclass, newclass):
if isinstance(new, (types.FunctionType, types.MethodType)):
if isinstance(old, property) and not isinstance(new, property):
# Removing a decorator
setattr(oldclass, name, six.get_unbound_function(new))
setattr(oldclass, name, new)
elif isinstance(new, types.FunctionType):
# Under Py3 there are only functions
_update_function(old, new)
Expand All @@ -230,6 +216,6 @@ def _update_class(oldclass, newclass):
setattr(oldclass, name, new)
except ClosureChanged:
# If the closure changed, we need to replace the entire function
setattr(oldclass, name, six.get_unbound_function(new))
setattr(oldclass, name, new)

return oldclass
Loading