@@ -791,6 +791,257 @@ The following internal attributes are also available for use in methods:
791791* ` _odoo ` (` odoorpc.ODOO ` ) - The OdooRPC connection object
792792* ` _env ` (` odoorpc.env.Environment ` ) - The OdooRPC environment object for the model
793793
794+ ## Mixins
795+
796+ Python supports [ multiple inheritance] ( https://docs.python.org/3/tutorial/classes.html#multiple-inheritance )
797+ when creating new classes. A common use case for multiple inheritance is to extend
798+ functionality of a class through the use of * mixin classes* , which are minimal
799+ classes that only consist of supplementary attributes and methods, that get added
800+ to other classes through subclassing.
801+
802+ The OpenStack Odoo Client library for Python supports the use of mixin classes
803+ to add functionality to custom record and manager classes in a modular way.
804+ Multiple mixins can be added to record and manager classes to allow mixing and
805+ matching additional functionality as required.
806+
807+ ### Using Mixins
808+
809+ To extend the functionality of your custom record and manager classes,
810+ append the mixins for the record class and/or record manager class
811+ ** AFTER** the inheritance for ` RecordBase ` and ` RecordManagerBase ` .
812+
813+ ``` python
814+ from __future__ import annotations
815+
816+ from openstack_odooclient import (
817+ NamedRecordManagerMixin,
818+ NamedRecordMixin,
819+ RecordBase,
820+ RecordManagerBase,
821+ )
822+
823+ class CustomRecord (RecordBase[" CustomRecordManager" ], NamedRecordMixin ):
824+ custom_field: str
825+ """ Description of the field."""
826+
827+ class CustomRecordManager (
828+ RecordManagerBase[CustomRecord],
829+ NamedRecordManagerMixin[CustomRecord],
830+ ):
831+ env_name = " custom.record"
832+ record_class = CustomRecord
833+ ```
834+
835+ That's all that needs to be done. The additional attributes and/or methods
836+ should now be available on your record and manager objects.
837+
838+ !!! note
839+
840+ Depending on how the mixin you're trying to use is implemented
841+ (for instance, if it provides custom methods), you may need to
842+ specify your record or manager class as a generic type argument
843+ when adding the mixin to your class.
844+
845+ The following mixins are provided with the Odoo Client library.
846+
847+ #### Named Records
848+
849+ If your record model has a unique ` name ` field on it (of ` str ` type),
850+ you can use the ` NamedRecordMixin ` and ` NamedRecordManagerMixin ` mixins
851+ to define the ` name ` field on the record class, and add the
852+ ` get_by_name ` method to your custom record manager class.
853+
854+ !!! note
855+
856+ `NamedRecordManagerMixin` requires that the record class be passed
857+ as a generic type argument to the mixin class when adding it to
858+ the manager class.
859+
860+ ``` python
861+ from __future__ import annotations
862+
863+ from openstack_odooclient import (
864+ NamedRecordManagerMixin,
865+ NamedRecordMixin,
866+ RecordBase,
867+ RecordManagerBase,
868+ )
869+
870+ class CustomRecord (RecordBase[" CustomRecordManager" ], NamedRecordMixin ):
871+ custom_field: str
872+ """ Description of the field."""
873+
874+ # Added by NamedRecordMixin:
875+ #
876+ # name: str
877+ # """The unique name of the record."""
878+
879+ class CustomRecordManager (
880+ RecordManagerBase[CustomRecord],
881+ NamedRecordManagerMixin[CustomRecord],
882+ ):
883+ env_name = " custom.record"
884+ record_class = CustomRecord
885+
886+ # Added by NamedRecordManagerMixin:
887+ #
888+ # def get_by_name(...):
889+ # ...
890+ ```
891+
892+ For more information on using record managers with unique ` name ` fields,
893+ see [ Named Record Managers] ( index.md#named-record-managers ) .
894+
895+ #### Coded Records
896+
897+ If your record model has a unique ` code ` field on it (of ` str ` type),
898+ you can use the ` CodedRecordMixin ` and ` CodedRecordManagerMixin ` mixins
899+ to define the ` code ` field on the record class, and add the
900+ ` get_by_code ` method to your custom record manager class.
901+
902+ !!! note
903+
904+ `CodedRecordManagerMixin` requires that the record class be passed
905+ as a generic type argument to the mixin class when adding it to
906+ the manager class.
907+
908+ ``` python
909+ from __future__ import annotations
910+
911+ from openstack_odooclient import (
912+ CodedRecordManagerMixin,
913+ CodedRecordMixin,
914+ RecordBase,
915+ RecordManagerBase,
916+ )
917+
918+ class CustomRecord (RecordBase[" CustomRecordManager" ], CodedRecordMixin ):
919+ custom_field: str
920+ """ Description of the field."""
921+
922+ # Added by CodedRecordMixin:
923+ #
924+ # code: str
925+ # """The unique name for this record."""
926+
927+ class CustomRecordManager (
928+ RecordManagerBase[CustomRecord],
929+ CodedRecordManagerMixin[CustomRecord],
930+ ):
931+ env_name = " custom.record"
932+ record_class = CustomRecord
933+
934+ # Added by CodedRecordManagerMixin:
935+ #
936+ # def get_by_code(...):
937+ # ...
938+ ```
939+
940+ For more information on using record managers with unique ` code ` fields,
941+ see [ Coded Record Managers] ( index.md#coded-record-managers ) .
942+
943+ ### Creating Mixins
944+
945+ It is possible to create your own custom mixins to incorporate into
946+ custom record and manager classes.
947+
948+ There are two mixin types: ** record mixins** and ** record manager mixins** .
949+
950+ #### Record Mixins
951+
952+ For simple cases creating a record mixin is easy; all you need are classes
953+ with the additional attributes or methods you want defined on them.
954+
955+ Here is the full implementation of ` NamedRecordMixin ` as an example
956+ of a mixin for a record class, that simply adds the ` name ` field:
957+
958+ ``` python
959+ class NamedRecordMixin :
960+ name: str
961+ """ The unique name of the record."""
962+ ```
963+
964+ You can then use the mixin as shown in [ Using Mixins] ( #using-mixins ) .
965+
966+ For methods that only use other attributes/methods defined within the mixin,
967+ you can define them as normal:
968+
969+ ``` python
970+ class NamedRecordMixin :
971+ name: str
972+ """ The unique name of the record."""
973+
974+ def custom_method (self ) -> None :
975+ self .name # type => str
976+ ```
977+
978+ Where it becomes more complex is when you need to access attributes/methods
979+ from the ` RecordBase ` class, and you want type hinting.
980+
981+ Because mixins are independent classes and not subclasses of ` RecordBase ` ,
982+ they are not annotated with the attributes/methods from that class, even
983+ though they actually would be usable when you add the mixin to your record
984+ class.
985+
986+ You can make these annotations available on a per-method basis by overriding
987+ the type of ` self ` to ` RecordProtocol ` , as shown below.
988+
989+ ``` python
990+ from __future__ import annotations
991+
992+ from openstack_odooclient import RecordProtocol
993+
994+ class NamedRecordMixin :
995+ name: str
996+ """ The unique name of the record."""
997+
998+ def custom_method (self : RecordProtocol) -> None :
999+ self ._env.custom_method(self .id)
1000+ ```
1001+
1002+ This makes the annotations ` self._env ` and all the other attributes and methods
1003+ available from the ` RecordBase ` class evaluate correctly, but with the disadvantage
1004+ of making annotations from within the mixin itself unavailable. ` self.name ` in the
1005+ above example ** is** actually available for use, but using it in ` custom_method `
1006+ would result in an error in type checkers that would need to be silenced.
1007+
1008+ This is a limitation of Python's typing system with regard to mixins, and does not
1009+ appear to have any other workaround available at this time.
1010+
1011+ #### Record Manager Mixins
1012+
1013+ For the most part, record manager mixins are expected to be used to add custom
1014+ methods to a record manager class, and use other methods on the base record
1015+ manager class.
1016+
1017+ ``` python
1018+ from __future__ import annotations
1019+
1020+ from typing import Generic
1021+
1022+ from openstack_odooclient import Record, RecordManagerProtocol
1023+
1024+ class NamedRecordManagerMixin (Generic[Record]):
1025+ def custom_method (
1026+ self : RecordManagerProtocol[Record],
1027+ record : int | Record,
1028+ ) -> None :
1029+ ...
1030+ ```
1031+
1032+ As with record mixins, because mixins are independent classes and not
1033+ subclasses of ` RecordManagerBase ` , they are not annotated with the
1034+ attributes/methods from that class, even though they actually would be usable
1035+ when you add the mixin to your record class. ` self ` is being overridden to
1036+ ` RecordManagerProtocol ` in the example above to allow type checking for record
1037+ manager base class attributes to work.
1038+
1039+ When ` RecordManagerProtocol ` is being used, any other attributes or methods
1040+ defined on the mixin class itself are still available from other methods,
1041+ but type checking will fail due to ` self ` being overridden. This is a
1042+ limitation of Python's typing system with regard to mixins, and does not
1043+ appear to have any other workaround available at this time.
1044+
7941045## Extending Existing Record Types
7951046
7961047The Odoo Client library provides * limited* support for extending the built-in record types.
0 commit comments