diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index b15ff11d2e..f7dea26746 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -15,6 +15,7 @@ jobs: - '3.9' - '3.10' - '3.11' + - '3.12' runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 @@ -22,7 +23,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: python -mpip install --upgrade wheel pytest tox + run: python -mpip install --upgrade wheel pytest tox setuptools - name: Get tox target id: toxtarget run: | diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 30b1ea4949..0000000000 --- a/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM centos:centos7 -RUN localedef -i en_US -f UTF-8 en_US.UTF-8 -ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' -RUN yum install -y centos-release-scl \ - && yum install -y rh-python36 \ - && yum install -y python-virtualenv \ - && yum install -y openssl-devel git \ - && virtualenv /py2 && /py2/bin/pip install -U pip tox future wheel -ENV PATH=/opt/rh/rh-python36/root/bin/:$PATH -RUN python -m venv /py3 && /py3/bin/pip install -U pip tox future wheel - -ENV VIRTUAL_ENV=/py3 -ENV PATH="$VIRTUAL_ENV/bin:$PATH" - -RUN useradd -ms /bin/bash tox -USER tox - -# Optimize for fixing tests -COPY --chown=tox:tox *.py /src/ -COPY --chown=tox:tox README.rst /src -COPY --chown=tox:tox MANIFEST.in /src -COPY --chown=tox:tox omero /src/omero -COPY --chown=tox:tox omeroweb /src/omeroweb -WORKDIR /src - -# Copy test-related files and run -COPY --chown=tox:tox *.ini /src/ -COPY --chown=tox:tox test /src/test -ENV PIP_CACHE_DIR=/tmp/pip-cache -ENTRYPOINT ["/py3/bin/tox"] diff --git a/omero/plugins/web.py b/omero/plugins/web.py index 0d96b36976..9653eb9a8b 100644 --- a/omero/plugins/web.py +++ b/omero/plugins/web.py @@ -9,7 +9,6 @@ """ import traceback -from future.utils import bytes_to_native_str from datetime import datetime from omero.cli import DiagnosticsControl from omero.cli import CLI @@ -21,11 +20,7 @@ from functools import wraps from omero_ext.argparse import SUPPRESS -try: - from omero_ext.path import path -except ImportError: - # Python 2 - from path import path +from omero_ext.path import path from pkg_resources import resource_string from omero.install.windows_warning import windows_warning, WINDOWS_WARNING @@ -348,9 +343,7 @@ def config(self, args, settings): self.ctx.die(679, "Web template configuration requires" "wsgi or wsgi-tcp.") template_file = "%s.conf.template" % server - c = bytes_to_native_str( - resource_string("omeroweb", "templates/" + template_file) - ) + c = resource_string("omeroweb", "templates/" + template_file).decode("utf-8") self.ctx.out(c % d) def syncmedia(self, args): diff --git a/omeroweb/connector.py b/omeroweb/connector.py index de6775e76a..b21071ec39 100644 --- a/omeroweb/connector.py +++ b/omeroweb/connector.py @@ -22,8 +22,6 @@ import re import logging -from future.utils import with_metaclass - from omero import client_wrapper from omeroweb.version import omeroweb_version as omero_version @@ -40,8 +38,7 @@ def __iter__(cls): return iter(cls._registry.values()) -# with_metaclass to support python2 and python3 -class ServerBase(with_metaclass(IterRegistry)): +class ServerBase(metaclass=IterRegistry): _next_id = 1 def __init__(self, host, port, server=None): diff --git a/omeroweb/custom_forms.py b/omeroweb/custom_forms.py index a9575c0977..f3dc0e098b 100644 --- a/omeroweb/custom_forms.py +++ b/omeroweb/custom_forms.py @@ -20,7 +20,6 @@ # import warnings -from past.builtins import basestring from django import forms from django.utils.encoding import smart_str @@ -62,12 +61,10 @@ def full_clean(self): initial = self.initial.get(name, field.initial) value = field.clean(value, initial) elif isinstance(field, CharField): - if ( - value is not None - and isinstance(value, basestring) - and len(value) > 0 - ): - value = str(smart_str(value)) + if value is not None and isinstance(value, str) and len(value) > 0: + value = str( + smart_str(value) + ) # TODO: This is probably a noop now else: value = field.clean(value) else: diff --git a/omeroweb/feedback/sendfeedback.py b/omeroweb/feedback/sendfeedback.py index 931cc256bf..0c3b9b659e 100644 --- a/omeroweb/feedback/sendfeedback.py +++ b/omeroweb/feedback/sendfeedback.py @@ -29,19 +29,9 @@ import logging from urllib.parse import urlencode -try: - # python2 - from urllib2 import urlopen, Request, HTTPError, URLError -except ImportError: - # python3 - from urllib.request import urlopen, Request - from urllib.error import HTTPError, URLError -try: - # python2 - from urlparse import urljoin -except ImportError: - # python3 - from urllib.parse import urljoin +from urllib.request import urlopen, Request +from urllib.error import HTTPError, URLError +from urllib.parse import urljoin from omeroweb.version import omeroweb_version as omero_version diff --git a/omeroweb/manage.py b/omeroweb/manage.py index f3edf35590..ec7f7c256c 100644 --- a/omeroweb/manage.py +++ b/omeroweb/manage.py @@ -38,7 +38,7 @@ except ImportError: # The above import may fail for some other reason. Ensure that the # issue is really that Django is missing to avoid masking other - # exceptions on Python 2. + # exceptions. try: import django except ImportError: diff --git a/omeroweb/settings.py b/omeroweb/settings.py index 47a87caf1e..b48f33ccc0 100755 --- a/omeroweb/settings.py +++ b/omeroweb/settings.py @@ -38,7 +38,6 @@ import json import random import string -from builtins import str as text import portalocker from omero.util.concurrency import get_event @@ -1259,18 +1258,6 @@ def check_worker_class(c): return str(c) -def check_threading(t): - t = int(t) - if t > 1: - try: - import concurrent.futures # NOQA - except ImportError: - raise ImportError( - "You are using sync workers with " "multiple threads. Install futures" - ) - return t - - # DEVELOPMENT_SETTINGS_MAPPINGS - WARNING: For each setting developer MUST open # a ticket that needs to be resolved before a release either by moving the # setting to CUSTOM_SETTINGS_MAPPINGS or by removing the setting at all. @@ -1298,7 +1285,7 @@ def check_threading(t): "omero.web.wsgi_threads": [ "WSGI_THREADS", 1, - check_threading, + int, ( "(SYNC WORKERS only) The number of worker threads for handling " "requests. Check Gunicorn Documentation " @@ -1770,8 +1757,8 @@ def report_settings(module): # Load server list and freeze def load_server_list(): for s in SERVER_LIST: # from CUSTOM_SETTINGS_MAPPINGS # noqa - server = (len(s) > 2) and text(s[2]) or None - Server(host=text(s[0]), port=int(s[1]), server=server) + server = (len(s) > 2) and str(s[2]) or None + Server(host=str(s[0]), port=int(s[1]), server=server) Server.freeze() diff --git a/omeroweb/testlib/__init__.py b/omeroweb/testlib/__init__.py index 80ac4ed313..90eda3cba3 100644 --- a/omeroweb/testlib/__init__.py +++ b/omeroweb/testlib/__init__.py @@ -20,9 +20,6 @@ """ Library for Web integration tests """ -from __future__ import print_function - -from future import standard_library import json import warnings @@ -32,7 +29,6 @@ from omero.testlib import ITest -standard_library.install_aliases() # noqa from urllib.parse import urlencode # noqa diff --git a/omeroweb/utils.py b/omeroweb/utils.py index a9bae69223..7145ffea33 100644 --- a/omeroweb/utils.py +++ b/omeroweb/utils.py @@ -26,7 +26,6 @@ from django.utils.http import urlencode from django.urls import reverse from django.urls import NoReverseMatch -from past.builtins import basestring logger = logging.getLogger(__name__) @@ -95,7 +94,7 @@ def reverse_with_params(*args, **kwargs): except NoReverseMatch: return url if qs: - if not isinstance(qs, basestring): + if not isinstance(qs, str): qs = urlencode(qs) url += "?" + qs return url diff --git a/omeroweb/webadmin/custom_forms.py b/omeroweb/webadmin/custom_forms.py index 6b7485f2ce..4d817bb665 100644 --- a/omeroweb/webadmin/custom_forms.py +++ b/omeroweb/webadmin/custom_forms.py @@ -34,8 +34,6 @@ from django.utils.encoding import smart_str from django.core.validators import validate_email, EMPTY_VALUES -from past.builtins import long - ################################################################## # Fields @@ -101,7 +99,7 @@ def to_python(self, value): return None res = False for q in self.queryset: - if long(value) == q.id: + if int(value) == q.id: res = True if not res: raise ValidationError(self.error_messages["invalid_choice"]) @@ -167,10 +165,10 @@ def to_python(self, value): exps = self.queryset for experimenter in exps: if hasattr(experimenter.id, "val"): - if long(value) == experimenter.id.val: + if int(value) == experimenter.id.val: res = True else: - if long(value) == experimenter.id: + if int(value) == experimenter.id: res = True if not res: raise ValidationError(self.error_messages["invalid_choice"]) @@ -221,17 +219,17 @@ def to_python(self, value): final_values = [] for val in value: try: - long(val) + int(val) except Exception: raise ValidationError(self.error_messages["invalid_choice"]) else: res = False for q in self.queryset: if hasattr(q.id, "val"): - if long(val) == q.id.val: + if int(val) == q.id.val: res = True else: - if long(val) == q.id: + if int(val) == q.id: res = True if not res: raise ValidationError(self.error_messages["invalid_choice"]) @@ -354,9 +352,9 @@ def checkValue(q, value): if not hasattr(q, "id"): return False if hasattr(q.id, "val"): - if long(value) == q.id.val: + if int(value) == q.id.val: return True - if long(value) == q.id: + if int(value) == q.id: return True for q in self.queryset: @@ -421,17 +419,17 @@ def to_python(self, value): final_values = [] for val in value: try: - long(val) + int(val) except Exception: raise ValidationError(self.error_messages["invalid_choice"]) else: res = False for q in self.queryset: if hasattr(q.id, "val"): - if long(val) == q.id.val: + if int(val) == q.id.val: res = True else: - if long(val) == q.id: + if int(val) == q.id: res = True if not res: raise ValidationError(self.error_messages["invalid_choice"]) diff --git a/omeroweb/webadmin/forms.py b/omeroweb/webadmin/forms.py index abf75fa4f0..a017ac1924 100644 --- a/omeroweb/webadmin/forms.py +++ b/omeroweb/webadmin/forms.py @@ -23,10 +23,7 @@ import logging -try: - from collections import OrderedDict # Python 2.7+ only -except Exception: - pass +from collections import OrderedDict from django import forms from django.forms.widgets import Textarea diff --git a/omeroweb/webclient/controller/history.py b/omeroweb/webclient/controller/history.py index bae3041fa3..436ed7b158 100755 --- a/omeroweb/webclient/controller/history.py +++ b/omeroweb/webclient/controller/history.py @@ -27,7 +27,6 @@ import datetime import time -from past.builtins import long from django.conf import settings from omeroweb.webclient.controller import BaseController @@ -172,8 +171,8 @@ def calendar_items(self, month, monthrange): ("%i-%s-%i 23:59:59" % (self.year, mn, monthrange)), "%Y-%m-%d %H:%M:%S" ) - start = long(time.mktime(d1.timetuple()) + 1e-6 * d1.microsecond) * 1000 - end = long(time.mktime(d2.timetuple()) + 1e-6 * d2.microsecond) * 1000 + start = int(time.mktime(d1.timetuple()) + 1e-6 * d1.microsecond) * 1000 + end = int(time.mktime(d2.timetuple()) + 1e-6 * d2.microsecond) * 1000 all_logs = self.conn.getEventsByPeriod(start, end, self.eid) items = dict() @@ -218,8 +217,8 @@ def get_items(self, page=None): ("%i-%s-%s 23:59:59" % (self.year, mn, dy)), "%Y-%m-%d %H:%M:%S" ) - start = long(time.mktime(d1.timetuple()) + 1e-6 * d1.microsecond) * 1000 - end = long(time.mktime(d2.timetuple()) + 1e-6 * d2.microsecond) * 1000 + start = int(time.mktime(d1.timetuple()) + 1e-6 * d1.microsecond) * 1000 + end = int(time.mktime(d2.timetuple()) + 1e-6 * d2.microsecond) * 1000 self.day_items = list() self.day_items_size = 0 diff --git a/omeroweb/webclient/controller/search.py b/omeroweb/webclient/controller/search.py index b77d0f348e..844b8210af 100755 --- a/omeroweb/webclient/controller/search.py +++ b/omeroweb/webclient/controller/search.py @@ -23,7 +23,6 @@ # Version: 1.0 # -from past.builtins import long import time import omero import logging @@ -89,11 +88,11 @@ def search( created = [ rtime( - long(time.mktime(d1.timetuple()) + 1e-6 * d1.microsecond) + int(time.mktime(d1.timetuple()) + 1e-6 * d1.microsecond) * 1000 ), rtime( - long(time.mktime(d2.timetuple()) + 1e-6 * d2.microsecond) + int(time.mktime(d2.timetuple()) + 1e-6 * d2.microsecond) * 1000 ), ] diff --git a/omeroweb/webclient/controller/share.py b/omeroweb/webclient/controller/share.py index 2085e43d94..15cf6a0fbc 100644 --- a/omeroweb/webclient/controller/share.py +++ b/omeroweb/webclient/controller/share.py @@ -25,7 +25,6 @@ import datetime import time -from past.builtins import long from omero.rtypes import rtime @@ -99,7 +98,7 @@ def createShare(self, host, images, message, members, enable, expiration=None): expiration + " 23:59:59", "%Y-%m-%d %H:%M:%S" ) expiration_date = ( - long(time.mktime(d1.timetuple()) + 1e-6 * d1.microsecond) * 1000 + int(time.mktime(d1.timetuple()) + 1e-6 * d1.microsecond) * 1000 ) image_objects = list(self.conn.getObjects("Image", images)) member_objects = list(self.conn.getObjects("Experimenter", members)) @@ -114,7 +113,7 @@ def createDiscussion(self, host, message, members, enable, expiration=None): expiration + " 23:59:59", "%Y-%m-%d %H:%M:%S" ) expiration_date = rtime( - long(time.mktime(d1.timetuple()) + 1e-6 * d1.microsecond) * 1000 + int(time.mktime(d1.timetuple()) + 1e-6 * d1.microsecond) * 1000 ) member_objects = list(self.conn.getObjects("Experimenter", members)) return self.conn.createShare( @@ -128,7 +127,7 @@ def updateShareOrDiscussion(self, host, message, members, enable, expiration=Non expiration + " 23:59:59", "%Y-%m-%d %H:%M:%S" ) expiration_date = ( - long(time.mktime(d1.timetuple()) + 1e-6 * d1.microsecond) * 1000 + int(time.mktime(d1.timetuple()) + 1e-6 * d1.microsecond) * 1000 ) old_groups = [m._obj for m in self.conn.getAllMembers(self.share.id)] diff --git a/omeroweb/webclient/tree.py b/omeroweb/webclient/tree.py index 8f6827acd4..67e3443056 100644 --- a/omeroweb/webclient/tree.py +++ b/omeroweb/webclient/tree.py @@ -23,7 +23,6 @@ import time import pytz import omero -from builtins import bytes from collections import defaultdict from omero.rtypes import rlong, unwrap, wrap diff --git a/omeroweb/webclient/views.py b/omeroweb/webclient/views.py index 017ca7e328..0978f6d4e0 100755 --- a/omeroweb/webclient/views.py +++ b/omeroweb/webclient/views.py @@ -36,8 +36,6 @@ import sys import warnings from collections import defaultdict -from past.builtins import unicode -from future.utils import bytes_to_native_str from django.utils.html import escape from django.utils.http import url_has_allowed_host_and_scheme @@ -129,10 +127,6 @@ from . import tree -try: - import long -except ImportError: - long = int logger = logging.getLogger(__name__) @@ -154,7 +148,7 @@ def get_long_or_default(request, name, default): val = None val_raw = request.GET.get(name, default) if val_raw is not None: - val = long(val_raw) + val = int(val_raw) return val @@ -292,7 +286,7 @@ def handle_not_logged_in(self, request, error=None, form=None): if form is None: server_id = request.GET.get("server", request.POST.get("server")) if server_id is not None: - initial = {"server": unicode(server_id)} + initial = {"server": str(server_id)} form = LoginForm(initial=initial) else: form = LoginForm() @@ -506,7 +500,7 @@ def _load_template(request, menu, conn=None, url=None, **kwargs): # if we're not already showing 'All Members'... user_id = initially_open_owner try: - user_id = long(user_id) + user_id = int(user_id) except Exception: user_id = None # check if user_id is in a currnt group @@ -554,8 +548,8 @@ def _load_template(request, menu, conn=None, url=None, **kwargs): } context["groups"] = groups context["myColleagues"] = myColleagues - context["active_group"] = conn.getObject("ExperimenterGroup", long(active_group)) - context["active_user"] = conn.getObject("Experimenter", long(user_id)) + context["active_group"] = conn.getObject("ExperimenterGroup", int(active_group)) + context["active_user"] = conn.getObject("Experimenter", int(user_id)) context["initially_select"] = show.initially_select context["initially_open"] = show.initially_open context["isLeader"] = conn.isLeader() @@ -642,7 +636,7 @@ def api_group_list(request, conn=None, **kwargs): def api_experimenter_detail(request, experimenter_id, conn=None, **kwargs): # Validate parameter try: - experimenter_id = long(experimenter_id) + experimenter_id = int(experimenter_id) except ValueError: return HttpResponseBadRequest("Invalid experimenter id") @@ -817,7 +811,7 @@ def api_image_list(request, conn=None, **kwargs): # a share connection in @login_required. # We don't support ?share_id in query string since this would allow a # share connection to be created for ALL urls, instead of just this one. - share_id = "share_id" in kwargs and long(kwargs["share_id"]) or None + share_id = "share_id" in kwargs and int(kwargs["share_id"]) or None try: # Get the images @@ -964,25 +958,25 @@ def create_link(parent_type, parent_id, child_type, child_id): # dataset/plate is an orphan return "orphan" if parent_type == "project": - project = ProjectI(long(parent_id), False) + project = ProjectI(int(parent_id), False) if child_type == "dataset": - dataset = DatasetI(long(child_id), False) + dataset = DatasetI(int(child_id), False) link = ProjectDatasetLinkI() link.setParent(project) link.setChild(dataset) return link elif parent_type == "dataset": - dataset = DatasetI(long(parent_id), False) + dataset = DatasetI(int(parent_id), False) if child_type == "image": - image = ImageI(long(child_id), False) + image = ImageI(int(child_id), False) link = DatasetImageLinkI() link.setParent(dataset) link.setChild(image) return link elif parent_type == "screen": - screen = ScreenI(long(parent_id), False) + screen = ScreenI(int(parent_id), False) if child_type == "plate": - plate = PlateI(long(child_id), False) + plate = PlateI(int(child_id), False) link = ScreenPlateLinkI() link.setParent(screen) link.setChild(plate) @@ -990,8 +984,8 @@ def create_link(parent_type, parent_id, child_type, child_id): elif parent_type == "tagset": if child_type == "tag": link = AnnotationAnnotationLinkI() - link.setParent(TagAnnotationI(long(parent_id), False)) - link.setChild(TagAnnotationI(long(child_id), False)) + link.setParent(TagAnnotationI(int(parent_id), False)) + link.setChild(TagAnnotationI(int(child_id), False)) return link return None @@ -1020,11 +1014,7 @@ def api_links(request, conn=None, **kwargs): {"Error": "Need to POST or DELETE JSON data to update links"}, status=405 ) # Handle link creation/deletion - try: - json_data = json.loads(request.body) - except TypeError: - # for Python 3.5 - json_data = json.loads(bytes_to_native_str(request.body)) + json_data = json.loads(request.body) if request.method == "POST": return _api_links_POST(conn, json_data) @@ -1462,7 +1452,7 @@ def load_plate(request, o1_type=None, o1_id=None, conn=None, **kwargs): kw = dict() if o1_type is not None: if o1_id is not None and int(o1_id) > 0: - kw[str(o1_type)] = long(o1_id) + kw[str(o1_type)] = int(o1_id) try: manager = BaseContainer(conn, **kw) @@ -1661,7 +1651,7 @@ def load_searching(request, form=None, conn=None, **kwargs): if len(queryId) == 0: continue try: - searchById = long(queryId) + searchById = int(queryId) if searchById in idSet: continue idSet.add(searchById) @@ -1767,7 +1757,7 @@ def load_metadata_details(request, c_type, c_id, conn=None, share_id=None, **kwa form_comment = CommentAnnotationForm(initial=initial) else: try: - manager = BaseContainer(conn, **{str(c_type): long(c_id), "index": index}) + manager = BaseContainer(conn, **{str(c_type): int(c_id), "index": index}) except AttributeError as x: return handlerInternalError(request, x) if share_id is not None: @@ -1805,7 +1795,7 @@ def load_metadata_preview(request, c_type, c_id, conn=None, share_id=None, **kwa # the index of a field within a well index = getIntOrDefault(request, "index", 0) - manager = BaseContainer(conn, **{str(c_type): long(c_id)}) + manager = BaseContainer(conn, **{str(c_type): int(c_id)}) if share_id: context["share"] = BaseShare(conn, share_id) if c_type == "well": @@ -1864,7 +1854,7 @@ def load_metadata_hierarchy(request, c_type, c_id, conn=None, **kwargs): static tree. Used by an AJAX call from the metadata_general panel. """ - manager = BaseContainer(conn, **{str(c_type): long(c_id)}) + manager = BaseContainer(conn, **{str(c_type): int(c_id)}) context = {"manager": manager} context["template"] = "webclient/annotations/metadata_hierarchy.html" @@ -1888,7 +1878,7 @@ def load_metadata_acquisition( manager.getComments(c_id) else: template = "webclient/annotations/metadata_acquisition.html" - manager = BaseContainer(conn, **{str(c_type): long(c_id)}) + manager = BaseContainer(conn, **{str(c_type): int(c_id)}) except AttributeError as x: return handlerInternalError(request, x) @@ -2853,7 +2843,7 @@ def edit_channel_names(request, imageId, conn=None, **kwargs): parentId = request.POST.get("parentId", None) if parentId is not None: ptype = parentId.split("-")[0].title() - pid = long(parentId.split("-")[1]) + pid = int(parentId.split("-")[1]) counts = conn.setChannelNames(ptype, [pid], nameDict, channelCount=sizeC) else: counts = conn.setChannelNames("Image", [image.getId()], nameDict) @@ -2962,7 +2952,7 @@ def manage_action_containers( else: d = dict() for e in form.errors.items(): - d.update({e[0]: unicode(e[1])}) + d.update({e[0]: str(e[1])}) rdict = {"bad": "true", "errs": d} return JsonResponse(rdict) @@ -3054,7 +3044,7 @@ def manage_action_containers( else: d = dict() for e in form.errors.items(): - d.update({e[0]: unicode(e[1])}) + d.update({e[0]: str(e[1])}) rdict = {"bad": "true", "errs": d} return JsonResponse(rdict) else: @@ -3086,7 +3076,7 @@ def manage_action_containers( else: d = dict() for e in form.errors.items(): - d.update({e[0]: unicode(e[1])}) + d.update({e[0]: str(e[1])}) rdict = {"bad": "true", "errs": d} return JsonResponse(rdict) else: @@ -3250,7 +3240,7 @@ def omero_table(request, file_id, mtype=None, conn=None, **kwargs): pass # Check if file exists since _table_query() doesn't check - file_id = long(file_id) + file_id = int(file_id) orig_file = conn.getObject("OriginalFile", file_id) if orig_file is None: raise Http404("OriginalFile %s not found" % file_id) @@ -3626,11 +3616,7 @@ def activities(request, conn=None, **kwargs): return rv elif request.method == "DELETE": - try: - json_data = json.loads(request.body) - except TypeError: - # for Python 3.5 - json_data = json.loads(bytes_to_native_str(request.body)) + json_data = json.loads(request.body) jobId = json_data.get("jobId", None) if jobId is not None: jobId = str(jobId) @@ -3872,7 +3858,7 @@ def activities(request, conn=None, **kwargs): if cb.returncode != 0: kwargs["Message"] = ( f"Script exited with failure." - f" (returncode={ cb.returncode })" + f" (returncode={cb.returncode})" ) update_callback(request, cbString, **kwargs) new_results.append(cbString) @@ -4116,7 +4102,7 @@ def script_ui(request, scriptId, conn=None, **kwargs): scriptService = conn.getScriptService() try: - params = scriptService.getParams(long(scriptId)) + params = scriptService.getParams(int(scriptId)) except Exception as ex: if ex.message.lower().startswith("no processor available"): return { @@ -4129,7 +4115,7 @@ def script_ui(request, scriptId, conn=None, **kwargs): paramData = {} - paramData["id"] = long(scriptId) + paramData["id"] = int(scriptId) paramData["name"] = params.name.replace("_", " ") paramData["description"] = params.description paramData["authors"] = ", ".join([a for a in params.authors]) @@ -4166,7 +4152,7 @@ def script_ui(request, scriptId, conn=None, **kwargs): i["default"] = ",".join([str(d) for d in i["default"]]) elif isinstance(pt, bool): i["boolean"] = True - elif isinstance(pt, int) or isinstance(pt, long): + elif isinstance(pt, int) or isinstance(pt, int): # will stop the user entering anything other than numbers. i["number"] = "number" elif isinstance(pt, float): @@ -4203,7 +4189,7 @@ def script_ui(request, scriptId, conn=None, **kwargs): # if we've not found a match, check whether we have "Well" selected if len(IDsParam["default"]) == 0 and request.GET.get("Well", None) is not None: if "Image" in Data_TypeParam["options"]: - wellIds = [long(j) for j in request.GET.get("Well", None).split(",")] + wellIds = [int(j) for j in request.GET.get("Well", None).split(",")] wellIdx = 0 try: wellIdx = int(request.GET.get("Index", 0)) @@ -4252,7 +4238,7 @@ def figure_script(request, scriptName, conn=None, **kwargs): wellIds = request.GET.get("Well", None) if wellIds is not None: - wellIds = [long(i) for i in wellIds.split(",")] + wellIds = [int(i) for i in wellIds.split(",")] wells = conn.getObjects("Well", wellIds) wellIdx = getIntOrDefault(request, "Index", 0) imageIds = [str(w.getImage(wellIdx).getId()) for w in wells] @@ -4677,7 +4663,7 @@ def chgrp(request, conn=None, **kwargs): group_id = getIntOrDefault(request, "group_id", None) if group_id is None: return JsonResponse({"Error": "chgrp: No group_id specified"}) - group_id = long(group_id) + group_id = int(group_id) def getObjectOwnerId(r): for t in ["Dataset", "Image", "Plate"]: @@ -4749,15 +4735,15 @@ def getObjectOwnerId(r): plate_ids = request.POST.get("Plate", []) if project_ids: - project_ids = [long(x) for x in project_ids.split(",")] + project_ids = [int(x) for x in project_ids.split(",")] if dataset_ids: - dataset_ids = [long(x) for x in dataset_ids.split(",")] + dataset_ids = [int(x) for x in dataset_ids.split(",")] if image_ids: - image_ids = [long(x) for x in image_ids.split(",")] + image_ids = [int(x) for x in image_ids.split(",")] if screen_ids: - screen_ids = [long(x) for x in screen_ids.split(",")] + screen_ids = [int(x) for x in screen_ids.split(",")] if plate_ids: - plate_ids = [long(x) for x in plate_ids.split(",")] + plate_ids = [int(x) for x in plate_ids.split(",")] # TODO Change this user_id to be an experimenter_id in the request as it # is possible that a user is chgrping data from another user so it is @@ -4833,7 +4819,7 @@ def script_run(request, scriptId, conn=None, **kwargs): inputMap = {} - sId = long(scriptId) + sId = int(scriptId) try: params = scriptService.getParams(sId) @@ -4911,8 +4897,6 @@ def script_run(request, scriptId, conn=None, **kwargs): listClass = pval[0].__class__ if listClass == int(1).__class__: listClass = omero.rtypes.rint - if listClass == long(1).__class__: - listClass = omero.rtypes.rlong # construct our list, using appropriate 'type' valueList = [] @@ -5018,7 +5002,7 @@ def ome_tiff_script(request, imageId, conn=None, **kwargs): if image is not None: gid = image.getDetails().group.id.val conn.SERVICE_OPTS.setOmeroGroup(gid) - imageIds = [long(imageId)] + imageIds = [int(imageId)] inputMap = { "Data_Type": wrap("Image"), "IDs": rlist([rlong(id) for id in imageIds]), @@ -5050,8 +5034,7 @@ def run_script(request, conn, sId, inputMap, scriptName="Script"): request.session.modified = True except Exception as x: jobId = str(time()) # E.g. 1312803670.6076391 - # handle python 2 or 3 errors - message = x.message if hasattr(x, "message") else (x.args[0] if x.args else "") + message = x.args[0] if x.args else "" if message and message.startswith("No processor available"): # omero.ResourceError logger.info(traceback.format_exc()) diff --git a/omeroweb/webclient/webclient_gateway.py b/omeroweb/webclient/webclient_gateway.py index 2d365e9ce1..334098abb3 100644 --- a/omeroweb/webclient/webclient_gateway.py +++ b/omeroweb/webclient/webclient_gateway.py @@ -57,31 +57,14 @@ lengthformat, ) -NSEXPERIMENTERPHOTO = omero.constants.namespaces.NSEXPERIMENTERPHOTO - -try: - import hashlib +from hashlib import sha1 as hash_sha1 +from PIL import Image - hash_sha1 = hashlib.sha1 -except Exception: - import sha - hash_sha1 = sha.new +NSEXPERIMENTERPHOTO = omero.constants.namespaces.NSEXPERIMENTERPHOTO logger = logging.getLogger(__name__) -try: - from PIL import Image # see ticket:2597 -except ImportError: - try: - import Image # see ticket:2597 - except Exception: - logger.error( - "You need to install the Python Imaging Library. Get it at" - " http://www.pythonware.com/products/pil/" - ) - logger.error(traceback.format_exc()) - def defaultThumbnail(size=(120, 120)): if isinstance(size, int): diff --git a/omeroweb/webgateway/marshal.py b/omeroweb/webgateway/marshal.py index 80008ea3c2..65b32c3ec3 100644 --- a/omeroweb/webgateway/marshal.py +++ b/omeroweb/webgateway/marshal.py @@ -23,7 +23,6 @@ import re import logging import traceback -from future.utils import isbytes, bytes_to_native_str from omero.rtypes import unwrap from omero_marshal import get_encoder @@ -296,8 +295,8 @@ def set_if(k, v, func=lambda a: a is not None): def decode(shape_field): value = shape_field.getValue() - if value and isbytes(value): - value = bytes_to_native_str(value) + if value and isinstance(value, bytes): + value = value.decode("utf-8") return value rv["id"] = shape.getId().getValue() @@ -380,7 +379,6 @@ def decode(shape_field): # FIXME: units ignored for stroke width set_if("strokeWidth", shape.getStrokeWidth().getValue()) if hasattr(shape, "getMarkerStart") and shape.getMarkerStart() is not None: - # Handle string for python2 and bytes python3. TODO: lower level fix rv["markerStart"] = decode(shape.getMarkerStart()) if hasattr(shape, "getMarkerEnd") and shape.getMarkerEnd() is not None: rv["markerEnd"] = decode(shape.getMarkerEnd()) diff --git a/omeroweb/webgateway/plategrid.py b/omeroweb/webgateway/plategrid.py index 3cacbe686f..636eb56d15 100644 --- a/omeroweb/webgateway/plategrid.py +++ b/omeroweb/webgateway/plategrid.py @@ -15,10 +15,6 @@ import omero.sys from omero.rtypes import rint -try: - import long -except ImportError: - long = int logger = logging.getLogger(__name__) @@ -36,7 +32,7 @@ def __init__(self, conn, pid, fid, thumbprefix="", plate_layout=None): param: plate_layout is "expand" to expand plate to multiple of 8 x 12 or "shrink" to ignore all wells before the first row/column """ - self.plate = conn.getObject("plate", long(pid)) + self.plate = conn.getObject("plate", int(pid)) self._conn = conn self.field = fid self._thumbprefix = thumbprefix diff --git a/omeroweb/webgateway/templatetags/common_filters.py b/omeroweb/webgateway/templatetags/common_filters.py index a660b8d7f3..939054007d 100644 --- a/omeroweb/webgateway/templatetags/common_filters.py +++ b/omeroweb/webgateway/templatetags/common_filters.py @@ -31,8 +31,6 @@ import json import random -from past.builtins import basestring - from django import template register = template.Library() @@ -112,7 +110,7 @@ def truncateafter(value, arg): length = int(arg) except ValueError: # invalid literal for int() return value # Fail silently. - if not isinstance(value, basestring): + if not isinstance(value, str): value = str(value) if len(value) > length: return value[:length] + "..." @@ -130,7 +128,7 @@ def truncatebefor(value, arg): length = int(arg) except ValueError: # invalid literal for int() return value # Fail silently. - if not isinstance(value, basestring): + if not isinstance(value, str): value = str(value) if len(value) > length: return "..." + value[len(value) - length :] @@ -146,7 +144,7 @@ def shortening(value, arg): return value # Fail silently. chunk = length // 2 - 3 - if not isinstance(value, basestring): + if not isinstance(value, str): value = str(value) try: if len(value) < length: diff --git a/omeroweb/webgateway/templatetags/defaulttags.py b/omeroweb/webgateway/templatetags/defaulttags.py index 1c99ee12ec..3975ad5314 100644 --- a/omeroweb/webgateway/templatetags/defaulttags.py +++ b/omeroweb/webgateway/templatetags/defaulttags.py @@ -26,12 +26,7 @@ import logging -try: - # python2 - from urlparse import urljoin -except ImportError: - # python3 - from urllib.parse import urljoin +from urllib.parse import urljoin from django import template from django.templatetags.static import PrefixNode diff --git a/omeroweb/webgateway/util.py b/omeroweb/webgateway/util.py index 7a3a2468c6..d7954a3f3f 100644 --- a/omeroweb/webgateway/util.py +++ b/omeroweb/webgateway/util.py @@ -23,11 +23,6 @@ import shutil import logging -try: - import long -except ImportError: - long = int - logger = logging.getLogger(__name__) LUTS_IN_PNG = [ @@ -95,7 +90,7 @@ def get_longs(request, name): vals = [] vals_raw = request.GET.getlist(name) for val_raw in vals_raw: - vals.append(long(val_raw)) + vals.append(int(val_raw)) return vals diff --git a/omeroweb/webgateway/views.py b/omeroweb/webgateway/views.py index d564a727fe..c9d5b94ff8 100644 --- a/omeroweb/webgateway/views.py +++ b/omeroweb/webgateway/views.py @@ -20,7 +20,6 @@ from functools import wraps import omero import omero.clients -from past.builtins import unicode from django.http import ( HttpResponse, @@ -61,11 +60,6 @@ except Exception: from md5 import md5 -try: - import long -except ImportError: - long = int - from io import BytesIO import tempfile @@ -96,26 +90,12 @@ from omeroweb.webgateway.util import zip_archived_files, LUTS_IN_PNG from omeroweb.webgateway.util import get_longs, getIntOrDefault -cache = CacheBase() -logger = logging.getLogger(__name__) +from PIL import Image, ImageDraw +import numpy -try: - from PIL import Image - from PIL import ImageDraw -except Exception: # pragma: nocover - try: - import Image - import ImageDraw - except Exception: - logger.error("No Pillow installed") - -try: - import numpy - numpyInstalled = True -except ImportError: - logger.error("No numpy installed") - numpyInstalled = False +cache = CacheBase() +logger = logging.getLogger(__name__) def index(request): @@ -124,7 +104,7 @@ def index(request): def _safestr(s): - return unicode(s).encode("utf-8") + return str(s).encode("utf-8") class UserProxy(object): @@ -476,7 +456,7 @@ def render_roi_thumbnail(request, roiId, w=None, h=None, conn=None, **kwargs): server_id = request.session["connector"]["server_id"] # need to find the z indices of the first shape in T - result = conn.getRoiService().findByRoi(long(roiId), None, conn.SERVICE_OPTS) + result = conn.getRoiService().findByRoi(int(roiId), None, conn.SERVICE_OPTS) if result is None or result.rois is None or len(result.rois) == 0: raise Http404 @@ -805,8 +785,6 @@ def resizeXY(xy): def render_shape_mask(request, shapeId, conn=None, **kwargs): """Returns mask as a png (supports transparency)""" - if not numpyInstalled: - raise NotImplementedError("numpy not installed") params = omero.sys.Parameters() params.map = {"id": rlong(shapeId)} shape = conn.getQueryService().findByQuery( @@ -982,8 +960,8 @@ def _get_prepared_image( img.setInvertedAxis(bool(r.get("ia", "0") == "1")) compress_quality = r.get("q", None) if saveDefs: - "z" in r and img.setDefaultZ(long(r["z"]) - 1) - "t" in r and img.setDefaultT(long(r["t"]) - 1) + "z" in r and img.setDefaultZ(int(r["z"]) - 1) + "t" in r and img.setDefaultT(int(r["t"]) - 1) img.saveDefaults() return (img, compress_quality) @@ -1672,7 +1650,7 @@ def plateGrid_json(request, pid, field=0, conn=None, **kwargs): Or "trim" to neither expand nor shrink """ try: - field = long(field or 0) + field = int(field or 0) except ValueError: field = 0 prefix = kwargs.get("thumbprefix", "webgateway_render_thumbnail") @@ -1987,7 +1965,7 @@ def searchOptFromRequest(request): try: r = request.GET opts = { - "search": unicode(r.get("text", "")).encode("utf8"), + "search": str(r.get("text", "")).encode("utf8"), "ctx": r.get("ctx", ""), "grabData": not not r.get("grabData", False), "parents": not not bool(r.get("parents", False)), @@ -2178,7 +2156,7 @@ def list_compatible_imgs_json(request, iid, conn=None, **kwargs): img_ew.sort() def compat(i): - if long(i.getId()) == long(iid): + if int(i.getId()) == int(iid): return False pp = i.getPrimaryPixels() if ( @@ -2287,9 +2265,9 @@ def applyRenderingSettings(image, rdef): else: image.setColorRenderingModel() if "z" in rdef: - image._re.setDefaultZ(long(rdef["z"]) - 1) + image._re.setDefaultZ(int(rdef["z"]) - 1) if "t" in rdef: - image._re.setDefaultT(long(rdef["t"]) - 1) + image._re.setDefaultT(int(rdef["t"]) - 1) image.saveDefaults() @@ -2385,8 +2363,8 @@ def copy_image_rdef_json(request, conn=None, **kwargs): # If we have both, apply settings... try: - fromid = long(fromid) - toids = [long(x) for x in toids] + fromid = int(fromid) + toids = [int(x) for x in toids] except TypeError: fromid = None except ValueError: @@ -2803,9 +2781,9 @@ def get_rois_json(request, imageId, conn=None, **kwargs): """ rois = [] roiService = conn.getRoiService() - # rois = webfigure_utils.getRoiShapes(roiService, long(imageId)) # gets a + # rois = webfigure_utils.getRoiShapes(roiService, int(imageId)) # gets a # whole json list of ROIs - result = roiService.findByImage(long(imageId), None, conn.SERVICE_OPTS) + result = roiService.findByImage(int(imageId), None, conn.SERVICE_OPTS) for r in result.rois: roi = {} @@ -3233,8 +3211,6 @@ def obj_id_bitmask(request, fileid, conn=None, query=None, **kwargs): or with an array of bytes as described above """ - if not numpyInstalled: - raise NotImplementedError("numpy not installed") col_name = request.GET.get("col_name", "object") if query is None: query = request.GET.get("query") diff --git a/omeroweb/webgateway/webgateway_cache.py b/omeroweb/webgateway/webgateway_cache.py index 576a50df30..43f4d3e7ef 100644 --- a/omeroweb/webgateway/webgateway_cache.py +++ b/omeroweb/webgateway/webgateway_cache.py @@ -19,12 +19,6 @@ from random import random from io import open import datetime - -# Support python2 and python3 -from past.builtins import basestring -from builtins import str - - import struct import time import os @@ -146,7 +140,7 @@ def set(self, key, value, timeout=None, invalidateGroup=None): @param invalidateGroup: Not used? """ - if not isinstance(value, basestring): + if not isinstance(value, str): raise ValueError("%s not a string, can't cache" % type(value)) fname = self._key_to_file(key) dirname = os.path.dirname(fname) @@ -231,7 +225,7 @@ def _check_entry(self, fname): @return True if entry is valid, False if expired """ try: - if isinstance(fname, basestring): + if isinstance(fname, str): f = open(fname, "rb") exp = struct.unpack("d", f.read(size_of_double))[0] else: diff --git a/setup.py b/setup.py index 65f1d02a76..71bf4abe85 100755 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ def read(fname): python_requires=">=3.8", install_requires=[ # requires Ice (use wheel for faster installs) - "omero-py>=5.7.0", + "omero-py>=5.19.0", # minimum requirements for `omero web start` "concurrent-log-handler>=0.9.20", "Django>=4.2.3,<4.3", diff --git a/test.sh b/test.sh deleted file mode 100755 index ca4545248c..0000000000 --- a/test.sh +++ /dev/null @@ -1,9 +0,0 @@ -set -e -set -u -set -x -IMAGE=${IMAGE:-${USER}-web-test} -PIP_CACHE_DIR=${PIP_CACHE_DIR:-/tmp/pip-cache} -mkdir -m 777 -p ${PIP_CACHE_DIR} -chmod a+t ${PIP_CACHE_DIR} -docker build -t ${IMAGE} . -docker run -ti --rm -v ${PIP_CACHE_DIR}:/tmp/pip-cache ${IMAGE} "$@" diff --git a/test/unit/test_web.py b/test/unit/test_web.py index fc09995682..bfb1a7894d 100644 --- a/test/unit/test_web.py +++ b/test/unit/test_web.py @@ -23,13 +23,8 @@ from difflib import unified_diff import re import os -import sys -try: - from omero_ext.path import path -except ImportError: - # Python 2 - from path import path +from omero_ext.path import path import getpass import Ice import omero.cli @@ -572,7 +567,6 @@ def setup_method(self, method): self.cli.register("config", PrefsControl, "TEST") self.args = ["config"] - @pytest.mark.xfail(sys.version_info < (3, 0), reason="py2 unicode issue") def test_parse(self): self.args += ["parse", "--rst"] self.cli.invoke(self.args, strict=True) diff --git a/tox.ini b/tox.ini index e648cbeecc..c943243656 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] # Default tox environment when run without `-e` -envlist = py38, py39, py310, py311 +envlist = py38, py39, py310, py311, py312 # https://tox.readthedocs.io/en/latest/config.html#conf-requires # Ensure pip is new so we can install manylinux wheel @@ -9,8 +9,8 @@ requires = pip >= 19.0.0 [testenv] deps = + setuptools pre-commit - future numpy>=1.9 pytest PyYAML @@ -21,8 +21,8 @@ deps = https://github.com/ome/zeroc-ice-py-github-ci/releases/download/0.2.0/zeroc_ice-3.6.5-cp38-cp38-linux_x86_64.whl; platform_system=="Linux" and python_version=="3.8" https://github.com/ome/zeroc-ice-py-github-ci/releases/download/0.2.0/zeroc_ice-3.6.5-cp39-cp39-linux_x86_64.whl; platform_system=="Linux" and python_version=="3.9" https://github.com/ome/zeroc-ice-py-github-ci/releases/download/0.2.0/zeroc_ice-3.6.5-cp310-cp310-linux_x86_64.whl; platform_system=="Linux" and python_version=="3.10" - https://github.com/glencoesoftware/zeroc-ice-py-linux-x86_64/releases/download/20231024/zeroc_ice-3.6.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl; platform_system=="Linux" and python_version=="3.11" - + https://github.com/glencoesoftware/zeroc-ice-py-linux-x86_64/releases/download/20240202/zeroc_ice-3.6.5-cp311-cp311-manylinux_2_28_x86_64.whl; platform_system=="Linux" and python_version=="3.11" + https://github.com/glencoesoftware/zeroc-ice-py-linux-x86_64/releases/download/20240202/zeroc_ice-3.6.5-cp312-cp312-manylinux_2_28_x86_64.whl; platform_system=="Linux" and python_version=="3.12" setenv = OMERODIR = {toxinidir} DJANGO_SETTINGS_MODULE = omeroweb.settings