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:
- Calling
super().__init__()to call the parent's__init__ - Calling
.add_pageafter 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.
