Commit d48a5150 authored by Jérome Perrin's avatar Jérome Perrin

ERP5 py3: WIP ( all changes squashed )

software/slapos-sr-testing: add erp5-py3

---

WIP ERP5: XXX dumps() parameter for longrequest promise 🚧

Not sure why it was OK on python2 and not sure if this has to be done
or the promise code needs to cast the results of getConfig()

---

py3: do not enable NEO test yet 🚧

at this point they all fail after long timeouts, because neo does not
support py3 yet

stack/erp5: version up APacheDEX 2.0 (py3 only)
Co-authored-by: Kazuhiko Shiozaki's avatarKazuhiko SHIOZAKI <kazuhiko@nexedi.com>
Co-authored-by: Arnaud Fontaine's avatarArnaud Fontaine <arnaud.fontaine@nexedi.com>
Co-authored-by: Bryton Lacquement's avatarBryton Lacquement <bryton.lacquement@nexedi.com>
parent 29739146
......@@ -57,6 +57,12 @@ egg-versions =
[ZODB5]
<= _ZODB
egg-versions =
ZODB = 5.8.1
transaction = 4.0.0
[ZODB5:python2]
<= _ZODB
egg-versions =
ZODB = 5.8.1
transaction = 3.0.1
......@@ -94,11 +100,17 @@ setup-eggs = ${python-cffi:egg}
# eggs that are common to ZODB4 and ZODB5.
[versions]
BTrees = 4.11.3
persistent = 4.9.3
zodbpickle = 2.6.0
BTrees = 5.1.0
persistent = 5.1.0
zodbpickle = 3.1.0
# Provide ZODB3 for those eggs that still care about ZODB3 compatibility -
# for example wendelin.core. ZODB3 3.11 is just a dependency egg on _latest_
# ZODB, persistent, BTrees and ZEO.
ZODB3 = 3.11.0
[versions:python2]
BTrees = 4.11.3
persistent = 4.9.3
zodbpickle = 2.6.0
From 3666a7afd46ea6d069606450c520b8b7e2b5fddf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Thu, 22 Feb 2024 23:33:41 +0900
Subject: [PATCH] Make dict views behave like their unrestricted versions
unlike the restricted versions, the unrestricted versions:
- are not iterators, they are views
- have a len
- are false when the mapping is empty, true otherwise
- are instances of collections.abc.MappingView
During this refactoring, also change `.items()` to validate
ach keys and values, like `.keys()` and `.values()` do.
---
CHANGES.rst | 7 ++++
src/AccessControl/ZopeGuards.py | 50 ++++++++++++++++++-----
src/AccessControl/tests/actual_python.py | 33 +++++++++++++++
src/AccessControl/tests/testZopeGuards.py | 34 +++++++++++----
4 files changed, 104 insertions(+), 20 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index f35a8d2..073b791 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -8,6 +8,13 @@ For changes before version 3.0, see ``HISTORY.rst``.
- Nothing changed yet.
+- Make dict views (`.keys()`, `.items()` and `.values()`) behave like their
+ unrestricted versions.
+ (`#147 <https://github.com/zopefoundation/AccessControl/pull/147>`_)
+
+- Make `.items()` validate each keys and values, like `.keys()` and
+ `.values()` do.
+
6.3 (2023-11-20)
----------------
diff --git a/src/AccessControl/ZopeGuards.py b/src/AccessControl/ZopeGuards.py
index 84c2e9e..bc24941 100644
--- a/src/AccessControl/ZopeGuards.py
+++ b/src/AccessControl/ZopeGuards.py
@@ -12,6 +12,7 @@
##############################################################################
+import collections.abc
import math
import random
import string
@@ -127,13 +128,18 @@ def guarded_pop(key, default=_marker):
return guarded_pop
-def get_iter(c, name):
- iter = getattr(c, name)
+def get_mapping_view(c, name):
- def guarded_iter():
- return SafeIter(iter(), c)
+ view_class = {
+ 'keys': SafeKeysView,
+ 'items': SafeItemsView,
+ 'values': SafeValuesView,
+ }
- return guarded_iter
+ def guarded_mapping_view():
+ return view_class[name](c)
+
+ return guarded_mapping_view
def get_list_pop(lst, name):
@@ -153,18 +159,15 @@ def guarded_pop(index=-1):
'copy': 1,
'fromkeys': 1,
'get': get_dict_get,
- 'items': 1,
+ 'items': get_mapping_view,
+ 'keys': get_mapping_view,
'pop': get_dict_pop,
'popitem': 1,
'setdefault': 1,
'update': 1,
+ 'values': get_mapping_view,
}
-_dict_white_list.update({
- 'keys': get_iter,
- 'values': get_iter,
-})
-
def _check_dict_access(name, value):
# Check whether value is a dict method
@@ -262,6 +265,31 @@ def __next__(self):
next = __next__
+class _SafeMappingView:
+ __allow_access_to_unprotected_subobjects__ = 1
+
+ def __iter__(self):
+ for e in super().__iter__():
+ guard(self._mapping, e)
+ yield e
+
+
+class SafeKeysView(_SafeMappingView, collections.abc.KeysView):
+ pass
+
+
+class SafeValuesView(_SafeMappingView, collections.abc.ValuesView):
+ pass
+
+
+class SafeItemsView(_SafeMappingView, collections.abc.ItemsView):
+ def __iter__(self):
+ for k, v in super().__iter__():
+ guard(self._mapping, k)
+ guard(self._mapping, v)
+ yield k, v
+
+
class NullIter(SafeIter):
def __init__(self, ob):
self._iter = ob
diff --git a/src/AccessControl/tests/actual_python.py b/src/AccessControl/tests/actual_python.py
index 3405b8e..866a480 100644
--- a/src/AccessControl/tests/actual_python.py
+++ b/src/AccessControl/tests/actual_python.py
@@ -123,6 +123,39 @@ def f7():
access = getattr(d, meth)
result = sorted(access())
assert result == expected[kind], (meth, kind, result, expected[kind])
+ assert len(access()) == len(expected[kind]), (meth, kind, "len")
+ iter_ = access() # iterate twice on the same view
+ assert list(iter_) == list(iter_)
+
+ assert sorted([k for k in getattr(d, meth)()]) == expected[kind]
+ assert sorted(k for k in getattr(d, meth)()) == expected[kind]
+ assert {k: v for k, v in d.items()} == d
+
+ assert 1 in d
+ assert 1 in d.keys()
+ assert 2 in d.values()
+ assert (1, 2) in d.items()
+
+ assert d
+ assert d.keys()
+ assert d.values()
+ assert d.items()
+
+ empty_d = {}
+ assert not empty_d
+ assert not empty_d.keys()
+ assert not empty_d.values()
+ assert not empty_d.items()
+
+ smaller_d = {1: 2}
+ for m, _ in methods:
+ assert getattr(d, m)() != getattr(smaller_d, m)()
+ assert not getattr(d, m)() == getattr(smaller_d, m)()
+ if m != 'values':
+ assert getattr(d, m)() > getattr(smaller_d, m)()
+ assert getattr(d, m)() >= getattr(smaller_d, m)()
+ assert getattr(smaller_d, m)() < getattr(d, m)()
+ assert getattr(smaller_d, m)() <= getattr(d, m)()
f7()
diff --git a/src/AccessControl/tests/testZopeGuards.py b/src/AccessControl/tests/testZopeGuards.py
index 533bfa2..50eeca9 100644
--- a/src/AccessControl/tests/testZopeGuards.py
+++ b/src/AccessControl/tests/testZopeGuards.py
@@ -258,23 +258,40 @@ def test_pop_validates(self):
self.assertTrue(sm.calls)
def test_keys_empty(self):
- from AccessControl.ZopeGuards import get_iter
- keys = get_iter({}, 'keys')
+ from AccessControl.ZopeGuards import get_mapping_view
+ keys = get_mapping_view({}, 'keys')
self.assertEqual(list(keys()), [])
+ def test_kvi_len(self):
+ from AccessControl.ZopeGuards import get_mapping_view
+ for attr in ("keys", "values", "items"):
+ with self.subTest(attr):
+ view = get_mapping_view({'a': 1}, attr)
+ self.assertEqual(len(view()), 1)
+
def test_keys_validates(self):
sm = SecurityManager()
old = self.setSecurityManager(sm)
keys = guarded_getattr({GuardTestCase: 1}, 'keys')
try:
- next(keys())
+ next(iter(keys()))
finally:
self.setSecurityManager(old)
self.assertTrue(sm.calls)
+ def test_items_validates(self):
+ sm = SecurityManager()
+ old = self.setSecurityManager(sm)
+ items = guarded_getattr({GuardTestCase: GuardTestCase}, 'items')
+ try:
+ next(iter(items()))
+ finally:
+ self.setSecurityManager(old)
+ self.assertEqual(len(sm.calls), 2)
+
def test_values_empty(self):
- from AccessControl.ZopeGuards import get_iter
- values = get_iter({}, 'values')
+ from AccessControl.ZopeGuards import get_mapping_view
+ values = get_mapping_view({}, 'values')
self.assertEqual(list(values()), [])
def test_values_validates(self):
@@ -282,18 +299,17 @@ def test_values_validates(self):
old = self.setSecurityManager(sm)
values = guarded_getattr({GuardTestCase: 1}, 'values')
try:
- next(values())
+ next(iter(values()))
finally:
self.setSecurityManager(old)
self.assertTrue(sm.calls)
def test_kvi_iteration(self):
- from AccessControl.ZopeGuards import SafeIter
d = dict(a=1, b=2)
for attr in ("keys", "values", "items"):
v = getattr(d, attr)()
- si = SafeIter(v)
- self.assertEqual(next(si), next(iter(v)))
+ si = guarded_getattr(d, attr)()
+ self.assertEqual(next(iter(si)), next(iter(v)))
class TestListGuards(GuardTestCase):
From 77f86b50f097dcf364e0d140e45593bf001d46bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Fri, 1 Mar 2024 09:49:17 +0900
Subject: [PATCH] set metadata in setup.py for compatibility with old slapos
buildout
---
setup.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/setup.py b/setup.py
index 1bf0bcff5..a93fe7b22 100755
--- a/setup.py
+++ b/setup.py
@@ -987,6 +987,11 @@ ext_modules = [
try:
setup(
+ name='pillow',
+ version='10.2.0',
+ packages=["PIL"],
+ include_package_data=True,
+ package_dir={"": "src"},
cmdclass={"build_ext": pil_build_ext},
ext_modules=ext_modules,
zip_safe=not (debug_build() or PLATFORM_MINGW),
--
2.42.0
From 3c6b815bbb2a9300984a7b50cb5ec5375bf4588e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Tue, 2 Apr 2024 21:54:07 +0900
Subject: [PATCH] Revive TRIGGER_WORKFLOW_METHOD support, ERP5 uses it
---
src/Products/DCWorkflow/DCWorkflow.py | 47 +++++++++++++++++++
src/Products/DCWorkflow/Transitions.py | 1 +
.../dtml/transition_properties.dtml | 10 ++++
src/Products/DCWorkflow/dtml/transitions.dtml | 3 +-
src/Products/DCWorkflow/exportimport.py | 2 +-
5 files changed, 61 insertions(+), 2 deletions(-)
diff --git a/src/Products/DCWorkflow/DCWorkflow.py b/src/Products/DCWorkflow/DCWorkflow.py
index 9adf05c..d0306dc 100644
--- a/src/Products/DCWorkflow/DCWorkflow.py
+++ b/src/Products/DCWorkflow/DCWorkflow.py
@@ -38,6 +38,7 @@ from .Expression import createExprContext
from .interfaces import IDCWorkflowDefinition
from .Transitions import TRIGGER_AUTOMATIC
from .Transitions import TRIGGER_USER_ACTION
+from .Transitions import TRIGGER_WORKFLOW_METHOD
from .utils import Message as _
from .utils import modifyRolesForGroup
from .utils import modifyRolesForPermission
@@ -278,6 +279,52 @@ class DCWorkflowDefinition(WorkflowUIMixin, Folder):
raise Unauthorized(action)
self._changeStateOf(ob, tdef, kw)
+ @security.private
+ def isWorkflowMethodSupported(self, ob, method_id):
+ '''
+ Returns a true value if the given workflow method
+ is supported in the current state.
+ '''
+ sdef = self._getWorkflowStateOf(ob)
+ if sdef is None:
+ return 0
+ if method_id in sdef.transitions:
+ tdef = self.transitions.get(method_id, None)
+ if (tdef is not None and
+ tdef.trigger_type == TRIGGER_WORKFLOW_METHOD and
+ self._checkTransitionGuard(tdef, ob)):
+ return 1
+ return 0
+
+ @security.private
+ def wrapWorkflowMethod(self, ob, method_id, func, args, kw):
+ '''
+ Allows the user to request a workflow action. This method
+ must perform its own security checks.
+ '''
+ sdef = self._getWorkflowStateOf(ob)
+ if sdef is None:
+ raise WorkflowException('Object is in an undefined state')
+ if method_id not in sdef.transitions:
+ raise Unauthorized(method_id)
+ tdef = self.transitions.get(method_id, None)
+ if tdef is None or tdef.trigger_type != TRIGGER_WORKFLOW_METHOD:
+ raise WorkflowException(
+ 'Transition %s is not triggered by a workflow method'
+ % method_id)
+ if not self._checkTransitionGuard(tdef, ob):
+ raise Unauthorized(method_id)
+ res = func(*args, **kw)
+ try:
+ self._changeStateOf(ob, tdef)
+ except ObjectDeleted:
+ # Re-raise with a different result.
+ raise ObjectDeleted(res)
+ except ObjectMoved as ex:
+ # Re-raise with a different result.
+ raise ObjectMoved(ex.getNewObject(), res)
+ return res
+
@security.private
def isInfoSupported(self, ob, name):
'''
diff --git a/src/Products/DCWorkflow/Transitions.py b/src/Products/DCWorkflow/Transitions.py
index a6e1e6f..b4e012c 100644
--- a/src/Products/DCWorkflow/Transitions.py
+++ b/src/Products/DCWorkflow/Transitions.py
@@ -31,6 +31,7 @@ from .utils import _dtmldir
TRIGGER_AUTOMATIC = 0
TRIGGER_USER_ACTION = 1
+TRIGGER_WORKFLOW_METHOD = 2
class TransitionDefinition(SimpleItem):
diff --git a/src/Products/DCWorkflow/dtml/transition_properties.dtml b/src/Products/DCWorkflow/dtml/transition_properties.dtml
index d6b8a74..6a0803e 100644
--- a/src/Products/DCWorkflow/dtml/transition_properties.dtml
+++ b/src/Products/DCWorkflow/dtml/transition_properties.dtml
@@ -55,6 +55,16 @@ Initiated by user action
</td>
</tr>
+<tr>
+<th></th>
+<td>
+<dtml-let checked="trigger_type==2 and 'checked' or ' '">
+<input type="radio" name="trigger_type" value="2" &dtml-checked; />
+Initiated by WorkflowMethod
+</dtml-let>
+</td>
+</tr>
+
<tr>
<th align="left">Script (before)</th>
<td>
diff --git a/src/Products/DCWorkflow/dtml/transitions.dtml b/src/Products/DCWorkflow/dtml/transitions.dtml
index 4cdd3d3..37e949c 100644
--- a/src/Products/DCWorkflow/dtml/transitions.dtml
+++ b/src/Products/DCWorkflow/dtml/transitions.dtml
@@ -17,7 +17,8 @@
<td>
Destination state: <code><dtml-if new_state_id>&dtml-new_state_id;<dtml-else>(Remain in state)</dtml-if></code> <br />
Trigger: <dtml-var expr="(trigger_type == 0 and 'Automatic') or
- (trigger_type == 1 and 'User action')">
+ (trigger_type == 1 and 'User action') or
+ (trigger_type == 2 and 'WorkflowMethod')">
<br />
<dtml-if script_name>
Script (before): &dtml-script_name;
diff --git a/src/Products/DCWorkflow/exportimport.py b/src/Products/DCWorkflow/exportimport.py
index f17264d..2374b6e 100644
--- a/src/Products/DCWorkflow/exportimport.py
+++ b/src/Products/DCWorkflow/exportimport.py
@@ -37,7 +37,7 @@ from .interfaces import IDCWorkflowDefinition
from .utils import _xmldir
-TRIGGER_TYPES = ('AUTOMATIC', 'USER')
+TRIGGER_TYPES = ('AUTOMATIC', 'USER', 'METHOD' )
_FILENAME = 'workflows.xml'
--
2.42.0
From 21a91db138cca3ada0e4dff475b061066362410c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Sat, 17 Feb 2024 23:25:43 +0900
Subject: [PATCH] backport changes from 0.52.29
We can not use 0.52.29 directly because it does not have a setup.py
and our buildout / setuptools tooling is too old.
---
src/SOAPpy/Client.py | 3 ++-
src/SOAPpy/Types.py | 2 ++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/SOAPpy/Client.py b/src/SOAPpy/Client.py
index e86c5ec..d2bbefb 100644
--- a/src/SOAPpy/Client.py
+++ b/src/SOAPpy/Client.py
@@ -45,6 +45,7 @@
ident = '$Id: Client.py 1496 2010-03-04 23:46:17Z pooryorick $'
from .version import __version__
+from io import StringIO
#import xml.sax
import urllib.request, urllib.parse, urllib.error
@@ -152,7 +153,7 @@ class HTTP:
return -1, e.line, None
self.headers = response.msg
- self.file = response.fp
+ self.file = StringIO(response.fp.read().decode('utf-8'))
return response.status, response.reason, response.msg
def close(self):
diff --git a/src/SOAPpy/Types.py b/src/SOAPpy/Types.py
index de9dcac..cf08d17 100644
--- a/src/SOAPpy/Types.py
+++ b/src/SOAPpy/Types.py
@@ -1451,6 +1451,8 @@ class arrayType(collections.UserList, compoundType):
def __getitem__(self, item):
try:
return self.data[int(item)]
+ except TypeError:
+ return self.data[item]
except ValueError:
return getattr(self, item)
--
2.42.0
From c8c52a14d481403f1db252631d3cc4b8e86e1798 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <perrinjerome@gmail.com>
Date: Sat, 17 Feb 2024 05:32:46 +0000
Subject: [PATCH] Fix authentication error viewing ZMI with a user defined
outside of zope root.
Fixes https://github.com/zopefoundation/Zope/issues/1195
5.9 (2023-11-24)
----------------
diff --git a/src/App/dtml/manage_page_header.dtml b/src/App/dtml/manage_page_header.dtml
index 2dcb047552..e8b8659e10 100644
--- a/src/App/dtml/manage_page_header.dtml
+++ b/src/App/dtml/manage_page_header.dtml
@@ -10,24 +10,27 @@
</dtml-let>
<title><dtml-if title_or_id><dtml-var title_or_id><dtml-else>Zope</dtml-if></title>
+<dtml-let basepath="'/'.join([''] + [p for p in (REQUEST['BASEPATH1'], REQUEST.get('AUTHENTICATION_PATH')) if p])">
+
<dtml-in css_urls>
- <link rel="stylesheet" type="text/css" href="&dtml-BASEPATH1;&dtml-sequence-item;" />
+ <link rel="stylesheet" type="text/css" href="&dtml-basepath;&dtml-sequence-item;" />
</dtml-in>
<dtml-in js_urls>
- <script src="&dtml-BASEPATH1;&dtml-sequence-item;"></script>
+ <script src="&dtml-basepath;&dtml-sequence-item;"></script>
</dtml-in>
-<link rel="shortcut icon" type="image/x-icon" href="&dtml-BASEPATH1;/++resource++zmi/logo/favicon/favicon.ico" />
-<link rel="apple-touch-icon" sizes="180x180" href="&dtml-BASEPATH1;/++resource++zmi/logo/favicon/apple-touch-icon.png" />
-<link rel="icon" type="image/png" sizes="32x32" href="&dtml-BASEPATH1;/++resource++zmi/logo/favicon/favicon-32x32.png" />
-<link rel="icon" type="image/png" sizes="16x16" href="&dtml-BASEPATH1;/++resource++zmi/logo/favicon/favicon-16x16.png" />
-<link rel="manifest" href="&dtml-BASEPATH1;/++resource++zmi/logo/favicon/site.webmanifest" />
-<link rel="mask-icon" href="&dtml-BASEPATH1;/++resource++zmi/logo/favicon/safari-pinned-tab.svg" color="#5bbad5" />
-<meta name="msapplication-config" content="&dtml-BASEPATH1;/++resource++zmi/logo/favicon/browserconfig.xml"/>
+<link rel="shortcut icon" type="image/x-icon" href="&dtml-basepath;/++resource++zmi/logo/favicon/favicon.ico" />
+<link rel="apple-touch-icon" sizes="180x180" href="&dtml-basepath;/++resource++zmi/logo/favicon/apple-touch-icon.png" />
+<link rel="icon" type="image/png" sizes="32x32" href="&dtml-basepath;/++resource++zmi/logo/favicon/favicon-32x32.png" />
+<link rel="icon" type="image/png" sizes="16x16" href="&dtml-basepath;/++resource++zmi/logo/favicon/favicon-16x16.png" />
+<link rel="manifest" href="&dtml-basepath;/++resource++zmi/logo/favicon/site.webmanifest" />
+<link rel="mask-icon" href="&dtml-basepath;/++resource++zmi/logo/favicon/safari-pinned-tab.svg" color="#5bbad5" />
+<meta name="msapplication-config" content="&dtml-basepath;/++resource++zmi/logo/favicon/browserconfig.xml"/>
<meta name="msapplication-TileColor" content="#2d89ef" />
<meta name="theme-color" content="#ffffff" />
</head>
+</dtml-let>
<!-- REFACT what is a better way to get the last part of the current URL? -->
<body id="nodeid-<dtml-var "getId()">" class="zmi zmi-<dtml-var "this().meta_type.replace(' ', '-').replace('(', '').replace(')', '')"> zmi-<dtml-var "URL0[_.len(URL1)+1:]">">
</dtml-unless>
diff --git a/src/zmi/styles/tests.py b/src/zmi/styles/tests.py
index 256edfa74b..36c7b65bec 100644
--- a/src/zmi/styles/tests.py
+++ b/src/zmi/styles/tests.py
@@ -21,6 +21,8 @@ def setupZCML():
class SubscriberTests(Testing.ZopeTestCase.FunctionalTestCase):
"""Testing .subscriber.*"""
+ base_path = f'/{Testing.ZopeTestCase.folder_name}'
+
def call_manage_main(self):
"""Call /folder/manage_main and return the HTML text."""
def _call_manage_main(self):
@@ -29,7 +31,7 @@ def _call_manage_main(self):
# which the WSGI publisher does not expect.
endInteraction()
response = self.publish(
- f'/{Testing.ZopeTestCase.folder_name}/manage_main',
+ f'{self.base_path}/manage_main',
basic=basic_auth)
return str(response)
return temporaryPlacelessSetUp(
@@ -40,11 +42,11 @@ def test_subscriber__css_paths__1(self):
from .subscriber import css_paths
body = self.call_manage_main()
for path in css_paths(None):
- self.assertIn(path, body)
+ self.assertIn(f'href="{self.base_path}{path}"', body)
def test_subscriber__js_paths__1(self):
"""The paths it returns are rendered in the ZMI."""
from .subscriber import js_paths
body = self.call_manage_main()
for path in js_paths(None):
- self.assertIn(path, body)
+ self.assertIn(f'src="{self.base_path}{path}"', body)
From 7f4c61c0d7fe0751be93e80683659271fa0c65a3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <perrinjerome@gmail.com>
Date: Sat, 17 Feb 2024 13:06:28 +0000
Subject: [PATCH] Fix Content-Disposition heeader for clients without rfc6266
support.
Since https://github.com/zopefoundation/Zope/pull/893 the
Content-Disposition header supports non-ascii filenames, by containing
the filename in ascii and the filename in UTF-8, but the ascii version
was produced by applying `str` on a `bytes` instance, so it looks like
`b'file.txt'` instead of `file.txt`.
---
CHANGES.rst | 3 +++
src/ZPublisher/HTTPResponse.py | 3 ++-
src/ZPublisher/tests/testHTTPResponse.py | 4 ++--
3 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index e8dedc6a66..30061e2093 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -18,6 +18,9 @@ https://github.com/zopefoundation/Zope/blob/4.x/CHANGES.rst
- Fix redirections to URLs with host given as IP-literal with brackets.
Fixes `#1191 <https://github.com/zopefoundation/Zope/issues/1191>`_.
+- Fix ``Content-Disposition`` filename for clients without rfc6266 support.
+ (`#1198 <https://github.com/zopefoundation/Zope/pull/1198>`_)
+
5.9 (2023-11-24)
----------------
diff --git a/src/ZPublisher/HTTPResponse.py b/src/ZPublisher/HTTPResponse.py
index ed6bc00a89..264a488a03 100644
--- a/src/ZPublisher/HTTPResponse.py
+++ b/src/ZPublisher/HTTPResponse.py
@@ -142,7 +142,8 @@ def make_content_disposition(disposition, file_name):
#
# a special header has to be crafted
# also see https://tools.ietf.org/html/rfc6266#appendix-D
- encoded_file_name = file_name.encode('us-ascii', errors='ignore')
+ encoded_file_name = file_name.encode(
+ 'us-ascii', errors='ignore').decode()
header += f'; filename="{encoded_file_name}"'
quoted_file_name = quote(file_name)
header += f'; filename*=UTF-8\'\'{quoted_file_name}'
diff --git a/src/ZPublisher/tests/testHTTPResponse.py b/src/ZPublisher/tests/testHTTPResponse.py
index 1613b0ea59..5d64b5b3dd 100644
--- a/src/ZPublisher/tests/testHTTPResponse.py
+++ b/src/ZPublisher/tests/testHTTPResponse.py
@@ -1433,7 +1433,7 @@ def test_ascii(self):
def test_latin_one(self):
self.assertEqual(
make_content_disposition('inline', 'Dänemark.png'),
- 'inline; filename="b\'Dnemark.png\'"; filename*=UTF-8\'\'D%C3%A4nemark.png' # noqa: E501
+ 'inline; filename="Dnemark.png"; filename*=UTF-8\'\'D%C3%A4nemark.png' # noqa: E501
)
def test_unicode(self):
@@ -1445,7 +1445,7 @@ def test_unicode(self):
"""
self.assertEqual(
make_content_disposition('inline', 'ıq.png'),
- 'inline; filename="b\'q.png\'"; filename*=UTF-8\'\'%C4%B1q.png'
+ 'inline; filename="q.png"; filename*=UTF-8\'\'%C4%B1q.png'
)
From c56146829ab065183c709229a9daa682cc445212 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Fri, 26 Apr 2024 15:09:39 +0900
Subject: [PATCH] fix loading font for ean13
use same technique as for code128
---
hubarcode/ean13/renderer.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/hubarcode/ean13/renderer.py b/hubarcode/ean13/renderer.py
index 654501e..ff5f518 100644
--- a/hubarcode/ean13/renderer.py
+++ b/hubarcode/ean13/renderer.py
@@ -78,8 +78,10 @@ class EAN13Renderer:
# Draw the text
font_size = font_sizes.get(bar_width, 24)
- # Use relative name, PIL will do searching for us
- fontfile = os.path.join("fonts", "courR%02d.pil" % font_size)
+ # Locate and load the font file relative to the module
+ ean13dir, _ = os.path.split(__file__)
+ rootdir, _ = os.path.split(ean13dir)
+ fontfile = os.path.join(rootdir, "fonts", "courR%02d.pil" % font_size)
font = ImageFont.load_path(fontfile)
draw = ImageDraw.Draw(img)
--
2.42.0
From 42fab4bbede61a384046646dbc2573bb79957a89 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Sat, 17 Feb 2024 16:07:18 +0900
Subject: [PATCH] python3 support
---
interval.py | 366 ++++++++++++++++++++++++++++------------------------
1 file changed, 194 insertions(+), 172 deletions(-)
diff --git a/interval.py b/interval.py
index fe9e595..8a4b2be 100644
--- a/interval.py
+++ b/interval.py
@@ -36,15 +36,18 @@ False
>>> "15:30" in myHours
True
>>> inOffice = officeHours & myHours
->>> print inOffice
+>>> print(inOffice)
['08:30'..'11:30'),('12:30'..'17:00']
>>> overtime = myHours - officeHours
->>> print overtime
+>>> print(overtime)
('17:00'..'19:30']
"""
import copy
+import functools
+
+@functools.total_ordering
class Smallest:
"""Represents the smallest value
@@ -70,11 +73,17 @@ class Smallest:
The opposite of negative infinity is infinity, the largest value.
- >>> print -Smallest()
+ >>> print(-Smallest())
~
"""
return Largest()
+ def __eq__(self, other):
+ return isinstance(other, self.__class__)
+
+ def __gt__(self, other):
+ return False
+
def __cmp__(self, other):
"""Compares this with another object
@@ -103,7 +112,7 @@ class Smallest:
The string for the smallest number is -~, which means negative infinity.
- >>> print Smallest()
+ >>> print(Smallest())
-~
"""
return "-~"
@@ -124,6 +133,7 @@ class Smallest:
return 0x55555555
+@functools.total_ordering
class Largest:
"""Class representing the universal largest value
@@ -149,11 +159,17 @@ class Largest:
The opposite of infinity is negative infinity, the smallest value.
- >>> print -Largest()
+ >>> print(-Largest())
-~
"""
return Smallest()
+ def __eq__(self, other):
+ return isinstance(other, self.__class__)
+
+ def __ge__(self, other):
+ return True
+
def __cmp__(self, other):
"""Compares object with another object
@@ -182,7 +198,7 @@ class Largest:
The largest number is displayed as ~ (it sort of looks like infinity...)
- >>> print Largest()
+ >>> print(Largest())
~
"""
return "~"
@@ -203,6 +219,8 @@ class Largest:
Inf = Largest()
# Use -Inf for the smallest value
+
+@functools.total_ordering
class Interval:
"""Represents a continuous range of values
@@ -255,7 +273,7 @@ class Interval:
Intervals that are not normalized, i.e. that have a lower bound
exceeding an upper bound, are silently normalized.
- >>> print Interval(5, 2, lower_closed=False)
+ >>> print(Interval(5, 2, lower_closed=False))
[2..5)
Intervals can represent an empty set.
@@ -343,27 +361,27 @@ class Interval:
consisting of only a single value are shown as that value. Empty
intervals are shown as the string <Empty>
- >>> print Interval.all()
+ >>> print(Interval.all())
(...)
- >>> print Interval.less_than(100)
+ >>> print(Interval.less_than(100))
(...100)
- >>> print Interval.less_than_or_equal_to(2593)
+ >>> print(Interval.less_than_or_equal_to(2593))
(...2593]
- >>> print Interval.greater_than(2378)
+ >>> print(Interval.greater_than(2378))
(2378...)
- >>> print Interval.between(26, 8234, False)
+ >>> print(Interval.between(26, 8234, False))
(26..8234)
- >>> print Interval(237, 2348, lower_closed=False)
+ >>> print(Interval(237, 2348, lower_closed=False))
(237..2348]
- >>> print Interval.greater_than_or_equal_to(347)
+ >>> print(Interval.greater_than_or_equal_to(347))
[347...)
- >>> print Interval(237, 278, upper_closed=False)
+ >>> print(Interval(237, 278, upper_closed=False))
[237..278)
- >>> print Interval.between(723, 2378)
+ >>> print(Interval.between(723, 2378))
[723..2378]
- >>> print Interval.equal_to(5)
+ >>> print(Interval.equal_to(5))
5
- >>> print Interval.none()
+ >>> print(Interval.none())
<Empty>
"""
if self.lower_bound == self.upper_bound:
@@ -399,20 +417,22 @@ class Interval:
retval = "".join([lbchar, lstr, between, ustr, ubchar])
return retval
- def __nonzero__(self):
+ def __bool__(self):
"""Tells whether the interval is empty
-
>>> if Interval(12, 12, closed=False):
- ... print "Non-empty"
+ ... print("Non-empty")
>>> if Interval(12, 12, upper_closed=False):
- ... print "Non-empty"
+ ... print("Non-empty")
>>> if Interval(12, 12):
- ... print "Non-empty"
+ ... print("Non-empty")
Non-empty
"""
return self.lower_bound != self.upper_bound \
or (self.upper_closed and self.lower_closed)
+ def __lt__(self, other):
+ return self.comes_before(other)
+
def __cmp__(self, other):
"""Compares two intervals for ordering purposes
@@ -442,15 +462,15 @@ class Interval:
def __and__(self, other):
"""Returns the intersection of two intervals
- >>> print Interval.greater_than(3) & Interval.greater_than(5)
+ >>> print(Interval.greater_than(3) & Interval.greater_than(5))
(5...)
- >>> print Interval.greater_than(3) & Interval.equal_to(3)
+ >>> print(Interval.greater_than(3) & Interval.equal_to(3))
<Empty>
- >>> print Interval.greater_than_or_equal_to(3) & Interval.equal_to(3)
+ >>> print(Interval.greater_than_or_equal_to(3) & Interval.equal_to(3))
3
- >>> print Interval.all() & Interval.all()
+ >>> print(Interval.all() & Interval.all())
(...)
- >>> print Interval.greater_than(3) & Interval.less_than(10)
+ >>> print(Interval.greater_than(3) & Interval.less_than(10))
(3..10)
"""
if self == other:
@@ -494,7 +514,7 @@ class Interval:
def none(cls):
"""Returns an empty interval
- >>> print Interval.none()
+ >>> print(Interval.none())
<Empty>
"""
return cls(0, 0, closed=False)
@@ -503,7 +523,7 @@ class Interval:
def all(cls):
"""Returns an interval encompassing all values
- >>> print Interval.all()
+ >>> print(Interval.all())
(...)
"""
return cls()
@@ -516,9 +536,9 @@ class Interval:
then the endpoints are included. Otherwise, the endpoints are
excluded.
- >>> print Interval.between(2, 4)
+ >>> print(Interval.between(2, 4))
[2..4]
- >>> print Interval.between(2, 4, False)
+ >>> print(Interval.between(2, 4, False))
(2..4)
"""
return cls(a, b, closed=closed)
@@ -529,7 +549,7 @@ class Interval:
Returns an interval containing only a.
- >>> print Interval.equal_to(32)
+ >>> print(Interval.equal_to(32))
32
"""
return cls(a, a)
@@ -541,7 +561,7 @@ class Interval:
Returns an interval containing all values less than a. If closed
is True, then all values less than or equal to a are returned.
- >>> print Interval.less_than(32)
+ >>> print(Interval.less_than(32))
(...32)
"""
return cls(upper_bound=a, upper_closed=False)
@@ -550,7 +570,7 @@ class Interval:
def less_than_or_equal_to(cls, a):
"""Returns an interval containing the given values and everything less
- >>> print Interval.less_than_or_equal_to(32)
+ >>> print(Interval.less_than_or_equal_to(32))
(...32]
"""
return cls(upper_bound=a, upper_closed=True)
@@ -559,7 +579,7 @@ class Interval:
def greater_than(cls, a):
"""Returns interval of all values greater than the given value
- >>> print Interval.greater_than(32)
+ >>> print(Interval.greater_than(32))
(32...)
"""
return cls(lower_bound=a, lower_closed=False)
@@ -568,7 +588,7 @@ class Interval:
def greater_than_or_equal_to(cls, a):
"""Returns interval of all values greater than or equal to the given value
- >>> print Interval.greater_than_or_equal_to(32)
+ >>> print(Interval.greater_than_or_equal_to(32))
[32...)
"""
return cls(lower_bound=a, lower_closed=True)
@@ -637,29 +657,29 @@ class Interval:
>>> r13 = Interval.greater_than(100)
>>> r14 = Interval.equal_to(100)
>>> r15 = Interval.greater_than_or_equal_to(100)
- >>> print r13.join(r15)
+ >>> print(r13.join(r15))
[100...)
- >>> print r7.join(r6)
+ >>> print(r7.join(r6))
(-100..100]
- >>> print r11.join(r2)
+ >>> print(r11.join(r2))
(...100]
- >>> print r4.join(r15)
+ >>> print(r4.join(r15))
(...)
- >>> print r8.join(r8)
+ >>> print(r8.join(r8))
(-100...)
- >>> print r3.join(r7)
+ >>> print(r3.join(r7))
(...100]
- >>> print r5.join(r10)
+ >>> print(r5.join(r10))
(...)
- >>> print r9.join(r1)
+ >>> print(r9.join(r1))
(...-100]
- >>> print r12.join(r5)
+ >>> print(r12.join(r5))
(...)
- >>> print r13.join(r1)
+ >>> print(r13.join(r1))
Traceback (most recent call last):
...
ArithmeticError: The Intervals are disjoint.
- >>> print r14.join(r2)
+ >>> print(r14.join(r2))
Traceback (most recent call last):
...
ArithmeticError: The Intervals are disjoint.
@@ -894,27 +914,27 @@ class BaseIntervalSet(object):
If no parameters are provided, then an empty IntervalSet is
constructed.
- >>> print IntervalSet() # An empty set
+ >>> print(IntervalSet()) # An empty set
<Empty>
Interval objects arguments are added directly to the IntervalSet.
- >>> print IntervalSet([Interval(4, 6, lower_closed=False)])
+ >>> print(IntervalSet([Interval(4, 6, lower_closed=False)]))
(4..6]
- >>> print IntervalSet([Interval.less_than_or_equal_to(2)])
+ >>> print(IntervalSet([Interval.less_than_or_equal_to(2)]))
(...2]
Each non-Interval value of an iterator is added as a discrete
value.
- >>> print IntervalSet(set([3, 7, 2, 1]))
+ >>> print(IntervalSet(set([3, 7, 2, 1])))
1,2,3,7
- >>> print IntervalSet(["Bob", "Fred", "Mary"])
+ >>> print(IntervalSet(["Bob", "Fred", "Mary"]))
'Bob','Fred','Mary'
- >>> print IntervalSet(range(10))
+ >>> print(IntervalSet(range(10)))
0,1,2,3,4,5,6,7,8,9
- >>> print IntervalSet(
- ... Interval.between(l, u) for l, u in [(10, 20), (30, 40)])
+ >>> print(IntervalSet(
+ ... Interval.between(l, u) for l, u in [(10, 20), (30, 40)]))
[10..20],[30..40]
"""
self.intervals = []
@@ -935,9 +955,9 @@ class BaseIntervalSet(object):
1
>>> nonempty = IntervalSet([3])
>>> if IntervalSet.empty():
- ... print "Non-empty"
+ ... print("Non-empty")
>>> if nonempty:
- ... print "Non-empty"
+ ... print("Non-empty")
Non-empty
"""
return len(self.intervals)
@@ -948,17 +968,17 @@ class BaseIntervalSet(object):
This function shows a string representation of an IntervalSet.
The string is shown sorted, with all intervals normalized.
- >>> print IntervalSet()
+ >>> print(IntervalSet())
<Empty>
- >>> print IntervalSet([62])
+ >>> print(IntervalSet([62]))
62
- >>> print IntervalSet([62, 56])
+ >>> print(IntervalSet([62, 56]))
56,62
- >>> print IntervalSet([23, Interval(26, 32, upper_closed=False)])
+ >>> print(IntervalSet([23, Interval(26, 32, upper_closed=False)]))
23,[26..32)
- >>> print IntervalSet.less_than(3) + IntervalSet.greater_than(3)
+ >>> print(IntervalSet.less_than(3) + IntervalSet.greater_than(3))
(...3),(3...)
- >>> print IntervalSet([Interval.less_than_or_equal_to(6)])
+ >>> print(IntervalSet([Interval.less_than_or_equal_to(6)]))
(...6]
"""
if len(self.intervals) == 0:
@@ -987,20 +1007,20 @@ class BaseIntervalSet(object):
...
IndexError: Index is out of range
>>> interval = IntervalSet.greater_than(5)
- >>> print interval[0]
+ >>> print(interval[0])
(5...)
- >>> print interval[1]
+ >>> print(interval[1])
Traceback (most recent call last):
...
IndexError: Index is out of range
- >>> print interval[-1]
+ >>> print(interval[-1])
(5...)
>>> interval = IntervalSet([3, 6])
- >>> print interval[1]
+ >>> print(interval[1])
6
- >>> print interval[0]
+ >>> print(interval[0])
3
- >>> print interval[2]
+ >>> print(interval[2])
Traceback (most recent call last):
...
IndexError: Index is out of range
@@ -1018,14 +1038,14 @@ class BaseIntervalSet(object):
that with the left-most lower bound to that with the right-most.
>>> for i in IntervalSet():
- ... print i
+ ... print(i)
...
>>> for i in IntervalSet.between(3, 5):
- ... print i
+ ... print(i)
...
[3..5]
>>> for i in IntervalSet([2, 5, 3]):
- ... print i
+ ... print(i)
...
2
3
@@ -1104,11 +1124,11 @@ class BaseIntervalSet(object):
def bounds(self):
"""Returns an interval that encompasses the entire BaseIntervalSet
- >>> print IntervalSet([Interval.between(4, 6), 2, 12]).bounds()
+ >>> print(IntervalSet([Interval.between(4, 6), 2, 12]).bounds())
[2..12]
- >>> print IntervalSet().bounds()
+ >>> print(IntervalSet().bounds())
<Empty>
- >>> print IntervalSet.all().bounds()
+ >>> print(IntervalSet.all().bounds())
(...)
"""
if len(self.intervals) == 0:
@@ -1240,7 +1260,7 @@ class BaseIntervalSet(object):
>>> for i in s:
... l.add(str(i))
...
- >>> print len(l)
+ >>> print(len(l))
6
>>> "2" in l
True
@@ -1284,15 +1304,15 @@ class BaseIntervalSet(object):
>>> zero = IntervalSet([0])
>>> nonzero = IntervalSet.not_equal_to(0)
>>> empty = IntervalSet.empty()
- >>> print evens + positives
+ >>> print(evens + positives)
-8,-6,-4,-2,[0...)
- >>> print negatives + zero
+ >>> print(negatives + zero)
(...0]
- >>> print empty + negatives
+ >>> print(empty + negatives)
(...0)
- >>> print empty + naturals
+ >>> print(empty + naturals)
[0...)
- >>> print nonzero + evens
+ >>> print(nonzero + evens)
(...)
"""
return self.__or__(other)
@@ -1309,17 +1329,17 @@ class BaseIntervalSet(object):
>>> nonzero = IntervalSet.not_equal_to(0)
>>> empty = IntervalSet.empty()
>>> all = IntervalSet.all()
- >>> print evens - nonzero
+ >>> print(evens - nonzero)
0
- >>> print empty - naturals
+ >>> print(empty - naturals)
<Empty>
- >>> print zero - naturals
+ >>> print(zero - naturals)
<Empty>
- >>> print positives - zero
+ >>> print(positives - zero)
(0...)
- >>> print naturals - negatives
+ >>> print(naturals - negatives)
[0...)
- >>> print all - zero
+ >>> print(all - zero)
(...0),(0...)
>>> all - zero == nonzero
True
@@ -1380,17 +1400,17 @@ class BaseIntervalSet(object):
>>> nonzero = IntervalSet.not_equal_to(0)
>>> empty = IntervalSet.empty()
>>> all = IntervalSet.all()
- >>> print evens.difference(nonzero)
+ >>> print(evens.difference(nonzero))
0
- >>> print empty.difference(naturals)
+ >>> print(empty.difference(naturals))
<Empty>
- >>> print zero.difference(naturals)
+ >>> print(zero.difference(naturals))
<Empty>
- >>> print positives.difference(zero)
+ >>> print(positives.difference(zero))
(0...)
- >>> print naturals.difference(negatives)
+ >>> print(naturals.difference(negatives))
[0...)
- >>> print all.difference(zero)
+ >>> print(all.difference(zero))
(...0),(0...)
>>> all.difference(zero) == nonzero
True
@@ -1413,15 +1433,15 @@ class BaseIntervalSet(object):
>>> zero = IntervalSet([0])
>>> nonzero = IntervalSet.not_equal_to(0)
>>> empty = IntervalSet.empty()
- >>> print naturals and naturals
+ >>> print(naturals and naturals)
[0...)
- >>> print evens & zero
+ >>> print(evens & zero)
0
- >>> print negatives & zero
+ >>> print(negatives & zero)
<Empty>
- >>> print nonzero & positives
+ >>> print(nonzero & positives)
(0...)
- >>> print empty & zero
+ >>> print(empty & zero)
<Empty>
>>> positives & [0]
Traceback (most recent call last):
@@ -1469,15 +1489,15 @@ class BaseIntervalSet(object):
>>> zero = IntervalSet([0])
>>> nonzero = IntervalSet.not_equal_to(0)
>>> empty = IntervalSet.empty()
- >>> print naturals.intersection(naturals)
+ >>> print(naturals.intersection(naturals))
[0...)
- >>> print evens.intersection(zero)
+ >>> print(evens.intersection(zero))
0
- >>> print negatives.intersection(zero)
+ >>> print(negatives.intersection(zero))
<Empty>
- >>> print nonzero.intersection(positives)
+ >>> print(nonzero.intersection(positives))
(0...)
- >>> print empty.intersection(zero)
+ >>> print(empty.intersection(zero))
<Empty>
"""
if isinstance(other, BaseIntervalSet):
@@ -1497,17 +1517,17 @@ class BaseIntervalSet(object):
>>> nonzero = IntervalSet.not_equal_to(0)
>>> empty = IntervalSet.empty()
>>> all = IntervalSet.all()
- >>> print evens | positives
+ >>> print(evens | positives)
-8,-6,-4,-2,[0...)
- >>> print negatives | zero
+ >>> print(negatives | zero)
(...0]
- >>> print empty | negatives
+ >>> print(empty | negatives)
(...0)
- >>> print empty | naturals
+ >>> print(empty | naturals)
[0...)
- >>> print nonzero | evens
+ >>> print(nonzero | evens)
(...)
- >>> print negatives | range(5)
+ >>> print(negatives | range(5))
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for |: expected BaseIntervalSet
@@ -1537,17 +1557,17 @@ class BaseIntervalSet(object):
>>> nonzero = IntervalSet.not_equal_to(0)
>>> empty = IntervalSet.empty()
>>> all = IntervalSet.all()
- >>> print evens.union(positives)
+ >>> print(evens.union(positives))
-8,-6,-4,-2,[0...)
- >>> print negatives.union(zero)
+ >>> print(negatives.union(zero))
(...0]
- >>> print empty.union(negatives)
+ >>> print(empty.union(negatives))
(...0)
- >>> print empty.union(naturals)
+ >>> print(empty.union(naturals))
[0...)
- >>> print nonzero.union(evens)
+ >>> print(nonzero.union(evens))
(...)
- >>> print negatives.union(range(5))
+ >>> print(negatives.union(range(5)))
(...0],1,2,3,4
"""
if isinstance(other, BaseIntervalSet):
@@ -1566,13 +1586,13 @@ class BaseIntervalSet(object):
>>> zero = IntervalSet([0])
>>> nonzero = IntervalSet.not_equal_to(0)
>>> empty = IntervalSet.empty()
- >>> print nonzero ^ naturals
+ >>> print(nonzero ^ naturals)
(...0]
- >>> print zero ^ negatives
+ >>> print(zero ^ negatives)
(...0]
- >>> print positives ^ empty
+ >>> print(positives ^ empty)
(0...)
- >>> print evens ^ zero
+ >>> print(evens ^ zero)
-8,-6,-4,-2,2,4,6,8
>>> negatives ^ [0]
Traceback (most recent call last):
@@ -1599,15 +1619,15 @@ class BaseIntervalSet(object):
>>> zero = IntervalSet([0])
>>> nonzero = IntervalSet.not_equal_to(0)
>>> empty = IntervalSet.empty()
- >>> print nonzero.symmetric_difference(naturals)
+ >>> print(nonzero.symmetric_difference(naturals))
(...0]
- >>> print zero.symmetric_difference(negatives)
+ >>> print(zero.symmetric_difference(negatives))
(...0]
- >>> print positives.symmetric_difference(empty)
+ >>> print(positives.symmetric_difference(empty))
(0...)
- >>> print evens.symmetric_difference(zero)
+ >>> print(evens.symmetric_difference(zero))
-8,-6,-4,-2,2,4,6,8
- >>> print evens.symmetric_difference(range(0, 9, 2))
+ >>> print(evens.symmetric_difference(range(0, 9, 2)))
-8,-6,-4,-2
"""
if isinstance(other, BaseIntervalSet):
@@ -1625,15 +1645,15 @@ class BaseIntervalSet(object):
>>> evens = IntervalSet([-8, -6, -4, -2, 0, 2, 4, 6, 8])
>>> zero = IntervalSet([0])
>>> nonzero = IntervalSet.not_equal_to(0)
- >>> print ~(IntervalSet.empty())
+ >>> print(~(IntervalSet.empty()))
(...)
>>> ~negatives == naturals
True
- >>> print ~positives
+ >>> print(~positives)
(...0]
>>> ~naturals == negatives
True
- >>> print ~evens
+ >>> print(~evens)
(...-8),(-8..-6),(-6..-4),(-4..-2),(-2..0),(0..2),(2..4),(4..6),(6..8),(8...)
>>> ~zero == nonzero
True
@@ -1954,9 +1974,9 @@ class BaseIntervalSet(object):
def less_than(cls, n):
"""Returns an IntervalSet containing values less than the given value
- >>> print IntervalSet.less_than(0)
+ >>> print(IntervalSet.less_than(0))
(...0)
- >>> print IntervalSet.less_than(-23)
+ >>> print(IntervalSet.less_than(-23))
(...-23)
"""
return cls([Interval.less_than(n)])
@@ -1966,9 +1986,9 @@ class BaseIntervalSet(object):
"""Returns an IntervalSet containing values less than or equal to the
given value
- >>> print IntervalSet.less_than_or_equal_to(0)
+ >>> print(IntervalSet.less_than_or_equal_to(0))
(...0]
- >>> print IntervalSet.less_than_or_equal_to(-23)
+ >>> print(IntervalSet.less_than_or_equal_to(-23))
(...-23]
"""
return cls([Interval.less_than_or_equal_to(n)])
@@ -1977,9 +1997,9 @@ class BaseIntervalSet(object):
def greater_than(cls, n):
"""Returns an IntervalSet containing values greater than the given value
- >>> print IntervalSet.greater_than(0)
+ >>> print(IntervalSet.greater_than(0))
(0...)
- >>> print IntervalSet.greater_than(-23)
+ >>> print(IntervalSet.greater_than(-23))
(-23...)
"""
return cls([Interval.greater_than(n)])
@@ -1989,9 +2009,9 @@ class BaseIntervalSet(object):
"""Returns an IntervalSet containing values greater than or equal to
the given value
- >>> print IntervalSet.greater_than_or_equal_to(0)
+ >>> print(IntervalSet.greater_than_or_equal_to(0))
[0...)
- >>> print IntervalSet.greater_than_or_equal_to(-23)
+ >>> print(IntervalSet.greater_than_or_equal_to(-23))
[-23...)
"""
return cls([Interval.greater_than_or_equal_to(n)])
@@ -2000,9 +2020,9 @@ class BaseIntervalSet(object):
def not_equal_to(cls, n):
"""Returns an IntervalSet of all values not equal to n
- >>> print IntervalSet.not_equal_to(0)
+ >>> print(IntervalSet.not_equal_to(0))
(...0),(0...)
- >>> print IntervalSet.not_equal_to(-23)
+ >>> print(IntervalSet.not_equal_to(-23))
(...-23),(-23...)
"""
return cls([Interval.less_than(n), Interval.greater_than(n)])
@@ -2014,9 +2034,9 @@ class BaseIntervalSet(object):
If closed is True, then the endpoints are included; otherwise, they
aren't.
- >>> print IntervalSet.between(0, 100)
+ >>> print(IntervalSet.between(0, 100))
[0..100]
- >>> print IntervalSet.between(-1, 1)
+ >>> print(IntervalSet.between(-1, 1))
[-1..1]
"""
return cls([Interval.between(a, b, closed)])
@@ -2025,7 +2045,7 @@ class BaseIntervalSet(object):
def all(cls):
"""Returns an interval set containing all values
- >>> print IntervalSet.all()
+ >>> print(IntervalSet.all())
(...)
"""
return cls([Interval.all()])
@@ -2034,7 +2054,7 @@ class BaseIntervalSet(object):
def empty(cls):
"""Returns an interval set containing no values.
- >>> print IntervalSet.empty()
+ >>> print(IntervalSet.empty())
<Empty>
"""
return cls()
@@ -2101,7 +2121,7 @@ class IntervalSet(BaseIntervalSet):
>>> del interval[1]
>>> len(interval)
2
- >>> print interval
+ >>> print(interval)
-2,7
"""
try:
@@ -2114,13 +2134,13 @@ class IntervalSet(BaseIntervalSet):
>>> r = IntervalSet()
>>> r.add(4)
- >>> print r
+ >>> print(r)
4
>>> r.add(Interval(23, 39, lower_closed=False))
- >>> print r
+ >>> print(r)
4,(23..39]
>>> r.add(Interval.less_than(25))
- >>> print r
+ >>> print(r)
(...39]
"""
BaseIntervalSet._add(self, obj)
@@ -2134,10 +2154,10 @@ class IntervalSet(BaseIntervalSet):
>>> r = IntervalSet.all()
>>> r.remove(4)
- >>> print r
+ >>> print(r)
(...4),(4...)
>>> r.remove(Interval(23, 39, lower_closed=False))
- >>> print r
+ >>> print(r)
(...4),(4..23],(39...)
>>> r.remove(Interval.less_than(25))
Traceback (most recent call last):
@@ -2157,13 +2177,13 @@ class IntervalSet(BaseIntervalSet):
>>> r = IntervalSet.all()
>>> r.discard(4)
- >>> print r
+ >>> print(r)
(...4),(4...)
>>> r.discard(Interval(23, 39, lower_closed=False))
- >>> print r
+ >>> print(r)
(...4),(4..23],(39...)
>>> r.discard(Interval.less_than(25))
- >>> print r
+ >>> print(r)
(39...)
"""
diff = self - IntervalSet([obj])
@@ -2177,18 +2197,18 @@ class IntervalSet(BaseIntervalSet):
>>> r = IntervalSet.all()
>>> r.difference_update([4])
- >>> print r
+ >>> print(r)
(...4),(4...)
>>> r.difference_update(
... IntervalSet([Interval(23, 39, lower_closed=False)]))
- >>> print r
+ >>> print(r)
(...4),(4..23],(39...)
>>> r.difference_update(IntervalSet.less_than(25))
- >>> print r
+ >>> print(r)
(39...)
>>> r2 = IntervalSet.all()
>>> r.difference_update(r2)
- >>> print r
+ >>> print(r)
<Empty>
"""
diff = self.difference(other)
@@ -2198,10 +2218,10 @@ class IntervalSet(BaseIntervalSet):
"""Removes all Intervals from the object
>>> s = IntervalSet([2, 7, Interval.greater_than(8), 2, 6, 34])
- >>> print s
+ >>> print(s)
2,6,7,(8...)
>>> s.clear()
- >>> print s
+ >>> print(s)
<Empty>
"""
self.intervals = []
@@ -2214,17 +2234,17 @@ class IntervalSet(BaseIntervalSet):
>>> r = IntervalSet()
>>> r.update([4])
- >>> print r
+ >>> print(r)
4
>>> r.update(IntervalSet([Interval(23, 39, lower_closed=False)]))
- >>> print r
+ >>> print(r)
4,(23..39]
>>> r.update(IntervalSet.less_than(25))
- >>> print r
+ >>> print(r)
(...39]
>>> r2 = IntervalSet.all()
>>> r.update(r2)
- >>> print r
+ >>> print(r)
(...)
"""
union = self.union(other)
@@ -2238,19 +2258,19 @@ class IntervalSet(BaseIntervalSet):
>>> r = IntervalSet.all()
>>> r.intersection_update([4])
- >>> print r
+ >>> print(r)
4
>>> r = IntervalSet.all()
>>> r.intersection_update(
... IntervalSet([Interval(23, 39, lower_closed=False)]))
- >>> print r
+ >>> print(r)
(23..39]
>>> r.intersection_update(IntervalSet.less_than(25))
- >>> print r
+ >>> print(r)
(23..25)
>>> r2 = IntervalSet.all()
>>> r.intersection_update(r2)
- >>> print r
+ >>> print(r)
(23..25)
"""
intersection = self.intersection(other)
@@ -2264,18 +2284,18 @@ class IntervalSet(BaseIntervalSet):
>>> r = IntervalSet.empty()
>>> r.symmetric_difference_update([4])
- >>> print r
+ >>> print(r)
4
>>> r.symmetric_difference_update(
... IntervalSet([Interval(23, 39, lower_closed=False)]))
- >>> print r
+ >>> print(r)
4,(23..39]
>>> r.symmetric_difference_update(IntervalSet.less_than(25))
- >>> print r
+ >>> print(r)
(...4),(4..23],[25..39]
>>> r2 = IntervalSet.all()
>>> r.symmetric_difference_update(r2)
- >>> print r
+ >>> print(r)
4,(23..25),(39...)
"""
xor = self.symmetric_difference(other)
@@ -2294,7 +2314,7 @@ class IntervalSet(BaseIntervalSet):
True
>>> "7" in l
True
- >>> print s
+ >>> print(s)
<Empty>
>>> i = s.pop()
Traceback (most recent call last):
@@ -2341,7 +2361,7 @@ class FrozenIntervalSet(BaseIntervalSet):
... FrozenIntervalSet.less_than(3) : 3}
"""
- def __new__(cls, items=[]):
+ def __new__(cls, items=None):
"""Constructs a new FrozenInteralSet
Object creation is just like with a regular IntervalSet, except for
@@ -2354,11 +2374,13 @@ class FrozenIntervalSet(BaseIntervalSet):
>>> id(fs1) == id(fs2)
True
"""
+ if items is None:
+ items = []
if (cls == FrozenIntervalSet) and isinstance(items, FrozenIntervalSet):
result = items
else:
s = IntervalSet(items)
- result = super(FrozenIntervalSet, cls).__new__(cls, items)
+ result = super(FrozenIntervalSet, cls).__new__(cls)
result.intervals = s.intervals
return result
--
2.42.0
......@@ -10,11 +10,12 @@ parts =
LDFLAGS = -L${hdf5:location}/lib -Wl,-rpath=${hdf5:location}/lib
CPPFLAGS = -I${hdf5:location}/include
LD_LIBRARY_PATH=${hdf5:location}/lib
HDF5_DIR=${hdf5:location}
[h5py]
recipe = zc.recipe.egg:custom
egg = h5py
setup-eggs =
setup-eggs =
${cython:egg}
${numpy:egg}
pkgconfig
......
......@@ -11,7 +11,7 @@ parts =
<= numpy-env
[ipython]
recipe = zc.recipe.egg:custom
recipe = zc.recipe.egg
egg = ipython
environment = ipython-env
setup-eggs =
......
......@@ -75,7 +75,7 @@ class ERP5Kernel(Kernel):
self.title = None
# Allowed HTTP request code list for making request to erp5 from Kernel
# This list should be to used check status_code before making requests to erp5
self.allowed_HTTP_request_code_list = range(500, 511)
self.allowed_HTTP_request_code_list = list(range(500, 511))
# Append request code 200 in the allowed HTTP status code list
self.allowed_HTTP_request_code_list.append(200)
......
......@@ -14,9 +14,8 @@ parts +=
# Always build GCC for Fortran (see openblas).
max_version = 0
[jupyter]
[jupyter:python2]
extra-eggs =
python_executable = ${buildout:bin-directory}/${:interpreter}
[download-file-base]
recipe = slapos.recipe.build:download
......@@ -46,7 +45,7 @@ context =
key develop_eggs_directory buildout:develop-eggs-directory
key eggs_directory buildout:eggs-directory
key openssl_output openssl-output:openssl
key python_executable jupyter:python_executable
key python_executable jupyter:python-executable
key jupyter_config_location jupyter-notebook-config:location
key jupyter_config_filename jupyter-notebook-config:filename
key jupyter_set_password_location jupyter-set-password:location
......@@ -59,7 +58,7 @@ context =
key custom_js_filename custom-js:filename
key monitor_template_rendered buildout:directory
[versions]
[versions:python2]
Pygments = 2.2.0
ipykernel = 4.5.2
ipython = 5.3.0
......
......@@ -15,11 +15,11 @@
[instance-jupyter-notebook]
filename = instance.cfg.in
md5sum = fd7ed44da8d8723983b8666df2971a36
md5sum = c335782940a8f3b1ff7d4280aeec336e
[jupyter-notebook-config]
filename = jupyter_notebook_config.py.jinja
md5sum = 9d579353b579b6e488ae6330c7f4ad68
md5sum = a68c96dc2c19f915bccb609c29ffd6c4
[jupyter-set-password]
filename = jupyter_set_password.cgi.jinja
......@@ -27,7 +27,7 @@ md5sum = ac10fbcf790bd8e58750cfdd069812d2
[erp5-kernel]
filename = ERP5kernel.py
md5sum = 7d5309fe79afbcb455c0d8181b42e56c
md5sum = da04b99b70b2e327c9e9b4cdd056098e
[kernel-json]
filename = kernel.json.jinja
......
......@@ -55,7 +55,7 @@ key_file = ${directory:etc}/jupyter_cert.key
[instance]
recipe = slapos.cookbook:wrapper
command-line =
{{ bin_directory }}/jupyter-lab
{{ bin_directory }}/jupyter-notebook
--no-browser
--ip=${instance-parameter:host}
--port=${instance-parameter:port}
......@@ -69,15 +69,19 @@ environment =
JUPYTER_PATH=${directory:jupyter_dir}
JUPYTER_CONFIG_DIR=${directory:jupyter_config_dir}
JUPYTER_RUNTIME_DIR=${directory:jupyter_runtime_dir}
JUPYTERLAB_DIR=${directory:jupyterlab-dir}
LANG=C.UTF-8
[jupyter-notebook-config]
recipe = slapos.recipe.template:jinja2
url = {{ jupyter_config_location }}/{{ jupyter_config_filename }}
output = ${directory:jupyter_config_dir}/jupyter_notebook_config.py
output = ${directory:jupyter_config_dir}/jupyter_server_config.py
context =
raw config_cfg ${buildout:directory}/knowledge0.cfg
[jupyter-notebook-config:python2]
output = ${directory:jupyter_config_dir}/jupyter_notebook_config.py
[directory]
recipe = slapos.cookbook:mkdirectory
home = ${buildout:directory}
......@@ -95,12 +99,13 @@ jupyter_runtime_dir = ${:jupyter_dir}/runtime
jupyter_custom_dir = ${:jupyter_config_dir}/custom
jupyter_nbextensions_dir = ${:jupyter_dir}/nbextensions
erp5_kernel_dir = ${:jupyter_kernel_dir}/ERP5
jupyterlab-dir = ${:jupyter_dir}/lab
[jupyter_notebook]
# This part is called like this because knowledge0.write uses the part name for
# the section name in the config file.
recipe = slapos.cookbook:zero-knowledge.write
password =
password =
filename = knowledge0.cfg
[read-knowledge0]
......@@ -136,7 +141,7 @@ output = ${directory:erp5_kernel_dir}/kernel.json
context =
raw python_executable {{ python_executable }}
raw kernel_dir ${erp5-kernel:target-directory}/{{ erp5_kernel_filename }}
key erp5_url slapconfiguration:configuration.erp5-url
key erp5_url slapconfiguration:configuration.erp5-url
raw display_name ERP5
raw language_name python
......
......@@ -2,28 +2,36 @@
This script initializes Jupyter's configuration such as passwords and other
things. It is run by IPython hence why it can use functions like get_config().
'''
import ConfigParser
import random
from notebook.auth import passwd
import os
import ssl
import sys
import six
from six.moves.configparser import ConfigParser
if six.PY3:
from jupyter_server.auth import passwd
import secrets
random_password = secrets.token_hex
else:
from notebook.auth import passwd
import random
def random_password(length=10):
result = ""
for i in range(0, length):
result = result + chr(random.randint(0, 25) + ord('a'))
return result
def random_password(length = 10):
result = ""
for i in range(0, length):
result = result + chr(random.randint(0, 25) + ord('a'))
return result
knowledge_0 = '{{ config_cfg }}'
if not os.path.exists(knowledge_0):
print "Your software does <b>not</b> embed 0-knowledge. \
This interface is useless in this case</body></html>"
print ("Your software does <b>not</b> embed 0-knowledge.\n"
"This interface is useless in this case</body></html>")
exit(0)
c = get_config()
parser = ConfigParser.ConfigParser()
parser = ConfigParser()
parser.read(knowledge_0)
if not parser.has_section("jupyter_notebook"):
......@@ -33,10 +41,49 @@ if not parser.has_option("jupyter_notebook", "password") or \
parser.get("jupyter_notebook", "password") == "":
parser.set("jupyter_notebook", "password", random_password())
c.NotebookApp.password = passwd(parser.get("jupyter_notebook", "password"))
c.NotebookApp.ssl_options = {
'ssl_version': ssl.PROTOCOL_TLSv1_2,
}
c.ServerApp.password = passwd(parser.get("jupyter_notebook", "password"))
if six.PY3:
import pathlib
import jupyterlab
jupyterlab_dir = pathlib.Path(os.environ['JUPYTERLAB_DIR'])
# symlink all schemas in a folder, jupyter seems to assume that everything is installed
# in the same place.
schemas_dir = jupyterlab_dir / 'schemas'
if not schemas_dir.exists():
schemas_dir.mkdir()
for p in sys.path:
for schema in (pathlib.Path(p) / 'share' / 'jupyter' / 'lab' / 'schemas').glob('*/'):
dest = (schemas_dir / schema.name)
if dest.exists():
dest.unlink()
dest.symlink_to(schema)
c.LabServerApp.schemas_dir = str(schemas_dir)
# static really needs to be a sub-folder of $JUPYTERLAB_DIR
static = pathlib.Path(jupyterlab.__file__).parent.parent / 'share' / 'jupyter' / 'lab' / 'static'
static_dir = jupyterlab_dir / 'static'
if static_dir.exists():
static_dir.unlink()
static_dir.symlink_to(static)
c.LabServerApp.themes_dir = str(pathlib.Path(jupyterlab.__file__).parent / 'themes')
c.ServerApp.jpserver_extensions = {
'notebook': True,
'jupyter_lsp':True,
'jupyter_server_terminals': True,
'jupyterlab': True,
'notebook_shim': True,
}
else:
c.ServerApp.ssl_options = {
'ssl_version': ssl.PROTOCOL_TLSv1_2,
}
with open(knowledge_0, 'w') as file:
parser.write(file)
......@@ -3,7 +3,7 @@ extends =
../numpy/openblas.cfg
../matplotlib/buildout.cfg
../ipython/buildout.cfg
../python-cffi/buildout.cfg
../python-argon2-cffi/buildout.cfg
../python-pyzmq/buildout.cfg
../scipy/buildout.cfg
../scikit-learn/buildout.cfg
......@@ -15,10 +15,6 @@ parts =
jupyter
jupyter-notebook-scripts
[argon2-cffi]
recipe = zc.recipe.egg:custom
egg = ${:_buildout_section_name_}
setup-eggs = ${python-cffi:egg}
[jupyter-env]
<= numpy-env
......@@ -73,6 +69,7 @@ scripts =
jupyter-migrate
jupyter-troubleshoot
jupyter-run
python-executable = ${buildout:bin-directory}/${:interpreter}
[jupyter-notebook-initialized-scripts]
recipe = zc.recipe.egg:scripts
......
......@@ -40,6 +40,7 @@ need-matplotlibrc = ${matplotlibrc:location}
[versions]
matplotlib = 2.1.2
cycler = 0.11.0
matplotlib-inline = 0.1.6:whl
[versions:sys.version_info < (3,8)]
cycler = 0.10.0
......@@ -21,7 +21,7 @@ environment = numpy-env
eggs = ${cython:egg}
[versions]
numpy = 1.22.0
numpy = 1.24.4
[numpy:sys.version_info < (3,8)]
depends =
......
......@@ -34,3 +34,8 @@ rpath =
${libjpeg:location}/lib
${libtiff:location}/lib
${zlib:location}/lib
Pillow-patches = ${:_profile_base_location_}/../../component/egg-patch/Pillow/0001-set-metadata-in-setup.py-for-compatibility-with-old-.patch#0a06cc5a94d3db24688938731e4b15e2
Pillow-patch-options = -p1
[pillow-python:python2]
Pillow-patches =
......@@ -8,10 +8,33 @@ extends =
[astroid]
recipe = zc.recipe.egg:custom
egg = astroid
setup-eggs = ${lazy-object-proxy:egg}
[astroid:python2]
patches =
${:_profile_base_location_}/astroid-six_moves_import_error.patch#377beb0c50f52b9608bb6be7bf93096e
patch-options = -p1
patch-binary = ${patch:location}/bin/patch
setup-eggs =
[lazy-object-proxy]
recipe = zc.recipe.egg:custom
egg = lazy-object-proxy
setup-eggs =
${setuptools-scm:egg}
typing-extensions
tomli
[setuptools-scm]
recipe = zc.recipe.egg:custom
egg = setuptools-scm
setup-eggs = packaging
[mccabe]
recipe = zc.recipe.egg:custom
egg = mccabe
setup-eggs = pytest-runner
[pylint]
recipe = zc.recipe.egg:custom
......@@ -21,3 +44,14 @@ patches =
${:_profile_base_location_}/pylint-redefining-builtins-modules.patch#043defc6e9002ac48b40e078797d4d17
patch-options = -p1
patch-binary = ${patch:location}/bin/patch
[pylint:python3]
recipe = zc.recipe.egg
patches =
[mccabe:python3]
recipe = zc.recipe.egg
setup-eggs =
[astroid:python3]
recipe = zc.recipe.egg
[buildout]
extends =
../python-cffi/buildout.cfg
parts = argon2-cffi
[argon2-cffi]
recipe = zc.recipe.egg:custom
egg = ${:_buildout_section_name_}
setup-eggs = ${python-cffi:egg}
[buildout]
parts =
python-ldap
python-ldap-python
extends =
../cyrus-sasl/buildout.cfg
../openldap/buildout.cfg
......@@ -10,19 +10,25 @@ extends =
[python-ldap-python]
recipe = zc.recipe.egg:custom
egg = python-ldap
patches =
${:_profile_base_location_}/python-ldap-no_default_dirs.patch#959115f13f1de5c63654c69b8dfacd69
patch-options = -p1
patch-binary = ${patch:location}/bin/patch
rpath =
${openldap:location}/lib
${cyrus-sasl:location}/lib
${openssl:location}/lib
include-dirs =
${openldap:location}/include
${cyrus-sasl:location}/include/sasl
${cyrus-sasl:location}/include
${openssl:location}/include
library-dirs =
${openldap:location}/lib
${cyrus-sasl:location}/lib
${openssl:location}/lib
[python-ldap-python:python2]
patches =
${:_profile_base_location_}/python-ldap-no_default_dirs.patch#959115f13f1de5c63654c69b8dfacd69
patch-options = -p1
patch-binary = ${patch:location}/bin/patch
include-dirs =
${openldap:location}/include
${cyrus-sasl:location}/include/sasl
${openssl:location}/include
......@@ -16,3 +16,5 @@ egg = pyzmq
environment = python-pyzmq-env
rpath =
${libzmq:location}/lib
setup-eggs =
packaging
......@@ -21,9 +21,19 @@ setup-eggs =
pkgconfig
pathlib2
setuptools-scm
toml
tomli
environment = python-xmlsec-env
[python-xmlsec:python2]
setup-eggs =
${lxml-python:egg}
pkgconfig
pathlib2
setuptools-scm
toml
[python-xmlsec-env]
PKG_CONFIG=${pkgconfig:location}/bin/pkg-config
PKG_CONFIG_PATH=${libxml2:location}/lib/pkgconfig:${libxslt:location}/lib/pkgconfig:${xmlsec:location}/lib/pkgconfig
......
......@@ -23,9 +23,21 @@ setup-eggs =
${PyWavelets:egg}
${pillow-python:egg}
networkx
pythran
packaging
rpath =
${openblas:location}/lib
[scikit-image:python2]
setup-eggs =
${numpy:egg}
${scipy:egg}
${cython:egg}
${PyWavelets:egg}
${pillow-python:egg}
networkx
[scikit-image-repository]
recipe = slapos.recipe.build:gitclone
git-executable = ${git:location}/bin/git
......
......@@ -15,6 +15,7 @@ recipe = zc.recipe.egg:custom
egg = scikit-learn
environment = scikit-learn-env
setup-eggs =
${cython:egg}
${numpy:egg}
${scipy:egg}
rpath =
......
[buildout]
extends =
../../stack/erp5/buildout.cfg
[python]
part = python3
[erp5]
repository = https://lab.nexedi.com/nexedi/erp5.git
branch = zope4py3
develop = true
software.cfg.json
\ No newline at end of file
......@@ -46,10 +46,14 @@ from cryptography.x509.oid import NameOID
from slapos.testing.testcase import ManagedResource, makeModuleSetUpAndTestCaseClass
from slapos.testing.utils import findFreeTCPPort
ERP5PY3 = os.environ['SLAPOS_SR_TEST_NAME'] == 'erp5-py3'
_setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass(
os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', 'software.cfg')))
os.path.join(os.path.dirname(__file__), '..', '..', 'software%s.cfg' % (
'-py3' if ERP5PY3 else ''))),
software_id=os.environ['SLAPOS_SR_TEST_NAME'],
)
setup_module_executed = False
......@@ -191,7 +195,10 @@ def neo(instance_parameter_dict):
class ERP5InstanceTestCase(SlapOSInstanceTestCase, metaclass=ERP5InstanceTestMeta):
"""ERP5 base test case
"""
__test_matrix__ = matrix((zeo, neo)) # switch between NEO and ZEO mode
if ERP5PY3:
__test_matrix__ = matrix((zeo, )) # TODO: NEO is not yet enabled for py3
else:
__test_matrix__ = matrix((zeo, neo)) # switch between NEO and ZEO mode
@classmethod
def isNEO(cls):
......
......@@ -51,7 +51,7 @@ import urllib3
from slapos.testing.utils import CrontabMixin
import zc.buildout.configparser
from . import CaucaseService, ERP5InstanceTestCase, default, matrix, neo, setUpModule
from . import CaucaseService, ERP5InstanceTestCase, default, matrix, neo, setUpModule, ERP5PY3
setUpModule # pyflakes
......@@ -1302,60 +1302,80 @@ class TestNEO(ZopeSkinsMixin, CrontabMixin, ERP5InstanceTestCase):
__partition_reference__ = 'n'
__test_matrix__ = matrix((neo,))
def _getCrontabCommand(self, crontab_name: str) -> str:
"""Read a crontab and return the command that is executed.
overloaded to use crontab from neo partition
"""
with open(
os.path.join(
if ERP5PY3:
# NEO is not ready for python3 at this time, this test is here to become
# an unexpected success once it starts working, so that we remember to
# remove this and enable neo in ERP5InstanceTestCase.__test_matrix__
setup_failed_exception = None
@classmethod
def setUpClass(cls):
try:
super().setUpClass()
except BaseException as e:
cls.setup_failed_exception = e
cls.setUp = lambda self: None
cls.tearDownClass = classmethod(lambda cls: None)
@unittest.expectedFailure
def test_neo_py3(self):
self.assertIsNone(self.setup_failed_exception)
else:
def _getCrontabCommand(self, crontab_name: str) -> str:
"""Read a crontab and return the command that is executed.
overloaded to use crontab from neo partition
"""
with open(
os.path.join(
self.getComputerPartitionPath('neo-0'),
'etc',
'cron.d',
crontab_name,
)) as f:
crontab_spec, = f.readlines()
self.assertNotEqual(crontab_spec[0], '@', crontab_spec)
return crontab_spec.split(None, 5)[-1]
def test_log_rotation(self):
# first run to create state files
self._executeCrontabAtDate('logrotate', '2000-01-01')
def check_sqlite_log(path):
with self.subTest(path), contextlib.closing(sqlite3.connect(path)) as con:
con.execute('select * from log')
logfiles = ('neoadmin.log', 'neomaster.log', 'neostorage-0.log')
for f in logfiles:
check_sqlite_log(
os.path.join(
self.getComputerPartitionPath('neo-0'),
'etc',
'cron.d',
crontab_name,
)) as f:
crontab_spec, = f.readlines()
self.assertNotEqual(crontab_spec[0], '@', crontab_spec)
return crontab_spec.split(None, 5)[-1]
def test_log_rotation(self):
# first run to create state files
self._executeCrontabAtDate('logrotate', '2000-01-01')
def check_sqlite_log(path):
with self.subTest(path), contextlib.closing(sqlite3.connect(path)) as con:
con.execute('select * from log')
'var',
'log',
f))
logfiles = ('neoadmin.log', 'neomaster.log', 'neostorage-0.log')
for f in logfiles:
check_sqlite_log(
os.path.join(
self.getComputerPartitionPath('neo-0'),
'var',
'log',
f))
self._executeCrontabAtDate('logrotate', '2050-01-01')
self._executeCrontabAtDate('logrotate', '2050-01-01')
for f in logfiles:
check_sqlite_log(
os.path.join(
self.getComputerPartitionPath('neo-0'),
'srv',
'backup',
'logrotate',
f'{f}-20500101'))
for f in logfiles:
check_sqlite_log(
os.path.join(
self.getComputerPartitionPath('neo-0'),
'srv',
'backup',
'logrotate',
f'{f}-20500101'))
self._executeCrontabAtDate('logrotate', '2050-01-02')
requests.get(self._getAuthenticatedZopeUrl('/'), verify=False).raise_for_status()
self._executeCrontabAtDate('logrotate', '2050-01-02')
requests.get(self._getAuthenticatedZopeUrl('/'), verify=False).raise_for_status()
for f in logfiles:
check_sqlite_log(
os.path.join(
self.getComputerPartitionPath('neo-0'),
'var',
'log',
f))
for f in logfiles:
check_sqlite_log(
os.path.join(
self.getComputerPartitionPath('neo-0'),
'var',
'log',
f))
class TestPassword(ERP5InstanceTestCase, TestPublishedURLIsReachableMixin):
__partition_reference__ = 'p'
......
......@@ -23,7 +23,7 @@ import unittest
from slapos.grid.utils import md5digest
from . import ERP5InstanceTestCase
from . import setUpModule as _setUpModule
from . import setUpModule as _setUpModule, ERP5PY3
from .test_erp5 import TestPublishedURLIsReachableMixin
......@@ -38,6 +38,8 @@ def setUpModule():
md5digest(cls.getSoftwareURL()),
'bin', 'wcfs')):
raise unittest.SkipTest("built with wendelin.core 1")
if ERP5PY3:
raise unittest.SkipTest("wendelin.core does not support python3 yet")
class TestWCFS(ERP5InstanceTestCase, TestPublishedURLIsReachableMixin):
......
......@@ -19,11 +19,11 @@ md5sum = 5f39952f94095b1f12f41db76867e71e
[instance-jupyter]
filename = instance-jupyter.cfg.in
md5sum = f9a0e5a134456d74ca8b4d87862f903d
md5sum = 1812fa797b9eb687a634ebe96134b504
[jupyter-notebook-config]
filename = jupyter_notebook_config.py.jinja
md5sum = 089e4c511a3c7b110471bf41ca2695a4
md5sum = 6c03113fb53d6ba98476f3353c083984
[erp5-kernel]
filename = ERP5kernel.py
......
......@@ -73,7 +73,7 @@ key_file = ${directory:etc}/jupyter_cert.key
[instance]
recipe = slapos.cookbook:wrapper
command-line =
{{ bin_directory }}/jupyter-lab
{{ bin_directory }}/jupyter-notebook
--no-browser
--ip=${instance-parameter:host}
--port=${instance-parameter:port}
......@@ -87,16 +87,16 @@ environment =
JUPYTER_PATH=${directory:jupyter_dir}
JUPYTER_CONFIG_DIR=${directory:jupyter_config_dir}
JUPYTER_RUNTIME_DIR=${directory:jupyter_runtime_dir}
JUPYTERLAB_DIR=${directory:jupyterlab-dir}
LANG=C.UTF-8
[jupyter-password]
recipe = slapos.cookbook:generate.password
bytes = 10
[jupyter-notebook-config]
recipe = slapos.recipe.template:jinja2
url = {{ jupyter_config_location }}/{{ jupyter_config_filename }}
output = ${directory:jupyter_config_dir}/jupyter_notebook_config.py
output = ${directory:jupyter_config_dir}/jupyter_server_config.py
context =
key password jupyter-password:passwd
raw gcc_location {{ gcc_location }}
......@@ -119,6 +119,7 @@ jupyter_runtime_dir = ${:jupyter_dir}/runtime
jupyter_custom_dir = ${:jupyter_config_dir}/custom
jupyter_nbextensions_dir = ${:jupyter_dir}/nbextensions
erp5_kernel_dir = ${:jupyter_kernel_dir}/ERP5
jupyterlab-dir = ${:jupyter_dir}/lab
[request-slave-frontend-base]
recipe = slapos.cookbook:requestoptional
......@@ -194,7 +195,7 @@ output = ${directory:erp5_kernel_dir}/kernel.json
context =
raw python_executable {{ python_executable }}
raw kernel_dir ${erp5-kernel:target-directory}/{{ erp5_kernel_filename }}
key erp5_url slapconfiguration:configuration.erp5-url
key erp5_url slapconfiguration:configuration.erp5-url
raw display_name ERP5
raw language_name python
......
......@@ -2,13 +2,47 @@
This script initializes Jupyter's configuration such as passwords and other
things. It is run by IPython hence why it can use functions like get_config().
'''
import configparser
from notebook.auth import passwd
import os
import pathlib
import sys
from jupyter_server.auth import passwd
import jupyterlab
c = get_config()
c.NotebookApp.password = passwd("{{ password }}")
c.ServerApp.password = passwd("{{ password }}")
jupyterlab_dir = pathlib.Path(os.environ['JUPYTERLAB_DIR'])
# symlink all schemas in a folder, jupyter seems to assume that everything is installed
# in the same place.
schemas_dir = jupyterlab_dir / 'schemas'
if not schemas_dir.exists():
schemas_dir.mkdir()
for p in sys.path:
for schema in (pathlib.Path(p) / 'share' / 'jupyter' / 'lab' / 'schemas').glob('*/'):
dest = (schemas_dir / schema.name)
if dest.exists():
dest.unlink()
dest.symlink_to(schema)
c.LabServerApp.schemas_dir = str(schemas_dir)
# static really needs to be a sub-folder of $JUPYTERLAB_DIR
static = pathlib.Path(jupyterlab.__file__).parent.parent / 'share' / 'jupyter' / 'lab' / 'static'
static_dir = jupyterlab_dir / 'static'
if static_dir.exists():
static_dir.unlink()
static_dir.symlink_to(static)
c.LabServerApp.themes_dir = str(pathlib.Path(jupyterlab.__file__).parent / 'themes')
c.ServerApp.jpserver_extensions = {
'notebook': True,
'jupyter_lsp':True,
'jupyter_server_terminals': True,
'jupyterlab': True,
'notebook_shim': True,
}
try:
os.environ['PATH'] = "{{ gcc_location }}/bin" + os.pathsep + os.environ['PATH']
......
......@@ -47,7 +47,7 @@ class TestJupyter(InstanceTestCase):
def test(self):
connection_dict = self.computer_partition.getConnectionParameterDict()
self.assertTrue('password' in connection_dict)
self.assertIn('password', connection_dict)
password = connection_dict['password']
self.assertEqual(
......
......@@ -22,7 +22,7 @@ md5sum = 102a7f1c1bc46a9b3fa5bd9b9a628e1d
[instance-neo-admin]
filename = instance-neo-admin.cfg.in
md5sum = b6e1ccb1d90160110202e5111eec2afa
md5sum = a0ec1dce4c7a237fbeef3f8aee62e55a
[instance-neo-master]
filename = instance-neo-master.cfg.in
......@@ -34,7 +34,7 @@ md5sum = 200ae55715cb735b0f97f8c835a3071f
[template-neo-my-cnf]
filename = my.cnf.in
md5sum = 56ea8f452d9e1526157ab9d03e631e1a
md5sum = 3ae93702f3890a504cc8a93eb5ad52bc
[template-neo]
filename = instance.cfg.in
......
......@@ -45,7 +45,7 @@ ssl = {{ dumps(bool(slapparameter_dict['ssl'])) }}
cluster = {{ dumps(slapparameter_dict['cluster']) }}
masters = {{ dumps(slapparameter_dict['masters']) }}
extra-options =
{%- for k, v in monitor_dict.iteritems() %}
{%- for k, v in six.iteritems(monitor_dict) %}
{%- if k == 'backup' %}
{%- set k = 'monitor-backup' %}
{%- endif %}
......
......@@ -45,7 +45,7 @@ innodb_locks_unsafe_for_binlog = 1
{{x}}sync_frm = 0
# Extra parameters.
{%- for k, v in extra_dict.iteritems() %}
{%- for k, v in six.iteritems(extra_dict) %}
{%- do assert('-' not in k) %}
{{ k }} = {{ v }}
{%- endfor %}
......
......@@ -134,10 +134,14 @@ inline =
exec "$basedir/bin/mysqld" --defaults-file='{{defaults_file}}' "$@"
[versions]
coverage = 5.5
coverage = 7.5.1
ecdsa = 0.13
mysqlclient = 1.3.12
mysqlclient = 2.0.1
PyMySQL = 0.10.1
pycrypto = 2.6.1
cython-zstd = 0.2
funcsigs = 1.0.2
[versions:python2]
coverage = 5.5
mysqlclient = 1.3.12
......@@ -15,4 +15,4 @@
[template]
filename = instance.cfg
md5sum = f10fbca22d1d30dd7a4f36e1cd521b97
md5sum = 59a5b559b22ad0590e691226cea45055
......@@ -67,6 +67,7 @@ inline =
command,
cwd={{ repr(folder) }},
summaryf=UnitTest.summary,
envadj={ 'SLAPOS_SR_TEST_NAME': {{ repr(name) }} },
)
{%- endif %}
{%- endfor %}
......
......@@ -440,6 +440,7 @@ tests =
dream ${slapos.test.dream-setup:setup}
dufs ${slapos.test.dufs-setup:setup}
erp5 ${slapos.test.erp5-setup:setup}
erp5-py3 ${slapos.test.erp5-setup:setup}
erp5testnode ${slapos.test.erp5testnode-setup:setup}
fluentd ${slapos.test.fluentd-setup:setup}
galene ${slapos.test.galene-setup:setup}
......@@ -487,7 +488,7 @@ recurls =
slapos.core =
# Various needed versions
Pillow = 9.2.0
Pillow = 10.2.0+SlapOSPatched001
forcediphttpsadapter = 1.0.1
image = 1.5.25
plantuml = 0.3.0:whl
......@@ -495,7 +496,6 @@ pysftp = 0.2.9
requests-toolbelt = 0.8.0
testfixtures = 6.11.0
mysqlclient = 2.1.1
pexpect = 4.8.0
ptyprocess = 0.6.0
paho-mqtt = 1.5.0
pcpp = 1.30
......
......@@ -2,7 +2,7 @@
extends =
# versions pins from zope, vendored with:
# curl https://zopefoundation.github.io/Zope/releases/4.8.9/versions-prod.cfg > zope-versions.cfg
# curl https://zopefoundation.github.io/Zope/releases/5.9/versions-prod.cfg > zope-versions.cfg
# When updating, keep in mind that some versions are defined in other places,
# for example component/ZEO , component/ZODB and stack/slapos
zope-versions.cfg
......@@ -63,6 +63,7 @@ extends =
../../component/pygolang/buildout.cfg
../../component/bcrypt/buildout.cfg
../../component/python-pynacl/buildout.cfg
../../component/python-pyzmq/buildout.cfg
../../component/python-xmlsec/buildout.cfg
../../component/selenium/buildout.cfg
../../stack/caucase/buildout.cfg
......@@ -476,7 +477,6 @@ eggs +=
ipython_genutils
ipykernel
ipywidgets
requests
[egg-with-zope-proxy]
......@@ -493,13 +493,14 @@ setup-eggs +=
[eggs]
<= neoppod
eggs = ${neoppod:eggs}
eggs =
${erp5-eggs-python-version-dependent:eggs}
${neoppod:eggs}
${caucase-eggs:eggs}
${wendelin.core:egg}
${numpy:egg}
${matplotlib:egg}
${lxml-python:egg}
${ocropy:egg}
${pandas:egg}
${pillow-python:egg}
${python-ldap-python:egg}
......@@ -521,14 +522,12 @@ eggs = ${neoppod:eggs}
astor
APacheDEX
Pympler
SOAPpy
chardet
collective.recipe.template
erp5diff
interval
ipdb
Jinja2
jsonschema
mechanize
oauthlib
objgraph
......@@ -540,39 +539,30 @@ eggs = ${neoppod:eggs}
PyPDF2
python-magic
python-memcached
pytz
requests
responses
uritemplate
urlnorm
uuid
xml_marshaller
xupdate_processor
feedparser
validictory
erp5.util
z3c.etestbrowser
huBarcode
qrcode
spyne
httplib2
suds
pprofile
pycountry
xfw
jsonschema
${selenium:egg}
pytesseract
decorator
networkx
# Needed for checking ZODB Components source code
${astroid:egg}
${pylint:egg}
jedi
yapf
typing
# Used for Python 2 only
${pytracemalloc:egg}
xlrd
pydot
# Zope
......@@ -585,25 +575,16 @@ eggs = ${neoppod:eggs}
# for runzeo
${ZEO:egg}
# Other Zope 2 packages
Products.PluggableAuthService
Products.DCWorkflow
# Other products
Products.PluggableAuthService
Products.MimetypesRegistry
Products.TIDStorage
# Currently forked in our repository
# Products.PortalTransforms
# Dependency for our fork of PortalTransforms
StructuredText
# Needed for parsing .po files from our Localizer subset
polib
# Needed for Google OAuth
google-api-python-client
# Need for Facebook OAuth
facebook-sdk
......@@ -629,11 +610,9 @@ eggs = ${neoppod:eggs}
docutils
zLOG
Products.ZSQLMethods
ZServer
Products.ExternalMethod
Products.SiteErrorLog
tempstorage
Products.DCWorkflow
Products.Sessions
Products.ZODBMountPoint
Record
......@@ -659,28 +638,48 @@ extra-paths =
patch-binary = ${patch:location}/bin/patch
Acquisition-patches = ${:_profile_base_location_}/../../component/egg-patch/Acquisition/aq_dynamic-4.7.patch#85b0090e216cead0fc86c5c274450d96
Acquisition-patch-options = -p1
DateTime-patches =
${:_profile_base_location_}/../../component/egg-patch/DateTime/0001-Cast-int-to-float-in-compare-methods.patch#9898a58ce90dd31c884a7183aeec4361
${:_profile_base_location_}/../../component/egg-patch/DateTime/0002-Fix-compare-methods-between-DateTime-0-and-None-fix-.patch#733903a564c8b14df65c45c4f2eec262
${:_profile_base_location_}/../../component/egg-patch/DateTime/0003-Make-it-possible-to-pickle-datetimes-returned-by-asd.patch#e94a71ef40de130720e621e296537000
${:_profile_base_location_}/../../component/egg-patch/DateTime/0004-Repair-equality-comparison-between-DateTime-instance.patch#ea146c00dfbc31c7d96af8abc6f0b301
DateTime-patch-options = -p1
Products.BTreeFolder2-patches = ${:_profile_base_location_}/../../component/egg-patch/Products.BTreeFolder2/0001-Add-a-confirmation-prompt-on-Delete-All-Objects-butt.patch#44de3abf382e287b8766c2f29ec1cf74
Products.BTreeFolder2-patch-options = -p1
Products.CMFCore-patches = ${:_profile_base_location_}/../../component/egg-patch/Products.CMFCore/portal_skins_ZMI_find.patch#19ec05c0477c50927ee1df6eb75d1e7f
Products.CMFCore-patch-options = -p1
Products.DCWorkflow-patches = ${:_profile_base_location_}/../../component/egg-patch/Products.DCWorkflow/workflow_method-2.4.1.patch#ec7bb56a9f1d37fcbf960cd1e96e6e6d
Products.DCWorkflow-patch-options = -p1
PyPDF2-patches =
${:_profile_base_location_}/../../component/egg-patch/PyPDF2/0001-Custom-implementation-of-warnings.formatwarning-remo.patch#d25bb0f5dde7f3337a0a50c2f986f5c8
${:_profile_base_location_}/../../component/egg-patch/PyPDF2/0002-fix-pdf-reader-getting-stuck-when-trying-to-read-lar.patch#c06a29b6b6a5df612ae36731b938fb95
PyPDF2-patch-options = -p1
python-magic-patches = ${:_profile_base_location_}/../../component/egg-patch/python_magic/magic.patch#de0839bffac17801e39b60873a6c2068
python-magic-patch-options = -p1
RestrictedPython-patches = ${:_profile_base_location_}/../../component/egg-patch/RestrictedPython/0001-compile-implicitly-enable-__future__.print_function-.patch#f746dccbf3b462e67386490b898512e4
RestrictedPython-patch-options = -p1
urlnorm-patches = ${:_profile_base_location_}/../../component/egg-patch/urlnorm/urlnorm-1.1.4-py3.patch#5ef268fb44cbc005b62140099c33b641
urlnorm-patch-options = -p1
SOAPpy-py3-patches = ${:_profile_base_location_}/../../component/egg-patch/SOAPpy-py3/0001-backport-changes-from-0.52.29.patch#28a08e587bf2e287ec3491c5ae7e8f1a
SOAPpy-py3-patch-options = -p1
Zope-patches =
${:_profile_base_location_}/../../component/egg-patch/Zope/0001-Fix-redirections-to-URLS-with-host-given-as-IP-litte.patch#093ad5755094d537c6a4deadc959ade0
${:_profile_base_location_}/../../component/egg-patch/Zope/1196.patch#1a1c32984cf4b2cb8558a3fdce4c4fb3
${:_profile_base_location_}/../../component/egg-patch/Zope/1198.patch#b108c121bc2de37460f330eb93ae5825
Zope-patch-options = -p1
[eggs:python3]
AccessControl-patches = ${:_profile_base_location_}/../../component/egg-patch/AccessControl/147.patch#5eb2b07dc79bef05dbc0c60d046d2847
AccessControl-patch-options = -p1
interval-patches = ${:_profile_base_location_}/../../component/egg-patch/interval/0001-python3-support.patch#66ac345f0a6d73e0bd29e394b7646311
interval-patch-options = -p1
Products.DCWorkflow-patches = ${:_profile_base_location_}/../../component/egg-patch/Products.DCWorkflow/workflow_method-3.0.0.patch#4cc8607213b1ef08331366d9873becaa
Products.DCWorkflow-patch-options = -p1
[eggs:python2]
DateTime-patches =
${:_profile_base_location_}/../../component/egg-patch/DateTime/0001-Cast-int-to-float-in-compare-methods.patch#9898a58ce90dd31c884a7183aeec4361
${:_profile_base_location_}/../../component/egg-patch/DateTime/0002-Fix-compare-methods-between-DateTime-0-and-None-fix-.patch#733903a564c8b14df65c45c4f2eec262
${:_profile_base_location_}/../../component/egg-patch/DateTime/0003-Make-it-possible-to-pickle-datetimes-returned-by-asd.patch#e94a71ef40de130720e621e296537000
${:_profile_base_location_}/../../component/egg-patch/DateTime/0004-Repair-equality-comparison-between-DateTime-instance.patch#ea146c00dfbc31c7d96af8abc6f0b301
DateTime-patch-options = -p1
huBarcode-patches =
${:_profile_base_location_}/../../component/egg-patch/huBarcode/fix-loading-font-for-ean13.patch#77879186092d0b55ee009c6ef3c3e2e4
huBarcode-patch-options = -p1
Products.DCWorkflow-patches = ${:_profile_base_location_}/../../component/egg-patch/Products.DCWorkflow/workflow_method-2.4.1.patch#ec7bb56a9f1d37fcbf960cd1e96e6e6d
Products.DCWorkflow-patch-options = -p1
RestrictedPython-patches = ${:_profile_base_location_}/../../component/egg-patch/RestrictedPython/0001-compile-implicitly-enable-__future__.print_function-.patch#f746dccbf3b462e67386490b898512e4
RestrictedPython-patch-options = -p1
# backported security patches for waitress-1.4.4 from Debian 1.4.4-1.1+deb11u1 package.
waitress-patches =
${:_profile_base_location_}/../../component/egg-patch/waitress/CVE-2022-24761-1.patch#a0508880f24662e48a20ce3bcbf440c2
......@@ -695,6 +694,27 @@ Zope-patches =
${:_profile_base_location_}/../../component/egg-patch/Zope/0001-Fix-redirections-to-URLS-with-host-given-as-IP-litte.patch#093ad5755094d537c6a4deadc959ade0
Zope-patch-options = -p1
# python version dependent eggs
[erp5-eggs-python-version-dependent:python2]
eggs =
huBarcode
${ocropy:egg}
${pytracemalloc:egg}
Products.TIDStorage
SOAPpy
suds
typing
uuid
xlrd
ZServer
[erp5-eggs-python-version-dependent:python3]
eggs =
${python-pyzmq:egg}
python-barcode
SOAPpy-py3
suds-py3
# neoppod installs bin/coverage so we inject erp5 plugin here so that coverage script can use it in report
[neoppod]
eggs +=
......@@ -725,123 +745,128 @@ depends =
# neoppod, mysqlclient, slapos.recipe.template
# patched eggs
Acquisition = 4.7+SlapOSPatched001
DateTime = 4.9+SlapOSPatched004
Products.DCWorkflow = 2.4.1+SlapOSPatched001
ocropy = 1.0+SlapOSPatched001
AccessControl = 6.3+SlapOSPatched001
Acquisition = 5.2+SlapOSPatched001
PyPDF2 = 1.26.0+SlapOSPatched002
pysvn = 1.9.15+SlapOSPatched001
python-ldap = 2.4.32+SlapOSPatched001
python-magic = 0.4.12+SlapOSPatched001
RestrictedPython = 5.4+SlapOSPatched001
waitress = 1.4.4+SlapOSPatched006
Zope = 4.8.9+SlapOSPatched002
## https://lab.nexedi.com/nexedi/slapos/merge_requests/648
pylint = 1.4.4+SlapOSPatched002
# astroid 1.4.1 breaks testDynamicClassGeneration
astroid = 1.3.8+SlapOSPatched001
# modified version that works fine for buildout installation
SOAPpy = 0.12.0nxd001
Zope = 5.9+SlapOSPatched003
# Pinned versions
alabaster = 0.7.12
APacheDEX = 1.8
APacheDEX = 2.0
astroid = 3.1.0:whl
Beaker = 1.11.0
cloudpickle = 0.5.3
cookies = 2.2.1
dask = 0.18.1
deepdiff = 3.3.0
deepdiff = 6.7.1
dill = 0.3.8:whl
docutils = 0.17.1
erp5-coverage-plugin = 0.0.1
erp5diff = 0.8.1.9
facebook-sdk = 2.0.0
five.formlib = 1.0.4
five.localsitemanager = 4.0
fpconst = 0.7.2
future = 0.18.2
google-api-python-client = 1.6.1
google-api-core = 2.17.1
google-api-python-client = 2.118.0
google-auth = 2.28.1
google-auth-httplib2 = 0.2.0
googleapis-common-protos = 1.62.0
graphviz = 0.5.2
haufe.requestmonitoring = 0.6.0
html5lib = 1.1
huBarcode = 1.0.0
interval = 1.0.0
huBarcode = 1.0.0+SlapOSPatched001
imageio = 2.34.0
interval = 1.0.0+SlapOSPatched001
ipdb = 0.10.2
isort = 5.13.2
jdcal = 1.3
jedi = 0.15.1
jsonpickle = 0.9.6
jsonpointer = 2.2
lazy-object-proxy = 1.10.0
logilab-common = 1.3.0
Mako = 1.1.4
mccabe = 0.7.0:whl
mechanize = 0.4.8
Missing = 5.0.0
mock = 4.0.3
mpmath = 0.19
munnel = 0.3
networkx = 2.1
networkx = 3.1
nt-svcutils = 2.13.0
numpy = 1.13.1
oauth2client = 4.0.0
oauthlib = 3.1.0
objgraph = 3.1.0
oic = 0.15.1
oic = 1.6.1
olefile = 0.44
openpyxl = 2.4.8
parso = 0.5.1
Pillow = 6.2.2
polib = 1.0.8
ordered-set = 4.1.0
Pillow = 10.2.0+SlapOSPatched001
polib = 1.2.0
pprofile = 2.0.4
Products.BTreeFolder2 = 4.4+SlapOSPatched001
Products.CMFCore = 2.7.0+SlapOSPatched001
Products.ExternalMethod = 4.7
Products.GenericSetup = 2.3.0
Products.MailHost = 4.13
Products.MimetypesRegistry = 2.1.8
Products.PluggableAuthService = 2.8.1
Products.PluginRegistry = 1.11
Products.PythonScripts = 4.15
Products.Sessions = 4.15
Products.SiteErrorLog = 5.7
Products.StandardCacheManagers = 4.2
Products.TIDStorage = 5.5.0
Products.ZODBMountPoint = 1.3
Products.ZSQLMethods = 3.16
pyasn1-modules = 0.0.8
Products.DCWorkflow = 3.0.0+SlapOSPatched001
Products.ExternalMethod = 5.0
Products.GenericSetup = 3.0.2
Products.MailHost = 5.2
Products.MimetypesRegistry = 3.0.1
Products.PluggableAuthService = 3.0
Products.PluginRegistry = 2.0
Products.PythonScripts = 5.0
Products.Sessions = 5.0
Products.SiteErrorLog = 6.0
Products.StandardCacheManagers = 5.0
Products.ZCatalog = 7.0
Products.ZODBMountPoint = 2.0
Products.ZSQLMethods = 4.1
protobuf = 4.25.3
pyasn1-modules = 0.3
pycountry = 17.1.8
pycrypto = 2.6.1
pycryptodomex = 3.10.1
pydantic-settings = 2.2.1:whl
pydot = 1.4.2
pyflakes = 1.5.0
pyjwkest = 1.4.2
Pympler = 0.4.3
pylint = 3.1.0:whl
Pympler = 1.0.1
pyPdf = 1.13
PyStemmer = 2.2.0.1
pytesseract = 0.2.2
python-barcode = 0.15.1:whl
python-dotenv = 1.0.1
python-gettext = 4.1
python-ldap = 3.4.4
python-libmilter = 1.0.3
python-memcached = 1.58
pytracemalloc = 1.2
PyWavelets = 0.5.2
pythran = 0.15.0:whl
PyWavelets = 1.4.0
qrcode = 5.3
Record = 4.1.0
responses = 0.10.6
rfc3987 = 1.3.8
rsa = 3.4.2
scikit-image = 0.14.0
scipy = 0.19.0
spyne = 2.12.14
scikit-image = 0.19.3
SOAPpy-py3 = 0.52.26+SlapOSPatched001
spyne = 2.14.0
strict-rfc3339 = 0.7
StructuredText = 2.11.1
suds = 0.4
suds-py3 = 1.4.5.0
tifffile = 2024.2.12
tomlkit = 0.12.4:whl
toolz = 0.9.0
typed-ast = 1.5.5
typing = 3.10.0.0
unidiff = 0.5.5
urlnorm = 1.1.4+SlapOSPatched001
uuid = 1.30
validictory = 1.1.0
webcolors = 1.10
webencodings = 0.5.1
WebOb = 1.8.5
WebTest = 2.0.33
wrapt = 1.16.0
WSGIProxy2 = 0.4.6
WSGIUtils = 0.7
wstools-py3 = 0.54.4
xfw = 0.10
xupdate-processor = 0.5
yapf = 0.28.0
......@@ -858,8 +883,127 @@ zope.authentication = 5.0
zope.error = 4.6
zope.minmax = 2.3
zope.password = 4.4
zope.sendmail = 6.1
zope.session = 4.5
zope.testbrowser = 5.5.1
[versions:python2]
AccessControl = 4.4
Acquisition = 4.13+SlapOSPatched001
APacheDEX = 1.8
astroid = 1.3.8+SlapOSPatched001
AuthEncoding = 4.3
Chameleon = 3.9.1
DateTime = 4.9+SlapOSPatched004
deepdiff = 3.3.0
DocumentTemplate = 3.4
ExtensionClass = 4.9
five.globalrequest = 99.1
five.localsitemanager = 3.4
interval = 1.0.0
ipython = 5.3.0
jedi = 0.15.1
Missing = 4.2
mock = 3.0.5
MultiMapping = 4.1
multipart = 0.1.1
networkx = 2.1
numpy = 1.13.1
ocropy = 1.0+SlapOSPatched001
oic = 0.15.1
openpyxl = 2.4.8
parso = 0.5.1
Paste = 3.5.2
PasteDeploy = 2.1.1
pbr = 5.11.0
Persistence = 3.6
Pillow = 6.2.2
Products.BTreeFolder2 = 4.4
Products.DCWorkflow = 2.4.1+SlapOSPatched001
Products.ExternalMethod = 4.7
Products.GenericSetup = 2.3.0
Products.MailHost = 4.13
Products.MimetypesRegistry = 2.1.8
Products.PluggableAuthService = 2.8.1
Products.PluginRegistry = 1.11
Products.PythonScripts = 4.15
Products.Sessions = 4.15
Products.SiteErrorLog = 5.7
Products.StandardCacheManagers = 4.2
Products.TIDStorage = 5.5.0
Products.ZCatalog = 5.4
Products.ZODBMountPoint = 1.3
Products.ZSQLMethods = 3.16
prompt-toolkit = 1.0.13
pyasn1-modules = 0.0.8
pylint = 1.4.4+SlapOSPatched002
Pympler = 0.4.3
PyStemmer = 1.3.0
python-ldap = 2.4.32+SlapOSPatched001
pytracemalloc = 1.2
pytz = 2022.7
PyWavelets = 0.5.2
Record = 3.6
RestrictedPython = 5.4+SlapOSPatched001
roman = 3.3
scikit-image = 0.14.0
scipy = 0.19.0
shutilwhich = 1.1.0
SOAPpy = 0.12.0nxd001
transaction = 3.0.1
waitress = 1.4.4+SlapOSPatched006
webcolors = 1.10
WebOb = 1.8.7
WebTest = 2.0.35
WSGIProxy2 = 0.4.6
z3c.pt = 3.3.1
zc.lockfile = 2.0
zdaemon = 4.4
zExceptions = 4.3
Zope = 4.8.9+SlapOSPatched002
zope.annotation = 4.8
zope.browser = 2.4
zope.browsermenu = 4.4
zope.browserpage = 4.4.0
zope.browserresource = 4.4
zope.cachedescriptors = 4.4
zope.component = 5.0.1
zope.componentvocabulary = 2.3.0
zope.configuration = 4.4.1
zope.container = 4.10
zope.contentprovider = 4.2.1
zope.contenttype = 4.6
zope.datetime = 4.3.0
zope.deferredimport = 4.4
zope.deprecation = 4.4.0
zope.dottedname = 4.3
zope.event = 4.6
zope.exceptions = 4.6
zope.filerepresentation = 5.0.0
zope.formlib = 5.0.1
zope.globalrequest = 1.6
zope.hookable = 5.4
zope.i18n = 4.9.0
zope.i18nmessageid = 5.1.1
zope.lifecycleevent = 4.4
zope.location = 4.3
zope.pagetemplate = 4.6.0
zope.processlifetime = 2.4
zope.proxy = 4.6.1
zope.ptresource = 4.3.0
zope.publisher = 6.1.0
zope.ramcache = 2.4
zope.schema = 6.2.1
zope.security = 5.8
zope.sendmail = 5.3
zope.sequencesort = 4.2
zope.site = 4.6.1
zope.size = 4.4
zope.structuredtext = 4.4
zope.tal = 4.5
zope.tales = 5.2
zope.testbrowser = 5.6.1
zope.traversing = 4.4.1
zope.viewlet = 4.3
ZServer = 4.0.2
......@@ -74,7 +74,7 @@ md5sum = ca0cb83950dd9079cc289891cce08e76
[template-erp5]
filename = instance-erp5.cfg.in
md5sum = edce1c63c13f0d8ec477711ea646444f
md5sum = d6f7d2fa1bde019892897c767f93e089
[template-zeo]
filename = instance-zeo.cfg.in
......@@ -86,11 +86,11 @@ md5sum = 0ac4b74436f554cd677f19275d18d880
[template-zope]
filename = instance-zope.cfg.in
md5sum = e94599e02c987ed5dfc26263a54ccc15
md5sum = 9547bacad0635b0f64cac48f15c4e9ae
[template-balancer]
filename = instance-balancer.cfg.in
md5sum = 727c6f045da382fe50916e6ea5ae6405
md5sum = 48b8b8b4b87973beaa1fd6299244ebd6
[template-haproxy-cfg]
filename = haproxy.cfg.in
......
......@@ -472,10 +472,9 @@ command = generate-apachedex-report
recipe = slapos.recipe.template
output = ${directory:etc}/${:_buildout_section_name_}
inline =
{% for line in slapparameter_dict['apachedex-configuration'] %}
{% for line in slapparameter_dict['apachedex-configuration'] -%}
{# apachedex config files use shlex.split, so we need to quote the arguments. #}
{# BBB: in python 3 we can use shlex.quote instead. #}
{{ repr(line.encode('utf-8')) }}
{{ six.moves.shlex_quote(line) }}
{% endfor %}
[apachedex-parameters]
......
......@@ -200,7 +200,7 @@ config-zodb-dict = {{ dumps(zodb_dict) }}
{% for server_type, server_dict in six.iteritems(storage_dict) -%}
{% if server_type == 'neo' -%}
config-neo-cluster = ${publish-early:neo-cluster}
config-neo-name = {{ server_dict.keys()[0] }}
config-neo-name = {{ list(server_dict.keys())[0] }}
config-neo-masters = ${publish-early:neo-masters}
{% else -%}
config-zodb-zeo = ${request-zodb:connection-storage-dict}
......
......@@ -80,6 +80,8 @@ environment +=
TZ={{ slapparameter_dict['timezone'] }}
MATPLOTLIBRC={{ parameter_dict['matplotlibrc'] }}
PYTHONUNBUFFERED=1
OFS_IMAGE_USE_DENYLIST=1
DISALLOWED_INLINE_MIMETYPES=
INSTANCE_HOME=${:instance-home}
FONTCONFIG_FILE=${fontconfig-conf:output}
JUPYTER_PATH=${directory:jupyter-dir}
......@@ -402,8 +404,8 @@ config-port = {{ '${' ~ zope_tunnel_section_name ~ ':ipv6-port}' }}
promise = check_error_on_zope_longrequest_log
name = {{'check-' ~ name ~ '-longrequest-error-log.py'}}
config-log-file = {{ '${' ~ conf_parameter_name ~ ':longrequest-logger-file}' }}
config-error-threshold = {{ slapparameter_dict["zope-longrequest-logger-error-threshold"] }}
config-maximum-delay = {{ slapparameter_dict["zope-longrequest-logger-maximum-delay"] }}
config-error-threshold = {{ dumps(slapparameter_dict["zope-longrequest-logger-error-threshold"]) }}
config-maximum-delay = {{ dumps(slapparameter_dict["zope-longrequest-logger-maximum-delay"]) }}
{% endif -%}
[{{ section('logrotate-entry-' ~ name) }}]
......
......@@ -2,150 +2,95 @@
# Version pins for required and commonly used dependencies.
[versions]
Zope = 4.8.9
Zope = 5.9
Zope2 = 4.0
# AccessControl 5+ no longer supports Zope 4.
AccessControl = 4.4
Acquisition = 4.13
AuthEncoding = 4.3
BTrees = 4.11.3
Chameleon = 3.10.2
DateTime = 4.9
DocumentTemplate = 4.1
ExtensionClass = 4.9
Missing = 4.2
MultiMapping = 4.1
Paste = 3.5.2
PasteDeploy = 3.0.1
Persistence = 3.6
Products.BTreeFolder2 = 4.4
# ZCatalog 6+ no longer supports Zope 4.
Products.ZCatalog = 5.4
Record = 3.6
# RestrictedPython >= 6 no longer supports Zope 4
RestrictedPython = 5.4
AccessControl = 6.3
Acquisition = 5.1
AuthEncoding = 5.0
BTrees = 5.1
Chameleon = 4.2.0
; DateTime = 5.3
DateTime = 5.5
DocumentTemplate = 4.6
ExtensionClass = 5.1
MultiMapping = 5.0
Paste = 3.7.1
PasteDeploy = 3.1.0
Persistence = 4.1
RestrictedPython = 7.0
WebTest = 3.0.0
WSGIProxy2 = 0.5.1
WebOb = 1.8.7
WebTest = 3.0.0
ZConfig = 3.6.1
ZEO = 5.3.0
ZODB = 5.8.0
five.globalrequest = 99.1
five.localsitemanager = 3.4
funcsigs = 1.0.2
future = 0.18.2
ipaddress = 1.0.23
mock = 4.0.3
ZConfig = 4.0
ZODB = 5.8.1
beautifulsoup4 = 4.12.2
cffi = 1.16.0
multipart = 0.2.4
pbr = 5.11.0
persistent = 4.9.3
pytz = 2022.7
roman = 3.3
shutilwhich = 1.1.0
persistent = 5.1
pycparser = 2.21
python-gettext = 5.0
pytz = 2023.3.post1
six = 1.16.0
transaction = 3.0.1
roman = 4.1
soupsieve = 2.5
transaction = 4.0
waitress = 2.1.2
z3c.pt = 3.3.1
zExceptions = 4.3
zc.lockfile = 2.0
zdaemon = 4.4
zodbpickle = 2.6
zope.annotation = 4.8
zope.browser = 2.4
zope.browsermenu = 4.4
zope.browserpage = 4.4.0
zope.browserresource = 4.4
zope.cachedescriptors = 4.4
zope.component = 5.0.1
zope.componentvocabulary = 2.3.0
zope.configuration = 4.4.1
zope.container = 5.1
zope.contentprovider = 4.2.1
zope.contenttype = 4.6
zope.datetime = 4.3.0
zope.deferredimport = 4.4
zope.deprecation = 4.4.0
zope.dottedname = 5.0
zope.event = 4.6
zope.exceptions = 4.6
zope.filerepresentation = 5.0.0
zope.formlib = 5.0.1
zope.globalrequest = 1.6
zope.hookable = 5.4
zope.i18n = 4.9.0
zope.i18nmessageid = 5.1.1
zope.interface = 5.5.2
zope.lifecycleevent = 4.4
zope.location = 4.3
zope.pagetemplate = 4.6.0
zope.processlifetime = 2.4
zope.proxy = 4.6.1
zope.ptresource = 4.3.0
zope.publisher = 6.1.0
zope.ramcache = 2.4
zope.schema = 6.2.1
zope.security = 5.8
zope.sendmail = 5.3
zope.sequencesort = 4.2
zope.site = 4.6.1
zope.size = 4.4
zope.structuredtext = 4.4
zope.tal = 4.5
zope.tales = 5.2
zope.testbrowser = 5.6.1
zope.testing = 4.10
zope.testrunner = 5.6
zope.traversing = 4.4.1
zope.viewlet = 4.3
[versions:python27]
# Chameleon 3.10 doesn't work on Python 2.7
Chameleon = 3.9.1
# DocumentTemplate 4+ requires Python 3.5 or higher
DocumentTemplate = 3.4
# PasteDeploy >3 requires Python 3.7
PasteDeploy = 2.1.1
# WSGIProxy 0.5 and up requires Python 3.7 and up
WSGIProxy2 = 0.4.6
# WebTest 3.0 and up requires Python 3.6 and up
WebTest = 2.0.35
# ZServer is only available for Python 2
ZServer = 4.0.2
# mock 4.0 and up requires Python 3.6 or higher
mock = 3.0.5
# multipart 0.2 and up requires Python 3
multipart = 0.1.1
# waitress 2 requires Python 3.6 or higher
waitress = 1.4.4
# zope.dottedname >= 5 requires Python 3.6 or higher
zope.dottedname = 4.3
# zope.container 5.x requires Python 3.7 or higher
zope.container = 4.10
z3c.pt = 4.0
zExceptions = 5.0
zc.lockfile = 3.0.post1
zc.recipe.egg = 2.0.7
zodbpickle = 3.1
zope.annotation = 5.0
zope.browser = 3.0
zope.browsermenu = 5.0
zope.browserpage = 5.0
zope.browserresource = 5.1
zope.cachedescriptors = 5.0
zope.component = 6.0
zope.configuration = 5.0
zope.container = 5.2
zope.contentprovider = 5.0
zope.contenttype = 5.1
zope.datetime = 5.0.0
zope.deferredimport = 5.0
zope.deprecation = 5.0
zope.dottedname = 6.0
zope.event = 5.0
zope.exceptions = 5.0.1
zope.filerepresentation = 6.0
zope.globalrequest = 2.0
zope.hookable = 6.0
zope.i18n = 5.1
zope.i18nmessageid = 6.1.0
zope.interface = 6.1
zope.lifecycleevent = 5.0
zope.location = 5.0
zope.pagetemplate = 5.0
zope.processlifetime = 3.0
zope.proxy = 5.1
zope.ptresource = 5.0
zope.publisher = 7.0
zope.schema = 7.0.1
zope.security = 6.2
zope.sequencesort = 5.0
zope.site = 5.0
zope.size = 5.0
zope.structuredtext = 5.0
zope.tal = 5.0.1
zope.tales = 6.0
zope.testbrowser = 6.0
zope.testing = 5.0.1
zope.traversing = 5.0
zope.viewlet = 5.0
[versions:python35]
# DocumentTemplate 4+ cannot be installed on Zope 4 for Python 3.5
DocumentTemplate = 3.4
# PasteDeploy >3 requires Python 3.7
PasteDeploy = 2.1.1
# WSGIProxy 0.5 and up requires Python 3.7 and up
WSGIProxy2 = 0.4.6
# WebTest 3.0 and up requires Python 3.6 and up
WebTest = 2.0.35
# mock 4.0 and up requires Python 3.6 or higher
mock = 3.0.5
# waitress 2 requires Python 3.6 or higher
waitress = 1.4.4
# zope.dottedname >= 5 requires Python 3.6 or higher
zope.dottedname = 4.3
# zope.container 5.x requires Python 3.7 or higher
zope.container = 4.10
[versions:python36]
# PasteDeploy >3 requires Python 3.7
PasteDeploy = 2.1.1
# WSGIProxy 0.5 and up requires Python 3.7 and up
WSGIProxy2 = 0.4.6
# waitress 2.1 requires Python 3.7 or higher
waitress = 2.0.0
# zope.container 5.x requires Python 3.7 or higher
zope.container = 4.10
# XXX this is commented out because slapos.buildout is based on a too old buildout
# which does not understands :python37 yet. We target a more recent python version
# so we don't use these versions.
# [versions:python37]
# # PasteDeploy 3.x works on Python 3.7 but pulls tons of dependencies
# PasteDeploy = 2.1.1
# # SoupSieve 2.5 and up requires Python 3.8
# soupsieve = 2.4.1
# # cffi 1.16.0 requires Python 3.8
# cffi = 1.15.1
......@@ -143,15 +143,20 @@ zc.recipe.egg = 2.0.3+slapos003
aiohttp = 3.8.5:whl
aiosignal = 1.3.1:whl
annotated-types = 0.6.0:whl
anyio = 4.3.0:whl
apache-libcloud = 2.4.0
argon2-cffi = 20.1.0
asn1crypto = 1.3.0
astor = 0.5
astor = 0.8.1
asttokens = 2.4.1:whl
async-generator = 1.10
async-lru = 2.0.4:whl
async-timeout = 4.0.3
atomicwrites = 1.4.0
atomize = 0.2.0
attrs = 23.1.0:whl
Babel = 2.14.0
backcall = 0.2.0
backports-abc = 0.5
backports.functools-lru-cache = 1.6.1:whl
......@@ -173,12 +178,14 @@ cliff = 2.8.3:whl
cmd2 = 0.7.0
collective.recipe.shelloutput = 0.1
collective.recipe.template = 2.0
comm = 0.2.1:whl
configparser = 4.0.2:whl
contextlib2 = 0.6.0.post1
croniter = 0.3.25
cryptography = 3.3.2+SlapOSPatched001
dataclasses = 0.8
dateparser = 0.7.6
debugpy = 1.8.1
decorator = 4.3.0
defusedxml = 0.7.1
distro = 1.7.0
......@@ -188,6 +195,7 @@ enum34 = 1.1.10
erp5.util = 0.4.75
et-xmlfile = 1.0.1
exceptiongroup = 1.1.3:whl
executing = 2.0.1:whl
fastjsonschema = 2.18.1
feedparser = 6.0.10
Flask = 3.0.0:whl
......@@ -201,8 +209,10 @@ gitdb = 4.0.10
GitPython = 3.1.30
greenlet = 3.0.1
h11 = 0.14.0
h5py = 2.7.1
h5py = 3.11.0
httpcore = 1.0.4:whl
httplib2 = 0.22.0
httpx = 0.27.0:whl
idna = 3.4:whl
igmp = 1.0.4
Importing = 1.10
......@@ -210,22 +220,34 @@ importlib-metadata = 6.8.0:whl
importlib-resources = 5.10.2:whl
inotify-simple = 1.1.1
ipaddress = 1.0.23
ipykernel = 5.3.4:whl
ipython = 7.16.3
ipython-genutils = 0.1.0
ipywidgets = 6.0.0
ipykernel = 6.29.3:whl
ipython = 8.18.1:whl
ipython-genutils = 0.2.0
ipywidgets = 8.1.2:whl
itsdangerous = 2.1.2
jdcal = 1.4
jedi = 0.17.2
Jinja2 = 3.1.2:whl
joblib = 1.3.2:whl
json5 = 0.9.20:whl
jsonpointer = 2.2
jsonschema = 4.17.3:whl
jupyter = 1.0.0
jupyter-client = 7.3.1
jupyter-console = 6.4.4
jupyter-core = 4.9.2
jupyterlab = 0.26.3
jupyterlab-launcher = 0.3.1
jupyterlab-pygments = 0.1.2
jupyter-client = 8.6.1:whl
jupyter-console = 6.6.3:whl
jupyter-core = 5.7.1:whl
jupyter-events = 0.6.3:whl
isoduration = 20.11.0
jupyter-lsp = 2.2.3:whl
jupyter-server = 2.10.0:whl
jupyter-server-terminals = 0.5.2:whl
jupyterlab = 4.1.3:whl
jupyterlab-launcher = 0.13.1
jupyterlab-pygments = 0.3.0:whl
jupyterlab-server = 2.24.0:whl
jupyterlab-widgets = 3.0.10:whl
arrow = 1.2.3
fqdn = 1.5.1
lock-file = 2.0
lockfile = 0.12.2:whl
lsprotocol = 2023.0.0b1:whl
......@@ -237,18 +259,20 @@ meld3 = 1.0.2
mistune = 0.8.4
mock = 3.0.5
more-itertools = 5.0.0
mpmath = 1.0.0
mpmath = 1.3.0
msgpack = 1.0.5
multidict = 6.0.4
nbclient = 0.5.1
nbclient = 0.10.0:whl
nbconvert = 6.5.4
nbformat = 5.9.2:whl
nest-asyncio = 1.5.6
netaddr = 0.7.19
netifaces = 0.10.7
notebook = 6.1.5
notebook = 7.1.2:whl
notebook-shim = 0.2.4:whl
openpyxl = 2.5.2
outcome = 1.2.0
overrides = 7.7.0
packaging = 23.2:whl
pandocfilters = 1.4.3
paramiko = 2.11.0
......@@ -262,21 +286,24 @@ pickleshare = 0.7.4
pim-dm = 1.4.0nxd002
pkgconfig = 1.5.1
pkgutil-resolve-name = 1.3.10
platformdirs = 4.2.0:whl
plone.recipe.command = 1.1
pluggy = 0.13.1:whl
ply = 3.11
prettytable = 0.7.2
prometheus-client = 0.9.0
prompt-toolkit = 3.0.19
prompt-toolkit = 3.0.43
psutil = 5.8.0
psycopg2 = 2.9.9
ptyprocess = 0.5.1
pure-eval = 0.2.2:whl
py = 1.11.0:whl
py-mld = 1.0.3
pyasn1 = 0.4.5
pyasn1 = 0.5.1
pycparser = 2.20
pycurl = 7.45.0
pydantic = 1.9.1
pydantic = 2.6.3:whl
pydantic-core = 2.16.3:whl
pygls = 1.1.0:whl
Pygments = 2.9.0
PyNaCl = 1.3.0
......@@ -288,19 +315,23 @@ PyRSS2Gen = 1.1
PySocks = 1.7.1
pytest-runner = 5.2:whl
python-dateutil = 2.8.2:whl
python-json-logger = 2.0.7
pytz = 2022.2.1
PyYAML = 5.4.1
pyzmq = 22.3.0
qtconsole = 4.3.0
pyzmq = 24.0.1
qtconsole = 5.5.1
qtpy = 2.4.1:whl
random2 = 1.0.1
regex = 2020.9.27
requests = 2.31.0
rfc3339-validator = 0.1.4
rfc3986-validator = 0.1.1:whl
rpdb = 0.1.5
rubygemsrecipe = 0.4.4
scandir = 1.10.0
scikit-learn = 0.20.4
scikit-learn = 0.24.2
seaborn = 0.7.1
Send2Trash = 1.5.0
Send2Trash = 1.8.2:whl
setproctitle = 1.1.10
setuptools-dso = 2.9
sgmllib3k = 1.0.0
......@@ -320,6 +351,7 @@ smmap = 5.0.0
sniffio = 1.3.0
sortedcontainers = 2.4.0
soupsieve = 1.9.5
stack-data = 0.6.3:whl
statsmodels = 0.13.5+SlapOSPatched001
stevedore = 1.21.0:whl
subprocess32 = 3.5.4
......@@ -327,24 +359,28 @@ supervisor = 4.1.0
sympy = 1.1.1
terminado = 0.9.1
testpath = 0.4.4
threadpoolctl = 3.3.0:whl
tinycss2 = 1.2.1:whl
tornado = 6.1
traitlets = 5.11.2:whl
tomli = 2.0.1:whl
tornado = 6.4
traitlets = 5.14.1:whl
trio = 0.22.0
trio-websocket = 0.9.2
typeguard = 3.0.2:whl
typing-extensions = 4.8.0:whl
tzlocal = 1.5.1
unicodecsv = 0.14.1
uri-template = 1.2.0
uritemplate = 4.1.1
urllib3 = 1.26.12
wcwidth = 0.2.5
webcolors = 1.12
webencodings = 0.5.1
websocket-client = 1.5.1
websockets = 10.4
Werkzeug = 3.0.0:whl
wheel = 0.41.2:whl
widgetsnbextension = 2.0.0
widgetsnbextension = 4.0.10:whl
wsproto = 1.2.0
xlrd = 1.1.0
xml-marshaller = 1.0.2
......@@ -357,9 +393,9 @@ zipp = 3.12.0:whl
zodburi = 2.5.0
zope.event = 4.6.0
zope.exceptions = 4.6
zope.interface = 5.4.0
zope.testing = 4.7
zope.testrunner = 5.2
zope.interface = 5.5.2
zope.testing = 4.10
zope.testrunner = 5.6
[versions:sys.version_info < (3,10)]
# keep old statsmodels by default until slapos.toolbox is updated
......@@ -387,28 +423,46 @@ gevent = 20.9.0
gitdb2 = 2.0.5
GitPython = 2.1.11
greenlet = 0.4.17
h5py = 2.7.1
idna = 2.9
importlib-metadata = 1.7.0:whl
itsdangerous = 0.24
Jinja2 = 2.11.3
ipykernel = 5.3.4:whl
ipython = 7.16.3
ipython-genutils = 0.1.0
ipywidgets = 6.0.0
jsonschema = 3.0.2:whl
jupyter-client = 7.3.1
jupyter-console = 6.4.4
jupyter-core = 4.9.2
jupyterlab = 0.26.3
jupyterlab-launcher = 0.3.1
jupyterlab-pygments = 0.1.2
MarkupSafe = 1.0
mpmath = 1.0.0
msgpack = 0.6.2
nbclient = 0.5.1
notebook = 6.1.5
packaging = 16.8
prompt-toolkit = 3.0.19
psycopg2 = 2.8.6
pycurl = 7.43.0
pyparsing = 2.4.7
pyrsistent = 0.16.1
pyzmq = 22.3.0
qtconsole = 4.3.0
requests = 2.27.1
scikit-learn = 0.20.4
selectors34 = 1.2
Send2Trash = 1.5.0
slapos.toolbox = 0.128.1
smmap = 0.9.0
smmap2 = 2.0.5
statsmodels = 0.11.1
tornado = 6.1
traitlets = 4.3.3
uritemplate = 3.0.0
Werkzeug = 1.0.1
wheel = 0.35.1:whl
widgetsnbextension = 2.0.0
zipp = 1.2.0:whl
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment