-5

Shouldn't field be undefined on line 50? It was my understanding that inner nested classes did not have visibility to outer classes, as I ran into on line 65... Just seems kind of inconsistent and I would love to have a better understanding of the outer nested classes visibility to avoid any confusion in the future.

"""######################################### Imports ############################################"""
import turtle
import time
import functools
"""######################################### Key press ##########################################"""

def key_space():  # Resets Level
    field.setLevel()

def key_p():      # Skips Level
    field.nextLevel()

def key_k():      # For making levels
    print([(b.x,b.y) for b in field.getBoxes()])
    
"""######################################## Interface(s) ########################################"""

def stamp(self):  # Interface for drawing polygons.
    turtle.goto(self.canvasX+field.offset[0],self.canvasY+field.offset[1])
    turtle.shape(self.shape)
    turtle.color(self.color)
    turtle.stamp()

def frameBoiler(func): #
    @functools.wraps(func)
    def wrapper_frame_boiler(*args,**kwargs):
        turtle.clear()
        #turtle.getscreen().bgpic('Untitled.png')
        r = func(*args, **kwargs)
        turtle.update()
        time.sleep(0.05)
        return r
    return wrapper_frame_boiler

"""######################################### Game Logic #########################################"""

class field:      # Field is an abstract class which facilitates the game Mechanics
    def place(o): # Place translates an objects board position to a pixel or canvas position
        o.canvasX = (o.x-31)*field.spaceSize # 31 beacuse it's half of the magic number...
        o.canvasY = (o.y-31)*field.spaceSize
        return(o) # This method should be a void //TODO refactor...
    class square:
        padding = 2
        color = "#078900"
        shape = "sq" # "sq" is the name of the stamp polygon geometry.
        #//TODO refactor: eliminate redundant literals. see def turtleSetup
        def __init__(self,x,y):
            self.x = x
            self.y = y
            field.place(self)
        def draw(self):
            stamp(self)
    class level:  # Levels are what make the game fun.
        class mando: # These are the spots you have to populate
            thickness = 2 # 
            trim = 1
            color = "#ee0000"
            shape = "bx" # "bx" is the name of the stamp polygon geometry. 
            #//TODO refactor: eliminate redundant literals. see def turtleSetup
            def draw(self): # each mando needs to be visable...
                stamp(self) # line: 15: `def stamp(self)`
            def __init__(self,x,y): # make a mandatory space
                self.x = x
                self.y = y
                self.canvasX = 0 # field isn't visible from this deeply nested class
                self.canvasY = 0 # so these are "unplaced"
        def __init__(self,text,spots): # make a level
            self.text = text
            self.mandos = []
            self.complete = False
            for spot in spots:
                self.mandos.append(self.mando(spot[0],spot[1]))
        def checkWin(self): # If the boxes on the board match the mandos you beat the level.
            oneLiner = set([(b.x,b.y) for b in field.getBoxes()])
            isTooLong = set([(m.x,m.y) for m in self.mandos])
            self.complete = oneLiner == isTooLong
        def draw(self): # This draws the mandos and the level's acompanying text.
            for mando in self.mandos:
                mando.draw()
            if self.complete:
                field.nextLevel()
    spaces = [[None]*63 for k in range(0,63)] # 63 is the magic number, ralistically it could be 8
    levels =[level("letsGo",[(31,33),(31,29),(29,31),(33,31)]),
            level("Never\nEat\nShreaded\nWheat",[(27, 31), (28, 30), (28, 32), (29, 30), (29, 32),
            (30, 28), (30, 29), (30, 33), (30, 34), (31, 27), (31, 35), (32, 28), (32, 29),
            (32, 33), (32, 34), (33, 30), (33, 32), (34, 30), (34, 32), (35, 31)]),
            level("Try:\nPress Space",[(29, 31), (30, 30), (31, 31), (31, 33), (32, 29), (33, 32),
            (34, 31)]),
            level("Flex",[(28, 27), (28, 28), (28, 29), (29, 26), (29, 30), (29, 35), (29, 36),
            (30, 29), (30, 31), (30, 33), (30, 34), (30, 35), (30, 37), (31, 28), (31, 29),
            (31, 33), (31, 35), (31, 36), (32, 29), (32, 31), (32, 33), (32, 34), (32, 35),
            (32, 37), (33, 26), (33, 30), (33, 35), (33, 36), (34, 27), (34, 28), (34, 29)]),
            level("Blast Off",[(28, 28), (28, 29), (28, 31), (29, 27), (29, 31), (29, 32), (30, 28),
            (30, 29), (30, 31), (30, 32), (30, 33), (30, 34), (31, 33), (31, 35), (32, 28), 
            (32, 29), (32, 31), (32, 32), (32, 33), (32, 34), (33, 27), (33, 31), (33, 32),
            (34, 28), (34, 29), (34, 31)]),
            level("Space\nInvaders",[(27, 31), (28, 30), (28, 32), (29, 27), (29, 28), (29, 30),
            (29, 31), (30, 26), (30, 28), (30, 29), (30, 33), (31, 27), (31, 28), (31, 31),
            (31, 32), (32, 26), (32, 28), (32, 29), (32, 33), (33, 27), (33, 28), (33, 30),
            (33, 31), (34, 30), (34, 32), (35, 31)]),
            level("big oof",[(31,31),(32,31),(31,33)])]
    levelIndex = 0   # literally the number indicating what level you're on
    spaceSize = 40   # the number of pixels to a in gmae space.
    offset = [-80,0] # you can arbitrailly move the gmae around the screen...
    def hit(x,y):    # toggle the presence of a box in a space on the board/field.
        try:
            if field.spaces[x][y] is None:
                field.spaces[x][y] = field.place(field.square(x,y))
            else:
                field.spaces[x][y] = None
        except IndexError:
            pass
    def setLevel():  # clears the board puts a box in the middle and places the mandos.
        field.spaces = [[None]*63 for k in range(0,63)]
        field.hit(31,31)
        [field.place(mando) for mando in field.levels[field.levelIndex].mandos]
    def nextLevel(): # the first level is also the level after the last level.
        field.levelIndex += 1
        if field.levelIndex >= len(field.levels):
            field.levelIndex = 0
        field.setLevel()
    @frameBoiler
    def draw():      # this is the draw method for the 
        field.levels[field.levelIndex].draw()
        for box in field.getBoxes():
            box.draw()
        turtle.color("#bad4af")# // Todo figure out why the text causes a flicker.
        turtle.goto(field.levels[field.levelIndex].
        mandos[-1].canvasX,field.levels[field.levelIndex].mandos[-1].canvasY)
        turtle.write(field.levels[field.levelIndex].text,
                    font=('Courier', 33, 'italic'), align='left')
    def click(x,y):
        spacex = int((float(x)-field.offset[0])/field.spaceSize +31) # more magic numbers...
        spacey = int((float(y)-field.offset[1])/field.spaceSize +32) # change them I dare you.
        try:
            field.bop(spacex,spacey)
        except IndexError:
            pass
        field.levels[field.levelIndex].checkWin()
    def getBoxes(): # in reality field.spaces should just be a dictionary... // TODO
        return sum([[box for box in boxes if not box is None] for boxes in field.spaces],[])
    def bop(x,y):
        if field.spaces[x][y] is None:
            pass
        else:
            field.hit(x,y)
            field.hit(x+1,y)
            field.hit(x-1,y)
            field.hit(x,y+1)
            field.hit(x,y-1)

"""##############################################################################################"""
def turtleSetup():
    turtle.tracer(0,0)
    turtle.register_shape("sq", # This is a square that denotes the boxes... wait
                        ((field.square.padding,
                            field.square.padding),
                        (field.spaceSize-field.square.padding,
                            field.square.padding),
                        (field.spaceSize-field.square.padding,
                            field.spaceSize-field.square.padding),
                        (field.square.padding,
                            field.spaceSize-field.square.padding)))
    turtle.register_shape("bx", # this is a box that is used to denote the mandos... woops.
                        ((field.level.mando.trim,field.level.mando.trim),
                        (field.level.mando.thickness,
                            field.level.mando.thickness),
                        (field.spaceSize-field.level.mando.thickness,
                            field.level.mando.thickness),
                        (field.spaceSize-field.level.mando.thickness,
                            field.spaceSize-field.level.mando.thickness),
                        (field.level.mando.thickness,
                            field.spaceSize-field.level.mando.thickness),
                        (field.level.mando.thickness,
                            field.level.mando.thickness),
                        (field.level.mando.trim,
                            field.level.mando.trim),
                        (field.level.mando.trim,
                            field.spaceSize-field.level.mando.trim),
                        (field.spaceSize-field.level.mando.trim,
                            field.spaceSize-field.level.mando.trim),
                        (field.spaceSize-field.level.mando.trim,
                            field.level.mando.trim)))
    turtle.ht() # Hide Turtle so you don't get a indicator on the rendering mechanism
    turtle.pu() # Pen up so you don't see the path of the rendering mechanism.
    turtle.onkey(key_space, "space") # Register key events.
    turtle.onkey(key_k, "k")
    turtle.onkey(key_p, "q")
    turtle.getscreen().onclick(field.click) # register the click event
    turtle.listen() # this probably needs to be here for some reason, idk delete it & see for yaself

"""##############################################################################################"""

class main:
    def __init__(self):
        turtleSetup()
        field.setLevel()
        while None is None:
            field.draw()
main() # Look at how clean that main function is! 

https://gist.github.com/Krewn/073a8cf8ed32d0d78171d409d71c74f0?fbclid=IwAR1yb7jREw91wloquHVGEM_bd2whzxPaBJ8GxJR7CcdigLJGlkfmI2RVrg0

kpie
  • 9,588
  • 5
  • 28
  • 50
  • 13
    Please try to create a minimal example that illustrates your confusion. Most of your code here seems to be irrelevant to the question you're asking. – Karl Knechtel Dec 03 '20 at 06:28
  • 2
    Using lowercase for class names is confusing, Python, like other languages has naming conventions and classes should be PascalCase. – Danny Varod Dec 10 '20 at 20:45
  • 2
    Just an advice, when facing such a problem - try to minimize it to a short and concise example. From the code above, it's very hard to understand your problem – Chen A. Dec 12 '20 at 08:46
  • Please consider, as @Glech mentioned, writing a minimal example to illustrate your problem - easier to understand by other readers, as well as more general, such that others could use the answer for their problem, if they encounter a similar one. The question, in this form would be more appropriate to ask on https://codereview.stackexchange.com/ As a recommendation, please try to follow at least PEP8 (e.g. avoiding lowercase for class names, as DannyVarod mentioned). – Mircea Dec 12 '20 at 17:40
  • Let's not go there with case sensitivity. – kpie Dec 16 '20 at 01:42
  • 1
    "Let's not go there with case sensitivity. - kpie" - I'm of the opinion that @DannyVarod has a solid point. PEP8 exists for a reason and it is what the Python community - who also happen to be same people who can answer your questions - are used to. – Leonardus Chen Dec 16 '20 at 17:53

3 Answers3

5

I've write very simplified example:

class A:
    class B:
        def __init__(self):
            print(A)
    class C:
        class D:
            def __init__(self):
                print(A)
A.B()
A.C.D()
A().C().D()

And class A is available as in inner as in inner inner class.

By initializing the levels after defining the game field rather than initializing the levels in the game field definition we are able to avoid the unresolved name error. See modified code.

import turtle
import time
import functools
"""######################################### Key press ##########################################"""

def key_space():  # Resets Level
    field.setLevel()

def key_p():      # Skips Level
    field.nextLevel()

def key_k():      # For making levels
    print([(b.x,b.y) for b in field.getBoxes()])
    
"""######################################## Interface(s) ########################################"""

def stamp(self):  # Interface for drawing polygons.
    turtle.goto(self.canvasX+field.offset[0],self.canvasY+field.offset[1])
    turtle.shape(self.shape)
    turtle.color(self.color)
    turtle.stamp()

def frameBoiler(func): #
    @functools.wraps(func)
    def wrapper_frame_boiler(*args,**kwargs):
        turtle.clear()
        #turtle.getscreen().bgpic('Untitled.png')
        r = func(*args, **kwargs)
        turtle.update()
        time.sleep(0.05)
        return r
    return wrapper_frame_boiler

"""######################################### Game Logic #########################################"""

class field:      # Field is an abstract class which facilitates the game Mechanics
    def place(o): # Place translates an objects board position to a pixel or canvas position
        o.canvasX = (o.x-31)*field.spaceSize # 31 beacuse it's half of the magic number...
        o.canvasY = (o.y-31)*field.spaceSize
        return(o) # This method should be a void //TODO refactor...
    class square:
        padding = 2
        color = "#078900"
        shape = "sq" # "sq" is the name of the stamp polygon geometry.
        #//TODO refactor: eliminate redundant literals. see def turtleSetup
        def __init__(self,x,y):
            self.x = x
            self.y = y
            field.place(self)
        def draw(self):
            stamp(self)
    class level:  # Levels are what make the game fun.
        class mando: # These are the spots you have to populate
            thickness = 2 # 
            trim = 1
            color = "#ee0000"
            shape = "bx" # "bx" is the name of the stamp polygon geometry. 
            #//TODO refactor: eliminate redundant literals. see def turtleSetup
            def draw(self): # each mando needs to be visable...
                stamp(self) # line: 15: `def stamp(self)`
            def __init__(self,x,y): # make a mandatory space
                self.x = x
                self.y = y
                field.place(self)
        def __init__(self,text,spots): # make a level
            self.text = text
            self.mandos = []
            self.complete = False
            for spot in spots:
                self.mandos.append(self.mando(spot[0],spot[1]))
        def checkWin(self): # If the boxes on the board match the mandos you beat the level.
            oneLiner = set([(b.x,b.y) for b in field.getBoxes()])
            isTooLong = set([(m.x,m.y) for m in self.mandos])
            self.complete = oneLiner == isTooLong
        def draw(self): # This draws the mandos and the level's acompanying text.
            for mando in self.mandos:
                mando.draw()
            if self.complete:
                field.nextLevel()
    spaces = [[None]*63 for k in range(0,63)] # 63 is the magic number, ralistically it could be 8
    levelIndex = 0   # literally the number indicating what level you're on
    spaceSize = 40   # the number of pixels to a in gmae space.
    offset = [-80,0] # you can arbitrailly move the gmae around the screen...
    def hit(x,y):    # toggle the presence of a box in a space on the board/field.
        try:
            if field.spaces[x][y] is None:
                field.spaces[x][y] = field.place(field.square(x,y))
            else:
                field.spaces[x][y] = None
        except IndexError:
            pass
    def setLevel():  # clears the board puts a box in the middle and places the mandos.
        field.spaces = [[None]*63 for k in range(0,63)]
        field.hit(31,31)
        [field.place(mando) for mando in field.levels[field.levelIndex].mandos]
    def nextLevel(): # the first level is also the level after the last level.
        field.levelIndex += 1
        if field.levelIndex >= len(field.levels):
            field.levelIndex = 0
        field.setLevel()
    @frameBoiler
    def draw():      # this is the draw method for the 
        field.levels[field.levelIndex].draw()
        for box in field.getBoxes():
            box.draw()
        turtle.color("#bad4af")# // Todo figure out why the text causes a flicker.
        turtle.goto(field.levels[field.levelIndex].
        mandos[-1].canvasX,field.levels[field.levelIndex].mandos[-1].canvasY)
        turtle.write(field.levels[field.levelIndex].text,
                    font=('Courier', 33, 'italic'), align='left')
    def click(x,y):
        spacex = int((float(x)-field.offset[0])/field.spaceSize +31) # more magic numbers...
        spacey = int((float(y)-field.offset[1])/field.spaceSize +32) # change them I dare you.
        try:
            field.bop(spacex,spacey)
        except IndexError:
            pass
        field.levels[field.levelIndex].checkWin()
    def getBoxes(): # in reality field.spaces should just be a dictionary... // TODO
        return sum([[box for box in boxes if not box is None] for boxes in field.spaces],[])
    def bop(x,y):
        if field.spaces[x][y] is None:
            pass
        else:
            field.hit(x,y)
            field.hit(x+1,y)
            field.hit(x-1,y)
            field.hit(x,y+1)
            field.hit(x,y-1)


"""################################## Initialize Leveles #########################################"""

field.levels =[field.level("Space For Resets",[(31,33),(31,29),(29,31),(33,31)]),
            field.level("Never\nEat\nShreaded\nWheat",[(27, 31), (28, 30), (28, 32), (29, 30), (29, 32),
            (30, 28), (30, 29), (30, 33), (30, 34), (31, 27), (31, 35), (32, 28), (32, 29),
            (32, 33), (32, 34), (33, 30), (33, 32), (34, 30), (34, 32), (35, 31)]),
            field.level("Ok?",[(27, 31), (31, 27), (31, 35), (35, 31)]),
            field.level("Try:\nPress Space",[(29, 31), (30, 30), (31, 31), (31, 33), (32, 29), (33, 32),
            (34, 31)]),
            field.level("Flex",[(28, 27), (28, 28), (28, 29), (29, 26), (29, 30), (29, 35), (29, 36),
            (30, 29), (30, 31), (30, 33), (30, 34), (30, 35), (30, 37), (31, 28), (31, 29),
            (31, 33), (31, 35), (31, 36), (32, 29), (32, 31), (32, 33), (32, 34), (32, 35),
            (32, 37), (33, 26), (33, 30), (33, 35), (33, 36), (34, 27), (34, 28), (34, 29)]),
            field.level("Blast Off",[(28, 28), (28, 29), (28, 31), (29, 27), (29, 31), (29, 32), (30, 28),
            (30, 29), (30, 31), (30, 32), (30, 33), (30, 34), (31, 33), (31, 35), (32, 28), 
            (32, 29), (32, 31), (32, 32), (32, 33), (32, 34), (33, 27), (33, 31), (33, 32),
            (34, 28), (34, 29), (34, 31)]),
            field.level("Space\nInvaders",[(27, 31), (28, 30), (28, 32), (29, 27), (29, 28), (29, 30),
            (29, 31), (30, 26), (30, 28), (30, 29), (30, 33), (31, 27), (31, 28), (31, 31),
            (31, 32), (32, 26), (32, 28), (32, 29), (32, 33), (33, 27), (33, 28), (33, 30),
            (33, 31), (34, 30), (34, 32), (35, 31)]),
            field.level("big oof",[(31,31),(32,31),(31,33)])]

"""##############################################################################################"""
def turtleSetup():
    turtle.tracer(0,0)
    turtle.register_shape("sq", # This is a square that denotes the boxes... wait
                        ((field.square.padding,
                            field.square.padding),
                        (field.spaceSize-field.square.padding,
                            field.square.padding),
                        (field.spaceSize-field.square.padding,
                            field.spaceSize-field.square.padding),
                        (field.square.padding,
                            field.spaceSize-field.square.padding)))
    turtle.register_shape("bx", # this is a box that is used to denote the mandos... woops.
                        ((field.level.mando.trim,field.level.mando.trim),
                        (field.level.mando.thickness,
                            field.level.mando.thickness),
                        (field.spaceSize-field.level.mando.thickness,
                            field.level.mando.thickness),
                        (field.spaceSize-field.level.mando.thickness,
                            field.spaceSize-field.level.mando.thickness),
                        (field.level.mando.thickness,
                            field.spaceSize-field.level.mando.thickness),
                        (field.level.mando.thickness,
                            field.level.mando.thickness),
                        (field.level.mando.trim,
                            field.level.mando.trim),
                        (field.level.mando.trim,
                            field.spaceSize-field.level.mando.trim),
                        (field.spaceSize-field.level.mando.trim,
                            field.spaceSize-field.level.mando.trim),
                        (field.spaceSize-field.level.mando.trim,
                            field.level.mando.trim)))
    turtle.ht() # Hide Turtle so you don't get a indicator on the rendering mechanism
    turtle.pu() # Pen up so you don't see the path of the rendering mechanism.
    turtle.onkey(key_space, "space") # Register key events.
    turtle.onkey(key_k, "k")
    turtle.onkey(key_p, "q")
    turtle.getscreen().onclick(field.click) # register the click event
    turtle.listen() # this probably needs to be here for some reason, idk delete it & see for yaself

"""##############################################################################################"""

class main:
    def __init__(self):
        turtleSetup()
        field.setLevel()
        while None is None:
            field.draw()
main() # Look at how clean that main function is! ```

  
kpie
  • 9,588
  • 5
  • 28
  • 50
Glech
  • 731
  • 3
  • 14
  • Yeah I see that... For whatever reason `field` is not defined in the constructor for `mando` in the code I provided. I have not replicated the troubles with any less code, and I read that inner classes do not have visibility on outer classes however that information appears to be erroneous. – kpie Dec 06 '20 at 04:23
  • 2
    Class `field` is located in global scope. And it falls to global scope after completely reading the definition of class. So in case were you want to use class before class definition ends. In your case you initialize `level` class instances. So if your try to access the `field` then it be undefined. Answer to your question is no difference between inner and inner-inner class. Just don't use variables before it completely defined. And keep in mind that variable availability is verified in runtime. – Glech Dec 06 '20 at 13:07
1

in inner class and inner inner class only difference is inner class/parent class can have several inner class but inner class mostly avoid it.In python inner class is know as nested class.

Vishal Pandey
  • 349
  • 1
  • 4
  • 15
  • So why does `field.place(self)` work in the constructor of `field.square` but not with `field.level.mando` therein? – kpie Dec 16 '20 at 01:44
  • 1
    this because mando can access `field.level` but it can't access main class without reference so if you want to do it first call main class in `level class` then only mando will access `field`. – Vishal Pandey Dec 16 '20 at 07:30
  • The longer answer that doesn't quite address the question says, "This doesn't have to do with whether the class is outer or inner. It only has to do with naming scope." I'd be interested to see if you have any comments there... – kpie Dec 18 '20 at 03:17
1

This doesn't have to do with whether the class is outer or inner. It only has to do with naming scope.

def assigns a function to a name

Take the following statement:

myfunction = lambda x: x

The above statement defines a name myfunction that points to a function. Compare with this one:

def myfunction(x):
    return x

Although this form has some minor differences (e.g. myfunction.__name__ will be myfunction after the second form, but <lambda> after the first form), the second statement is exactly equivalent to the first statement in that it defines a name myfunction that happens to be pointing to a function.

class assigns a class to a name

Class definitions work the same (although they have no lambda equivalent):

class Outer:
    def myfunc(self):
        Outer

    class Inner:
        def myfunc(self):
            Outer

Outer.Inner().myfunc()  # No error

What this does is that it defines a (global) name Outer that happens to be pointing to a class. When the last line of the above snippet runs, the Outer name has been defined. When Outer.Inner.myfunc() is executing, it doesn't do anything other than access the name Outer.

In fact, you could just as well write this:

class A:
    def myfunc(self):
        B

class B:
    pass

A().myfunc()

When the last line of this snippet executes, (global) names A and B have been defined; when A.myfunc() runs, these names exist.

While a function is being created, the name pointing to it doesn't exist

myfunction = lambda x: x

This assignment assigns the expression lambda x: x to the name myfunction. The expression is evaluated first, and the function is constructed, and only when the result of the expression—the function—is ready does the interpreter create the myfunction name and make it point to the newly created function.

Likewise,

def myfunction(x):
    return x

The same thing happens. A function is created, and when the function is ready, the name myfunction is created and made to point to the newly created function.

While a class is being created, the name pointing to it doesn't exist

class Outer:
    class Inner:
        my_class_attribute = Outer  # This will cause an error

While the last line of this snippet is being executed, the class Outer is still being created. Since its creation hasn't finished, the name Outer hasn't been created yet. Only after the class Outer had been created would the interpreter create a name Outer and make it point to the newly created class. Since no such name exists yet while the last line of the snippet is being executed, it will throw the error that the name Outer is undefined.

It would throw the same error if you did this:

class A:
    my_class_attribute = A

But note that you can do this:

class A:
    pass


class A:
    this_points_to_the_previous_A = A
Antonis Christofides
  • 6,990
  • 2
  • 39
  • 57
  • Thank you for this. Your response is thoughtful and your examples are concise and well described. However I am still unclear on why in the code I provided `field.place(self)` throws an error in the `field.level.mando.__init__` and runs fine in `field.square.__init__`. If you can make that connection I will be happy to reward you the bounty. – kpie Dec 18 '20 at 02:49
  • @kpie It's because of line 83. At that point the `field` class isn't ready yet but a `level` instance is created, which causes a `mando` instance to be created in line 72. So `field.level.mando.__init__()` is executed before `field` has been fully declared. The traceback should show all that. – Antonis Christofides Dec 18 '20 at 07:06
  • Hey @kpie, I agree with the other people that your question isn't the minimal code required to reproduce the problem. Also, using all-lowercase class names is really not good Python style. So I don't think that the edit you made in my answer would help any future reader. Besides, this is a non-trivial edit—I don't think it's a good idea to make non-trivial edits to answers signed by other people. – Antonis Christofides Dec 20 '20 at 07:36
  • But without knowing the problem how could I produce a minimal example? and my edit was just to add the stack trace... – kpie Dec 21 '20 at 03:28