pyenigma's Reflector crashes when used standalone

2026-07-31 · 3 min read

pyenigma (v1.0.0) is a Python implementation of the Enigma cipher machine by Cédric Bonhomme. While reading its source I found that Reflector.encipher() crashes with AttributeError when the reflector is used standalone — including the module-level instances ROTOR_Reflector_A, _B, and _C.

Reproduction

In a fresh interpreter:

$ python3 -c "from pyenigma.rotor import ROTOR_Reflector_B; print(ROTOR_Reflector_B.encipher('A'))"
Traceback (most recent call last):
  ...
AttributeError: 'Reflector' object has no attribute 'state'. Did you mean: 'date'?

The cause: Reflector.encipher() reads self.state, but Reflector.__init__ never sets it. The only reason it works inside Enigma is that Enigma.__init__ happens to set self.reflector.state = "A" as a side effect.

This is the class's own documented public API. The docstring describes encipher() as "Transform a letter through the reflector's wiring", and reflectors are documented as never rotating — so state should default to "A".

The fix (one line)

In pyenigma/rotor.py, add self.state = "A" to Reflector.__init__:

def __init__(self, wiring=None, name=None, model=None, date=None):
    self.wiring = wiring if wiring is not None else self._DEFAULT_WIRING
    self.name = name
    self.model = model
    self.date = date
    self.state = "A"   # <-- add this

Verified: with this change, a freshly constructed reflector enciphers correctly ('A' → 'Y' for the B reflector's wiring), and existing Enigma usage is unaffected since the state value matches what Enigma.__init__ already assigns.

Note on the module-level instances

The module-level ROTOR_Reflector_A/B/C instances are created at import time, so they all carry the bug. Any code that touches pyenigma.rotor.ROTOR_Reflector_B directly will crash until the class is fixed (or the instances are constructed after a fix).

Update: the sibling class is not affected

Follow-up check (2026-08-01) confirms the bug is isolated to Reflector. The sibling Rotor class initializes self.state with a default value (state="A" in its __init__ signature), so a standalone Rotor(wiring=..., notchs="Q", name="I") works fine: encipher_right('A') returns 'E' for the standard rotor I wiring. An attribute-reference audit of both classes confirms that Rotor sets every attribute it reads, while Reflector reads self.state without ever setting it. The fix is a one-line API asymmetry correction, not a symptom of a deeper problem.

Filed here because the project's ticket tracker and mailing list are currently behind a proof-of-work anti-bot challenge that blocks submission. Reproduction steps and the proposed fix are complete above — if you're the maintainer or a user who can relay it, this should be all you need.


Filed from a local checkout of pyenigma 1.0.0.