odoo-ide / pycharm-odoo

PyCharm plugin for Odoo
https://plugins.jetbrains.com/plugin/13499-odoo
Other
44 stars 5 forks source link

Stubs for OCA Connector module #453

Closed jcfernandez-890825 closed 3 months ago

jcfernandez-890825 commented 8 months ago

In file OCA/connector/component/core.py we can see that Pycharm doesn't give a type on most properties and methods.

AbstractComponent

image

image

image

image

WorkContext

image

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 !!!

jcfernandez-890825 commented 5 months ago

@trinhanhngoc Any news about this and #451 ? I have been developing several connectors and having this will be of great help.

trinhanhngoc commented 5 months ago

@jcfernandez-890825 ,

No news yet =)) Please be patient, these features will be added early.

trinhanhngoc commented 4 months ago

Hello @jcfernandez-890825 ,

The new version 2024.4.0 has been released with stubs for OCA component module (Odoo 13+).

jcfernandez-890825 commented 4 months ago

Awesome timing. I'm currently developing a connector. This will help me a lot.

Thank you @trinhanhngoc

jcfernandez-890825 commented 4 months ago

Hi @trinhanhngoc

After updating the Odoo module, all the Components and Collections are showing Unresolved attribute reference. image

image

image

image

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: image

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 ?

trinhanhngoc commented 4 months ago

@jcfernandez-890825 ,

Thanks for the detailed explanation. I think I need to do some magic to address that problem. Please wait for the magic.

jcfernandez-890825 commented 4 months ago

@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.

trinhanhngoc commented 4 months ago

@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: ...
trinhanhngoc commented 3 months ago

@jcfernandez-890825 ,

The new version 2024.5.0 has been released with some magic for better OCA component support =))

jcfernandez-890825 commented 3 months ago

@trinhanhngoc Thanks, I will try ASAP

jcfernandez-890825 commented 3 months ago

@trinhanhngoc

I'm still getting Expected type 'xgo.web.service.collection.nat.reg', got 'Collection' instead. image

self is a subclass of Component image

image

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).

trinhanhngoc commented 3 months ago

@jcfernandez-890825 ,

Thanks for the feedback. I will add more improvements to address your problems. Please wait for the next release.

jcfernandez-890825 commented 3 months ago

@trinhanhngoc

This is marked as Closed, shouldn't be marked as Open ?

trinhanhngoc commented 3 months ago

@jcfernandez-890825 , OK!

trinhanhngoc commented 3 months ago

Hi @jcfernandez-890825 ,

The new version 2024.5.1 has been released to address your problems.