odoo-dev 0.3.3__tar.gz → 1.0.0__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.
- odoo_dev-1.0.0/.github/workflows/release-please.yml +43 -0
- odoo_dev-1.0.0/.release-please-manifest.json +3 -0
- odoo_dev-1.0.0/CHANGELOG.md +19 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/PKG-INFO +26 -7
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/README.md +25 -6
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/pyproject.toml +1 -1
- odoo_dev-1.0.0/release-please-config.json +12 -0
- odoo_dev-1.0.0/src/odoo_dev/__init__.py +3 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/commands/run.py +38 -10
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/commands/setup.py +26 -7
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/config.py +50 -0
- odoo_dev-1.0.0/src/odoo_dev/preflight.py +158 -0
- odoo_dev-1.0.0/tests/test_db_config.py +49 -0
- odoo_dev-1.0.0/tests/test_preflight.py +132 -0
- odoo_dev-1.0.0/tests/test_setup_db.py +41 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/uv.lock +49 -1
- odoo_dev-0.3.3/.claude/settings.json +0 -8
- odoo_dev-0.3.3/.claude/settings.local.json +0 -13
- odoo_dev-0.3.3/src/odoo_dev/__init__.py +0 -3
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/.gitignore +0 -78
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/.gitlab-ci.yml +0 -8
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/.gitmodules +0 -0
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/.repos/.gitkeep +0 -0
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/Notes/Warning Upgrade all +0 -45
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/README.md +0 -0
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/addons/.gitkeep +0 -0
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/note.txt +0 -331
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/requirements.txt +0 -0
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/abstorelsymlink.sh +0 -12
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/adaptation.md +0 -99
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/after_migration.sh +0 -13
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/checkout2 +0 -17
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/cleanup.sql +0 -60
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/deploy +0 -108
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/fix_mail_templates.sql +0 -18
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/gitpull +0 -17
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/main.py +0 -569
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/modules_cleanup.sql +0 -58
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/post-upgrade steps.md +0 -78
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/pre_commit_hook.py +0 -81
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/requirements.txt +0 -3
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/test +0 -62
- odoo_dev-0.3.3/tests/fixtures/odoo-empty/tools/update_addon_versions.py +0 -24
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/.gitignore +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/.gitmodules +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/.python-version +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/cli.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/commands/__init__.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/commands/db.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/commands/docker.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/templates/__init__.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/templates/docker/Dockerfile +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/templates/docker/__init__.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/templates/docker/docker-entrypoint.sh +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/templates/vscode/__init__.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/templates/vscode/launch.json +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/templates/vscode/settings.json +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/templates/vscode/tasks.json +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/utils/__init__.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/src/odoo_dev/utils/console.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/tests/__init__.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/tests/fixtures/__init__.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/tests/test_cli.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/tests/test_config.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/tests/test_db.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/tests/test_drop_database.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/tests/test_integration.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/tests/test_run.py +0 -0
- {odoo_dev-0.3.3 → odoo_dev-1.0.0}/tests/test_setup.py +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release-please:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
outputs:
|
|
15
|
+
release_created: ${{ steps.release.outputs.release_created }}
|
|
16
|
+
tag_name: ${{ steps.release.outputs.tag_name }}
|
|
17
|
+
steps:
|
|
18
|
+
# Maintains a release PR from Conventional Commits; when that PR is merged
|
|
19
|
+
# it creates the git tag + GitHub release and sets release_created=true.
|
|
20
|
+
- uses: googleapis/release-please-action@v4
|
|
21
|
+
id: release
|
|
22
|
+
with:
|
|
23
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
24
|
+
# release-please-config.json and .release-please-manifest.json are
|
|
25
|
+
# read from the repo root by default.
|
|
26
|
+
|
|
27
|
+
publish:
|
|
28
|
+
needs: release-please
|
|
29
|
+
if: ${{ needs.release-please.outputs.release_created == 'true' }}
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
permissions:
|
|
32
|
+
contents: read
|
|
33
|
+
id-token: write # OIDC: mint a short-lived token for PyPI Trusted Publishing
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
- name: Install uv
|
|
37
|
+
uses: astral-sh/setup-uv@v5
|
|
38
|
+
with:
|
|
39
|
+
python-version: "3.12"
|
|
40
|
+
- name: Build sdist + wheel
|
|
41
|
+
run: uv build
|
|
42
|
+
- name: Publish to PyPI (Trusted Publishing / OIDC)
|
|
43
|
+
run: uv publish --trusted-publishing always
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.0.0](https://github.com/bemade/odoo-dev/compare/v0.4.0...v1.0.0) (2026-06-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* macOS `setup` no longer installs PostgreSQL. Fresh macOS setups must install a server themselves (or point DB_HOST/DB_PORT at a remote/Docker server).
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* treat PostgreSQL as an external prerequisite on all platforms ([#5](https://github.com/bemade/odoo-dev/issues/5)) ([e817107](https://github.com/bemade/odoo-dev/commit/e817107ac434a5b23dd2675d59a95d1a6f8d1e71))
|
|
13
|
+
|
|
14
|
+
## [0.4.0](https://github.com/bemade/odoo-dev/compare/v0.3.3...v0.4.0) (2026-06-11)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* configurable DB connection + connectivity preflight ([#3](https://github.com/bemade/odoo-dev/issues/3)) ([632738e](https://github.com/bemade/odoo-dev/commit/632738e37c1d22d79ec4a03cba72a28ca580f7c8))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: odoo-dev
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Odoo Development Environment Helper
|
|
5
5
|
Project-URL: Homepage, https://git.bemade.org/bemade/odoo-dev
|
|
6
6
|
Project-URL: Repository, https://git.bemade.org/bemade/odoo-dev
|
|
@@ -55,15 +55,18 @@ Docker at the end — answer "no" if you only want the local venv workflow.
|
|
|
55
55
|
## Database setup (read this before your first `run`/`test`)
|
|
56
56
|
|
|
57
57
|
The generated `conf/odoo.conf` connects as PostgreSQL user **`odoo`** over the local
|
|
58
|
-
socket.
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
socket. **A running PostgreSQL server is a prerequisite you provide yourself** — on
|
|
59
|
+
every platform. `setup` installs only the PostgreSQL *client* and build dependencies
|
|
60
|
+
(macOS: `libpq`; Linux: `postgresql-client` + `libpq-dev`); it never installs, starts,
|
|
61
|
+
or configures a server, and never creates the `odoo` role. So on a fresh machine,
|
|
62
|
+
install a server, start it, and create the role once:
|
|
61
63
|
|
|
62
64
|
**macOS (Homebrew):**
|
|
63
65
|
|
|
64
66
|
```bash
|
|
65
|
-
brew
|
|
66
|
-
|
|
67
|
+
brew install postgresql@18 # install a server (pick your version)
|
|
68
|
+
brew services start postgresql@18 # start it
|
|
69
|
+
createuser -s odoo # create the role odoo.conf expects
|
|
67
70
|
# Homebrew's versioned postgres is keg-only; add its bin to PATH if psql/createuser aren't found:
|
|
68
71
|
# export PATH="$(brew --prefix postgresql@18)/bin:$PATH"
|
|
69
72
|
```
|
|
@@ -71,11 +74,19 @@ createuser -s odoo # create the role odoo.conf expects
|
|
|
71
74
|
**Debian/Ubuntu:**
|
|
72
75
|
|
|
73
76
|
```bash
|
|
77
|
+
sudo apt-get install postgresql # install a server if you don't already have one
|
|
74
78
|
sudo systemctl start postgresql
|
|
75
79
|
sudo -u postgres createuser -s odoo
|
|
76
80
|
```
|
|
77
81
|
|
|
78
|
-
|
|
82
|
+
**Using a different / remote / Docker PostgreSQL:** set `DB_HOST`, `DB_PORT`,
|
|
83
|
+
`DB_USER`, `DB_PASSWORD` in `.env` before running `setup` (it writes them into
|
|
84
|
+
`conf/odoo.conf`), or edit `conf/odoo.conf` directly. With no `DB_HOST`, odoo-dev
|
|
85
|
+
connects over the local socket as the `odoo` role.
|
|
86
|
+
|
|
87
|
+
Before launching, `run`/`test`/`shell`/`update` run a quick connection preflight: if
|
|
88
|
+
the server is unreachable, the role is missing, or authentication fails, you get a
|
|
89
|
+
specific one-line fix instead of a stack trace.
|
|
79
90
|
|
|
80
91
|
## Commands
|
|
81
92
|
|
|
@@ -152,6 +163,14 @@ Create a `.env` file in your project root:
|
|
|
152
163
|
```bash
|
|
153
164
|
ODOO_VERSION=19.0
|
|
154
165
|
PYTHON_VERSION=3.12
|
|
166
|
+
|
|
167
|
+
# Optional — DB connection, written into conf/odoo.conf by `setup`.
|
|
168
|
+
# Omit DB_HOST/DB_PORT to use the local socket (the default). Set these to
|
|
169
|
+
# point at a remote / Docker / non-default PostgreSQL:
|
|
170
|
+
# DB_HOST=localhost
|
|
171
|
+
# DB_PORT=5432
|
|
172
|
+
# DB_USER=odoo
|
|
173
|
+
# DB_PASSWORD=odoo
|
|
155
174
|
```
|
|
156
175
|
|
|
157
176
|
## Requirements
|
|
@@ -41,15 +41,18 @@ Docker at the end — answer "no" if you only want the local venv workflow.
|
|
|
41
41
|
## Database setup (read this before your first `run`/`test`)
|
|
42
42
|
|
|
43
43
|
The generated `conf/odoo.conf` connects as PostgreSQL user **`odoo`** over the local
|
|
44
|
-
socket.
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
socket. **A running PostgreSQL server is a prerequisite you provide yourself** — on
|
|
45
|
+
every platform. `setup` installs only the PostgreSQL *client* and build dependencies
|
|
46
|
+
(macOS: `libpq`; Linux: `postgresql-client` + `libpq-dev`); it never installs, starts,
|
|
47
|
+
or configures a server, and never creates the `odoo` role. So on a fresh machine,
|
|
48
|
+
install a server, start it, and create the role once:
|
|
47
49
|
|
|
48
50
|
**macOS (Homebrew):**
|
|
49
51
|
|
|
50
52
|
```bash
|
|
51
|
-
brew
|
|
52
|
-
|
|
53
|
+
brew install postgresql@18 # install a server (pick your version)
|
|
54
|
+
brew services start postgresql@18 # start it
|
|
55
|
+
createuser -s odoo # create the role odoo.conf expects
|
|
53
56
|
# Homebrew's versioned postgres is keg-only; add its bin to PATH if psql/createuser aren't found:
|
|
54
57
|
# export PATH="$(brew --prefix postgresql@18)/bin:$PATH"
|
|
55
58
|
```
|
|
@@ -57,11 +60,19 @@ createuser -s odoo # create the role odoo.conf expects
|
|
|
57
60
|
**Debian/Ubuntu:**
|
|
58
61
|
|
|
59
62
|
```bash
|
|
63
|
+
sudo apt-get install postgresql # install a server if you don't already have one
|
|
60
64
|
sudo systemctl start postgresql
|
|
61
65
|
sudo -u postgres createuser -s odoo
|
|
62
66
|
```
|
|
63
67
|
|
|
64
|
-
|
|
68
|
+
**Using a different / remote / Docker PostgreSQL:** set `DB_HOST`, `DB_PORT`,
|
|
69
|
+
`DB_USER`, `DB_PASSWORD` in `.env` before running `setup` (it writes them into
|
|
70
|
+
`conf/odoo.conf`), or edit `conf/odoo.conf` directly. With no `DB_HOST`, odoo-dev
|
|
71
|
+
connects over the local socket as the `odoo` role.
|
|
72
|
+
|
|
73
|
+
Before launching, `run`/`test`/`shell`/`update` run a quick connection preflight: if
|
|
74
|
+
the server is unreachable, the role is missing, or authentication fails, you get a
|
|
75
|
+
specific one-line fix instead of a stack trace.
|
|
65
76
|
|
|
66
77
|
## Commands
|
|
67
78
|
|
|
@@ -138,6 +149,14 @@ Create a `.env` file in your project root:
|
|
|
138
149
|
```bash
|
|
139
150
|
ODOO_VERSION=19.0
|
|
140
151
|
PYTHON_VERSION=3.12
|
|
152
|
+
|
|
153
|
+
# Optional — DB connection, written into conf/odoo.conf by `setup`.
|
|
154
|
+
# Omit DB_HOST/DB_PORT to use the local socket (the default). Set these to
|
|
155
|
+
# point at a remote / Docker / non-default PostgreSQL:
|
|
156
|
+
# DB_HOST=localhost
|
|
157
|
+
# DB_PORT=5432
|
|
158
|
+
# DB_USER=odoo
|
|
159
|
+
# DB_PASSWORD=odoo
|
|
141
160
|
```
|
|
142
161
|
|
|
143
162
|
## Requirements
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
|
3
|
+
"packages": {
|
|
4
|
+
".": {
|
|
5
|
+
"release-type": "python",
|
|
6
|
+
"package-name": "odoo-dev",
|
|
7
|
+
"changelog-path": "CHANGELOG.md",
|
|
8
|
+
"include-component-in-tag": false,
|
|
9
|
+
"extra-files": ["src/odoo_dev/__init__.py"]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -10,6 +10,7 @@ from typing import Annotated
|
|
|
10
10
|
import typer
|
|
11
11
|
|
|
12
12
|
from odoo_dev.config import load_config
|
|
13
|
+
from odoo_dev.preflight import require_db
|
|
13
14
|
from odoo_dev.utils.console import error, info, success, warning
|
|
14
15
|
|
|
15
16
|
|
|
@@ -53,6 +54,8 @@ def run(
|
|
|
53
54
|
error("Run 'odoo-dev setup' first to clone Odoo repositories.")
|
|
54
55
|
raise typer.Exit(1)
|
|
55
56
|
|
|
57
|
+
require_db(cfg)
|
|
58
|
+
|
|
56
59
|
venv_python = cfg.venv_path / "bin" / "python"
|
|
57
60
|
|
|
58
61
|
# Build command
|
|
@@ -114,6 +117,8 @@ def shell(
|
|
|
114
117
|
error("Run 'odoo-dev setup' first.")
|
|
115
118
|
raise typer.Exit(1)
|
|
116
119
|
|
|
120
|
+
require_db(cfg)
|
|
121
|
+
|
|
117
122
|
venv_python = cfg.venv_path / "bin" / "python"
|
|
118
123
|
|
|
119
124
|
success(f"Opening Odoo shell with database {db_name}...")
|
|
@@ -148,6 +153,8 @@ def update(
|
|
|
148
153
|
error("Run 'odoo-dev setup' first.")
|
|
149
154
|
raise typer.Exit(1)
|
|
150
155
|
|
|
156
|
+
require_db(cfg)
|
|
157
|
+
|
|
151
158
|
venv_python = cfg.venv_path / "bin" / "python"
|
|
152
159
|
|
|
153
160
|
success(f"Updating modules: {modules} on database {db_name}...")
|
|
@@ -192,7 +199,8 @@ def test(
|
|
|
192
199
|
bool, typer.Option("--coverage/--no-coverage", help="Enable coverage reporting")
|
|
193
200
|
] = True,
|
|
194
201
|
keep_db: Annotated[
|
|
195
|
-
bool,
|
|
202
|
+
bool,
|
|
203
|
+
typer.Option("--keep-db/--no-keep-db", help="Keep test database after tests"),
|
|
196
204
|
] = False,
|
|
197
205
|
exclude: Annotated[
|
|
198
206
|
str | None,
|
|
@@ -254,6 +262,8 @@ def test(
|
|
|
254
262
|
if db_name is None:
|
|
255
263
|
db_name = f"test_{int(time.time())}"
|
|
256
264
|
|
|
265
|
+
require_db(cfg)
|
|
266
|
+
|
|
257
267
|
venv_python = cfg.venv_path / "bin" / "python"
|
|
258
268
|
|
|
259
269
|
# Determine modules to test
|
|
@@ -291,7 +301,15 @@ def test(
|
|
|
291
301
|
if not clone_dir.exists():
|
|
292
302
|
branch = os.environ.get("ODOO_VERSION", cfg.odoo_version)
|
|
293
303
|
result = subprocess.run(
|
|
294
|
-
[
|
|
304
|
+
[
|
|
305
|
+
"git",
|
|
306
|
+
"clone",
|
|
307
|
+
"--depth=1",
|
|
308
|
+
"-b",
|
|
309
|
+
branch,
|
|
310
|
+
repo_url,
|
|
311
|
+
str(clone_dir),
|
|
312
|
+
],
|
|
295
313
|
capture_output=True,
|
|
296
314
|
)
|
|
297
315
|
if result.returncode != 0:
|
|
@@ -370,7 +388,9 @@ def test(
|
|
|
370
388
|
|
|
371
389
|
if coverage:
|
|
372
390
|
# Build coverage source paths
|
|
373
|
-
coverage_source = ",".join(
|
|
391
|
+
coverage_source = ",".join(
|
|
392
|
+
str(cfg.addons_dir / m) for m in modules.split(",")
|
|
393
|
+
)
|
|
374
394
|
|
|
375
395
|
test_cmd = [
|
|
376
396
|
str(venv_python),
|
|
@@ -512,9 +532,12 @@ def _drop_database(cfg, db_name: str) -> None:
|
|
|
512
532
|
result = subprocess.run(
|
|
513
533
|
[
|
|
514
534
|
"dropdb",
|
|
515
|
-
"-h",
|
|
516
|
-
|
|
517
|
-
"-
|
|
535
|
+
"-h",
|
|
536
|
+
db_config["host"],
|
|
537
|
+
"-p",
|
|
538
|
+
db_config["port"],
|
|
539
|
+
"-U",
|
|
540
|
+
db_config["user"],
|
|
518
541
|
"--if-exists",
|
|
519
542
|
db_name,
|
|
520
543
|
],
|
|
@@ -534,10 +557,14 @@ def _terminate_connections(db_name: str, db_config: dict[str, str]) -> None:
|
|
|
534
557
|
subprocess.run(
|
|
535
558
|
[
|
|
536
559
|
"psql",
|
|
537
|
-
"-h",
|
|
538
|
-
|
|
539
|
-
"-
|
|
540
|
-
"
|
|
560
|
+
"-h",
|
|
561
|
+
db_config["host"],
|
|
562
|
+
"-p",
|
|
563
|
+
db_config["port"],
|
|
564
|
+
"-U",
|
|
565
|
+
db_config["user"],
|
|
566
|
+
"-d",
|
|
567
|
+
"postgres",
|
|
541
568
|
"-c",
|
|
542
569
|
f"SELECT pg_terminate_backend(pid) FROM pg_stat_activity "
|
|
543
570
|
f"WHERE datname = '{db_name}' AND pid <> pg_backend_pid()",
|
|
@@ -547,6 +574,7 @@ def _terminate_connections(db_name: str, db_config: dict[str, str]) -> None:
|
|
|
547
574
|
env=env,
|
|
548
575
|
)
|
|
549
576
|
|
|
577
|
+
|
|
550
578
|
def _get_addons_path(config_file) -> str:
|
|
551
579
|
"""Extract addons_path from odoo.conf."""
|
|
552
580
|
for line in config_file.read_text().splitlines():
|
|
@@ -309,12 +309,28 @@ def _setup_odoo_config(cfg, community_only: bool = False) -> None:
|
|
|
309
309
|
# Filter to only existing paths
|
|
310
310
|
addons_paths = [p for p in addons_paths if Path(p).exists()]
|
|
311
311
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
db_user = odoo
|
|
316
|
-
db_password = odoo
|
|
317
|
-
"""
|
|
312
|
+
# DB connection is configurable via .env (loaded into the environment by
|
|
313
|
+
# load_config). Host/port are written only when set, so the default is the
|
|
314
|
+
# local socket — matching odoo-bin's behavior when db_host is absent.
|
|
315
|
+
db_user = os.getenv("DB_USER", "odoo")
|
|
316
|
+
db_password = os.getenv("DB_PASSWORD", "odoo")
|
|
317
|
+
db_host = os.getenv("DB_HOST", "")
|
|
318
|
+
db_port = os.getenv("DB_PORT", "")
|
|
319
|
+
|
|
320
|
+
db_lines = []
|
|
321
|
+
if db_host:
|
|
322
|
+
db_lines.append(f"db_host = {db_host}")
|
|
323
|
+
if db_port:
|
|
324
|
+
db_lines.append(f"db_port = {db_port}")
|
|
325
|
+
db_lines.append(f"db_user = {db_user}")
|
|
326
|
+
db_lines.append(f"db_password = {db_password}")
|
|
327
|
+
|
|
328
|
+
config_content = (
|
|
329
|
+
"[options]\n"
|
|
330
|
+
f"addons_path = {','.join(addons_paths)}\n"
|
|
331
|
+
"admin_passwd = admin\n"
|
|
332
|
+
f"{chr(10).join(db_lines)}\n"
|
|
333
|
+
)
|
|
318
334
|
|
|
319
335
|
conf_file.write_text(config_content)
|
|
320
336
|
conf_file.chmod(0o600)
|
|
@@ -477,11 +493,14 @@ def _install_system_dependencies() -> None:
|
|
|
477
493
|
|
|
478
494
|
if system == "Darwin":
|
|
479
495
|
success("Installing dependencies for macOS...")
|
|
496
|
+
# PostgreSQL is a prerequisite you provide yourself (see the README's
|
|
497
|
+
# "Database setup" section) — we install only the client library and
|
|
498
|
+
# build deps, never the server. `libpq` is keg-only but ships psql /
|
|
499
|
+
# createuser, which is all the client side needs.
|
|
480
500
|
subprocess.run(
|
|
481
501
|
[
|
|
482
502
|
"brew",
|
|
483
503
|
"install",
|
|
484
|
-
"postgresql",
|
|
485
504
|
"libpq",
|
|
486
505
|
"openssl",
|
|
487
506
|
"libxml2",
|
|
@@ -9,6 +9,56 @@ DEFAULT_ODOO_VERSION = "19.0"
|
|
|
9
9
|
DEFAULT_PYTHON_VERSION = "3.12"
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
@dataclass
|
|
13
|
+
class DbConfig:
|
|
14
|
+
"""PostgreSQL connection settings, as odoo-bin will use them.
|
|
15
|
+
|
|
16
|
+
An empty ``host``/``port`` means "use the libpq defaults" (i.e. the local
|
|
17
|
+
Unix socket), which is exactly what odoo-bin does when ``db_host`` is absent
|
|
18
|
+
from odoo.conf. The preflight check must mirror that, so we keep them empty
|
|
19
|
+
rather than substituting ``localhost``/``5432`` (which would force TCP and
|
|
20
|
+
can spuriously fail on socket/peer-auth setups).
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
host: str = ""
|
|
24
|
+
port: str = ""
|
|
25
|
+
user: str = "odoo"
|
|
26
|
+
password: str = ""
|
|
27
|
+
name: str | None = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def read_db_config(config_file: Path) -> "DbConfig":
|
|
31
|
+
"""Read DB connection settings from an odoo.conf file.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
config_file: Path to odoo.conf. A missing file yields defaults.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
DbConfig reflecting the connection odoo-bin would make.
|
|
38
|
+
"""
|
|
39
|
+
db = DbConfig()
|
|
40
|
+
if not config_file.exists():
|
|
41
|
+
return db
|
|
42
|
+
|
|
43
|
+
for line in config_file.read_text().splitlines():
|
|
44
|
+
line = line.strip()
|
|
45
|
+
if "=" not in line or line.startswith("#"):
|
|
46
|
+
continue
|
|
47
|
+
key, _, value = line.partition("=")
|
|
48
|
+
key, value = key.strip(), value.strip()
|
|
49
|
+
if key == "db_host":
|
|
50
|
+
db.host = value
|
|
51
|
+
elif key == "db_port":
|
|
52
|
+
db.port = value
|
|
53
|
+
elif key == "db_user":
|
|
54
|
+
db.user = value
|
|
55
|
+
elif key == "db_password":
|
|
56
|
+
db.password = value
|
|
57
|
+
elif key == "db_name":
|
|
58
|
+
db.name = value
|
|
59
|
+
return db
|
|
60
|
+
|
|
61
|
+
|
|
12
62
|
@dataclass
|
|
13
63
|
class ProjectConfig:
|
|
14
64
|
"""Configuration for an Odoo development project."""
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Fast PostgreSQL connectivity preflight for commands that need a database.
|
|
2
|
+
|
|
3
|
+
odoo-dev does not own the PostgreSQL server lifecycle (you bring your own:
|
|
4
|
+
a local service, Docker, or a remote/managed DB). Instead, commands that need
|
|
5
|
+
a database call :func:`require_db` first, which connects exactly the way
|
|
6
|
+
odoo-bin will and, on failure, prints a specific, actionable diagnostic rather
|
|
7
|
+
than letting Odoo fail later with a noisy stack trace.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
from odoo_dev.config import DbConfig, read_db_config
|
|
17
|
+
from odoo_dev.utils.console import error, warning
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class PreflightResult:
|
|
22
|
+
"""Outcome of a connectivity check."""
|
|
23
|
+
|
|
24
|
+
ok: bool
|
|
25
|
+
category: str # ok | client_missing | unreachable | auth | role_missing | db_missing | unknown
|
|
26
|
+
message: str # raw stderr (or short note), for context
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def psql_argv(
|
|
30
|
+
db: DbConfig, dbname: str = "postgres", sql: str = "SELECT 1"
|
|
31
|
+
) -> list[str]:
|
|
32
|
+
"""Build a psql argv that connects the way odoo-bin would.
|
|
33
|
+
|
|
34
|
+
Host/port are only passed when set, so an unset host uses the local socket
|
|
35
|
+
(libpq default) rather than forcing TCP to localhost.
|
|
36
|
+
"""
|
|
37
|
+
# -w / --no-password: never prompt interactively (would hang a preflight);
|
|
38
|
+
# fail fast instead so we can classify the failure.
|
|
39
|
+
argv = ["psql", "-w"]
|
|
40
|
+
if db.host:
|
|
41
|
+
argv += ["-h", db.host]
|
|
42
|
+
if db.port:
|
|
43
|
+
argv += ["-p", db.port]
|
|
44
|
+
argv += ["-U", db.user, "-d", dbname, "-tAc", sql]
|
|
45
|
+
return argv
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def classify_psql_failure(returncode: int, stderr: str) -> str:
|
|
49
|
+
"""Map a failed psql attempt to a category for actionable guidance."""
|
|
50
|
+
s = stderr.lower()
|
|
51
|
+
# Order matters: the "does not exist" variants must be checked before the
|
|
52
|
+
# generic connection failures.
|
|
53
|
+
if "does not exist" in s and "role" in s:
|
|
54
|
+
return "role_missing"
|
|
55
|
+
if "does not exist" in s and "database" in s:
|
|
56
|
+
return "db_missing"
|
|
57
|
+
if "authentication failed" in s or "no password supplied" in s:
|
|
58
|
+
return "auth"
|
|
59
|
+
if (
|
|
60
|
+
"could not connect" in s
|
|
61
|
+
or "connection refused" in s
|
|
62
|
+
or "could not translate host" in s
|
|
63
|
+
or "no such file or directory" in s
|
|
64
|
+
or "is the server running" in s
|
|
65
|
+
):
|
|
66
|
+
return "unreachable"
|
|
67
|
+
return "unknown"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def check_db_connection(db: DbConfig, dbname: str = "postgres") -> PreflightResult:
|
|
71
|
+
"""Attempt a trivial query and report whether (and why) it failed."""
|
|
72
|
+
env = os.environ.copy()
|
|
73
|
+
if db.password:
|
|
74
|
+
env["PGPASSWORD"] = db.password
|
|
75
|
+
try:
|
|
76
|
+
proc = subprocess.run(
|
|
77
|
+
psql_argv(db, dbname=dbname),
|
|
78
|
+
capture_output=True,
|
|
79
|
+
text=True,
|
|
80
|
+
env=env,
|
|
81
|
+
)
|
|
82
|
+
except FileNotFoundError:
|
|
83
|
+
return PreflightResult(
|
|
84
|
+
False,
|
|
85
|
+
"client_missing",
|
|
86
|
+
"psql executable not found on PATH",
|
|
87
|
+
)
|
|
88
|
+
if proc.returncode == 0:
|
|
89
|
+
return PreflightResult(True, "ok", "")
|
|
90
|
+
return PreflightResult(
|
|
91
|
+
False,
|
|
92
|
+
classify_psql_failure(proc.returncode, proc.stderr),
|
|
93
|
+
proc.stderr.strip(),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _target(db: DbConfig) -> str:
|
|
98
|
+
"""Human-readable connection target for messages."""
|
|
99
|
+
where = f"{db.host}:{db.port or '5432'}" if db.host else "the local socket"
|
|
100
|
+
return f"PostgreSQL as user '{db.user}' on {where}"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _guidance(category: str, db: DbConfig) -> str:
|
|
104
|
+
"""Actionable next step for a failure category."""
|
|
105
|
+
if category == "client_missing":
|
|
106
|
+
return (
|
|
107
|
+
"The psql client isn't on your PATH. Install the PostgreSQL client "
|
|
108
|
+
"(macOS: `brew install libpq` and add it to PATH; Debian/Ubuntu: "
|
|
109
|
+
"`sudo apt-get install postgresql-client`)."
|
|
110
|
+
)
|
|
111
|
+
if category == "unreachable":
|
|
112
|
+
return (
|
|
113
|
+
"Could not reach a PostgreSQL server. Start one (macOS: "
|
|
114
|
+
"`brew services start postgresql@18`; Debian/Ubuntu: "
|
|
115
|
+
"`sudo systemctl start postgresql`), or set db_host/db_port in "
|
|
116
|
+
"conf/odoo.conf (or DB_HOST/DB_PORT in .env) to point at an "
|
|
117
|
+
"existing / remote / Docker PostgreSQL."
|
|
118
|
+
)
|
|
119
|
+
if category == "auth":
|
|
120
|
+
return (
|
|
121
|
+
f"Authentication failed for role '{db.user}'. Check db_user/"
|
|
122
|
+
"db_password in conf/odoo.conf, or adjust the server's pg_hba.conf "
|
|
123
|
+
"(peer vs. password auth). A ~/.pgpass entry can supply the password."
|
|
124
|
+
)
|
|
125
|
+
if category == "role_missing":
|
|
126
|
+
return (
|
|
127
|
+
f"The role '{db.user}' does not exist. Create it once: "
|
|
128
|
+
f"`createuser -s {db.user}` (Debian/Ubuntu: prefix with "
|
|
129
|
+
"`sudo -u postgres`)."
|
|
130
|
+
)
|
|
131
|
+
if category == "db_missing":
|
|
132
|
+
return (
|
|
133
|
+
"The maintenance database is missing — unusual; your PostgreSQL "
|
|
134
|
+
"install may be incomplete."
|
|
135
|
+
)
|
|
136
|
+
return "See the PostgreSQL error above."
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def require_db(cfg, dbname: str = "postgres") -> None:
|
|
140
|
+
"""Ensure the configured database server is reachable, or exit with guidance.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
cfg: ProjectConfig (only ``config_file`` is used).
|
|
144
|
+
dbname: Database to connect to for the check (default: ``postgres``).
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
typer.Exit: if the server cannot be reached / authenticated.
|
|
148
|
+
"""
|
|
149
|
+
db = read_db_config(cfg.config_file)
|
|
150
|
+
result = check_db_connection(db, dbname=dbname)
|
|
151
|
+
if result.ok:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
error(f"Cannot connect to {_target(db)}.")
|
|
155
|
+
if result.message:
|
|
156
|
+
warning(result.message)
|
|
157
|
+
error(_guidance(result.category, db))
|
|
158
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Tests for reading DB connection settings the way odoo-bin will use them."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from odoo_dev.config import DbConfig, read_db_config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestReadDbConfig:
|
|
9
|
+
def test_parses_full_config(self, tmp_path: Path):
|
|
10
|
+
conf = tmp_path / "odoo.conf"
|
|
11
|
+
conf.write_text(
|
|
12
|
+
"[options]\n"
|
|
13
|
+
"db_host = db.example.com\n"
|
|
14
|
+
"db_port = 5544\n"
|
|
15
|
+
"db_user = appuser\n"
|
|
16
|
+
"db_password = secret\n"
|
|
17
|
+
"db_name = mydb\n"
|
|
18
|
+
)
|
|
19
|
+
db = read_db_config(conf)
|
|
20
|
+
assert db == DbConfig(
|
|
21
|
+
host="db.example.com",
|
|
22
|
+
port="5544",
|
|
23
|
+
user="appuser",
|
|
24
|
+
password="secret",
|
|
25
|
+
name="mydb",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def test_unset_host_port_stay_empty_for_socket(self, tmp_path: Path):
|
|
29
|
+
# odoo-bin with no db_host uses the local socket; preflight must mirror that
|
|
30
|
+
conf = tmp_path / "odoo.conf"
|
|
31
|
+
conf.write_text("[options]\ndb_user = odoo\ndb_password = odoo\n")
|
|
32
|
+
db = read_db_config(conf)
|
|
33
|
+
assert db.host == ""
|
|
34
|
+
assert db.port == ""
|
|
35
|
+
assert db.user == "odoo"
|
|
36
|
+
assert db.password == "odoo"
|
|
37
|
+
assert db.name is None
|
|
38
|
+
|
|
39
|
+
def test_defaults_when_file_missing(self, tmp_path: Path):
|
|
40
|
+
db = read_db_config(tmp_path / "nonexistent.conf")
|
|
41
|
+
assert db == DbConfig()
|
|
42
|
+
assert db.user == "odoo"
|
|
43
|
+
|
|
44
|
+
def test_handles_whitespace(self, tmp_path: Path):
|
|
45
|
+
conf = tmp_path / "odoo.conf"
|
|
46
|
+
conf.write_text("db_user = spacey \ndb_host=nospace\n")
|
|
47
|
+
db = read_db_config(conf)
|
|
48
|
+
assert db.user == "spacey"
|
|
49
|
+
assert db.host == "nospace"
|