mcp-postgresql-ops 3.3.9__tar.gz → 3.4.1__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.
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/workflows/pypi-publish.yml +33 -8
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/workflows/test.yml +1 -1
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/PKG-INFO +28 -9
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/README.md +27 -8
- mcp_postgresql_ops-3.4.1/scripts/check_release.py +72 -0
- mcp_postgresql_ops-3.4.1/scripts/publish_release.py +141 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops/mcp_main.py +33 -33
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/PKG-INFO +28 -9
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/SOURCES.txt +4 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/scm_file_list.json +4 -0
- mcp_postgresql_ops-3.4.1/src/mcp_postgresql_ops.egg-info/scm_version.json +8 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/tests/conftest.py +1 -1
- mcp_postgresql_ops-3.4.1/tests/test_release.py +86 -0
- mcp_postgresql_ops-3.4.1/tests/test_tool_schema_null_defaults.py +33 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/tests/test_tools_integration.py +1 -1
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/uv.lock +18 -29
- mcp_postgresql_ops-3.3.9/src/mcp_postgresql_ops.egg-info/scm_version.json +0 -8
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.env.example +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/CONTRIBUTING.md +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/FUNDING.yml +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/copilot-instructions.md +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/dependabot.yml +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/pull_request_template.md +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.gitignore +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.gitleaks.toml +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.pre-commit-config.yaml +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.python-version +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/Dockerfile.MCP-Server +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/Dockerfile.MCPO-Proxy +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/LICENSE +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/MANIFEST.in +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/SECURITY.md +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/_config.yml +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/_includes/head-custom-google-analytics.html +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/build-mcp-server-docker-image.sh +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/build-mcpo-server-docker-image.sh +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/docker-compose.custom-db.yml +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/docker-compose.yml +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/MCP-Workflow-of-Quickstart-Tutorial.png +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-000.png +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-001.png +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-002.png +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-004.png +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-005.png +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-claude-desktop-airflow-connections-html.png +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-claude-desktop-mermaid-diagram.png +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-claude-desktop-question-001.png +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-swagger-api.png +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/mcp-config.json.http +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/mcp-config.json.stdio +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/pyproject.toml +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/run-mcp-inspector-local.sh +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/run-mcp-inspector-pypi.sh +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/scripts/create-test-data.sh +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/scripts/create-test-data.sql +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/scripts/enable-pgsql-extensions.sh +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/scripts/mcp-server-docker-cmd.sh +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/setup.cfg +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops/__init__.py +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops/__main__.py +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops/functions.py +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops/prompt_template.md +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops/version_compat.py +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/dependency_links.txt +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/entry_points.txt +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/requires.txt +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/top_level.txt +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/tests/__init__.py +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/tests/docker/docker-compose.test.yml +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/tests/docker/init-test-db.sql +0 -0
- {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/tests/test_version_compat.py +0 -0
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
|
3
|
-
|
|
4
|
-
name: Publish to PyPI on tag
|
|
1
|
+
name: Publish Packages and GitHub Release on Tag
|
|
5
2
|
|
|
6
3
|
on:
|
|
7
4
|
push:
|
|
@@ -9,7 +6,6 @@ on:
|
|
|
9
6
|
|
|
10
7
|
permissions:
|
|
11
8
|
contents: read
|
|
12
|
-
id-token: write
|
|
13
9
|
|
|
14
10
|
jobs:
|
|
15
11
|
build:
|
|
@@ -26,10 +22,15 @@ jobs:
|
|
|
26
22
|
run: pip install --upgrade build
|
|
27
23
|
- name: Build distributions
|
|
28
24
|
run: python -m build
|
|
25
|
+
- name: Verify tag and distribution versions
|
|
26
|
+
env:
|
|
27
|
+
RELEASE_TAG: ${{ github.ref_name }}
|
|
28
|
+
run: python -m scripts.check_release "$RELEASE_TAG" dist
|
|
29
29
|
- uses: actions/upload-artifact@v7
|
|
30
30
|
with:
|
|
31
31
|
name: dist
|
|
32
32
|
path: dist/
|
|
33
|
+
if-no-files-found: error
|
|
33
34
|
|
|
34
35
|
publish-to-testpypi:
|
|
35
36
|
name: Publish to TestPyPI
|
|
@@ -55,8 +56,7 @@ jobs:
|
|
|
55
56
|
name: Publish to PyPI
|
|
56
57
|
if: startsWith(github.ref, 'refs/tags/')
|
|
57
58
|
runs-on: ubuntu-latest
|
|
58
|
-
needs:
|
|
59
|
-
#needs: publish-to-testpypi
|
|
59
|
+
needs: publish-to-testpypi
|
|
60
60
|
permissions:
|
|
61
61
|
id-token: write
|
|
62
62
|
environment:
|
|
@@ -70,4 +70,29 @@ jobs:
|
|
|
70
70
|
- name: Publish to PyPI
|
|
71
71
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
72
72
|
with:
|
|
73
|
-
repository-url: https://upload.pypi.org/legacy/
|
|
73
|
+
repository-url: https://upload.pypi.org/legacy/
|
|
74
|
+
|
|
75
|
+
release:
|
|
76
|
+
name: Publish GitHub Release
|
|
77
|
+
needs: publish-to-pypi
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
timeout-minutes: 5
|
|
80
|
+
permissions:
|
|
81
|
+
contents: write
|
|
82
|
+
concurrency:
|
|
83
|
+
group: github-release-${{ github.repository }}
|
|
84
|
+
cancel-in-progress: false
|
|
85
|
+
queue: max
|
|
86
|
+
env:
|
|
87
|
+
GH_TOKEN: ${{ github.token }}
|
|
88
|
+
RELEASE_TAG: ${{ github.ref_name }}
|
|
89
|
+
steps:
|
|
90
|
+
- uses: actions/checkout@v7
|
|
91
|
+
with:
|
|
92
|
+
persist-credentials: false
|
|
93
|
+
fetch-depth: 0
|
|
94
|
+
- uses: actions/setup-python@v7
|
|
95
|
+
with:
|
|
96
|
+
python-version: "3.12.x"
|
|
97
|
+
- name: Publish release with commit messages
|
|
98
|
+
run: python -m scripts.publish_release "$RELEASE_TAG"
|
|
@@ -32,7 +32,7 @@ jobs:
|
|
|
32
32
|
run: uv sync --extra dev
|
|
33
33
|
|
|
34
34
|
- name: Run unit tests
|
|
35
|
-
run: uv run pytest tests/test_version_compat.py -v --tb=short
|
|
35
|
+
run: uv run pytest tests/test_version_compat.py tests/test_tool_schema_null_defaults.py tests/test_release.py -v --tb=short
|
|
36
36
|
integration-tests:
|
|
37
37
|
name: Integration Tests (PG 12-18)
|
|
38
38
|
runs-on: ubuntu-latest
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-postgresql-ops
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.1
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Author-email: JungJungIn <call518@gmail.com>
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -34,7 +34,7 @@ Dynamic: license-file
|
|
|
34
34
|
|
|
35
35
|
## Architecture & Internal (DeepWiki)
|
|
36
36
|
|
|
37
|
-
[](https://deepwiki.com/call518/MCP-PostgreSQL-Ops)
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
@@ -71,7 +71,7 @@ Dynamic: license-file
|
|
|
71
71
|
|
|
72
72
|
## Tool Usage Examples
|
|
73
73
|
|
|
74
|
-
### 📸 **[More Examples with Screenshots →](https://github.com/call518/MCP-PostgreSQL-Ops/wiki
|
|
74
|
+
### 📸 **[More Examples with Screenshots →](https://github.com/call518/MCP-PostgreSQL-Ops/wiki)**
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
@@ -147,7 +147,8 @@ docker-compose logs -f
|
|
|
147
147
|
docker-compose ps
|
|
148
148
|
|
|
149
149
|
# Verify PostgreSQL is ready
|
|
150
|
-
docker
|
|
150
|
+
docker compose exec -T postgres sh -c \
|
|
151
|
+
'pg_isready -h 127.0.0.1 -p 5432 -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
|
|
151
152
|
```
|
|
152
153
|
|
|
153
154
|
### 3. Access to OpenWebUI
|
|
@@ -180,7 +181,7 @@ http://localhost:3003/
|
|
|
180
181
|
|
|
181
182
|
#### 📖 **Next Steps:**
|
|
182
183
|
- Browse the **[Example Queries section](#usage-examples)** below for more query examples
|
|
183
|
-
- Check out **[Tool Usage Examples with Screenshots](https://github.com/call518/MCP-PostgreSQL-Ops/wiki
|
|
184
|
+
- Check out **[Tool Usage Examples with Screenshots](https://github.com/call518/MCP-PostgreSQL-Ops/wiki)** for visual guides
|
|
184
185
|
- Explore the **[Tool Compatibility Matrix](#tool-compatibility-matrix)** to understand available features
|
|
185
186
|
|
|
186
187
|
---
|
|
@@ -308,6 +309,20 @@ The `create-test-data.sql` script is executed by the `postgres-init-extensions`
|
|
|
308
309
|
|
|
309
310
|
---
|
|
310
311
|
|
|
312
|
+
## Tagged Releases
|
|
313
|
+
|
|
314
|
+
The GitHub Actions workflow publishes a pushed version tag (for example,
|
|
315
|
+
`v1.2.3`, `1.2.3`, or `1.2.3rc1`) to TestPyPI, then PyPI, and finally creates
|
|
316
|
+
a GitHub Release with the tag as its title. Both distribution versions must match
|
|
317
|
+
the tag. The existing `testpypi` and `pypi` GitHub environments must have Trusted
|
|
318
|
+
Publishing configured on their respective package indexes.
|
|
319
|
+
|
|
320
|
+
Release notes include full commit messages since the most recently published
|
|
321
|
+
GitHub Release, or all commits for the first release. The previous release tag
|
|
322
|
+
must be an ancestor of the new tag. Alpha, beta, and release candidate tags create
|
|
323
|
+
prereleases. Existing published releases are preserved; an existing draft requires
|
|
324
|
+
manual review. A failed publication prevents subsequent publication jobs.
|
|
325
|
+
|
|
311
326
|
## Installation
|
|
312
327
|
|
|
313
328
|
### From PyPI (Recommended)
|
|
@@ -952,11 +967,12 @@ mcp-postgresql-ops --log-level DEBUG
|
|
|
952
967
|
|
|
953
968
|
### Running Tests
|
|
954
969
|
|
|
955
|
-
|
|
970
|
+
The following test suites are available:
|
|
956
971
|
|
|
957
972
|
| Suite | File | Requires Docker |
|
|
958
973
|
|-------|------|----------------|
|
|
959
974
|
| Unit tests (version compatibility logic) | `tests/test_version_compat.py` | No |
|
|
975
|
+
| Unit tests (null-default tool argument schemas) | `tests/test_tool_schema_null_defaults.py` | No |
|
|
960
976
|
| Integration tests (all tools × PG 12–18) | `tests/test_tools_integration.py` | Yes |
|
|
961
977
|
|
|
962
978
|
`uv run pytest` automatically starts the Docker test containers (PG 12–18), waits for them to be fully initialized, runs all tests, then tears everything down.
|
|
@@ -965,14 +981,17 @@ Two test suites are available:
|
|
|
965
981
|
# Run all tests (unit + integration) — Docker is managed automatically
|
|
966
982
|
uv run pytest -v
|
|
967
983
|
|
|
968
|
-
# Unit tests only (no Docker needed)
|
|
969
|
-
uv run pytest tests/test_version_compat.py -v
|
|
984
|
+
# Unit tests only (no Docker or PostgreSQL needed)
|
|
985
|
+
uv run pytest tests/test_version_compat.py tests/test_tool_schema_null_defaults.py -v
|
|
986
|
+
|
|
987
|
+
# Tool argument schema tests only (no Docker or PostgreSQL needed)
|
|
988
|
+
uv run pytest tests/test_tool_schema_null_defaults.py -v
|
|
970
989
|
|
|
971
990
|
# Integration tests only
|
|
972
991
|
uv run pytest tests/test_tools_integration.py -v
|
|
973
992
|
```
|
|
974
993
|
|
|
975
|
-
> **Note**: Docker must be running. The test stack uses ports 5412–5418 (PG 12–18).
|
|
994
|
+
> **Note**: Docker must be running for integration tests. The test stack uses ports 5412–5418 (PG 12–18).
|
|
976
995
|
|
|
977
996
|
### Version Compatibility Testing
|
|
978
997
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
## Architecture & Internal (DeepWiki)
|
|
18
18
|
|
|
19
|
-
[](https://deepwiki.com/call518/MCP-PostgreSQL-Ops)
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
|
|
54
54
|
## Tool Usage Examples
|
|
55
55
|
|
|
56
|
-
### 📸 **[More Examples with Screenshots →](https://github.com/call518/MCP-PostgreSQL-Ops/wiki
|
|
56
|
+
### 📸 **[More Examples with Screenshots →](https://github.com/call518/MCP-PostgreSQL-Ops/wiki)**
|
|
57
57
|
|
|
58
58
|
---
|
|
59
59
|
|
|
@@ -129,7 +129,8 @@ docker-compose logs -f
|
|
|
129
129
|
docker-compose ps
|
|
130
130
|
|
|
131
131
|
# Verify PostgreSQL is ready
|
|
132
|
-
docker
|
|
132
|
+
docker compose exec -T postgres sh -c \
|
|
133
|
+
'pg_isready -h 127.0.0.1 -p 5432 -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
|
|
133
134
|
```
|
|
134
135
|
|
|
135
136
|
### 3. Access to OpenWebUI
|
|
@@ -162,7 +163,7 @@ http://localhost:3003/
|
|
|
162
163
|
|
|
163
164
|
#### 📖 **Next Steps:**
|
|
164
165
|
- Browse the **[Example Queries section](#usage-examples)** below for more query examples
|
|
165
|
-
- Check out **[Tool Usage Examples with Screenshots](https://github.com/call518/MCP-PostgreSQL-Ops/wiki
|
|
166
|
+
- Check out **[Tool Usage Examples with Screenshots](https://github.com/call518/MCP-PostgreSQL-Ops/wiki)** for visual guides
|
|
166
167
|
- Explore the **[Tool Compatibility Matrix](#tool-compatibility-matrix)** to understand available features
|
|
167
168
|
|
|
168
169
|
---
|
|
@@ -290,6 +291,20 @@ The `create-test-data.sql` script is executed by the `postgres-init-extensions`
|
|
|
290
291
|
|
|
291
292
|
---
|
|
292
293
|
|
|
294
|
+
## Tagged Releases
|
|
295
|
+
|
|
296
|
+
The GitHub Actions workflow publishes a pushed version tag (for example,
|
|
297
|
+
`v1.2.3`, `1.2.3`, or `1.2.3rc1`) to TestPyPI, then PyPI, and finally creates
|
|
298
|
+
a GitHub Release with the tag as its title. Both distribution versions must match
|
|
299
|
+
the tag. The existing `testpypi` and `pypi` GitHub environments must have Trusted
|
|
300
|
+
Publishing configured on their respective package indexes.
|
|
301
|
+
|
|
302
|
+
Release notes include full commit messages since the most recently published
|
|
303
|
+
GitHub Release, or all commits for the first release. The previous release tag
|
|
304
|
+
must be an ancestor of the new tag. Alpha, beta, and release candidate tags create
|
|
305
|
+
prereleases. Existing published releases are preserved; an existing draft requires
|
|
306
|
+
manual review. A failed publication prevents subsequent publication jobs.
|
|
307
|
+
|
|
293
308
|
## Installation
|
|
294
309
|
|
|
295
310
|
### From PyPI (Recommended)
|
|
@@ -934,11 +949,12 @@ mcp-postgresql-ops --log-level DEBUG
|
|
|
934
949
|
|
|
935
950
|
### Running Tests
|
|
936
951
|
|
|
937
|
-
|
|
952
|
+
The following test suites are available:
|
|
938
953
|
|
|
939
954
|
| Suite | File | Requires Docker |
|
|
940
955
|
|-------|------|----------------|
|
|
941
956
|
| Unit tests (version compatibility logic) | `tests/test_version_compat.py` | No |
|
|
957
|
+
| Unit tests (null-default tool argument schemas) | `tests/test_tool_schema_null_defaults.py` | No |
|
|
942
958
|
| Integration tests (all tools × PG 12–18) | `tests/test_tools_integration.py` | Yes |
|
|
943
959
|
|
|
944
960
|
`uv run pytest` automatically starts the Docker test containers (PG 12–18), waits for them to be fully initialized, runs all tests, then tears everything down.
|
|
@@ -947,14 +963,17 @@ Two test suites are available:
|
|
|
947
963
|
# Run all tests (unit + integration) — Docker is managed automatically
|
|
948
964
|
uv run pytest -v
|
|
949
965
|
|
|
950
|
-
# Unit tests only (no Docker needed)
|
|
951
|
-
uv run pytest tests/test_version_compat.py -v
|
|
966
|
+
# Unit tests only (no Docker or PostgreSQL needed)
|
|
967
|
+
uv run pytest tests/test_version_compat.py tests/test_tool_schema_null_defaults.py -v
|
|
968
|
+
|
|
969
|
+
# Tool argument schema tests only (no Docker or PostgreSQL needed)
|
|
970
|
+
uv run pytest tests/test_tool_schema_null_defaults.py -v
|
|
952
971
|
|
|
953
972
|
# Integration tests only
|
|
954
973
|
uv run pytest tests/test_tools_integration.py -v
|
|
955
974
|
```
|
|
956
975
|
|
|
957
|
-
> **Note**: Docker must be running. The test stack uses ports 5412–5418 (PG 12–18).
|
|
976
|
+
> **Note**: Docker must be running for integration tests. The test stack uses ports 5412–5418 (PG 12–18).
|
|
958
977
|
|
|
959
978
|
### Version Compatibility Testing
|
|
960
979
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Require both distribution metadata versions to match the release tag."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import re
|
|
5
|
+
import tarfile
|
|
6
|
+
import zipfile
|
|
7
|
+
from email.parser import BytesParser
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def release_version(tag: str) -> str:
|
|
12
|
+
"""Validate a canonical release tag and return its package version."""
|
|
13
|
+
version = tag.removeprefix("v")
|
|
14
|
+
number = r"(?:0|[1-9][0-9]*)"
|
|
15
|
+
if not re.fullmatch(
|
|
16
|
+
rf"{number}\.{number}\.{number}(?:(?:a|b|rc){number})?", version
|
|
17
|
+
):
|
|
18
|
+
raise ValueError("Use a tag such as 0.5.7, v0.5.7, or 0.5.7rc1")
|
|
19
|
+
return version
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def check_release(tag: str, directory: Path) -> None:
|
|
23
|
+
"""Validate a canonical release tag and the wheel/sdist prepared for upload."""
|
|
24
|
+
version = release_version(tag)
|
|
25
|
+
wheels = list(directory.glob("*.whl"))
|
|
26
|
+
sdists = list(directory.glob("*.tar.gz"))
|
|
27
|
+
if len(wheels) != 1 or len(sdists) != 1:
|
|
28
|
+
raise ValueError("Expected exactly one wheel and one source distribution")
|
|
29
|
+
|
|
30
|
+
with zipfile.ZipFile(wheels[0]) as archive:
|
|
31
|
+
metadata_files = [
|
|
32
|
+
name for name in archive.namelist() if name.endswith(".dist-info/METADATA")
|
|
33
|
+
]
|
|
34
|
+
if len(metadata_files) != 1:
|
|
35
|
+
raise ValueError("Expected one wheel metadata file")
|
|
36
|
+
wheel_metadata = archive.read(metadata_files[0])
|
|
37
|
+
with tarfile.open(sdists[0], "r:gz") as archive:
|
|
38
|
+
metadata_files = [
|
|
39
|
+
item
|
|
40
|
+
for item in archive.getmembers()
|
|
41
|
+
if len(Path(item.name).parts) == 2 and item.name.endswith("/PKG-INFO")
|
|
42
|
+
]
|
|
43
|
+
if len(metadata_files) != 1:
|
|
44
|
+
raise ValueError("Expected one source distribution metadata file")
|
|
45
|
+
stream = archive.extractfile(metadata_files[0])
|
|
46
|
+
if stream is None:
|
|
47
|
+
raise ValueError("Source distribution metadata is not a file")
|
|
48
|
+
with stream:
|
|
49
|
+
sdist_metadata = stream.read()
|
|
50
|
+
|
|
51
|
+
for path, raw in zip((wheels[0], sdists[0]), (wheel_metadata, sdist_metadata)):
|
|
52
|
+
metadata = BytesParser().parsebytes(raw)
|
|
53
|
+
if metadata.get("Name") != "mcp-postgresql-ops" or metadata.get("Version") != version:
|
|
54
|
+
raise ValueError(f"{path.name}: expected mcp-postgresql-ops {version}")
|
|
55
|
+
print(f"Verified {path.name}: mcp-postgresql-ops {version}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main() -> None:
|
|
59
|
+
"""Check release artifacts without uploading or modifying them."""
|
|
60
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
61
|
+
parser.add_argument("tag")
|
|
62
|
+
parser.add_argument("directory", type=Path)
|
|
63
|
+
args = parser.parse_args()
|
|
64
|
+
try:
|
|
65
|
+
check_release(args.tag, args.directory)
|
|
66
|
+
except ValueError as exc:
|
|
67
|
+
parser.exit(1, f"Release validation failed: {exc}\n")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
main()
|
|
72
|
+
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Publish a tag's GitHub Release after package publication has succeeded."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
import tempfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
from urllib.parse import quote
|
|
12
|
+
|
|
13
|
+
from scripts.check_release import release_version
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def command(*args: str) -> str:
|
|
17
|
+
"""Run an argument list without interpreting commit messages as shell code."""
|
|
18
|
+
return subprocess.run(
|
|
19
|
+
args, check=True, capture_output=True, text=True
|
|
20
|
+
).stdout.strip()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def release_notes(repository: str, tag: str, previous: str | None) -> str:
|
|
24
|
+
"""Collect full commit messages in oldest-first topological order."""
|
|
25
|
+
current = command("git", "rev-parse", "--verify", f"refs/tags/{tag}^{{commit}}")
|
|
26
|
+
revision = current
|
|
27
|
+
if previous:
|
|
28
|
+
base = command(
|
|
29
|
+
"git", "rev-parse", "--verify", f"refs/tags/{previous}^{{commit}}"
|
|
30
|
+
)
|
|
31
|
+
command("git", "merge-base", "--is-ancestor", base, current)
|
|
32
|
+
revision = f"{base}..{current}"
|
|
33
|
+
raw = command(
|
|
34
|
+
"git",
|
|
35
|
+
"log",
|
|
36
|
+
"--reverse",
|
|
37
|
+
"--topo-order",
|
|
38
|
+
"--format=%H%x00%B%x00",
|
|
39
|
+
revision,
|
|
40
|
+
"--",
|
|
41
|
+
)
|
|
42
|
+
parts = raw.split("\0")
|
|
43
|
+
notes = [
|
|
44
|
+
f"## {tag}",
|
|
45
|
+
f"Changes since {previous}." if previous else "Initial release.",
|
|
46
|
+
"",
|
|
47
|
+
]
|
|
48
|
+
for index in range(0, len(parts) - 1, 2):
|
|
49
|
+
sha, message = parts[index].strip(), parts[index + 1].strip()
|
|
50
|
+
# A longer fence preserves messages containing Markdown code fences.
|
|
51
|
+
fence = "`" * max(
|
|
52
|
+
3, max((len(m) + 1 for m in re.findall(r"`+", message)), default=3)
|
|
53
|
+
)
|
|
54
|
+
notes.extend(
|
|
55
|
+
[
|
|
56
|
+
f"### [{sha[:7]}](https://github.com/{repository}/commit/{sha})",
|
|
57
|
+
f"{fence}text",
|
|
58
|
+
message,
|
|
59
|
+
fence,
|
|
60
|
+
"",
|
|
61
|
+
]
|
|
62
|
+
)
|
|
63
|
+
if previous:
|
|
64
|
+
notes.append(
|
|
65
|
+
f"[Full Changelog](https://github.com/{repository}/compare/{quote(previous, safe='')}...{quote(tag, safe='')})"
|
|
66
|
+
)
|
|
67
|
+
return "\n".join(notes) + "\n"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def publish_release(repository: str, tag: str) -> None:
|
|
71
|
+
"""Create a release once, using the last published release as the baseline."""
|
|
72
|
+
version = release_version(tag)
|
|
73
|
+
current = command("git", "rev-parse", "--verify", f"refs/tags/{tag}^{{commit}}")
|
|
74
|
+
remote = command("gh", "api", f"repos/{repository}/commits/{tag}", "--jq", ".sha")
|
|
75
|
+
if current != remote or current != command("git", "rev-parse", "HEAD"):
|
|
76
|
+
raise ValueError(
|
|
77
|
+
"The remote tag, local tag, and checked-out commit must match."
|
|
78
|
+
)
|
|
79
|
+
pages = json.loads(
|
|
80
|
+
command(
|
|
81
|
+
"gh",
|
|
82
|
+
"api",
|
|
83
|
+
"--paginate",
|
|
84
|
+
"--slurp",
|
|
85
|
+
f"repos/{repository}/releases?per_page=100",
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
releases: list[dict[str, Any]] = [release for page in pages for release in page]
|
|
89
|
+
existing = next(
|
|
90
|
+
(release for release in releases if release["tag_name"] == tag), None
|
|
91
|
+
)
|
|
92
|
+
if existing:
|
|
93
|
+
if existing["draft"]:
|
|
94
|
+
raise ValueError("A draft already exists for this tag; review it manually.")
|
|
95
|
+
print(f"Release {tag} already exists; leaving its content unchanged.")
|
|
96
|
+
return
|
|
97
|
+
published = [release for release in releases if not release["draft"]]
|
|
98
|
+
previous = (
|
|
99
|
+
max(published, key=lambda release: release["published_at"])["tag_name"]
|
|
100
|
+
if published
|
|
101
|
+
else None
|
|
102
|
+
)
|
|
103
|
+
notes = release_notes(repository, tag, previous)
|
|
104
|
+
with tempfile.TemporaryDirectory(prefix="mcp-postgresql-ops-release-") as temporary:
|
|
105
|
+
path = Path(temporary) / "release-notes.md"
|
|
106
|
+
path.write_text(notes, encoding="utf-8")
|
|
107
|
+
args = [
|
|
108
|
+
"gh",
|
|
109
|
+
"release",
|
|
110
|
+
"create",
|
|
111
|
+
tag,
|
|
112
|
+
"--repo",
|
|
113
|
+
repository,
|
|
114
|
+
"--verify-tag",
|
|
115
|
+
"--title",
|
|
116
|
+
tag,
|
|
117
|
+
"--notes-file",
|
|
118
|
+
str(path),
|
|
119
|
+
]
|
|
120
|
+
if re.search(r"(?:a|b|rc)\d+$", version):
|
|
121
|
+
args.extend(["--prerelease", "--latest=false"])
|
|
122
|
+
command(*args)
|
|
123
|
+
print(f"Published GitHub Release {tag} (previous release: {previous or 'none'}).")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def main() -> None:
|
|
127
|
+
"""Publish only when explicitly called by the final release job."""
|
|
128
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
129
|
+
parser.add_argument("tag")
|
|
130
|
+
args = parser.parse_args()
|
|
131
|
+
try:
|
|
132
|
+
publish_release(os.environ["GITHUB_REPOSITORY"], args.tag)
|
|
133
|
+
except subprocess.CalledProcessError as error:
|
|
134
|
+
parser.exit(1, f"Release command failed: {error.stderr or error}\n")
|
|
135
|
+
except (ValueError, KeyError, OSError) as error:
|
|
136
|
+
parser.exit(1, f"GitHub Release failed: {error}\n")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
main()
|
|
141
|
+
|