Home > Software engineering >  pyFPDF .add_page() function breaking when instantiating from classmethod
pyFPDF .add_page() function breaking when instantiating from classmethod

Time:06-27

The add_page() function errors whenever I try to get any user input in __init__() or a classmethod. It works fine when I don't have any methods to get user input, so I think it's somehow interfering.

AttributeError: 'PDF' object has no attribute 'state'. Did you mean: 'rotate'?

Error

Name: ttt
Traceback (most recent call last):
  File "/workspaces/106404228/shirtificate/shirtificate.py", line 63, in <module>
    pdf = PDF.get_name()
  File "/workspaces/106404228/shirtificate/shirtificate.py", line 44, in get_name
    return cls(name)
  File "/workspaces/106404228/shirtificate/shirtificate.py", line 32, in __init__
    self.add_page(self, format='a4')
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fpdf/fpdf.py", line 813, in add_page
    if self.state == DocumentState.CLOSED:
AttributeError: 'PDF' object has no attribute 'state'. Did you mean: 'rotate'?

Code

from fpdf import FPDF


class PDF(FPDF):

    def __init__(self, name):
        if not name:
            raise ValueError("no name")

        self.name = name
        self.add_page(self, format='a4')

    # header
    def header(self):
        self.image("shirtificate.png")
        self.ln(20)


    @classmethod
    def get_name(cls):
        name = input("Name: ")
        return cls(name)


    @property
    def name(self):
        return self._name


    @name.setter
    def name(self, name):
        if not name:
            raise ValueError("no name")
        self._name = name

pdf = PDF.get_name()
pdf.set_font("helvetica", "B", 16)
pdf.output("shirtificate.pdf")

CodePudding user response:

It's not because of the user input, but it's because you are calling .add_page when the FPDF object is not yet properly instantiated. You can replace the user input with a hardcoded name and you would still get the same error. The classmethod is just making the problem more visible.

You can see the sequence of what's happening from the Traceback:

  File "/workspaces/106404228/shirtificate/shirtificate.py", line 44, in get_name
    return cls(name)
  File "/workspaces/106404228/shirtificate/shirtificate.py", line 32, in __init__
    self.add_page(self, format='a4')

The return cls(name) code will instantiate the FDPF object, which then calls your __init__, which then calls .add_page. But .add_page is an instance method of the FDPF class. It's expected to be called after the object has been instantiated. In your code, since the __init__ of the parent FPDF class wasn't called, then your object would be missing a .state and all the other attributes of a FPDF object, which leads to the error:

'PDF' object has no attribute 'state'

You can check the __init__ method of the FPDF class to see what it does. If you are creating a custom subclass, you typically call the parent's __init__ as part of your subclass __init__. (see Why aren't superclass __init__ methods automatically invoked?).

The fix is to reorganize your code to something like this:

from fpdf import FPDF

class PDF(FPDF):
    def __init__(self, name):
        if not name:
            raise ValueError("no name")

        # Call the parent FPDF init
        super().__init__()

        # Add your custom code after
        self.name = name
 
    ...

    @classmethod
    def get_name(cls):
        name = input("Name: ")
        # Create the object
        obj = cls(name)
        # Call FPDF object instance methods
        # after it is created to customize
        obj.add_page(format="A4")
        return obj

    ...

pdf = PDF.get_name()
pdf.set_font("helvetica", "B", 16)
pdf.output("shirtificate.pdf")

Here, the main changes are:

  1. Calling super().__init__() to call the parent's __init__
  2. Calling .add_page after instantiating an object

Another recommended change would be renaming that classmethod since it does more than just "getting the name". It should be called something like create_from_name or create_with_prompted_name.

  • Related