nutti / fake-bpy-module

Fake Blender Python API module collection for the code completion.
MIT License
1.35k stars 96 forks source link

bpy_prop_collection[NodeSocketInterface] has no attribute "new" #119

Closed Lathreas closed 1 year ago

Lathreas commented 1 year ago

System Information

Expected behavior

Many Blender objects which are union of type bpy_prop_collection[...] have a 'new' function. For example:

node_tree.outputs.new("NodeSocketBool", "My node socket")

node_tree.outputs is of type NodeTreeOutputs, and has a 'new' function.

This should not give rise to an error in mypy when used correctly.

Description about the bug

Mypy gives many false positives such as

Item "bpy_prop_collection[NodeSocketInterface]" of "Union[NodeTreeOutputs, bpy_prop_collection[NodeSocketInterface]]" has no attribute "new"

Item "bpy_prop_collection[Node]" of "Union[Nodes, bpy_prop_collection[Node]]" has no attribute "new"

Item "bpy_prop_collection[NodeSocketInterface]" of "Union[NodeTreeInputs, bpy_prop_collection[NodeSocketInterface]]" has no attribute "new"

Item "bpy_prop_collection[Modifier]" of "Union[ObjectModifiers, bpy_prop_collection[Modifier]]" has no attribute "new"

Item "bpy_prop_collection[NodeLink]" of "Union[NodeLinks, bpy_prop_collection[NodeLink]]" has no attribute "new"

Item "bpy_prop_collection[NodeTree]" of "Union[BlendDataNodeTrees, bpy_prop_collection[NodeTree]]" has no attribute "new"

despite correct usage of "new" - the fact that these are unions with bpy_prop_collection results in errors, since fake_bpy_module's bpy_prop_collection contains no "new" function.

nutti commented 1 year ago

@Lathreas

fake_bpy_module's bpy_prop_collection contains no "new" function.

This is correct behavior. https://docs.blender.org/api/current/bpy.types.bpy_prop_collection.html bpy_prop_collection does not contain new function. Instead, NodeLinks, BlendDataNodeTrees and etc have a new function. https://docs.blender.org/api/current/bpy.types.NodeLinks.html https://docs.blender.org/api/current/bpy.types.BlendDataNodeTrees.html

BTW, why mypy does not check NodeLinks and etc? These class has a new function. Perhaps, do I mistake the usage of typing.Union?

xixixao commented 1 year ago

This is the same issue I reported here: https://github.com/nutti/fake-bpy-module/issues/30#issuecomment-1304367370 I'll move the convo to this issue.

@nutti

the base class of BlendDataMeshes is bpy_struct which has new method.

bpy_struct doesn't have new method, BlendDataMeshes does.

Actually, this works well in VSCode with Pylance environment.

Looks like it's not working well for me or @Lathreas, is it working for you when you try to repro the code in my screenshots?

We will not fix this because we want to follow the official document not to increase the maintenance cost.

I hear you on this. I'll have a look at what the actual hierarchy is. Can you link to a patch you submit for the Blender docs before, as an example?

nutti commented 1 year ago

@xixixao

bpy_struct doesn't have new method, BlendDataMeshes does.

Sorry, I mistook the reference. Yes, BlendDataMeshes has the new method. https://docs.blender.org/api/current/bpy.types.BlendDataMeshes.html

is it working for you when you try to repro the code in my screenshots?

Yes, it works on my environment. I will share the screenshot.

github_20221106

Can you link to a patch you submit for the Blender docs before, as an example?

https://docs.blender.org/api/current/bpy.types.BlendDataMeshes.html Above the link, you can see the class definition like;

class bpy.types.BlendDataMeshes(bpy_struct)

So the inheritance relationship is bpy_struct (parent) <- BlendDataMeshes (child).

xixixao commented 1 year ago

@nutti I think you don't have type checking turned on. Try to change your VS Code settings:

Screen Shot 2022-11-05 at 17 53 54

You need to have at least basic selected, and off is the default (which only gives type completion). I think

nutti commented 1 year ago

@xixixao

Thanks. I can reproduce this error. BTW, I'm not sure why typing.Union is not worked in this context. The official docs says bpy.data.meshes is the combine of BlendDataMeshes and bpy_prop_collection[Mesh]. Do you have an idea of this issue?

Lathreas commented 1 year ago

Thanks for the replies!

@nutti

BTW, why mypy does not check NodeLinks and etc? These class has a new function. Perhaps, do I mistake the usage of typing.Union?

BTW, I'm not sure why typing.Union is not worked in this context. The official docs says > bpy.data.meshes is the combine of BlendDataMeshes and bpy_prop_collection[Mesh]. Do you have an idea of this issue?

You are right that the problem is with typing.Union and not with bpy_prop_collection. You might have been thinking of the mathematical definition of a union acting on the members of the classes as if it were a set (https://en.wikipedia.org/wiki/Union_(set_theory)), but that definition is (confusingly) different from the programmatic definition of a union type (https://en.wikipedia.org/wiki/Union_type). typing.Union instead means "this object can be any one of the entries, but not all at the same time". For example, typing.Any is a typing.Union of all possible types. A bit confusingly named, alas. But the behavior you are seeking is probably multiple inheritance, which combines the members of multiple classes.

What's happening in mypy

Since an object with type typing.Union can be either of the types, but not all at the same time, mypy checks all entries in the union separately and will check if any of them give an error message. This is because mypy cannot infer whether the object is, say, a NodeLinks or a bpy_prop_collection[NodeLink] at that particular point in time. For safety reasons, it will check both and produce an error if any of the entries of the Union has an error. The check for NodeLinks doesn't give an error, since it contains a 'new'. But the check for bpy_prop_collection[NodeLink] gives an error, causing the entire check to fail.

Possible fixes

The correct behavior would have the object have access to the members of both NodeLinks and bpy_prop_collection[NodeLink] at the same time. Programmatically, this is accomplished through subclassing and multiple inheritance. For example, the following class would represent the combination of NodeLinks and bpy_prop_collection[NodeLink] that you are seeking:

class both_NodeLinks_and_PropCollection(NodeLinks, bpy_prop_collection[NodeLink])

Of course, this is not particularly elegant, because the class would just be empty boilerplate. But it would fix the issue. Looking at the Blender documentation, though, I am inclined to believe that the documentation itself could possibly be wrong.

A fix such as redefining NodeLinks itself as

class bpy.types.NodeLinks(bpy_struct, bpy_prop_collection[NodeLink])

(and removing the typing.Union stuff) would solve the issue as well, but that is indeed not what the Blender documentation says. That said, I have not encountered any type of collection type such as NodeLinks without it being a combination with bpy_prop_collection, so possibly changing the definition of such collection types to have multiple inheritance would be what Blender is doing internally.

It is important to note that both bpy_struct and bpy_prop_collection[] both aren't actually available in Blender --- they only exist in the Blender documentation according to the documentation itself. (see the note at https://docs.blender.org/api/current/bpy.types.bpy_prop_collection.html, for example. A similar note exists for bpy_struct). As such, it may very well be that NodeLinks is indeed meant to be using multiple inheritance, but that the documentation is in error. But that should be investigated.

Either way, using inheritance will fix the issue, since a typing.Union indeed works differently than what you intended in this context.

(Unrelatedly, thanks so much for this project by the way!)

Lathreas commented 1 year ago

Doing some more digging, it looks like the concept we are looking for is something like typing.Intersection rather than typing.Union. Alas, it is not accepted for inclusion into Python yet: https://github.com/python/typing/issues/213

So we'd have to use multiple inheritance instead for the time being.

xixixao commented 1 year ago

We're going off of the documentation that simply is not correct / precise. I looked at the C source code and these collections are simply collections with additional methods.

So

class NodeLinks(bpy_struct, bpy_prop_collection[NodeLink]):
  pass

is correct, and this is also correct:

class bpy_prop_collection(bpy_struct, typing.Generic[GenericType]):
  pass

class NodeLinks(bpy_prop_collection[NodeLink]):
  pass

We should adopt either of them here. We could also change the Blender docs, but I'm not sure the Blender docs support either of these inheritance declarations.

nutti commented 1 year ago

This issue is now fixed. Thanks for your suggestion to solve this issue, and sorry for a late response.