odoo-typegen 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,258 @@
1
+ Metadata-Version: 2.3
2
+ Name: odoo-typegen
3
+ Version: 0.1.2
4
+ Summary: Static type discovery and stub generation for Odoo addons.
5
+ License: MIT
6
+ Keywords: odoo,typing,stubs,pyright
7
+ Author: jb
8
+ Author-email: jeanb.rocher@gmail.com
9
+ Requires-Python: >=3.10,<3.14
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Requires-Dist: astroid (>=3.3.10,<5.0.0)
20
+ Requires-Dist: cyclopts (>=3.22.5,<5.0.0)
21
+ Requires-Dist: pydantic (>=2.11.7,<3.0.0)
22
+ Project-URL: Homepage, https://github.com/jbrocher/odoo-typegen
23
+ Project-URL: Issues, https://github.com/jbrocher/odoo-typegen/issues
24
+ Project-URL: Repository, https://github.com/jbrocher/odoo-typegen
25
+ Description-Content-Type: text/markdown
26
+
27
+ # odoo-typegen
28
+
29
+ [![Python versions](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue.svg)](https://www.python.org/downloads/)
30
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jbrocher/odoo-typegen/blob/master/LICENSE)
31
+ [![CI](https://github.com/jbrocher/odoo-typegen/actions/workflows/ci.yml/badge.svg)](https://github.com/jbrocher/odoo-typegen/actions/workflows/ci.yml)
32
+
33
+ Automatic static type discovery and stub generation for Odoo projects.
34
+
35
+ odoo-typegen generates type stubs that expose addon-defined fields and methods on models retrieved
36
+ through the Odoo environment.
37
+
38
+ It discovers model extensions across your addons and consolidates them, so expressions such as
39
+ `env["crm.lead"]` evaluate to a typed models, inlcuding your own overrides. This enables accurate autocompletion and static type checking—with zero runtime impact.
40
+
41
+ #### :zap: Quickstart
42
+
43
+ ```bash
44
+ pip install odoo-typegen
45
+ odoo-typegen my_addons
46
+ ```
47
+
48
+ If you have a local Odoo core copy, you can also include interfaces defined by Odoo’s base models and
49
+ environment:
50
+
51
+ ```bash
52
+ pip install odoo-typegen
53
+ odoo-typegen my_addons --odoo-path .vendor/dooo
54
+ ```
55
+
56
+ ## ✨ Highlights
57
+
58
+ - 🪄 **Automatic discovery:** scan existing addons without adding imports or changing
59
+ application code.
60
+ - 💡 **Typed model lookups:** resolve expressions such as `env["crm.lead"]` to the
61
+ generated model type.
62
+ - 🧩 **Model-aware stubs:** collect supported fields and methods across Odoo model
63
+ extensions.
64
+ - 🏗️ **Core overlays:** generate stubs for `Environment`, `BaseModel`, and `Model`,
65
+ with richer overlays when an Odoo source path is provided.
66
+ - 🪶 **Zero runtime impact:** generated `.pyi` files are consumed exclusively by
67
+ static type checkers.
68
+
69
+ ## 📦 Installing
70
+
71
+ ### 🐍 With pip
72
+
73
+ ```bash
74
+ pip install odoo-typegen
75
+ ```
76
+
77
+ ### ❄️ With Nix
78
+
79
+ This repository includes a `flake.nix`. If you use Nix, add it to a development
80
+ shell like this:
81
+
82
+ ```nix
83
+ {
84
+ inputs.odoo-typegen.url = "path:/home/jb/Projects/side-projects/odoo-typegen";
85
+
86
+ outputs = { nixpkgs, odoo-typegen, ... }:
87
+ let
88
+ system = "x86_64-linux";
89
+ pkgs = nixpkgs.legacyPackages.${system};
90
+ in {
91
+ devShells.${system}.default = pkgs.mkShell {
92
+ packages = [
93
+ odoo-typegen.packages.${system}.default
94
+ ];
95
+ };
96
+ };
97
+ }
98
+ ```
99
+
100
+ ## 🚀 Usage
101
+
102
+ ### Basic usage
103
+
104
+ Pass the directory containing your Odoo addons:
105
+
106
+ ```bash
107
+ odoo-typegen ./addons
108
+ ```
109
+
110
+ The generated stubs are written to `./typings`. Pyright automatically discovers
111
+ this conventional stub directory, so its inferred Odoo types are available
112
+ without additional configuration.
113
+
114
+ The explicit `generate` command is equivalent:
115
+
116
+ ```bash
117
+ odoo-typegen generate ./addons
118
+ ```
119
+
120
+ ### With an Odoo source path
121
+
122
+ Provide a local Odoo checkout to enrich the generated `Environment`,
123
+ `BaseModel`, and `Model` stubs with interfaces discovered from Odoo's source:
124
+
125
+ ```bash
126
+ odoo-typegen ./addons --odoo-path ./.vendor/odoo
127
+ ```
128
+
129
+ ### Type-checker integration
130
+
131
+ #### Pyright
132
+
133
+ With the default output directory, simply generate the stubs and run Pyright:
134
+
135
+ ```bash
136
+ odoo-typegen ./addons
137
+ pyright
138
+ ```
139
+
140
+ For a custom output directory, pass `--output-path` and set the same directory
141
+ as `stubPath` in `pyrightconfig.json`:
142
+
143
+ ```bash
144
+ odoo-typegen ./addons --output-path ./generated-stubs
145
+ ```
146
+
147
+ ```json
148
+ {
149
+ "stubPath": "./generated-stubs"
150
+ }
151
+ ```
152
+
153
+ #### mypy
154
+
155
+ mypy does not automatically discover `./typings`. Point `mypy_path` at the
156
+ generated stubs in `pyproject.toml`:
157
+
158
+ ```toml
159
+ [tool.mypy]
160
+ mypy_path = "./typings"
161
+ ```
162
+
163
+ Then generate the stubs and check your addons:
164
+
165
+ ```bash
166
+ odoo-typegen ./addons
167
+ mypy ./addons
168
+ ```
169
+
170
+ To use a custom location, pass `--output-path` and update `mypy_path` to match.
171
+ For a one-off run, you can set it without changing your configuration:
172
+
173
+ ```bash
174
+ odoo-typegen ./addons --output-path ./generated-stubs
175
+ MYPYPATH=./generated-stubs mypy ./addons
176
+ ```
177
+
178
+ ## ⚙️ How it works
179
+
180
+ Suppose you have a few addons that add some methods and attributes to the `crm.lead` model. For instance the first addon add a custom source field:
181
+
182
+ ```python
183
+ # addons/crm_base_extension/models/crm_lead.py
184
+ from odoo import fields, models
185
+
186
+
187
+ class CrmLead(models.Model):
188
+ _inherit = "crm.lead"
189
+
190
+ my_custom_source = fields.Char()
191
+
192
+ ```
193
+
194
+ The second addon adds a priority code and a helper method:
195
+
196
+ ```python
197
+ # addons/crm_followup_extension/models/crm_lead.py
198
+ from odoo import fields, models
199
+
200
+
201
+ class CrmLead(models.Model):
202
+ _inherit = "crm.lead"
203
+
204
+ priority = fields.Integer()
205
+
206
+ def is_high_priorty(self) -> bool:
207
+ return self.priority > 3
208
+ ```
209
+
210
+ odoo-typegen follows both extensions and consolidates their members into one
211
+ stub for `crm.lead`:
212
+
213
+ ```python
214
+ # typings/crm/lead.pyi
215
+ # Generated by odoo-typegen.
216
+ from odoo.models import Model
217
+
218
+
219
+ class CrmLead(Model):
220
+ my_custom_source: str
221
+ priority: int
222
+ def is_high_priorty(self) -> bool: ...
223
+ ```
224
+
225
+ These models are then used in Stubs for Environment, models.Model, and models.BaseModel, which is how
226
+ the typing are automatically "injected" in your poject. This is how `self.env["crm.lead"]` will actually
227
+ be infered as implementing CrmLead.
228
+
229
+ ```python
230
+ # ./typings/odoo/orm/environments.pyi
231
+ import typing
232
+ from crm.lead import CrmLead
233
+
234
+
235
+ class Environment:
236
+ @typing.overload
237
+ def __getitem__(self, model_name: typing.Literal["crm.lead"]) -> CrmLead: ...
238
+ @typing.overload
239
+ def __getitem__(self, model_name: str) -> typing.Any: ...
240
+ ```
241
+
242
+ ```python
243
+ # ./typings/odoo/orm/models.pyi
244
+ from odoo.orm.environments import Environment
245
+
246
+
247
+ class BaseModel:
248
+ env: Environment
249
+
250
+ class Model(BaseModel):
251
+ ...
252
+ ```
253
+
254
+ ## 🗺️ Roadmap
255
+
256
+ - 👀 Add a `--watch` option to regenerate types automatically.
257
+ - Include Odoo Core models overrides
258
+
@@ -0,0 +1,231 @@
1
+ # odoo-typegen
2
+
3
+ [![Python versions](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue.svg)](https://www.python.org/downloads/)
4
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jbrocher/odoo-typegen/blob/master/LICENSE)
5
+ [![CI](https://github.com/jbrocher/odoo-typegen/actions/workflows/ci.yml/badge.svg)](https://github.com/jbrocher/odoo-typegen/actions/workflows/ci.yml)
6
+
7
+ Automatic static type discovery and stub generation for Odoo projects.
8
+
9
+ odoo-typegen generates type stubs that expose addon-defined fields and methods on models retrieved
10
+ through the Odoo environment.
11
+
12
+ It discovers model extensions across your addons and consolidates them, so expressions such as
13
+ `env["crm.lead"]` evaluate to a typed models, inlcuding your own overrides. This enables accurate autocompletion and static type checking—with zero runtime impact.
14
+
15
+ #### :zap: Quickstart
16
+
17
+ ```bash
18
+ pip install odoo-typegen
19
+ odoo-typegen my_addons
20
+ ```
21
+
22
+ If you have a local Odoo core copy, you can also include interfaces defined by Odoo’s base models and
23
+ environment:
24
+
25
+ ```bash
26
+ pip install odoo-typegen
27
+ odoo-typegen my_addons --odoo-path .vendor/dooo
28
+ ```
29
+
30
+ ## ✨ Highlights
31
+
32
+ - 🪄 **Automatic discovery:** scan existing addons without adding imports or changing
33
+ application code.
34
+ - 💡 **Typed model lookups:** resolve expressions such as `env["crm.lead"]` to the
35
+ generated model type.
36
+ - 🧩 **Model-aware stubs:** collect supported fields and methods across Odoo model
37
+ extensions.
38
+ - 🏗️ **Core overlays:** generate stubs for `Environment`, `BaseModel`, and `Model`,
39
+ with richer overlays when an Odoo source path is provided.
40
+ - 🪶 **Zero runtime impact:** generated `.pyi` files are consumed exclusively by
41
+ static type checkers.
42
+
43
+ ## 📦 Installing
44
+
45
+ ### 🐍 With pip
46
+
47
+ ```bash
48
+ pip install odoo-typegen
49
+ ```
50
+
51
+ ### ❄️ With Nix
52
+
53
+ This repository includes a `flake.nix`. If you use Nix, add it to a development
54
+ shell like this:
55
+
56
+ ```nix
57
+ {
58
+ inputs.odoo-typegen.url = "path:/home/jb/Projects/side-projects/odoo-typegen";
59
+
60
+ outputs = { nixpkgs, odoo-typegen, ... }:
61
+ let
62
+ system = "x86_64-linux";
63
+ pkgs = nixpkgs.legacyPackages.${system};
64
+ in {
65
+ devShells.${system}.default = pkgs.mkShell {
66
+ packages = [
67
+ odoo-typegen.packages.${system}.default
68
+ ];
69
+ };
70
+ };
71
+ }
72
+ ```
73
+
74
+ ## 🚀 Usage
75
+
76
+ ### Basic usage
77
+
78
+ Pass the directory containing your Odoo addons:
79
+
80
+ ```bash
81
+ odoo-typegen ./addons
82
+ ```
83
+
84
+ The generated stubs are written to `./typings`. Pyright automatically discovers
85
+ this conventional stub directory, so its inferred Odoo types are available
86
+ without additional configuration.
87
+
88
+ The explicit `generate` command is equivalent:
89
+
90
+ ```bash
91
+ odoo-typegen generate ./addons
92
+ ```
93
+
94
+ ### With an Odoo source path
95
+
96
+ Provide a local Odoo checkout to enrich the generated `Environment`,
97
+ `BaseModel`, and `Model` stubs with interfaces discovered from Odoo's source:
98
+
99
+ ```bash
100
+ odoo-typegen ./addons --odoo-path ./.vendor/odoo
101
+ ```
102
+
103
+ ### Type-checker integration
104
+
105
+ #### Pyright
106
+
107
+ With the default output directory, simply generate the stubs and run Pyright:
108
+
109
+ ```bash
110
+ odoo-typegen ./addons
111
+ pyright
112
+ ```
113
+
114
+ For a custom output directory, pass `--output-path` and set the same directory
115
+ as `stubPath` in `pyrightconfig.json`:
116
+
117
+ ```bash
118
+ odoo-typegen ./addons --output-path ./generated-stubs
119
+ ```
120
+
121
+ ```json
122
+ {
123
+ "stubPath": "./generated-stubs"
124
+ }
125
+ ```
126
+
127
+ #### mypy
128
+
129
+ mypy does not automatically discover `./typings`. Point `mypy_path` at the
130
+ generated stubs in `pyproject.toml`:
131
+
132
+ ```toml
133
+ [tool.mypy]
134
+ mypy_path = "./typings"
135
+ ```
136
+
137
+ Then generate the stubs and check your addons:
138
+
139
+ ```bash
140
+ odoo-typegen ./addons
141
+ mypy ./addons
142
+ ```
143
+
144
+ To use a custom location, pass `--output-path` and update `mypy_path` to match.
145
+ For a one-off run, you can set it without changing your configuration:
146
+
147
+ ```bash
148
+ odoo-typegen ./addons --output-path ./generated-stubs
149
+ MYPYPATH=./generated-stubs mypy ./addons
150
+ ```
151
+
152
+ ## ⚙️ How it works
153
+
154
+ Suppose you have a few addons that add some methods and attributes to the `crm.lead` model. For instance the first addon add a custom source field:
155
+
156
+ ```python
157
+ # addons/crm_base_extension/models/crm_lead.py
158
+ from odoo import fields, models
159
+
160
+
161
+ class CrmLead(models.Model):
162
+ _inherit = "crm.lead"
163
+
164
+ my_custom_source = fields.Char()
165
+
166
+ ```
167
+
168
+ The second addon adds a priority code and a helper method:
169
+
170
+ ```python
171
+ # addons/crm_followup_extension/models/crm_lead.py
172
+ from odoo import fields, models
173
+
174
+
175
+ class CrmLead(models.Model):
176
+ _inherit = "crm.lead"
177
+
178
+ priority = fields.Integer()
179
+
180
+ def is_high_priorty(self) -> bool:
181
+ return self.priority > 3
182
+ ```
183
+
184
+ odoo-typegen follows both extensions and consolidates their members into one
185
+ stub for `crm.lead`:
186
+
187
+ ```python
188
+ # typings/crm/lead.pyi
189
+ # Generated by odoo-typegen.
190
+ from odoo.models import Model
191
+
192
+
193
+ class CrmLead(Model):
194
+ my_custom_source: str
195
+ priority: int
196
+ def is_high_priorty(self) -> bool: ...
197
+ ```
198
+
199
+ These models are then used in Stubs for Environment, models.Model, and models.BaseModel, which is how
200
+ the typing are automatically "injected" in your poject. This is how `self.env["crm.lead"]` will actually
201
+ be infered as implementing CrmLead.
202
+
203
+ ```python
204
+ # ./typings/odoo/orm/environments.pyi
205
+ import typing
206
+ from crm.lead import CrmLead
207
+
208
+
209
+ class Environment:
210
+ @typing.overload
211
+ def __getitem__(self, model_name: typing.Literal["crm.lead"]) -> CrmLead: ...
212
+ @typing.overload
213
+ def __getitem__(self, model_name: str) -> typing.Any: ...
214
+ ```
215
+
216
+ ```python
217
+ # ./typings/odoo/orm/models.pyi
218
+ from odoo.orm.environments import Environment
219
+
220
+
221
+ class BaseModel:
222
+ env: Environment
223
+
224
+ class Model(BaseModel):
225
+ ...
226
+ ```
227
+
228
+ ## 🗺️ Roadmap
229
+
230
+ - 👀 Add a `--watch` option to regenerate types automatically.
231
+ - Include Odoo Core models overrides
@@ -0,0 +1,59 @@
1
+ [project]
2
+ name = "odoo-typegen"
3
+ version = "0.1.2"
4
+ description = "Static type discovery and stub generation for Odoo addons."
5
+ authors = [
6
+ {name = "jb",email = "jeanb.rocher@gmail.com"}
7
+ ]
8
+ readme = "README.md"
9
+ license = {text = "MIT"}
10
+ requires-python = ">=3.10,<3.14"
11
+ dependencies = [
12
+ "pydantic (>=2.11.7,<3.0.0)",
13
+ "astroid (>=3.3.10,<5.0.0)",
14
+ "cyclopts (>=3.22.5,<5.0.0)"
15
+ ]
16
+ keywords = ["odoo", "typing", "stubs", "pyright"]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Environment :: Console",
20
+ "Intended Audience :: Developers",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/jbrocher/odoo-typegen"
31
+ Repository = "https://github.com/jbrocher/odoo-typegen"
32
+ Issues = "https://github.com/jbrocher/odoo-typegen/issues"
33
+
34
+ [project.scripts]
35
+ odoo-typegen = "odoo_typegen.cli:main"
36
+
37
+
38
+ [build-system]
39
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
40
+ build-backend = "poetry.core.masonry.api"
41
+
42
+ [tool.poetry]
43
+ packages = [
44
+ { include = "odoo_typegen", from = "src" },
45
+ ]
46
+ exclude = [
47
+ "src/odoo_typegen/**/tests/**",
48
+ ]
49
+
50
+ [tool.poetry.group.dev.dependencies]
51
+ pytest = "^9.1.1"
52
+ ruff = "^0.12.10"
53
+ twine = "^7.0.0"
54
+
55
+ [tool.pytest.ini_options]
56
+ addopts = "-m 'not integration'"
57
+ markers = [
58
+ "integration: tests that use local integration fixtures and are skipped by default",
59
+ ]
@@ -0,0 +1,87 @@
1
+ from pathlib import Path
2
+
3
+ from cyclopts import App
4
+
5
+ from odoo_typegen.compiler.compiler import Compiler
6
+ from odoo_typegen.compiler.environment_compiler import EnvironmentCompiler
7
+ from odoo_typegen.compiler.model_compiler import ModelCompiler
8
+ from odoo_typegen.emitter.type_emitter import TypeEmitter
9
+ from odoo_typegen.registry.registry_service import RegistryService
10
+
11
+
12
+ app = App()
13
+
14
+
15
+ def _generate(
16
+ addon_path: Path,
17
+ output_path: Path,
18
+ odoo_path: Path | None = None,
19
+ ) -> tuple[Path, ...]:
20
+ registry = RegistryService(addon_path=addon_path).build()
21
+ models = Compiler().compile(registry)
22
+ environment = EnvironmentCompiler(odoo_path=odoo_path).compile(models)
23
+ core_models = ModelCompiler(odoo_path=odoo_path).compile()
24
+ emitter = TypeEmitter(output_path=output_path)
25
+
26
+ emitted_files = (
27
+ *emitter.emit(models),
28
+ *emitter.emit_environment(environment),
29
+ *emitter.emit_core_models(core_models),
30
+ )
31
+
32
+ for emitted_file in emitted_files:
33
+ print(emitted_file)
34
+
35
+ return emitted_files
36
+
37
+
38
+ @app.default
39
+ def default(
40
+ addon_path: Path,
41
+ output_path: Path = Path("typings"),
42
+ *,
43
+ odoo_path: Path | None = None,
44
+ ) -> None:
45
+ """Generate Python stub files for Odoo addons.
46
+
47
+ Parameters
48
+ ----------
49
+ addon_path: Path
50
+ Directory containing Odoo addon directories.
51
+ output_path: Path
52
+ Directory where generated .pyi files should be written. Defaults to
53
+ ./typings, which Pyright discovers automatically.
54
+ odoo_path: Path | None
55
+ Optional path to a local Odoo checkout used to overlay core stubs.
56
+ """
57
+ _generate(addon_path, output_path, odoo_path)
58
+
59
+
60
+ @app.command
61
+ def generate(
62
+ addon_path: Path,
63
+ output_path: Path = Path("typings"),
64
+ *,
65
+ odoo_path: Path | None = None,
66
+ ) -> None:
67
+ """Generate Python stub files for Odoo addons.
68
+
69
+ Parameters
70
+ ----------
71
+ addon_path: Path
72
+ Directory containing Odoo addon directories.
73
+ output_path: Path
74
+ Directory where generated .pyi files should be written. Defaults to
75
+ ./typings, which Pyright discovers automatically.
76
+ odoo_path: Path | None
77
+ Optional path to a local Odoo checkout used to overlay core stubs.
78
+ """
79
+ _generate(addon_path, output_path, odoo_path)
80
+
81
+
82
+ def main() -> None:
83
+ app()
84
+
85
+
86
+ if __name__ == "__main__":
87
+ main()