Closed jcfernandez-890825 closed 3 months ago
@trinhanhngoc Any news about this and #451 ? I have been developing several connectors and having this will be of great help.
@jcfernandez-890825 ,
No news yet =)) Please be patient, these features will be added early.
Hello @jcfernandez-890825 ,
The new version 2024.4.0 has been released with stubs for OCA component module (Odoo 13+).
Awesome timing. I'm currently developing a connector. This will help me a lot.
Thank you @trinhanhngoc
Hi @trinhanhngoc
After updating the Odoo module, all the Components and Collections are showing Unresolved attribute reference
.
I think this is happening because:
@property
def _xgo_iobs_srv(self):
"""
:return: IOBS service component
"""
coll = self.env.ref('xgo_iobs.xgo_coll_iobs')
with coll.work_on(coll._name) as work:
return work.component(usage='xgo.iobs')
work.component(usage='xgo.iobs')
is returning a generic Component instead of a specific one:
To avoid this, the Component
and AbstractComponent
provide several private attributes:
class AbstractComponent(metaclass=MetaComponent):
"""Main Component Model
All components have a Python inheritance either on
:class:`AbstractComponent` or either on :class:`Component`.
Abstract Components will not be returned by lookups on components, however
they can be used as a base for other Components through inheritance (using
``_inherit``).
Inheritance mechanism
The inheritance mechanism is like the Odoo's one for Models. Each
component has a ``_name``. This is the absolute minimum in a Component
class.
::
class MyComponent(Component):
_name = 'my.component'
def speak(self, message):
print message
Every component implicitly inherit from the `'base'` component.
There are two close but distinct inheritance types, which look
familiar if you already know Odoo. The first uses ``_inherit`` with
an existing name, the name of the component we want to extend. With
the following example, ``my.component`` is now able to speak and to
yell.
::
class MyComponent(Component): # name of the class does not matter
_inherit = 'my.component'
def yell(self, message):
print message.upper()
The second has a different ``_name``, it creates a new component,
including the behavior of the inherited component, but without
modifying it. In the following example, ``my.component`` is still able
to speak and to yell (brough by the previous inherit), but not to
sing. ``another.component`` is able to speak, to yell and to sing.
::
class AnotherComponent(Component):
_name = 'another.component'
_inherit = 'my.component'
def sing(self, message):
print message.upper()
Registration and lookups
It is handled by 3 attributes on the class:
_collection
The name of the collection where we want to register the
component. This is not strictly mandatory as a component can be
shared across several collections. But usually, you want to set a
collection to segregate the components for a domain. A collection
can be for instance ``magento.backend``. It is also the name of a
model that inherits from ``collection.base``. See also
:class:`~WorkContext` and
:class:`~odoo.addons.component.models.collection.Collection`.
_apply_on
List of names or name of the Odoo model(s) for which the component
can be used. When not set, the component can be used on any model.
_usage
The collection and the model (``_apply_on``) will help to filter
the candidate components according to our working context (e.g. I'm
working on ``magento.backend`` with the model
``magento.res.partner``). The usage will define **what** kind of
task the component we are looking for serves to. For instance, it
might be ``record.importer``, ``export.mapper```... but you can be
as creative as you want.
Now, to get a component, you'll likely use
:meth:`WorkContext.component` when you start to work with components
in your flow, but then from within your components, you are more
likely to use one of:
* :meth:`component`
* :meth:`many_components`
* :meth:`component_by_name` (more rarely though)
Declaration of some Components can look like::
class FooBar(models.Model):
_name = 'foo.bar.collection'
_inherit = 'collection.base' # this inherit is required
class FooBarBase(AbstractComponent):
_name = 'foo.bar.base'
_collection = 'foo.bar.collection' # name of the model above
class Foo(Component):
_name = 'foo'
_inherit = 'foo.bar.base' # we will inherit the _collection
_apply_on = 'res.users'
_usage = 'speak'
def utter(self, message):
print message
class Bar(Component):
_name = 'bar'
_inherit = 'foo.bar.base' # we will inherit the _collection
_apply_on = 'res.users'
_usage = 'yell'
def utter(self, message):
print message.upper() + '!!!'
class Vocalizer(Component):
_name = 'vocalizer'
_inherit = 'foo.bar.base'
_usage = 'vocalizer'
# can be used for any model
def vocalize(action, message):
self.component(usage=action).utter(message)
And their usage::
>>> coll = self.env['foo.bar.collection'].browse(1)
>>> with coll.work_on('res.users') as work:
... vocalizer = work.component(usage='vocalizer')
... vocalizer.vocalize('speak', 'hello world')
...
hello world
... vocalizer.vocalize('yell', 'hello world')
HELLO WORLD!!!
Hints:
* If you want to create components without ``_apply_on``, choose a
``_usage`` that will not conflict other existing components.
* Unless this is what you want and in that case you use
:meth:`many_components` which will return all components for a usage
with a matching or a not set ``_apply_on``.
* It is advised to namespace the names of the components (e.g.
``magento.xxx``) to prevent conflicts between addons.
"""
_register = False
_abstract = True
# used for inheritance
_name = None #: Name of the component
#: Name or list of names of the component(s) to inherit from
_inherit = None
#: name of the collection to subscribe in
_collection = None
#: List of models on which the component can be applied.
#: None means any Model, can be a list ['res.users', ...]
_apply_on = None
#: Component purpose ('import.mapper', ...).
_usage = None
Is it possible to use this attributes in the same as _name
and _inherit
to get the appropriate component type
and collection type
?
@jcfernandez-890825 ,
Thanks for the detailed explanation. I think I need to do some magic to address that problem. Please wait for the magic.
@trinhanhngoc
While you are adjusting this, could you please disable the Stubs for these feature? I'm getting a lot of warnings because of this and before this, Pycharm was at least providing some help regarding navigation and type guessing. I'm currently working with some connectors and this situation is affecting me.
@jcfernandez-890825 ,
As a quick workaround, you can edit the stub file to replace Component
annotations with Any
annotations:
def component(
self, usage: str | None = None, model_name: str | BaseModel | None = None, **kw
) -> Any: ...
@jcfernandez-890825 ,
The new version 2024.5.0 has been released with some magic for better OCA component support =))
@trinhanhngoc Thanks, I will try ASAP
@trinhanhngoc
I'm still getting Expected type 'xgo.web.service.collection.nat.reg', got 'Collection' instead
.
self
is a subclass of Component
Is it possible to use _collection
attribute to check the type of the collection?
Also, I'm getting Access to a protected member
when using a private method inside a component
class and when also using a private method of a component
inside a Odoo class (Ex: model).
@jcfernandez-890825 ,
Thanks for the feedback. I will add more improvements to address your problems. Please wait for the next release.
@trinhanhngoc
This is marked as Closed
, shouldn't be marked as Open
?
@jcfernandez-890825 , OK!
Hi @jcfernandez-890825 ,
The new version 2024.5.1 has been released to address your problems.
In file
OCA/connector/component/core.py
we can see that Pycharm doesn't give a type on most properties and methods.AbstractComponent
WorkContext
Due to this, Pycharm can properly recognize types on Components.
@trinhanhngoc please do the magic and show Pycharm the light in the OCA Component dungeon !!!