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.
Files changed (76) hide show
  1. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/workflows/pypi-publish.yml +33 -8
  2. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/workflows/test.yml +1 -1
  3. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/PKG-INFO +28 -9
  4. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/README.md +27 -8
  5. mcp_postgresql_ops-3.4.1/scripts/check_release.py +72 -0
  6. mcp_postgresql_ops-3.4.1/scripts/publish_release.py +141 -0
  7. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops/mcp_main.py +33 -33
  8. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/PKG-INFO +28 -9
  9. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/SOURCES.txt +4 -0
  10. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/scm_file_list.json +4 -0
  11. mcp_postgresql_ops-3.4.1/src/mcp_postgresql_ops.egg-info/scm_version.json +8 -0
  12. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/tests/conftest.py +1 -1
  13. mcp_postgresql_ops-3.4.1/tests/test_release.py +86 -0
  14. mcp_postgresql_ops-3.4.1/tests/test_tool_schema_null_defaults.py +33 -0
  15. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/tests/test_tools_integration.py +1 -1
  16. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/uv.lock +18 -29
  17. mcp_postgresql_ops-3.3.9/src/mcp_postgresql_ops.egg-info/scm_version.json +0 -8
  18. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.env.example +0 -0
  19. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/CONTRIBUTING.md +0 -0
  20. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/FUNDING.yml +0 -0
  21. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  22. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  23. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
  24. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  25. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/ISSUE_TEMPLATE/question.md +0 -0
  26. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/copilot-instructions.md +0 -0
  27. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/dependabot.yml +0 -0
  28. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.github/pull_request_template.md +0 -0
  29. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.gitignore +0 -0
  30. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.gitleaks.toml +0 -0
  31. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.pre-commit-config.yaml +0 -0
  32. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/.python-version +0 -0
  33. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/Dockerfile.MCP-Server +0 -0
  34. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/Dockerfile.MCPO-Proxy +0 -0
  35. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/LICENSE +0 -0
  36. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/MANIFEST.in +0 -0
  37. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/SECURITY.md +0 -0
  38. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/_config.yml +0 -0
  39. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/_includes/head-custom-google-analytics.html +0 -0
  40. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/build-mcp-server-docker-image.sh +0 -0
  41. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/build-mcpo-server-docker-image.sh +0 -0
  42. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/docker-compose.custom-db.yml +0 -0
  43. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/docker-compose.yml +0 -0
  44. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/MCP-Workflow-of-Quickstart-Tutorial.png +0 -0
  45. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-000.png +0 -0
  46. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-001.png +0 -0
  47. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-002.png +0 -0
  48. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-004.png +0 -0
  49. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-005.png +0 -0
  50. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-claude-desktop-airflow-connections-html.png +0 -0
  51. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-claude-desktop-mermaid-diagram.png +0 -0
  52. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-claude-desktop-question-001.png +0 -0
  53. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/img/screenshot-swagger-api.png +0 -0
  54. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/mcp-config.json.http +0 -0
  55. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/mcp-config.json.stdio +0 -0
  56. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/pyproject.toml +0 -0
  57. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/run-mcp-inspector-local.sh +0 -0
  58. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/run-mcp-inspector-pypi.sh +0 -0
  59. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/scripts/create-test-data.sh +0 -0
  60. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/scripts/create-test-data.sql +0 -0
  61. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/scripts/enable-pgsql-extensions.sh +0 -0
  62. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/scripts/mcp-server-docker-cmd.sh +0 -0
  63. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/setup.cfg +0 -0
  64. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops/__init__.py +0 -0
  65. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops/__main__.py +0 -0
  66. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops/functions.py +0 -0
  67. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops/prompt_template.md +0 -0
  68. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops/version_compat.py +0 -0
  69. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/dependency_links.txt +0 -0
  70. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/entry_points.txt +0 -0
  71. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/requires.txt +0 -0
  72. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/src/mcp_postgresql_ops.egg-info/top_level.txt +0 -0
  73. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/tests/__init__.py +0 -0
  74. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/tests/docker/docker-compose.test.yml +0 -0
  75. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/tests/docker/init-test-db.sql +0 -0
  76. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.1}/tests/test_version_compat.py +0 -0
@@ -1,7 +1,4 @@
1
- # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
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: build
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.9
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
- [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/call518/MCP-PostgreSQL-Ops)
37
+ [![Ask DeepWiki](https://img.shields.io/badge/Ask-DeepWiki-blue)](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/Tool-Usage-Example)**
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-compose logs postgres | grep "ready to accept connections"
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/Tool-Usage-Example)** for visual guides
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
- Two test suites are available:
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
- [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/call518/MCP-PostgreSQL-Ops)
19
+ [![Ask DeepWiki](https://img.shields.io/badge/Ask-DeepWiki-blue)](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/Tool-Usage-Example)**
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-compose logs postgres | grep "ready to accept connections"
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/Tool-Usage-Example)** for visual guides
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
- Two test suites are available:
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
+