# Blender 2.8 - Field property declaration and dynamic class creation

Now that we need to use field for property declarations in Blender 2.8. I was wondering how this can be done when creating class dynamically using type() like so:

type('TestPropGrp', (bpy.types.PropertyGroup,), {‘MyProp’:StringProperty(default=‘MyStr’)})


=> this would work in Blender 2.8 but will throw a 'make field' warning.

Any idea how to declare field when using type() ?

Thank you !

edit (this is actually wrong - see answer): Of course one way to do it is to write this in 2 lines:

clss = type('TestPropGrp', (bpy.types.PropertyGroup,), {})

clss.myProp : StringProperty(default=‘MyStr’)

but I was wondering if there could be a more compact solution ?

The annotations for an object (which can be a class or a function as well) are stored in its __annotations__ property, as defined by PEP 526 and introduced in Python 3.6. This gives rise to the following solution:

The following approach works as I suspect you're looking for:

>>> clss = type('TestPropGrp2', (bpy.types.PropertyGroup,), {
...     '__annotations__': {'myProp': (StringProperty, {'default': 'MyStr'})}
... })
>>> print(clss.__annotations__)
{'myProp': (<built-in function StringProperty>, {'default': 'MyStr'})}


This also allows you to see that your proposed "one way to do it" approach doesn't actually work as intended:

>>> clss = type('TestPropGrp', (bpy.types.PropertyGroup,), {})
>>> clss.myProp : StringProperty(default='MyStr')
>>> print(clss.__annotations__)
Traceback (most recent call last):
File "<blender_console>", line 1, in <module>
AttributeError: type object 'TestPropGrp' has no attribute '__annotations__'

• Thx Sybren! I’m still confuse with the use a field property but that will help. I need to test but There is si till some blurred case like what would be the init value if you declare: myProp: bpy.props.StringProperty(default=‘MyString’) = ‘myString2’. Sep 18 '18 at 11:48
• You wouldn't pass default='MyString', I think. Just write clear, unambiguous code ;-) Sep 20 '18 at 14:14
• Well that is the way we were passing a default value for StringProperty before so it means that now with field property this doesn’t make any sense. BTW, This notation would end up with myProp having a value of ´myString’. Sep 20 '18 at 17:07

I’ll extend Sybren's answer above with a warning, after some bug just found in our @orientation_helper() IO class decorator today (see https://developer.blender.org/T58772 ). It does not directly affect your example, but it could in some slightly different dynamic class generation cases.

Do ensure you have an __annotations__ dict defined in the class you want to add fields to! Otherwise:

• If one of your parent classes defines it, you will actually add the field to that parent's __annotations__ dict, effectively adding that field to all the children classes as well, and not only the one you wanted to.
• If there is no __annotations__ defined anywhere in parent's hierarchy, you'll just get an error (which is much simpler to debug than the first case).

Bug linked above leaded to all IO add-ons using ImportHelper as parent of their Import operator to display the Up/Front axes selectors, even though they would not need them. We had to switch from that (simplified code):

def orientation_helper(axis_forward='Y', axis_up='Z'):
def wrapper(cls):
cls.__annotations__['axis_forward'] = StringProperty()


…to that:

def orientation_helper(axis_forward='Y', axis_up='Z'):
def wrapper(cls):
# Without that, we may end up adding those fields to some **parent** class' __annotations__ property
# (like the ImportHelper or ExportHelper ones)! See T58772.
if "__annotations__" not in cls.__dict__:
setattr(cls, "__annotations__", {})

cls.__annotations__['axis_forward'] = StringProperty()