Repository: cython/cython

Issue:
[BUG] Unable to pickle cpdef enum values from pure python because of attribute lookup error
### Describe the bug

pickle.dumps throws an exception when attempted to serialize enum value
```
>>> pickle.dumps(ValEnum.val1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <enum 'ValEnum'>: attribute lookup ValEnum on importlib._bootstrap failed
```

### Code to reproduce the behaviour:

```cython
cpdef enum ValEnum:
    val1 = 1
    val2 = 2
```

```python
import pickle
pickle.dumps(ValEnum.val1)
```

### Expected behaviour

pickle.dumps should return serialized value instead of throwing an exception

### Environment

OS: Linux 5.4.0-124-generic #140-Ubuntu SMP Thu Aug 4 02:23:37 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
Python version [e.g. 3.10.6]
Cython version [e.g. 3.0.0a11]


### Additional context

_No response_


Discussion:
I wonder if there is any easy workaround for this?
Thanks for the report. I suspect we just need to pass module and/or qualname arguments when we construct the enum.

The workaround probably involves casting it to int and pickling the int I suggest.

Resolving patch:
diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py
--- a/Cython/Compiler/PyrexTypes.py
+++ b/Cython/Compiler/PyrexTypes.py
@@ -4277,6 +4277,7 @@ def create_type_wrapper(self, env):
                 "items": tuple(self.values),
                 "underlying_type": self.underlying_type.empty_declaration_code(),
                 "enum_doc": self.doc,
+                "static_modname": env.qualified_name,
             },
             outer_module_scope=env.global_scope())
 
@@ -4387,6 +4388,7 @@ def create_type_wrapper(self, env):
             context={"name": self.name,
                      "items": tuple(self.values),
                      "enum_doc": self.doc,
+                     "static_modname": env.qualified_name,
                      },
             outer_module_scope=env.global_scope()))
 


Test patch:
diff --git a/tests/run/cpdef_enums.pyx b/tests/run/cpdef_enums.pyx
--- a/tests/run/cpdef_enums.pyx
+++ b/tests/run/cpdef_enums.pyx
@@ -131,3 +131,18 @@ def check_docs():
     'Home is where...'
     """
     pass
+
+def test_pickle():
+    """
+    >>> from pickle import loads, dumps
+    >>> import sys
+
+    Pickling enums won't work without the enum module, so disable the test
+    >>> if sys.version_info < (3, 4):
+    ...     loads = dumps = lambda x: x
+    >>> loads(dumps(PyxEnum.TWO)) == PyxEnum.TWO
+    True
+    >>> loads(dumps(PxdEnum.RANK_2)) == PxdEnum.RANK_2
+    True
+    """
+    pass
diff --git a/tests/run/cpdef_scoped_enums.pyx b/tests/run/cpdef_scoped_enums.pyx
--- a/tests/run/cpdef_scoped_enums.pyx
+++ b/tests/run/cpdef_scoped_enums.pyx
@@ -40,3 +40,19 @@ def test_enum_doc():
     True
     """
     pass
+
+
+def test_pickle():
+    """
+    >>> from pickle import loads, dumps
+    >>> import sys
+
+    Pickling enums won't work without the enum module, so disable the test
+    >>> if sys.version_info < (3, 4):
+    ...     loads = dumps = lambda x: x
+    >>> loads(dumps(Enum1.Item2)) == Enum1.Item2
+    True
+    >>> loads(dumps(Enum2.Item4)) == Enum2.Item4
+    True
+    """
+    pass