Repository: cta-observatory/ctapipe
Pull request #2107 (2022-10-26 10:21:18)

Support python 3.11
Python 3.11 changed the behavior of the format string for a `class Foo(str, Enum)` but also introduced a new class `StrEnum` that keeps the old behavior.

I added a `ctapipe.compat` module that encapsulates the version dependent behavior needed for ctapipe and added the previous implementation for `StrEnum` there.

Numba also needs to be at least 0.57 for python 3.11.

maxnoe: Seems we have to wait for a numba release adding python 3.11 support
kosack: just updated this to master - let's see if there are still issues
maxnoe: Yes, still no numba for 3.11
maxnoe: Tests are now passing using the 0.57rc1 nunba pre release

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 24a34c73..21f36406 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -46,6 +46,10 @@ jobs:
             install-method: mamba
             extra-args: ["codecov"]
 
+          - os: ubuntu-latest
+            python-version: "3.11"
+            install-method: pip
+
           - os: ubuntu-latest
             python-version: "3.10"
             install-method: pip
diff --git a/ctapipe/compat.py b/ctapipe/compat.py
new file mode 100644
index 00000000..e9efad38
--- /dev/null
+++ b/ctapipe/compat.py
@@ -0,0 +1,17 @@
+"""
+Module for python version compatibility
+"""
+import sys
+
+__all__ = [
+    "StrEnum",
+]
+
+
+if sys.version_info >= (3, 11):
+    from enum import StrEnum
+else:
+    from enum import Enum
+
+    class StrEnum(str, Enum):
+        """Compatibility backfill of StrEnum for python < 3.11"""
diff --git a/ctapipe/instrument/optics.py b/ctapipe/instrument/optics.py
index aaf2830a..54e63bfd 100644
--- a/ctapipe/instrument/optics.py
+++ b/ctapipe/instrument/optics.py
@@ -9,6 +9,7 @@ import astropy.units as u
 import numpy as np
 from astropy.table import QTable
 
+from ..compat import StrEnum
 from ..utils import get_table_dataset
 
 logger = logging.getLogger(__name__)
@@ -37,7 +38,7 @@ class FocalLengthKind(Enum):
 
 
 @unique
-class SizeType(str, Enum):
+class SizeType(StrEnum):
     """
     Enumeration of different telescope sizes (LST, MST, SST)
     """
diff --git a/ctapipe/reco/reconstructor.py b/ctapipe/reco/reconstructor.py
index 9b38bf08..28402449 100644
--- a/ctapipe/reco/reconstructor.py
+++ b/ctapipe/reco/reconstructor.py
@@ -1,6 +1,5 @@
 import weakref
 from abc import abstractmethod
-from enum import Enum
 
 import astropy.units as u
 import joblib
@@ -11,6 +10,7 @@ from ctapipe.containers import ArrayEventContainer, TelescopeImpactParameterCont
 from ctapipe.core import Provenance, QualityQuery, TelescopeComponent
 from ctapipe.core.traits import List
 
+from ..compat import StrEnum
 from ..coordinates import shower_impact_distance
 
 __all__ = [
@@ -22,7 +22,7 @@ __all__ = [
 ]
 
 
-class ReconstructionProperty(str, Enum):
+class ReconstructionProperty(StrEnum):
     """
     Primary particle properties estimated by a `Reconstructor`
 
diff --git a/docs/changes/2107.maintenance.rst b/docs/changes/2107.maintenance.rst
new file mode 100644
index 00000000..93153ceb
--- /dev/null
+++ b/docs/changes/2107.maintenance.rst
@@ -0,0 +1,1 @@
+Add support for python 3.11.
diff --git a/docs/conf.py b/docs/conf.py
index b848261c..f2cb9a72 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -119,6 +119,7 @@ nitpick_ignore = [
     ("py:class", "astropy.coordinates.baseframe.BaseCoordinateFrame"),
     ("py:class", "astropy.table.table.Table"),
     ("py:class", "eventio.simtel.simtelfile.SimTelFile"),
+    ("py:class", "ctapipe.compat.StrEnum"),
 ]
 
 # The suffix(es) of source filenames.
diff --git a/setup.cfg b/setup.cfg
index 8085914b..ea77a987 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -33,7 +33,7 @@ install_requires=
     importlib_metadata  ; python_version < "3.10"
     joblib
     matplotlib ~=3.0
-    numba ~=0.56.0
+    numba >=0.56
     numpy ~=1.16
     psutil
     pyyaml >=5.1


Test patch:
diff --git a/ctapipe/tests/test_compat.py b/ctapipe/tests/test_compat.py
new file mode 100644
index 00000000..2fc74140
--- /dev/null
+++ b/ctapipe/tests/test_compat.py
@@ -0,0 +1,9 @@
+from ctapipe.compat import StrEnum
+
+
+def test_str_enum():
+    class Foo(StrEnum):
+        A = "A"
+        B = "B"
+
+    assert f"{Foo.A}" == "A"