From a8ab348db8106c1ef574150049c8984d4964689e Mon Sep 17 00:00:00 2001 From: kipchubett Date: Mon, 26 Oct 2026 08:20:37 +0300 Subject: [PATCH 1/5] Enhance SENAITE configuration and performance tuning in Docker --- senaite-sso/buildout.cfg | 2 +- senaite-sso/docker-entrypoint.sh | 9 ++++++++- senaite-sso/docker-initialize.py | 26 +++++++++++++++++++++++++- senaite/README.md | 4 ++++ senaite/resources/buildout.cfg | 2 +- senaite/resources/docker-entrypoint.sh | 9 ++++++++- senaite/resources/docker-initialize.py | 26 +++++++++++++++++++++++++- 7 files changed, 72 insertions(+), 6 deletions(-) diff --git a/senaite-sso/buildout.cfg b/senaite-sso/buildout.cfg index 9b4478d..7a5f58d 100644 --- a/senaite-sso/buildout.cfg +++ b/senaite-sso/buildout.cfg @@ -1,5 +1,5 @@ [buildout] -index = https://pypi.python.org/simple/ +index = https://pypi.org/simple/ extends = buildout-base.cfg extensions = mr.developer diff --git a/senaite-sso/docker-entrypoint.sh b/senaite-sso/docker-entrypoint.sh index b6ed3dd..1eae029 100755 --- a/senaite-sso/docker-entrypoint.sh +++ b/senaite-sso/docker-entrypoint.sh @@ -18,8 +18,15 @@ for f in /data/**/*.json; do \ envsubst < "$f" | sponge "$f"; \ done -if [ -e "custom.cfg" ]; then +# Opt-in: skip re-running buildout when custom.cfg is unchanged since the last +# successful run (note: plonesite profiles are then not re-applied on restart) +BUILDOUT_STAMP=".custom.cfg.sha256" +if [ -e "custom.cfg" ] && [[ "${BUILDOUT_SKIP_IF_UNCHANGED,,}" == "true" ]] \ + && sha256sum --status -c "$BUILDOUT_STAMP" 2>/dev/null; then + echo "custom.cfg unchanged, skipping buildout" +elif [ -e "custom.cfg" ]; then buildout -c custom.cfg + sha256sum custom.cfg > "$BUILDOUT_STAMP" find /data -not -user senaite -exec chown senaite:senaite {} \+ find /home/senaite -not -user senaite -exec chown senaite:senaite {} \+ gosu senaite python /docker-initialize.py diff --git a/senaite-sso/docker-initialize.py b/senaite-sso/docker-initialize.py index e06c7dc..34a57d1 100755 --- a/senaite-sso/docker-initialize.py +++ b/senaite-sso/docker-initialize.py @@ -13,7 +13,8 @@ def __init__( custom_conf="/home/senaite/senaitelims/custom.cfg", zeopack_conf="/home/senaite/senaitelims/bin/zeopack", zeoserver_conf="/home/senaite/senaitelims/parts/zeoserver/etc/zeo.conf", - cors_conf="/home/senaite/senaitelims/parts/instance/etc/package-includes/999-additional-overrides.zcml" + cors_conf="/home/senaite/senaitelims/parts/instance/etc/package-includes/999-additional-overrides.zcml", + wsgi_conf="/home/senaite/senaitelims/parts/instance/etc/wsgi.ini" ): self.env = env self.zope_conf = zope_conf @@ -21,6 +22,7 @@ def __init__( self.zeopack_conf = zeopack_conf self.zeoserver_conf = zeoserver_conf self.cors_conf = cors_conf + self.wsgi_conf = wsgi_conf def zeoclient(self): """ ZEO Client @@ -94,6 +96,27 @@ def zeoserver(self): with open(self.zeoserver_conf, 'w') as cfile: cfile.write(text) + def performance(self): + """ Zope performance tuning (only applied when the variables are set) + """ + zodb_cache_size = self.env.get("ZODB_CACHE_SIZE", "").strip() + if zodb_cache_size and os.path.exists(self.zope_conf): + with open(self.zope_conf, "r") as cfile: + config = cfile.read() + pattern = re.compile(r"(.*?cache-size\s+)\d+", re.DOTALL) + config = re.sub(pattern, r"\g<1>%s" % zodb_cache_size, config, count=1) + with open(self.zope_conf, "w") as cfile: + cfile.write(config) + + wsgi_threads = self.env.get("WSGI_THREADS", "").strip() + if wsgi_threads and os.path.exists(self.wsgi_conf): + with open(self.wsgi_conf, "r") as cfile: + config = cfile.read() + pattern = re.compile(r"^threads\s*=\s*\d+", re.MULTILINE) + config = re.sub(pattern, "threads = %s" % wsgi_threads, config, count=1) + with open(self.wsgi_conf, "w") as cfile: + cfile.write(config) + def cors(self): """ Configure CORS Policies """ @@ -285,6 +308,7 @@ def setup(self, **kwargs): self.zeoclient() self.zeopack() self.zeoserver() + self.performance() __call__ = setup diff --git a/senaite/README.md b/senaite/README.md index 824c39e..f1f8665 100644 --- a/senaite/README.md +++ b/senaite/README.md @@ -25,6 +25,10 @@ Additionally, the image will accept the following environment variable when run: | `ADMIN_USER` | | Name of the administrator user | `ADMIN_PASSWORD` | | Password of the administrator user name | `SITE` | | The 'site' name, eg, 'clinic1' | +| `ZODB_CACHE_SIZE` | `30000` | Number of objects kept in the ZODB cache per connection. Raising it (e.g. `100000`–`300000`) speeds up large SENAITE sites at the cost of RAM | +| `WSGI_THREADS` | `4` | Number of WSGI worker threads per instance (each thread holds its own ZODB cache) | +| `ZEO_CLIENT_CACHE_SIZE` | `128MB` | ZEO client cache size (only when `ZEO_ADDRESS` is set) | +| `BUILDOUT_SKIP_IF_UNCHANGED` | `false` | When `true`, skip re-running buildout at startup if `custom.cfg` has not changed since the last successful run. Faster restarts, but `plonesite` profiles are no longer re-applied on every restart | ### Volumes diff --git a/senaite/resources/buildout.cfg b/senaite/resources/buildout.cfg index 1064e6f..2a3a827 100644 --- a/senaite/resources/buildout.cfg +++ b/senaite/resources/buildout.cfg @@ -1,5 +1,5 @@ [buildout] -index = https://pypi.python.org/simple/ +index = https://pypi.org/simple/ extends = buildout-base.cfg extensions = mr.developer diff --git a/senaite/resources/docker-entrypoint.sh b/senaite/resources/docker-entrypoint.sh index 4389fb8..38fb03c 100755 --- a/senaite/resources/docker-entrypoint.sh +++ b/senaite/resources/docker-entrypoint.sh @@ -28,8 +28,15 @@ function git_fixture { # https://github.com/senaite/senaite.docker/issues/17 git_fixture -if [ -e "custom.cfg" ]; then +# Opt-in: skip re-running buildout when custom.cfg is unchanged since the last +# successful run (note: plonesite profiles are then not re-applied on restart) +BUILDOUT_STAMP=".custom.cfg.sha256" +if [ -e "custom.cfg" ] && [[ "${BUILDOUT_SKIP_IF_UNCHANGED,,}" == "true" ]] \ + && sha256sum --status -c "$BUILDOUT_STAMP" 2>/dev/null; then + echo "custom.cfg unchanged, skipping buildout" +elif [ -e "custom.cfg" ]; then buildout -c custom.cfg + sha256sum custom.cfg > "$BUILDOUT_STAMP" find /data -not -user senaite -exec chown senaite:senaite {} \+ find /home/senaite -not -user senaite -exec chown senaite:senaite {} \+ gosu senaite python /docker-initialize.py diff --git a/senaite/resources/docker-initialize.py b/senaite/resources/docker-initialize.py index 14a86f7..601cbb9 100755 --- a/senaite/resources/docker-initialize.py +++ b/senaite/resources/docker-initialize.py @@ -13,7 +13,8 @@ def __init__( custom_conf="/home/senaite/senaitelims/custom.cfg", zeopack_conf="/home/senaite/senaitelims/bin/zeopack", zeoserver_conf="/home/senaite/senaitelims/parts/zeoserver/etc/zeo.conf", - cors_conf="/home/senaite/senaitelims/parts/instance/etc/package-includes/999-additional-overrides.zcml" + cors_conf="/home/senaite/senaitelims/parts/instance/etc/package-includes/999-additional-overrides.zcml", + wsgi_conf="/home/senaite/senaitelims/parts/instance/etc/wsgi.ini" ): self.env = env self.zope_conf = zope_conf @@ -21,6 +22,7 @@ def __init__( self.zeopack_conf = zeopack_conf self.zeoserver_conf = zeoserver_conf self.cors_conf = cors_conf + self.wsgi_conf = wsgi_conf def zeoclient(self): """ ZEO Client @@ -94,6 +96,27 @@ def zeoserver(self): with open(self.zeoserver_conf, 'w') as cfile: cfile.write(text) + def performance(self): + """ Zope performance tuning (only applied when the variables are set) + """ + zodb_cache_size = self.env.get("ZODB_CACHE_SIZE", "").strip() + if zodb_cache_size and os.path.exists(self.zope_conf): + with open(self.zope_conf, "r") as cfile: + config = cfile.read() + pattern = re.compile(r"(.*?cache-size\s+)\d+", re.DOTALL) + config = re.sub(pattern, r"\g<1>%s" % zodb_cache_size, config, count=1) + with open(self.zope_conf, "w") as cfile: + cfile.write(config) + + wsgi_threads = self.env.get("WSGI_THREADS", "").strip() + if wsgi_threads and os.path.exists(self.wsgi_conf): + with open(self.wsgi_conf, "r") as cfile: + config = cfile.read() + pattern = re.compile(r"^threads\s*=\s*\d+", re.MULTILINE) + config = re.sub(pattern, "threads = %s" % wsgi_threads, config, count=1) + with open(self.wsgi_conf, "w") as cfile: + cfile.write(config) + def cors(self): """ Configure CORS Policies """ @@ -277,6 +300,7 @@ def setup(self, **kwargs): self.zeoclient() self.zeopack() self.zeoserver() + self.performance() __call__ = setup From e56b190c1d4785bf1b2203083ae5cd809c6a384b Mon Sep 17 00:00:00 2001 From: kipchubett Date: Mon, 26 Oct 2026 08:33:53 +0300 Subject: [PATCH 2/5] Update Dockerfile to use archived Debian repositories for EOL support --- senaite-sso/Dockerfile | 9 +++++++++ senaite/Dockerfile | 10 +++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/senaite-sso/Dockerfile b/senaite-sso/Dockerfile index 38638c6..3bbeebc 100644 --- a/senaite-sso/Dockerfile +++ b/senaite-sso/Dockerfile @@ -6,6 +6,15 @@ COPY requirements.txt buildout.cfg develop.cfg $SENAITE_INSTANCE_HOME/ # Copy the build dependencies and startup scripts COPY build_deps.txt run_deps.txt / +# Debian buster is EOL: its repositories moved to archive.debian.org +# (repeated here in case the base image predates this fix) +RUN sed -i \ + -e 's|deb.debian.org/debian|archive.debian.org/debian|g' \ + -e 's|security.debian.org/debian-security|archive.debian.org/debian-security|g' \ + -e '/buster-updates/d' \ + /etc/apt/sources.list \ + && echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99archive + # Install moreutils and gettext RUN apt-get update \ && apt-get install -y --no-install-recommends moreutils gettext \ diff --git a/senaite/Dockerfile b/senaite/Dockerfile index a891ddd..20283cd 100644 --- a/senaite/Dockerfile +++ b/senaite/Dockerfile @@ -32,13 +32,21 @@ COPY resources/requirements.txt resources/versions.cfg resources/buildout.cfg re # Copy the build dependencies and startup scripts COPY resources/build_deps.txt resources/run_deps.txt / +# Debian buster is EOL: its repositories moved to archive.debian.org +RUN sed -i \ + -e 's|deb.debian.org/debian|archive.debian.org/debian|g' \ + -e 's|security.debian.org/debian-security|archive.debian.org/debian-security|g' \ + -e '/buster-updates/d' \ + /etc/apt/sources.list \ + && echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99archive + # Note: we concatenate all commands to avoid multiple layer generation and reduce the image size RUN apt-get update \ # Install system packages && apt-get install -y --no-install-recommends $(grep -vE "^\s*#" /build_deps.txt | tr "\n" " ") \ && apt-get install -y --no-install-recommends $(grep -vE "^\s*#" /run_deps.txt | tr "\n" " ") \ # Fetch unified installer - && wget -O Plone.tgz https://launchpad.net/plone/$PLONE_MAJOR/$PLONE_VERSION/+download/$PLONE_UNIFIED_INSTALLER.tgz \ + && wget --tries=5 --retry-connrefused --waitretry=10 --timeout=60 -O Plone.tgzhttps://launchpad.net/plone/$PLONE_MAJOR/$PLONE_VERSION/+download/$PLONE_UNIFIED_INSTALLER.tgz \ && echo "$PLONE_MD5 Plone.tgz" | md5sum -c - \ && tar -xzf /Plone.tgz \ && cp -rv $PLONE_UNIFIED_INSTALLER/base_skeleton/* $SENAITE_INSTANCE_HOME \ From f9775c2eb0a429e427987c145781194494e0afaf Mon Sep 17 00:00:00 2001 From: kipchubett Date: Mon, 26 Oct 2026 09:58:07 +0300 Subject: [PATCH 3/5] Fix typo in Dockerfile wget command for Plone installer download --- senaite/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/senaite/Dockerfile b/senaite/Dockerfile index 20283cd..63c6075 100644 --- a/senaite/Dockerfile +++ b/senaite/Dockerfile @@ -46,7 +46,7 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends $(grep -vE "^\s*#" /build_deps.txt | tr "\n" " ") \ && apt-get install -y --no-install-recommends $(grep -vE "^\s*#" /run_deps.txt | tr "\n" " ") \ # Fetch unified installer - && wget --tries=5 --retry-connrefused --waitretry=10 --timeout=60 -O Plone.tgzhttps://launchpad.net/plone/$PLONE_MAJOR/$PLONE_VERSION/+download/$PLONE_UNIFIED_INSTALLER.tgz \ + && wget --tries=5 --retry-connrefused --waitretry=10 --timeout=60 -O Plone.tgz https://launchpad.net/plone/$PLONE_MAJOR/$PLONE_VERSION/+download/$PLONE_UNIFIED_INSTALLER.tgz \ && echo "$PLONE_MD5 Plone.tgz" | md5sum -c - \ && tar -xzf /Plone.tgz \ && cp -rv $PLONE_UNIFIED_INSTALLER/base_skeleton/* $SENAITE_INSTANCE_HOME \ From 15b18a364cdc68af58d850b4986564a3ae1b2670 Mon Sep 17 00:00:00 2001 From: kipchubett Date: Mon, 26 Oct 2026 10:37:37 +0300 Subject: [PATCH 4/5] Pin dependencies to known-good Python 2.7 releases in buildout.cfg --- senaite-sso/buildout.cfg | 10 ++++++++++ senaite/resources/buildout.cfg | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/senaite-sso/buildout.cfg b/senaite-sso/buildout.cfg index 7a5f58d..3dc389f 100644 --- a/senaite-sso/buildout.cfg +++ b/senaite-sso/buildout.cfg @@ -99,3 +99,13 @@ RelStorage = 2.1.1 pg8000 = 1.12.5 perfmetrics = 3.3.0 et-xmlfile = 1.1.0 +# Pin otherwise unpinned dependencies to known-good Python 2.7 releases +# (e.g. magnitude >= 1.1 requires Python 3.9) +magnitude = 1.0.1 +Products.DataGridField = 1.9.6 +collective.recipe.plonesite = 1.12.0 +cssselect2 = 0.2.2 +more-itertools = 5.0.0 +plone.app.jquerytools = 1.9.5 +plone.jsonapi.core = 0.7.0 +tinycss2 = 0.6.1 diff --git a/senaite/resources/buildout.cfg b/senaite/resources/buildout.cfg index 2a3a827..f0ef0ea 100644 --- a/senaite/resources/buildout.cfg +++ b/senaite/resources/buildout.cfg @@ -79,3 +79,13 @@ et-xmlfile = 1.1.0 RelStorage = 2.1.1 pg8000 = 1.12.5 perfmetrics = 3.3.0 +# Pin otherwise unpinned dependencies to known-good Python 2.7 releases +# (e.g. magnitude >= 1.1 requires Python 3.9) +magnitude = 1.0.1 +Products.DataGridField = 1.9.6 +collective.recipe.plonesite = 1.12.0 +cssselect2 = 0.2.2 +more-itertools = 5.0.0 +plone.app.jquerytools = 1.9.5 +plone.jsonapi.core = 0.7.0 +tinycss2 = 0.6.1 From 1a9aa61af02c9206d13bd3d06e2a3c6fa5f9f841 Mon Sep 17 00:00:00 2001 From: kipchubett Date: Mon, 26 Oct 2026 10:57:45 +0300 Subject: [PATCH 5/5] Add known-good-versions.cfg for reproducible builds and update Dockerfile to include it --- .github/workflows/build.yml | 2 + senaite-sso/Dockerfile | 1 + senaite-sso/buildout.cfg | 11 +- senaite/Dockerfile | 2 +- senaite/resources/buildout.cfg | 11 +- senaite/resources/known-good-versions.cfg | 359 ++++++++++++++++++++++ 6 files changed, 365 insertions(+), 21 deletions(-) create mode 100644 senaite/resources/known-good-versions.cfg diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9a033fd..3c6efdb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,6 +19,8 @@ jobs: DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_REGISTRY_USERNAME }} DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_REGISTRY_PASSWORD }} build-publish-sso: + # senaite-sso is built FROM the base image, so wait for it to be published + needs: build-publish uses: mekomsolutions/shared-github-workflow/.github/workflows/docker-build-publish.yml@main with: context: senaite-sso/ diff --git a/senaite-sso/Dockerfile b/senaite-sso/Dockerfile index 3bbeebc..8eb44a1 100644 --- a/senaite-sso/Dockerfile +++ b/senaite-sso/Dockerfile @@ -1,6 +1,7 @@ FROM mekomsolutions/senaite # Copy Buildout +# known-good-versions.cfg comes from the base image COPY requirements.txt buildout.cfg develop.cfg $SENAITE_INSTANCE_HOME/ # Copy the build dependencies and startup scripts diff --git a/senaite-sso/buildout.cfg b/senaite-sso/buildout.cfg index 3dc389f..e0b031e 100644 --- a/senaite-sso/buildout.cfg +++ b/senaite-sso/buildout.cfg @@ -2,6 +2,7 @@ index = https://pypi.org/simple/ extends = buildout-base.cfg + known-good-versions.cfg extensions = mr.developer var-dir=/data @@ -99,13 +100,3 @@ RelStorage = 2.1.1 pg8000 = 1.12.5 perfmetrics = 3.3.0 et-xmlfile = 1.1.0 -# Pin otherwise unpinned dependencies to known-good Python 2.7 releases -# (e.g. magnitude >= 1.1 requires Python 3.9) -magnitude = 1.0.1 -Products.DataGridField = 1.9.6 -collective.recipe.plonesite = 1.12.0 -cssselect2 = 0.2.2 -more-itertools = 5.0.0 -plone.app.jquerytools = 1.9.5 -plone.jsonapi.core = 0.7.0 -tinycss2 = 0.6.1 diff --git a/senaite/Dockerfile b/senaite/Dockerfile index 63c6075..c8f195c 100644 --- a/senaite/Dockerfile +++ b/senaite/Dockerfile @@ -25,7 +25,7 @@ RUN useradd --system -m -d $SENAITE_HOME -U -u 500 $SENAITE_USER RUN mkdir -p $SENAITE_INSTANCE_HOME $SENAITE_FILESTORAGE $SENAITE_BLOBSTORAGE # Copy Buildout -COPY resources/requirements.txt resources/versions.cfg resources/buildout.cfg resources/develop.cfg $SENAITE_INSTANCE_HOME/ +COPY resources/requirements.txt resources/versions.cfg resources/known-good-versions.cfg resources/buildout.cfg resources/develop.cfg $SENAITE_INSTANCE_HOME/ #RUN chown -R senaite:senaite $SENAITE_INSTANCE_HOME $SENAITE_FILESTORAGE $SENAITE_BLOBSTORAGE diff --git a/senaite/resources/buildout.cfg b/senaite/resources/buildout.cfg index f0ef0ea..30ac078 100644 --- a/senaite/resources/buildout.cfg +++ b/senaite/resources/buildout.cfg @@ -2,6 +2,7 @@ index = https://pypi.org/simple/ extends = buildout-base.cfg + known-good-versions.cfg extensions = mr.developer var-dir=/data @@ -79,13 +80,3 @@ et-xmlfile = 1.1.0 RelStorage = 2.1.1 pg8000 = 1.12.5 perfmetrics = 3.3.0 -# Pin otherwise unpinned dependencies to known-good Python 2.7 releases -# (e.g. magnitude >= 1.1 requires Python 3.9) -magnitude = 1.0.1 -Products.DataGridField = 1.9.6 -collective.recipe.plonesite = 1.12.0 -cssselect2 = 0.2.2 -more-itertools = 5.0.0 -plone.app.jquerytools = 1.9.5 -plone.jsonapi.core = 0.7.0 -tinycss2 = 0.6.1 diff --git a/senaite/resources/known-good-versions.cfg b/senaite/resources/known-good-versions.cfg new file mode 100644 index 0000000..242463f --- /dev/null +++ b/senaite/resources/known-good-versions.cfg @@ -0,0 +1,359 @@ +[versions] +# Exact versions of every egg in the last known-good images +# (mekomsolutions/senaite:2.5.0-1.0.0 and mekomsolutions/senaite-sso). +# Shared by both images: senaite-sso inherits this file from the base image. +# Many dependencies are not pinned by Plone 5.2.14 and their newer releases +# no longer support Python 2.7, so pinning everything keeps builds reproducible. +# buildout.cfg [versions] still takes precedence over this file. +AccessControl = 4.4 +Acquisition = 4.13 +archetypes.schemaextender = 3.0.2 +attrs = 21.4.0 +AuthEncoding = 4.3 +backports.functools-lru-cache = 1.6.4 +beautifulsoup4 = 4.9.3 +bleach = 3.3.1 +bobtemplates.plone = 5.2.2 +borg.localrole = 3.1.9 +BTrees = 4.11.3 +cairocffi = 0.9.0 +CairoSVG = 1.0.20 +calmjs.parse = 1.2.5 +case-conversion = 2.1.0 +certifi = 2020.4.5.1 +cffi = 1.15.1 +Chameleon = 3.9.1 +chardet = 4.0.0 +collective.checkdocs = 0.2 +collective.monkeypatcher = 1.2.1 +collective.recipe.backup = 4.1.0 +collective.recipe.plonesite = 1.12.0 +collective.xmltestreport = 2.0.2 +collective.z3cform.datagridfield = 1.5.3 +colorama = 0.4.6 +configparser = 4.0.2 +contextlib2 = 0.6.0.post1 +cryptography = 3.3.2 +cssselect = 1.1.0 +cssselect2 = 0.2.2 +DateTime = 4.9 +decorator = 4.4.2 +diazo = 1.5.0 +dicttoxml = 1.7.16 +distlib = 0.3.6 +DocumentTemplate = 3.4 +docutils = 0.15.2 +enum34 = 1.1.10 +et-xmlfile = 1.1.0 +ExtensionClass = 4.9 +feedparser = 5.2.1 +filelock = 3.2.1 +five.customerize = 2.1.0 +five.globalrequest = 99.1 +five.intid = 1.2.6 +five.localsitemanager = 3.4 +funcsigs = 1.0.2 +functools32 = 3.2.3.post2 +future = 0.18.2 +futures = 3.3.0 +html5lib = 1.1 +i18ndude = 5.5.0 +icalendar = 4.1.0 +idna = 2.10 +importlib-metadata = 0.23 +importlib-resources = 3.3.1 +ipaddress = 1.0.23 +isort = 4.3.21 +jdcal = 1.4.1 +Jinja2 = 2.11.3 +jsonschema = 3.2.0 +lxml = 4.8.0 +magnitude = 1.0.1 +Markdown = 3.1.1 +MarkupSafe = 1.1.1 +Missing = 4.2 +mockup = 3.2.8 +more-itertools = 5.0.0 +mr.bob = 1.0.1 +mr.developer = 2.0.1 +MultiMapping = 4.1 +multipart = 0.1.1 +openpyxl = 2.6.4 +packaging = 20.9 +Paste = 3.5.2 +PasteDeploy = 2.1.1 +pathlib = 1.0.1 +pathlib2 = 2.3.7.post1 +pdfrw = 0.4 +perfmetrics = 3.3.0 +Persistence = 3.6 +persistent = 4.9.3 +pg8000 = 1.12.5 +piexif = 1.1.3 +Pillow = 6.2.2 +pkginfo = 1.8.3 +platformdirs = 2.0.2 +Plone = 5.2.14 +plone.alterego = 1.1.5 +plone.api = 1.11.1 +plone.app.blob = 1.8.2 +plone.app.caching = 2.2.1 +plone.app.collection = 1.2.8 +plone.app.content = 3.8.10 +plone.app.contentlisting = 2.0.8 +plone.app.contentmenu = 2.3.4 +plone.app.contentrules = 4.1.6 +plone.app.contenttypes = 2.2.3 +plone.app.customerize = 1.3.12 +plone.app.dexterity = 2.6.11 +plone.app.discussion = 3.4.7 +plone.app.event = 3.2.14 +plone.app.folder = 1.3.2 +plone.app.i18n = 3.0.6 +plone.app.imaging = 2.1.2 +plone.app.intid = 1.1.4 +plone.app.iterate = 4.0.3 +plone.app.jquerytools = 1.9.5 +plone.app.layout = 3.5.2 +plone.app.linkintegrity = 3.6.1 +plone.app.locales = 5.1.33 +plone.app.lockingbehavior = 1.0.7 +plone.app.multilingual = 5.6.6 +plone.app.portlets = 4.4.9 +plone.app.querystring = 1.6.1 +plone.app.redirector = 2.2.1 +plone.app.referenceablebehavior = 0.7.9 +plone.app.registry = 1.7.9 +plone.app.relationfield = 2.0.3 +plone.app.textfield = 1.3.7 +plone.app.theming = 4.1.8 +plone.app.upgrade = 2.1.7 +plone.app.users = 2.6.8 +plone.app.uuid = 2.0.2 +plone.app.versioningbehavior = 1.4.6 +plone.app.viewletmanager = 3.1.3 +plone.app.vocabularies = 4.3.0 +plone.app.widgets = 3.0.7 +plone.app.workflow = 4.0.4 +plone.app.z3cform = 3.2.4 +plone.autoform = 1.9.1 +plone.batching = 1.1.7 +plone.behavior = 1.4.0 +plone.browserlayer = 2.2.4 +plone.cachepurging = 2.0.4 +plone.caching = 1.2.2 +plone.contentrules = 2.1.3 +plone.dexterity = 2.10.5 +plone.event = 1.4.2 +plone.folder = 3.1.0 +plone.formwidget.namedfile = 2.1.3 +plone.formwidget.recurrence = 2.1.5 +plone.i18n = 4.0.7 +plone.indexer = 1.0.7 +plone.intelligenttext = 3.1.0 +plone.jsonapi.core = 0.7.0 +plone.keyring = 3.2.1 +plone.locking = 2.3.0 +plone.memoize = 3.0.0 +plone.namedfile = 5.6.1 +plone.outputfilters = 4.1.0 +plone.portlet.collection = 3.3.7 +plone.portlet.static = 3.1.6 +plone.portlets = 2.3.3 +plone.protect = 4.1.8 +plone.recipe.command = 1.1 +plone.recipe.precompiler = 0.7.2 +plone.recipe.unifiedinstaller = 5.2b1 +plone.recipe.zeoserver = 2.0.3 +plone.recipe.zope2instance = 6.12.1 +plone.registry = 1.2.1 +plone.reload = 3.0.2 +plone.resource = 2.1.4 +plone.resourceeditor = 3.0.4 +plone.rest = 1.6.2 +plone.restapi = 7.8.3 +plone.rfc822 = 2.0.2 +plone.scale = 3.1.2 +plone.schema = 1.4.0 +plone.schemaeditor = 3.0.3 +plone.session = 3.7.5 +plone.staticresources = 1.4.6 +plone.stringinterp = 1.3.3 +plone.subrequest = 1.9.3 +plone.supermodel = 1.6.5 +plone.synchronize = 1.0.4 +plone.theme = 3.0.7 +plone.transformchain = 2.0.2 +plone.uuid = 1.0.6 +plone.z3cform = 1.1.3 +plonetheme.barceloneta = 2.1.11 +pluggy = 0.13.1 +ply = 3.11 +Products.Archetypes = 1.16.6 +Products.ATContentTypes = 3.0.7 +Products.BTreeFolder2 = 4.4 +Products.CMFCore = 2.7.1 +Products.CMFDiffTool = 3.3.3 +Products.CMFDynamicViewFTI = 6.0.3 +Products.CMFEditions = 3.3.5 +Products.CMFFormController = 4.1.4 +Products.CMFPlacefulWorkflow = 2.0.4 +Products.CMFPlone = 5.2.14 +Products.CMFQuickInstallerTool = 4.0.4 +Products.CMFUid = 3.5 +Products.contentmigration = 2.2.2 +Products.DataGridField = 1.9.6 +Products.DateRecurringIndex = 3.0.1 +Products.DCWorkflow = 2.7.0 +Products.ExtendedPathIndex = 4.0.1 +Products.ExternalEditor = 3.1.0 +Products.ExternalMethod = 4.7 +Products.GenericSetup = 2.3.0 +Products.isurlinportal = 1.2.1 +Products.MailHost = 4.13 +Products.MimetypesRegistry = 2.1.9 +Products.PlonePAS = 6.0.8 +Products.PluggableAuthService = 2.8.1 +Products.PluginRegistry = 1.11 +Products.PortalTransforms = 3.2.2 +Products.PythonScripts = 4.15 +Products.Sessions = 4.15 +Products.SiteErrorLog = 5.7 +Products.StandardCacheManagers = 4.2 +Products.statusmessages = 5.0.5 +Products.TemporaryFolder = 5.3 +Products.validation = 2.1.3 +Products.ZCatalog = 5.4 +Products.ZopeVersionControl = 3.0.0 +Products.ZSQLMethods = 3.16 +py = 1.11.0 +pycountry = 18.12.8 +pycparser = 2.21 +Pygments = 2.5.2 +PyJWT = 1.7.1 +pyparsing = 2.4.7 +pyphen = 0.11.0 +pyrsistent = 0.15.7 +pyScss = 1.3.7 +python-dateutil = 2.8.2 +python-dotenv = 0.18.0 +python-gettext = 4.1 +pytz = 2022.7 +readme-renderer = 28.0 +Record = 3.6 +regex = 2020.2.20 +RelStorage = 2.1.1 +repoze.xmliter = 0.6.1 +requests = 2.27.1 +requests-toolbelt = 0.10.1 +RestrictedPython = 5.4 +roman = 3.3 +scandir = 1.10.0 +senaite.app.listing = 2.5.0 +senaite.app.spotlight = 2.5.0 +senaite.app.supermodel = 2.5.0 +senaite.core = 2.5.0 +senaite.impress = 2.5.0 +senaite.jsonapi = 2.5.0 +senaite.lims = 2.5.0 +simplejson = 3.18.1 +singledispatch = 3.7.0 +six = 1.16.0 +soupsieve = 1.9.6 +tempstorage = 5.2 +tinycss2 = 0.6.1 +toml = 0.10.2 +tox = 3.28.0 +tqdm = 4.64.1 +transaction = 3.0.1 +trollius = 2.2.post1 +twine = 1.15.0 +typing = 3.10.0.0 +Unidecode = 0.4.1 +urllib3 = 1.26.13 +virtualenv = 20.15.1 +waitress = 1.4.4 +WeasyPrint = 0.42.3 +webencodings = 0.5.1 +WebOb = 1.8.7 +WebTest = 2.0.35 +Werkzeug = 1.0.0 +WSGIProxy2 = 0.4.6 +z3c.autoinclude = 0.4.1 +z3c.caching = 2.2 +z3c.form = 3.7.1 +z3c.formwidget.query = 1.0.0 +z3c.jbot = 1.1.1 +z3c.objpath = 1.3 +z3c.pt = 3.3.1 +z3c.recipe.usercrontab = 1.5 +z3c.relationfield = 0.9.0 +z3c.zcmlhook = 1.1 +zc.lockfile = 2.0 +zc.recipe.egg = 2.0.7 +zc.relation = 1.1.post2 +ZConfig = 3.6.1 +zdaemon = 4.4 +ZEO = 5.3.0 +zest.pocompile = 1.6.0 +zest.releaser = 6.22.2 +zExceptions = 4.3 +zipp = 1.2.0 +ZODB = 5.8.0 +ZODB3 = 3.11.0 +zodbpickle = 2.6 +Zope = 4.8.10 +zope.annotation = 4.8 +zope.app.locales = 4.3 +zope.browser = 2.4 +zope.browsermenu = 4.4 +zope.browserpage = 4.4.0 +zope.browserresource = 4.4 +zope.cachedescriptors = 4.4 +zope.component = 4.6.2 +zope.componentvocabulary = 2.3.0 +zope.configuration = 4.4.1 +zope.container = 4.10 +zope.contentprovider = 4.2.1 +zope.contenttype = 4.6 +zope.copy = 4.3 +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.globalrequest = 1.6 +zope.hookable = 5.4 +zope.i18n = 4.9.0 +zope.i18nmessageid = 5.1.1 +zope.interface = 5.5.2 +zope.intid = 4.4.0 +zope.keyreference = 4.2.0 +zope.lifecycleevent = 4.4 +zope.location = 4.3 +zope.mkzeoinstance = 4.1 +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 +Zope2 = 4.0 +ZopeUndo = 4.3 +ZServer = 4.0.2