Skip to content

Commit

Permalink
Merge pull request #18 from plone/python312
Browse files Browse the repository at this point in the history
Add Support for Python 3.12 and drop Python 2 Support
  • Loading branch information
mauritsvanrees authored Apr 22, 2024
2 parents 3100a1b + 958bab3 commit cbdbb58
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 162 deletions.
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

0 comments on commit cbdbb58

Please sign in to comment.