mcp-postgresql-ops 3.3.9__tar.gz → 3.4.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.
Files changed (73) hide show
  1. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.github/workflows/test.yml +1 -1
  2. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/PKG-INFO +11 -6
  3. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/README.md +10 -5
  4. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops/mcp_main.py +33 -33
  5. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops.egg-info/PKG-INFO +11 -6
  6. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops.egg-info/SOURCES.txt +1 -0
  7. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops.egg-info/scm_file_list.json +1 -0
  8. mcp_postgresql_ops-3.4.0/src/mcp_postgresql_ops.egg-info/scm_version.json +8 -0
  9. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/tests/conftest.py +1 -1
  10. mcp_postgresql_ops-3.4.0/tests/test_tool_schema_null_defaults.py +33 -0
  11. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/tests/test_tools_integration.py +1 -1
  12. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/uv.lock +15 -26
  13. mcp_postgresql_ops-3.3.9/src/mcp_postgresql_ops.egg-info/scm_version.json +0 -8
  14. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.env.example +0 -0
  15. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.github/CONTRIBUTING.md +0 -0
  16. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.github/FUNDING.yml +0 -0
  17. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  18. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  19. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
  20. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  21. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
  22. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.github/copilot-instructions.md +0 -0
  23. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.github/dependabot.yml +0 -0
  24. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.github/pull_request_template.md +0 -0
  25. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.github/workflows/pypi-publish.yml +0 -0
  26. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.gitignore +0 -0
  27. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.gitleaks.toml +0 -0
  28. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.pre-commit-config.yaml +0 -0
  29. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/.python-version +0 -0
  30. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/Dockerfile.MCP-Server +0 -0
  31. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/Dockerfile.MCPO-Proxy +0 -0
  32. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/LICENSE +0 -0
  33. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/MANIFEST.in +0 -0
  34. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/SECURITY.md +0 -0
  35. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/_config.yml +0 -0
  36. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/_includes/head-custom-google-analytics.html +0 -0
  37. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/build-mcp-server-docker-image.sh +0 -0
  38. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/build-mcpo-server-docker-image.sh +0 -0
  39. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/docker-compose.custom-db.yml +0 -0
  40. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/docker-compose.yml +0 -0
  41. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/img/MCP-Workflow-of-Quickstart-Tutorial.png +0 -0
  42. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/img/screenshot-000.png +0 -0
  43. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/img/screenshot-001.png +0 -0
  44. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/img/screenshot-002.png +0 -0
  45. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/img/screenshot-004.png +0 -0
  46. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/img/screenshot-005.png +0 -0
  47. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/img/screenshot-claude-desktop-airflow-connections-html.png +0 -0
  48. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/img/screenshot-claude-desktop-mermaid-diagram.png +0 -0
  49. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/img/screenshot-claude-desktop-question-001.png +0 -0
  50. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/img/screenshot-swagger-api.png +0 -0
  51. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/mcp-config.json.http +0 -0
  52. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/mcp-config.json.stdio +0 -0
  53. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/pyproject.toml +0 -0
  54. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/run-mcp-inspector-local.sh +0 -0
  55. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/run-mcp-inspector-pypi.sh +0 -0
  56. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/scripts/create-test-data.sh +0 -0
  57. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/scripts/create-test-data.sql +0 -0
  58. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/scripts/enable-pgsql-extensions.sh +0 -0
  59. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/scripts/mcp-server-docker-cmd.sh +0 -0
  60. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/setup.cfg +0 -0
  61. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops/__init__.py +0 -0
  62. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops/__main__.py +0 -0
  63. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops/functions.py +0 -0
  64. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops/prompt_template.md +0 -0
  65. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops/version_compat.py +0 -0
  66. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops.egg-info/dependency_links.txt +0 -0
  67. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops.egg-info/entry_points.txt +0 -0
  68. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops.egg-info/requires.txt +0 -0
  69. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/src/mcp_postgresql_ops.egg-info/top_level.txt +0 -0
  70. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/tests/__init__.py +0 -0
  71. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/tests/docker/docker-compose.test.yml +0 -0
  72. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/tests/docker/init-test-db.sql +0 -0
  73. {mcp_postgresql_ops-3.3.9 → mcp_postgresql_ops-3.4.0}/tests/test_version_compat.py +0 -0
@@ -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 -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.0
4
4
  Summary: Add your description here
5
5
  Author-email: JungJungIn <call518@gmail.com>
6
6
  Requires-Python: >=3.12
@@ -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
@@ -952,11 +953,12 @@ mcp-postgresql-ops --log-level DEBUG
952
953
 
953
954
  ### Running Tests
954
955
 
955
- Two test suites are available:
956
+ The following test suites are available:
956
957
 
957
958
  | Suite | File | Requires Docker |
958
959
  |-------|------|----------------|
959
960
  | Unit tests (version compatibility logic) | `tests/test_version_compat.py` | No |
961
+ | Unit tests (null-default tool argument schemas) | `tests/test_tool_schema_null_defaults.py` | No |
960
962
  | Integration tests (all tools × PG 12–18) | `tests/test_tools_integration.py` | Yes |
961
963
 
962
964
  `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 +967,17 @@ Two test suites are available:
965
967
  # Run all tests (unit + integration) — Docker is managed automatically
966
968
  uv run pytest -v
967
969
 
968
- # Unit tests only (no Docker needed)
969
- uv run pytest tests/test_version_compat.py -v
970
+ # Unit tests only (no Docker or PostgreSQL needed)
971
+ uv run pytest tests/test_version_compat.py tests/test_tool_schema_null_defaults.py -v
972
+
973
+ # Tool argument schema tests only (no Docker or PostgreSQL needed)
974
+ uv run pytest tests/test_tool_schema_null_defaults.py -v
970
975
 
971
976
  # Integration tests only
972
977
  uv run pytest tests/test_tools_integration.py -v
973
978
  ```
974
979
 
975
- > **Note**: Docker must be running. The test stack uses ports 5412–5418 (PG 12–18).
980
+ > **Note**: Docker must be running for integration tests. The test stack uses ports 5412–5418 (PG 12–18).
976
981
 
977
982
  ### Version Compatibility Testing
978
983
 
@@ -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
@@ -934,11 +935,12 @@ mcp-postgresql-ops --log-level DEBUG
934
935
 
935
936
  ### Running Tests
936
937
 
937
- Two test suites are available:
938
+ The following test suites are available:
938
939
 
939
940
  | Suite | File | Requires Docker |
940
941
  |-------|------|----------------|
941
942
  | Unit tests (version compatibility logic) | `tests/test_version_compat.py` | No |
943
+ | Unit tests (null-default tool argument schemas) | `tests/test_tool_schema_null_defaults.py` | No |
942
944
  | Integration tests (all tools × PG 12–18) | `tests/test_tools_integration.py` | Yes |
943
945
 
944
946
  `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 +949,17 @@ Two test suites are available:
947
949
  # Run all tests (unit + integration) — Docker is managed automatically
948
950
  uv run pytest -v
949
951
 
950
- # Unit tests only (no Docker needed)
951
- uv run pytest tests/test_version_compat.py -v
952
+ # Unit tests only (no Docker or PostgreSQL needed)
953
+ uv run pytest tests/test_version_compat.py tests/test_tool_schema_null_defaults.py -v
954
+
955
+ # Tool argument schema tests only (no Docker or PostgreSQL needed)
956
+ uv run pytest tests/test_tool_schema_null_defaults.py -v
952
957
 
953
958
  # Integration tests only
954
959
  uv run pytest tests/test_tools_integration.py -v
955
960
  ```
956
961
 
957
- > **Note**: Docker must be running. The test stack uses ports 5412–5418 (PG 12–18).
962
+ > **Note**: Docker must be running for integration tests. The test stack uses ports 5412–5418 (PG 12–18).
958
963
 
959
964
  ### Version Compatibility Testing
960
965
 
@@ -97,12 +97,12 @@ PROMPT_TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), "prompt_template.
97
97
 
98
98
  @mcp.tool()
99
99
  async def get_lock_monitoring(
100
- database_name: str = None,
101
- granted: str = None,
102
- state: str = None,
103
- mode: str = None,
104
- locktype: str = None,
105
- username: str = None
100
+ database_name: Optional[str] = None,
101
+ granted: Optional[str] = None,
102
+ state: Optional[str] = None,
103
+ mode: Optional[str] = None,
104
+ locktype: Optional[str] = None,
105
+ username: Optional[str] = None
106
106
  ) -> str:
107
107
  """
108
108
  [Tool Purpose]: Monitor current locks and potential deadlocks in PostgreSQL
@@ -492,7 +492,7 @@ async def get_server_info() -> str:
492
492
 
493
493
 
494
494
  @mcp.tool()
495
- async def get_current_database_info(database_name: str = None) -> str:
495
+ async def get_current_database_info(database_name: Optional[str] = None) -> str:
496
496
  """
497
497
  [Tool Purpose]: Get information about the current database connection
498
498
 
@@ -613,7 +613,7 @@ async def get_database_list() -> str:
613
613
 
614
614
 
615
615
  @mcp.tool()
616
- async def get_table_list(database_name: str = None) -> str:
616
+ async def get_table_list(database_name: Optional[str] = None) -> str:
617
617
  """
618
618
  [Tool Purpose]: Retrieve list of all tables and their information from specified database (or current DB)
619
619
 
@@ -713,7 +713,7 @@ async def get_user_list() -> str:
713
713
 
714
714
 
715
715
  @mcp.tool()
716
- async def get_table_schema_info(database_name: str, table_name: str = None, schema_name: str = "public") -> str:
716
+ async def get_table_schema_info(database_name: str, table_name: Optional[str] = None, schema_name: str = "public") -> str:
717
717
  """
718
718
  [Tool Purpose]: Retrieve detailed schema information for specific table or all tables in a database
719
719
 
@@ -971,7 +971,7 @@ async def get_table_schema_info(database_name: str, table_name: str = None, sche
971
971
 
972
972
 
973
973
  @mcp.tool()
974
- async def get_database_schema_info(database_name: str, schema_name: str = None) -> str:
974
+ async def get_database_schema_info(database_name: str, schema_name: Optional[str] = None) -> str:
975
975
  """
976
976
  [Tool Purpose]: Retrieve detailed information about database schemas (namespaces) and their contents
977
977
 
@@ -1178,7 +1178,7 @@ async def get_database_schema_info(database_name: str, schema_name: str = None)
1178
1178
 
1179
1179
 
1180
1180
  @mcp.tool()
1181
- async def get_table_relationships(database_name: str, table_name: str = None, schema_name: str = "public", relationship_type: str = "all") -> str:
1181
+ async def get_table_relationships(database_name: str, table_name: Optional[str] = None, schema_name: str = "public", relationship_type: str = "all") -> str:
1182
1182
  """
1183
1183
  [Tool Purpose]: Analyze table relationships including foreign keys, dependencies, and inheritance
1184
1184
 
@@ -1557,7 +1557,7 @@ async def get_active_connections() -> str:
1557
1557
 
1558
1558
 
1559
1559
  @mcp.tool()
1560
- async def get_pg_stat_statements_top_queries(limit: int = 20, database_name: str = None) -> str:
1560
+ async def get_pg_stat_statements_top_queries(limit: int = 20, database_name: Optional[str] = None) -> str:
1561
1561
  """
1562
1562
  [Tool Purpose]: Analyze top queries that consumed the most time using pg_stat_statements extension
1563
1563
 
@@ -1605,7 +1605,7 @@ async def get_pg_stat_statements_top_queries(limit: int = 20, database_name: str
1605
1605
 
1606
1606
 
1607
1607
  @mcp.tool()
1608
- async def get_pg_stat_monitor_recent_queries(limit: int = 20, database_name: str = None) -> str:
1608
+ async def get_pg_stat_monitor_recent_queries(limit: int = 20, database_name: Optional[str] = None) -> str:
1609
1609
  """
1610
1610
  [Tool Purpose]: Analyze recently executed queries and detailed monitoring information using pg_stat_monitor extension
1611
1611
 
@@ -1708,7 +1708,7 @@ async def get_database_size_info() -> str:
1708
1708
 
1709
1709
 
1710
1710
  @mcp.tool()
1711
- async def get_table_size_info(schema_name: str = "public", database_name: str = None) -> str:
1711
+ async def get_table_size_info(schema_name: str = "public", database_name: Optional[str] = None) -> str:
1712
1712
  """
1713
1713
  [Tool Purpose]: Analyze size information and index usage of all tables in specified schema
1714
1714
 
@@ -1778,7 +1778,7 @@ async def get_table_size_info(schema_name: str = "public", database_name: str =
1778
1778
 
1779
1779
 
1780
1780
  @mcp.tool()
1781
- async def get_postgresql_config(config_name: str = None, filter_text: str = None) -> str:
1781
+ async def get_postgresql_config(config_name: Optional[str] = None, filter_text: Optional[str] = None) -> str:
1782
1782
  """
1783
1783
  [Tool Purpose]: Retrieve and analyze PostgreSQL server configuration parameter values
1784
1784
 
@@ -1879,7 +1879,7 @@ async def get_postgresql_config(config_name: str = None, filter_text: str = None
1879
1879
 
1880
1880
 
1881
1881
  @mcp.tool()
1882
- async def get_index_usage_stats(database_name: str = None) -> str:
1882
+ async def get_index_usage_stats(database_name: Optional[str] = None) -> str:
1883
1883
  """
1884
1884
  [Tool Purpose]: Analyze usage rate and performance statistics of all indexes in database
1885
1885
 
@@ -1937,7 +1937,7 @@ async def get_index_usage_stats(database_name: str = None) -> str:
1937
1937
 
1938
1938
 
1939
1939
  @mcp.tool()
1940
- async def get_vacuum_analyze_stats(database_name: str = None) -> str:
1940
+ async def get_vacuum_analyze_stats(database_name: Optional[str] = None) -> str:
1941
1941
  """
1942
1942
  [Tool Purpose]: Analyze VACUUM and ANALYZE execution history and statistics per table
1943
1943
 
@@ -2007,7 +2007,7 @@ async def get_vacuum_analyze_stats(database_name: str = None) -> str:
2007
2007
 
2008
2008
 
2009
2009
  @mcp.tool()
2010
- async def get_table_bloat_analysis(database_name: str = None, schema_name: str = None, table_pattern: str = None, min_dead_tuples: int = 1, limit: int = 20) -> str:
2010
+ async def get_table_bloat_analysis(database_name: Optional[str] = None, schema_name: Optional[str] = None, table_pattern: Optional[str] = None, min_dead_tuples: int = 1, limit: int = 20) -> str:
2011
2011
  """
2012
2012
  [Tool Purpose]: Analyze table bloat based on dead tuple statistics and size information
2013
2013
 
@@ -2160,7 +2160,7 @@ async def get_table_bloat_analysis(database_name: str = None, schema_name: str =
2160
2160
 
2161
2161
 
2162
2162
  @mcp.tool()
2163
- async def get_database_bloat_overview(database_name: str = None, limit: int = 20) -> str:
2163
+ async def get_database_bloat_overview(database_name: Optional[str] = None, limit: int = 20) -> str:
2164
2164
  """
2165
2165
  [Tool Purpose]: Provide database-wide bloat overview and summary statistics
2166
2166
 
@@ -2247,7 +2247,7 @@ async def get_database_bloat_overview(database_name: str = None, limit: int = 20
2247
2247
 
2248
2248
 
2249
2249
  @mcp.tool()
2250
- async def get_autovacuum_status(database_name: str = None, schema_name: str = None, table_pattern: str = None, limit: int = 50) -> str:
2250
+ async def get_autovacuum_status(database_name: Optional[str] = None, schema_name: Optional[str] = None, table_pattern: Optional[str] = None, limit: int = 50) -> str:
2251
2251
  """
2252
2252
  [Tool Purpose]: Analyze autovacuum configuration and current maintenance status for tables
2253
2253
 
@@ -2395,7 +2395,7 @@ async def get_autovacuum_status(database_name: str = None, schema_name: str = No
2395
2395
 
2396
2396
 
2397
2397
  @mcp.tool()
2398
- async def get_autovacuum_activity(database_name: str = None, schema_name: str = None, hours_back: int = 24, limit: int = 50) -> str:
2398
+ async def get_autovacuum_activity(database_name: Optional[str] = None, schema_name: Optional[str] = None, hours_back: int = 24, limit: int = 50) -> str:
2399
2399
  """
2400
2400
  [Tool Purpose]: Monitor recent autovacuum and autoanalyze activity patterns and execution history
2401
2401
 
@@ -2544,7 +2544,7 @@ async def get_autovacuum_activity(database_name: str = None, schema_name: str =
2544
2544
 
2545
2545
 
2546
2546
  @mcp.tool()
2547
- async def get_running_vacuum_operations(database_name: str = None) -> str:
2547
+ async def get_running_vacuum_operations(database_name: Optional[str] = None) -> str:
2548
2548
  """
2549
2549
  [Tool Purpose]: Monitor currently running VACUUM and ANALYZE operations in real-time
2550
2550
 
@@ -2650,7 +2650,7 @@ async def get_running_vacuum_operations(database_name: str = None) -> str:
2650
2650
 
2651
2651
 
2652
2652
  @mcp.tool()
2653
- async def get_vacuum_effectiveness_analysis(database_name: str = None, schema_name: str = None, limit: int = 30) -> str:
2653
+ async def get_vacuum_effectiveness_analysis(database_name: Optional[str] = None, schema_name: Optional[str] = None, limit: int = 30) -> str:
2654
2654
  """
2655
2655
  [Tool Purpose]: Analyze VACUUM effectiveness and maintenance patterns using existing statistics
2656
2656
 
@@ -3012,7 +3012,7 @@ async def get_bgwriter_stats() -> str:
3012
3012
 
3013
3013
 
3014
3014
  @mcp.tool()
3015
- async def get_io_stats(limit: int = 20, database_name: str = None) -> str:
3015
+ async def get_io_stats(limit: int = 20, database_name: Optional[str] = None) -> str:
3016
3016
  """
3017
3017
  [Tool Purpose]: Analyze comprehensive I/O statistics across all database operations with version compatibility
3018
3018
 
@@ -3151,7 +3151,7 @@ async def get_io_stats(limit: int = 20, database_name: str = None) -> str:
3151
3151
 
3152
3152
 
3153
3153
  @mcp.tool()
3154
- async def get_table_io_stats(database_name: str = None, schema_name: str = "public") -> str:
3154
+ async def get_table_io_stats(database_name: Optional[str] = None, schema_name: str = "public") -> str:
3155
3155
  """
3156
3156
  [Tool Purpose]: Analyze I/O performance statistics for tables (disk reads vs buffer cache hits)
3157
3157
 
@@ -3248,7 +3248,7 @@ async def get_table_io_stats(database_name: str = None, schema_name: str = "publ
3248
3248
 
3249
3249
 
3250
3250
  @mcp.tool()
3251
- async def get_index_io_stats(database_name: str = None, schema_name: str = "public") -> str:
3251
+ async def get_index_io_stats(database_name: Optional[str] = None, schema_name: str = "public") -> str:
3252
3252
  """
3253
3253
  [Tool Purpose]: Analyze I/O performance statistics for indexes (disk reads vs buffer cache hits)
3254
3254
 
@@ -3335,7 +3335,7 @@ async def get_index_io_stats(database_name: str = None, schema_name: str = "publ
3335
3335
 
3336
3336
 
3337
3337
  @mcp.tool()
3338
- async def get_all_tables_stats(database_name: str = None, include_system: bool = False) -> str:
3338
+ async def get_all_tables_stats(database_name: Optional[str] = None, include_system: bool = False) -> str:
3339
3339
  """
3340
3340
  [Tool Purpose]: Get comprehensive statistics for all tables (including system tables if requested)
3341
3341
 
@@ -3382,7 +3382,7 @@ async def get_all_tables_stats(database_name: str = None, include_system: bool =
3382
3382
 
3383
3383
 
3384
3384
  @mcp.tool()
3385
- async def get_user_functions_stats(database_name: str = None) -> str:
3385
+ async def get_user_functions_stats(database_name: Optional[str] = None) -> str:
3386
3386
  """
3387
3387
  [Tool Purpose]: Analyze performance statistics for user-defined functions
3388
3388
 
@@ -3457,7 +3457,7 @@ async def get_user_functions_stats(database_name: str = None) -> str:
3457
3457
 
3458
3458
 
3459
3459
  @mcp.tool()
3460
- async def get_database_conflicts_stats(database_name: str = None) -> str:
3460
+ async def get_database_conflicts_stats(database_name: Optional[str] = None) -> str:
3461
3461
  """
3462
3462
  [Tool Purpose]: Analyze query conflicts in standby/replica database environments
3463
3463
 
@@ -3530,7 +3530,7 @@ async def get_database_conflicts_stats(database_name: str = None) -> str:
3530
3530
  # =============================================================================
3531
3531
 
3532
3532
  @mcp.tool()
3533
- async def get_wait_events(database_name: str = None, wait_event_type: str = None) -> str:
3533
+ async def get_wait_events(database_name: Optional[str] = None, wait_event_type: Optional[str] = None) -> str:
3534
3534
  """
3535
3535
  [Tool Purpose]: List available wait event types and their descriptions (PostgreSQL 17+)
3536
3536
 
@@ -3636,7 +3636,7 @@ async def get_wait_events(database_name: str = None, wait_event_type: str = None
3636
3636
 
3637
3637
 
3638
3638
  @mcp.tool()
3639
- async def get_wal_summarizer_status(database_name: str = None) -> str:
3639
+ async def get_wal_summarizer_status(database_name: Optional[str] = None) -> str:
3640
3640
  """
3641
3641
  [Tool Purpose]: Monitor WAL summarizer status for incremental backup support (PostgreSQL 17+)
3642
3642
 
@@ -3710,7 +3710,7 @@ async def get_wal_summarizer_status(database_name: str = None) -> str:
3710
3710
 
3711
3711
 
3712
3712
  @mcp.tool()
3713
- async def get_async_io_status(database_name: str = None) -> str:
3713
+ async def get_async_io_status(database_name: Optional[str] = None) -> str:
3714
3714
  """
3715
3715
  [Tool Purpose]: Monitor asynchronous I/O subsystem status (PostgreSQL 18+)
3716
3716
 
@@ -3772,7 +3772,7 @@ async def get_async_io_status(database_name: str = None) -> str:
3772
3772
 
3773
3773
 
3774
3774
  @mcp.tool()
3775
- async def get_per_backend_io_stats(database_name: str = None, limit: int = 20) -> str:
3775
+ async def get_per_backend_io_stats(database_name: Optional[str] = None, limit: int = 20) -> str:
3776
3776
  """
3777
3777
  [Tool Purpose]: Analyze per-backend I/O and WAL statistics (PostgreSQL 18+)
3778
3778
 
@@ -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.0
4
4
  Summary: Add your description here
5
5
  Author-email: JungJungIn <call518@gmail.com>
6
6
  Requires-Python: >=3.12
@@ -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
@@ -952,11 +953,12 @@ mcp-postgresql-ops --log-level DEBUG
952
953
 
953
954
  ### Running Tests
954
955
 
955
- Two test suites are available:
956
+ The following test suites are available:
956
957
 
957
958
  | Suite | File | Requires Docker |
958
959
  |-------|------|----------------|
959
960
  | Unit tests (version compatibility logic) | `tests/test_version_compat.py` | No |
961
+ | Unit tests (null-default tool argument schemas) | `tests/test_tool_schema_null_defaults.py` | No |
960
962
  | Integration tests (all tools × PG 12–18) | `tests/test_tools_integration.py` | Yes |
961
963
 
962
964
  `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 +967,17 @@ Two test suites are available:
965
967
  # Run all tests (unit + integration) — Docker is managed automatically
966
968
  uv run pytest -v
967
969
 
968
- # Unit tests only (no Docker needed)
969
- uv run pytest tests/test_version_compat.py -v
970
+ # Unit tests only (no Docker or PostgreSQL needed)
971
+ uv run pytest tests/test_version_compat.py tests/test_tool_schema_null_defaults.py -v
972
+
973
+ # Tool argument schema tests only (no Docker or PostgreSQL needed)
974
+ uv run pytest tests/test_tool_schema_null_defaults.py -v
970
975
 
971
976
  # Integration tests only
972
977
  uv run pytest tests/test_tools_integration.py -v
973
978
  ```
974
979
 
975
- > **Note**: Docker must be running. The test stack uses ports 5412–5418 (PG 12–18).
980
+ > **Note**: Docker must be running for integration tests. The test stack uses ports 5412–5418 (PG 12–18).
976
981
 
977
982
  ### Version Compatibility Testing
978
983
 
@@ -63,6 +63,7 @@ src/mcp_postgresql_ops.egg-info/scm_version.json
63
63
  src/mcp_postgresql_ops.egg-info/top_level.txt
64
64
  tests/__init__.py
65
65
  tests/conftest.py
66
+ tests/test_tool_schema_null_defaults.py
66
67
  tests/test_tools_integration.py
67
68
  tests/test_version_compat.py
68
69
  tests/docker/docker-compose.test.yml
@@ -58,6 +58,7 @@
58
58
  "tests/conftest.py",
59
59
  "tests/docker/docker-compose.test.yml",
60
60
  "tests/docker/init-test-db.sql",
61
+ "tests/test_tool_schema_null_defaults.py",
61
62
  "tests/test_tools_integration.py",
62
63
  "tests/test_version_compat.py",
63
64
  "uv.lock"
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "3.4.0",
3
+ "distance": 0,
4
+ "node": "g290b364e43b7b9174f1b4b7969e48af8ab3dc3f6",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-09"
8
+ }
@@ -94,7 +94,7 @@ def _wait_for_all_pg(timeout: int = WAIT_TIMEOUT_SEC) -> None:
94
94
  )
95
95
 
96
96
 
97
- @pytest.fixture(scope="session", autouse=True)
97
+ @pytest.fixture(scope="session")
98
98
  def docker_compose_pg():
99
99
  """Start test PostgreSQL containers and tear them down after the session.
100
100
 
@@ -0,0 +1,33 @@
1
+ """Every tool argument that defaults to null must also accept null.
2
+
3
+ FastMCP advertises `"default": null` for an argument written as
4
+ `x: str = None`, but types it `"type": "string"`, so a client that sends the
5
+ advertised default back (some agent frameworks fill in every optional
6
+ argument) fails validation before the tool runs. Offline: no database needed.
7
+ """
8
+ import asyncio
9
+
10
+ from fastmcp import Client
11
+
12
+ from mcp_postgresql_ops.mcp_main import mcp
13
+
14
+
15
+ def _null_default_args_rejecting_null():
16
+ async def collect():
17
+ async with Client(mcp) as client:
18
+ tools = await client.list_tools()
19
+ bad = []
20
+ for tool in tools:
21
+ for name, prop in tool.inputSchema.get("properties", {}).items():
22
+ if "default" not in prop or prop["default"] is not None:
23
+ continue
24
+ types = [prop.get("type")] + [s.get("type") for s in prop.get("anyOf", [])]
25
+ if "null" not in types:
26
+ bad.append(f"{tool.name}.{name}")
27
+ return bad
28
+
29
+ return asyncio.run(collect())
30
+
31
+
32
+ def test_null_default_arguments_accept_null():
33
+ assert _null_default_args_rejecting_null() == []
@@ -52,7 +52,7 @@ get_async_io_status = _fn("get_async_io_status")
52
52
  get_per_backend_io_stats = _fn("get_per_backend_io_stats")
53
53
 
54
54
 
55
- pytestmark = pytest.mark.asyncio
55
+ pytestmark = [pytest.mark.asyncio, pytest.mark.usefixtures("docker_compose_pg")]
56
56
 
57
57
 
58
58
  def assert_tool_result(result, tool_name):
@@ -31,16 +31,15 @@ wheels = [
31
31
 
32
32
  [[package]]
33
33
  name = "anyio"
34
- version = "4.11.0"
34
+ version = "4.14.2"
35
35
  source = { registry = "https://pypi.org/simple" }
36
36
  dependencies = [
37
37
  { name = "idna" },
38
- { name = "sniffio" },
39
38
  { name = "typing-extensions", marker = "python_full_version < '3.13'" },
40
39
  ]
41
- sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" }
40
+ sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" }
42
41
  wheels = [
43
- { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" },
42
+ { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" },
44
43
  ]
45
44
 
46
45
  [[package]]
@@ -1066,11 +1065,11 @@ wheels = [
1066
1065
 
1067
1066
  [[package]]
1068
1067
  name = "pyjwt"
1069
- version = "2.13.0"
1068
+ version = "2.15.0"
1070
1069
  source = { registry = "https://pypi.org/simple" }
1071
- sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
1070
+ sdist = { url = "https://files.pythonhosted.org/packages/02/a5/5197bfd06417837ac079921c66fa6393f1dea3557272a263cebfef69e432/pyjwt-2.15.0.tar.gz", hash = "sha256:b11c5f9791d7bf51c2b39a81ed669f6b2dbbd669df2942f6c60167e9e3d1abe4", size = 120513, upload-time = "2026-09-23T16:56:00.689Z" }
1072
1071
  wheels = [
1073
- { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
1072
+ { url = "https://files.pythonhosted.org/packages/e8/55/40e45bf052ee8ee12a4dfd785519660f8effa7b065442b91646ec6828619/pyjwt-2.15.0-py3-none-any.whl", hash = "sha256:7a3742debf6b879e912dbb9819ceec1594be812452b78c5f2e2dfc56564954f8", size = 33680, upload-time = "2026-09-23T16:55:59.241Z" },
1074
1073
  ]
1075
1074
 
1076
1075
  [package.optional-dependencies]
@@ -1118,15 +1117,14 @@ wheels = [
1118
1117
 
1119
1118
  [[package]]
1120
1119
  name = "python-discovery"
1121
- version = "1.2.2"
1120
+ version = "1.6.1"
1122
1121
  source = { registry = "https://pypi.org/simple" }
1123
1122
  dependencies = [
1124
1123
  { name = "filelock" },
1125
- { name = "platformdirs" },
1126
1124
  ]
1127
- sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872, upload-time = "2026-04-07T17:28:49.249Z" }
1125
+ sdist = { url = "https://files.pythonhosted.org/packages/0c/57/250bd238b966cece44328235eb85290045d059265fdaf7527a3a958123db/python_discovery-1.6.1.tar.gz", hash = "sha256:cf87d3627dfb4412437fdd5b13eae402607722998d21567993aedbc59b23c15e", size = 84338, upload-time = "2026-09-18T01:31:53.971Z" }
1128
1126
  wheels = [
1129
- { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" },
1127
+ { url = "https://files.pythonhosted.org/packages/16/7d/e9ffbadfbf89c93848412d04594135c4ae8c1d37d9e053b9c3ed718fabc4/python_discovery-1.6.1-py3-none-any.whl", hash = "sha256:d43fcdef879fe795352bd13ccf8d185ba5a9f86f36cfcd00529f596e737442b3", size = 38664, upload-time = "2026-09-18T01:31:52.448Z" },
1130
1128
  ]
1131
1129
 
1132
1130
  [[package]]
@@ -1370,15 +1368,6 @@ wheels = [
1370
1368
  { url = "https://files.pythonhosted.org/packages/5c/e1/342c4434df56aa537f6ce7647eefee521d96fbb828b08acd709865767652/setuptools_scm-10.0.5-py3-none-any.whl", hash = "sha256:f611037d8aae618221503b8fa89319f073438252ae3420e01c9ceec249131a0a", size = 21695, upload-time = "2026-03-27T15:57:03.969Z" },
1371
1369
  ]
1372
1370
 
1373
- [[package]]
1374
- name = "sniffio"
1375
- version = "1.3.1"
1376
- source = { registry = "https://pypi.org/simple" }
1377
- sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
1378
- wheels = [
1379
- { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
1380
- ]
1381
-
1382
1371
  [[package]]
1383
1372
  name = "sse-starlette"
1384
1373
  version = "3.0.2"
@@ -1456,11 +1445,11 @@ wheels = [
1456
1445
 
1457
1446
  [[package]]
1458
1447
  name = "urllib3"
1459
- version = "2.7.0"
1448
+ version = "2.8.0"
1460
1449
  source = { registry = "https://pypi.org/simple" }
1461
- sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
1450
+ sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" }
1462
1451
  wheels = [
1463
- { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
1452
+ { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" },
1464
1453
  ]
1465
1454
 
1466
1455
  [[package]]
@@ -1490,7 +1479,7 @@ wheels = [
1490
1479
 
1491
1480
  [[package]]
1492
1481
  name = "virtualenv"
1493
- version = "21.2.4"
1482
+ version = "21.7.13"
1494
1483
  source = { registry = "https://pypi.org/simple" }
1495
1484
  dependencies = [
1496
1485
  { name = "distlib" },
@@ -1498,9 +1487,9 @@ dependencies = [
1498
1487
  { name = "platformdirs" },
1499
1488
  { name = "python-discovery" },
1500
1489
  ]
1501
- sdist = { url = "https://files.pythonhosted.org/packages/0c/98/3a7e644e19cb26133488caff231be390579860bbbb3da35913c49a1d0a46/virtualenv-21.2.4.tar.gz", hash = "sha256:b294ef68192638004d72524ce7ef303e9d0cf5a44c95ce2e54a7500a6381cada", size = 5850742, upload-time = "2026-04-14T22:15:31.438Z" }
1490
+ sdist = { url = "https://files.pythonhosted.org/packages/13/50/c9b84eb106d0db420b9878dac0a386c5791726f127ce20b06897f6e3a1e9/virtualenv-21.7.13.tar.gz", hash = "sha256:0355558b6f33619aab31347e43643b0ebc97f61ea3acf617b2b69e1f8a843d11", size = 5360306, upload-time = "2026-09-18T04:35:49.354Z" }
1502
1491
  wheels = [
1503
- { url = "https://files.pythonhosted.org/packages/27/8d/edd0bd910ff803c308ee9a6b7778621af0d10252219ad9f19ef4d4982a61/virtualenv-21.2.4-py3-none-any.whl", hash = "sha256:29d21e941795206138d0f22f4e45ff7050e5da6c6472299fb7103318763861ac", size = 5831232, upload-time = "2026-04-14T22:15:29.342Z" },
1492
+ { url = "https://files.pythonhosted.org/packages/9a/ce/e74453531b0c49a58d0e8a4f9bab4495705859fee4a4f7de27d58f4a791a/virtualenv-21.7.13-py3-none-any.whl", hash = "sha256:1bea5af7463f59c4719db48fe739579a2a4f569c96f26c086edda85c96da9f59", size = 5328680, upload-time = "2026-09-18T04:35:47.194Z" },
1504
1493
  ]
1505
1494
 
1506
1495
  [[package]]
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "3.3.9",
3
- "distance": 0,
4
- "node": "g863ec500f01400500395cb329143f233f74d74c0",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-09-14"
8
- }