39 Commits

Author SHA1 Message Date
Xuwznln
34fa239d19 release: bump version 0.1.5 → 0.1.6 2026-02-28 20:50:05 +08:00
Xuwznln
37010bdeb1 refactor: migrate from bump2version to bump-my-version for version management
- Removed .bumpversion.cfg and integrated version management into pyproject.toml.
- Updated pre-commit hooks and dependencies for code formatting, linting, and type checking.
- Added new functionality to JSONSchemaMessageInstance for generating default values from JSON schema.
- Enhanced test coverage for the new default value generation feature.
2026-02-28 20:49:02 +08:00
Xuwznln
c1e65915c2 Merge pull request #12 from ZGCA-Forge/dependabot/github_actions/actions/upload-artifact-6
ci(deps): bump actions/upload-artifact from 5 to 6
2026-01-03 22:30:19 +08:00
Xuwznln
64963e02d9 Merge pull request #10 from ZGCA-Forge/dependabot/github_actions/actions/download-artifact-7
ci(deps): bump actions/download-artifact from 6 to 7
2026-01-03 22:30:06 +08:00
Xuwznln
588b2cffbd Merge pull request #11 from ZGCA-Forge/dependabot/github_actions/actions/cache-5
ci(deps): bump actions/cache from 4 to 5
2026-01-03 22:27:52 +08:00
dependabot[bot]
a93f926448 ci(deps): bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 06:21:32 +00:00
dependabot[bot]
67e1c43a4a ci(deps): bump actions/cache from 4 to 5
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 06:21:26 +00:00
dependabot[bot]
083c4b0cc2 ci(deps): bump actions/download-artifact from 6 to 7
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 06:21:22 +00:00
Xuwznln
e5b99ca134 Bump version: 0.1.4 → 0.1.5 2025-11-25 13:25:09 +08:00
Xuwznln
31c89ccc26 Add TypedDict Support (Experimental) 2025-11-25 13:24:59 +08:00
Xuwznln
58628910f4 Merge pull request #9 from ZGCA-Forge/dependabot/github_actions/actions/checkout-6
ci(deps): bump actions/checkout from 5 to 6
2025-11-25 13:21:55 +08:00
Xuwznln
4153d99344 Merge pull request #8 from ZGCA-Forge/dependabot/github_actions/actions/upload-artifact-5
ci(deps): bump actions/upload-artifact from 4 to 5
2025-11-25 13:21:47 +08:00
Xuwznln
3d0cdcd423 Merge pull request #7 from ZGCA-Forge/dependabot/github_actions/actions/download-artifact-6
ci(deps): bump actions/download-artifact from 5 to 6
2025-11-25 13:21:38 +08:00
dependabot[bot]
419ed15d70 ci(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 06:42:50 +00:00
dependabot[bot]
5262e9bfd5 ci(deps): bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 06:58:35 +00:00
dependabot[bot]
2d5fd8b74e ci(deps): bump actions/download-artifact from 5 to 6
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 06:54:41 +00:00
Xuwznln
043f934a42 Update ci 2025-10-01 17:28:01 +08:00
Xuwznln
0faaa9ebdb Bump version: 0.1.3 → 0.1.4 2025-10-01 16:25:50 +08:00
Xuwznln
c4d55fed1b Fix release build 2025-10-01 16:21:00 +08:00
Xuwznln
0684e823c0 Bump version: 0.1.2 → 0.1.3 2025-10-01 16:10:24 +08:00
Xuwznln
98e0b9966e Update safety report 2025-10-01 16:07:17 +08:00
Xuwznln
041710d331 Bump version: 0.1.1 → 0.1.2 2025-10-01 15:40:58 +08:00
Xuwznln
cb9dd57f19 Bump version to 0.1.1 2025-10-01 15:40:46 +08:00
Xuwznln
3d032c0b98 Update gitignore 2025-10-01 15:38:25 +08:00
Xuwznln
1ccf2d4ac7 Merge pull request #6 from ZGCA-Forge/dependabot/github_actions/actions/setup-python-6
ci(deps): bump actions/setup-python from 5 to 6
2025-10-01 15:36:58 +08:00
dependabot[bot]
477d7075d5 ci(deps): bump actions/setup-python from 5 to 6
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 07:29:29 +00:00
Xuwznln
ec2fafb363 Bump version: 0.0.6 → 0.1.0 2025-09-05 01:28:49 +08:00
Xuwznln
74e7a15ef9 Merge pull request #5 from ZGCA-Forge/dependabot/github_actions/actions/checkout-5
ci(deps): bump actions/checkout from 4 to 5
2025-09-05 01:27:15 +08:00
dependabot[bot]
661ee43a1c ci(deps): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 17:27:06 +00:00
Xuwznln
0dff62e738 Merge pull request #4 from ZGCA-Forge/dependabot/github_actions/actions/configure-pages-5
ci(deps): bump actions/configure-pages from 4 to 5
2025-09-05 01:26:56 +08:00
Xuwznln
823b04dcbc Merge branch 'main' into dependabot/github_actions/actions/configure-pages-5 2025-09-05 01:26:41 +08:00
Xuwznln
80726163dd Merge pull request #3 from ZGCA-Forge/dependabot/github_actions/softprops/action-gh-release-2
ci(deps): bump softprops/action-gh-release from 1 to 2
2025-09-05 01:26:18 +08:00
Xuwznln
656ef7ca80 Merge pull request #2 from ZGCA-Forge/dependabot/github_actions/actions/download-artifact-5
ci(deps): bump actions/download-artifact from 4 to 5
2025-09-05 01:26:11 +08:00
Xuwznln
cc394fd63c Merge pull request #1 from ZGCA-Forge/dependabot/github_actions/actions/upload-pages-artifact-4
ci(deps): bump actions/upload-pages-artifact from 3 to 4
2025-09-05 01:26:01 +08:00
Xuwznln
2d83bad8ce Merge branch 'main' into dependabot/github_actions/actions/upload-pages-artifact-4 2025-09-05 01:25:52 +08:00
dependabot[bot]
cda31464d3 ci(deps): bump actions/configure-pages from 4 to 5
Bumps [actions/configure-pages](https://github.com/actions/configure-pages) from 4 to 5.
- [Release notes](https://github.com/actions/configure-pages/releases)
- [Commits](https://github.com/actions/configure-pages/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/configure-pages
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-03 08:01:31 +00:00
dependabot[bot]
20a904ef8a ci(deps): bump softprops/action-gh-release from 1 to 2
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-03 05:52:50 +00:00
dependabot[bot]
d8070b5e49 ci(deps): bump actions/download-artifact from 4 to 5
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-03 05:47:49 +00:00
dependabot[bot]
d5704e78c3 ci(deps): bump actions/upload-pages-artifact from 3 to 4
Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-pages-artifact/releases)
- [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-pages-artifact
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-03 05:41:50 +00:00
16 changed files with 746 additions and 129 deletions

View File

@@ -1,10 +0,0 @@
[bumpversion]
current_version = 0.0.6
commit = True
tag = True
tag_name = v{new_version}
message = Bump version: {current_version} → {new_version}
[bumpversion:file:msgcenterpy/__init__.py]
search = __version__ = "{current_version}"
replace = __version__ = "{new_version}"

View File

@@ -16,10 +16,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.10" # Use minimum version for consistency python-version: "3.10" # Use minimum version for consistency
@@ -40,15 +40,15 @@ jobs:
needs: [code-format] # Only run after code formatting passes needs: [code-format] # Only run after code formatting passes
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Python 3.10 - name: Set up Python 3.10
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.10" python-version: "3.10"
- name: Cache pip dependencies - name: Cache pip dependencies
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: ~/.cache/pip path: ~/.cache/pip
key: ubuntu-pip-3.10-${{ hashFiles('**/pyproject.toml') }} key: ubuntu-pip-3.10-${{ hashFiles('**/pyproject.toml') }}
@@ -72,7 +72,7 @@ jobs:
needs: [basic-build] # Only run after basic build passes needs: [basic-build] # Only run after basic build passes
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup Miniconda - name: Setup Miniconda
uses: conda-incubator/setup-miniconda@v3 uses: conda-incubator/setup-miniconda@v3
@@ -112,23 +112,17 @@ jobs:
needs: [basic-build] # Run in parallel with ROS2 test after basic build needs: [basic-build] # Run in parallel with ROS2 test after basic build
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Python - name: Run Safety CLI to check for vulnerabilities
uses: actions/setup-python@v5 uses: pyupio/safety-action@v1
with: with:
python-version: "3.10" # Use minimum version for consistency api-key: ${{ secrets.SAFETY_CHECK }}
output-format: json
- name: Install security tools args: --detailed-output --output-format json
run: |
python -m pip install --upgrade pip
pip install "safety>=3.0.0" "typer<0.12.0" "marshmallow<4.0.0"
- name: Run safety security scan
run: safety check --output json > safety-report.json
- name: Upload security reports - name: Upload security reports
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: security-reports name: security-reports
path: | path: |
@@ -142,10 +136,10 @@ jobs:
needs: [basic-build] # Run in parallel with other checks needs: [basic-build] # Run in parallel with other checks
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.10" # Use minimum version for consistency python-version: "3.10" # Use minimum version for consistency
@@ -161,7 +155,7 @@ jobs:
run: twine check dist/* run: twine check dist/*
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: dist name: dist
path: dist/ path: dist/
@@ -182,15 +176,15 @@ jobs:
python-version: "3.10" python-version: "3.10"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Cache pip dependencies - name: Cache pip dependencies
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: ~/.cache/pip path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }} key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }}
@@ -201,20 +195,9 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
python -m pip install flake8 pytest python -m pip install pytest
pip install -e .[dev] pip install -e .[dev]
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-line-length=200 --extend-ignore=E203,W503,F401,E402,E721,F841 --statistics
- name: Type checking with mypy
run: |
mypy msgcenterpy --disable-error-code=unused-ignore
- name: Test with pytest - name: Test with pytest
run: | run: |
pytest pytest

View File

@@ -36,12 +36,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: ${{ github.event.inputs.branch || github.ref }} ref: ${{ github.event.inputs.branch || github.ref }}
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.10" python-version: "3.10"
@@ -55,7 +55,7 @@ jobs:
- name: Setup Pages - name: Setup Pages
id: pages id: pages
uses: actions/configure-pages@v4 uses: actions/configure-pages@v5
if: github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_pages == 'true') if: github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_pages == 'true')
- name: Build Sphinx documentation - name: Build Sphinx documentation
@@ -64,7 +64,7 @@ jobs:
make html make html
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v3 uses: actions/upload-pages-artifact@v4
if: github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_pages == 'true') if: github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_pages == 'true')
with: with:
path: docs/_build/html path: docs/_build/html

View File

@@ -29,10 +29,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.10" # Use minimum version for consistency python-version: "3.10" # Use minimum version for consistency
@@ -53,15 +53,15 @@ jobs:
needs: [code-format] # Only run after code formatting passes needs: [code-format] # Only run after code formatting passes
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Python 3.10 - name: Set up Python 3.10
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.10" python-version: "3.10"
- name: Cache pip dependencies - name: Cache pip dependencies
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: ~/.cache/pip path: ~/.cache/pip
key: ubuntu-pip-3.10-${{ hashFiles('**/pyproject.toml') }} key: ubuntu-pip-3.10-${{ hashFiles('**/pyproject.toml') }}
@@ -85,7 +85,7 @@ jobs:
needs: [basic-build] # Only run after basic build passes needs: [basic-build] # Only run after basic build passes
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup Miniconda - name: Setup Miniconda
uses: conda-incubator/setup-miniconda@v3 uses: conda-incubator/setup-miniconda@v3
@@ -125,23 +125,17 @@ jobs:
needs: [basic-build] # Run in parallel with ROS2 test after basic build needs: [basic-build] # Run in parallel with ROS2 test after basic build
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Python - name: Run Safety CLI to check for vulnerabilities
uses: actions/setup-python@v5 uses: pyupio/safety-action@v1
with: with:
python-version: "3.10" # Use minimum version for consistency api-key: ${{ secrets.SAFETY_CHECK }}
output-format: json
- name: Install security tools args: --detailed-output --output-format json
run: |
python -m pip install --upgrade pip
pip install "safety>=3.0.0" "typer<0.12.0" "marshmallow<4.0.0"
- name: Run safety security scan
run: safety check --output json > safety-report.json
- name: Upload security reports - name: Upload security reports
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: security-reports name: security-reports
path: | path: |
@@ -154,10 +148,10 @@ jobs:
needs: [test-with-ros2] needs: [test-with-ros2]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.10" # Use minimum version for consistency python-version: "3.10" # Use minimum version for consistency
@@ -203,7 +197,7 @@ jobs:
twine check dist/* twine check dist/*
- name: Upload distributions - name: Upload distributions
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: release-dists name: release-dists
path: dist/ path: dist/
@@ -223,7 +217,7 @@ jobs:
steps: steps:
- name: Retrieve release distributions - name: Retrieve release distributions
uses: actions/download-artifact@v4 uses: actions/download-artifact@v7
with: with:
name: release-dists name: release-dists
path: dist/ path: dist/
@@ -246,7 +240,7 @@ jobs:
steps: steps:
- name: Retrieve release distributions - name: Retrieve release distributions
uses: actions/download-artifact@v4 uses: actions/download-artifact@v7
with: with:
name: release-dists name: release-dists
path: dist/ path: dist/
@@ -266,13 +260,13 @@ jobs:
steps: steps:
- name: Retrieve release distributions - name: Retrieve release distributions
uses: actions/download-artifact@v4 uses: actions/download-artifact@v7
with: with:
name: release-dists name: release-dists
path: dist/ path: dist/
- name: Upload release assets - name: Upload release assets
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
files: dist/* files: dist/*
env: env:
@@ -285,7 +279,7 @@ jobs:
if: always() && (needs.pypi-publish.result == 'success' || needs.test-pypi-publish.result == 'success') if: always() && (needs.pypi-publish.result == 'success' || needs.test-pypi-publish.result == 'success')
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Create deployment summary - name: Create deployment summary
run: | run: |

3
.gitignore vendored
View File

@@ -48,6 +48,9 @@ docs/_static/
# Visual Studio Code # Visual Studio Code
.vscode/ .vscode/
.cursor/
.cursorignore
pyrightconfig.json
# ================================ # ================================
# Operating System files # Operating System files

View File

@@ -1,81 +1,77 @@
# MsgCenterPy pre-commit hooks
# All tool configs are centralised in pyproject.toml
repos: repos:
# Code formatting # ── Code formatting ───────────────────────────────────────────
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 23.12.1 rev: 26.1.0
hooks: hooks:
- id: black - id: black
language_version: python3 language_version: python3
args: ["--line-length=120"] # Reads [tool.black] from pyproject.toml
# Import sorting # ── Import sorting ────────────────────────────────────────────
- repo: https://github.com/pycqa/isort - repo: https://github.com/pycqa/isort
rev: 5.13.2 rev: 6.1.0
hooks: hooks:
- id: isort - id: isort
args: ["--profile", "black", "--multi-line", "3"] # Reads [tool.isort] from pyproject.toml
# Linting # ── Linting ───────────────────────────────────────────────────
- repo: https://github.com/pycqa/flake8 - repo: https://github.com/pycqa/flake8
rev: 7.0.0 rev: 7.3.0
hooks: hooks:
- id: flake8 - id: flake8
args: additional_dependencies: ["Flake8-pyproject"]
- "--max-line-length=200" # Allow longer lines after black formatting # Reads [tool.flake8] from pyproject.toml via Flake8-pyproject
- "--extend-ignore=E203,W503,F401,E402,E721,F841"
- "--exclude=build,dist,__pycache__,.mypy_cache,.pytest_cache,htmlcov,.idea,.vscode,docs/_build,msgcenterpy.egg-info"
# Type checking # ── Type checking ─────────────────────────────────────────────
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0 rev: v1.19.1
hooks: hooks:
- id: mypy - id: mypy
additional_dependencies: [types-PyYAML, types-jsonschema, pydantic] args: ["--config-file=pyproject.toml"]
args: ["--ignore-missing-imports", "--disable-error-code=unused-ignore"] files: "^(msgcenterpy/)"
files: "^(msgcenterpy/)" # Check both source code and tests additional_dependencies:
- "pydantic>=2.0"
- "types-PyYAML"
- "types-jsonschema"
# General pre-commit hooks # ── Basic file checks ────────────────────────────────────────
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0 rev: v6.0.0
hooks: hooks:
# File checks
- id: trailing-whitespace - id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer - id: end-of-file-fixer
- id: check-yaml - id: check-yaml
- id: check-json - id: check-json
- id: check-toml - id: check-toml
- id: check-xml
# Security
- id: check-merge-conflict - id: check-merge-conflict
- id: check-case-conflict - id: check-case-conflict
- id: check-symlinks - id: check-symlinks
- id: check-added-large-files - id: check-added-large-files
args: ["--maxkb=1000"]
# Python specific
- id: check-ast - id: check-ast
- id: debug-statements - id: debug-statements
- id: name-tests-test - id: name-tests-test
args: ["--django"] args: ["--pytest-test-first"]
# Security scanning # ── Security scanning ────────────────────────────────────────
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/PyCQA/bandit
rev: 1.7.5 rev: 1.9.3
hooks: hooks:
- id: bandit - id: bandit
args: ["-c", "pyproject.toml"] args: ["-c", "pyproject.toml"]
additional_dependencies: ["bandit[toml]", "pbr"] additional_dependencies: ["bandit[toml]", "pbr"]
exclude: "^tests/" exclude: "^(tests/)"
# YAML/JSON formatting # ── YAML/JSON/Markdown formatting ────────────────────────────
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8 rev: v4.0.0-alpha.8
hooks: hooks:
- id: prettier - id: prettier
types_or: [yaml, json, markdown] types_or: [yaml, json, markdown]
exclude: "^(build/|dist/|__pycache__/|\\.mypy_cache/|\\.pytest_cache/|htmlcov/|\\.idea/|\\.vscode/|docs/_build/|msgcenterpy\\.egg-info/)" exclude: "^(build/|dist/|__pycache__/|\\.mypy_cache/|\\.pytest_cache/|htmlcov/|\\.vscode/|docs/_build/|msgcenterpy\\.egg-info/)"
# Global settings # Global settings
default_stages: [pre-commit, pre-push] default_stages: [pre-commit]
fail_fast: false fail_fast: false

View File

@@ -184,7 +184,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright [yyyy] [name of copyright owner] Copyright 2025 ZGCA-Forge/MsgCenterPy
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -88,18 +88,18 @@ Testing
Version Management Version Management
------------------ ------------------
This project uses `bump2version` for version management. It's automatically installed with dev dependencies. This project uses `bump-my-version <https://github.com/callowayproject/bump-my-version>`_ for version management. It's automatically installed with dev dependencies. Configuration lives in ``pyproject.toml`` under ``[tool.bumpversion]``.
.. code-block:: bash .. code-block:: bash
# Bug fixes (0.0.1 → 0.0.2) # Bug fixes (0.0.1 → 0.0.2)
bump2version patch bump-my-version bump patch
# New features (0.0.2 → 0.1.0) # New features (0.0.2 → 0.1.0)
bump2version minor bump-my-version bump minor
# Breaking changes (0.1.0 → 1.0.0) # Breaking changes (0.1.0 → 1.0.0)
bump2version major bump-my-version bump major
After bumping version, push changes and tags: After bumping version, push changes and tags:

View File

@@ -5,7 +5,7 @@ A multi-format message conversion system supporting seamless conversion
between ROS2, Pydantic, Dataclass, JSON, Dict, YAML and JSON Schema. between ROS2, Pydantic, Dataclass, JSON, Dict, YAML and JSON Schema.
""" """
__version__ = "0.0.6" __version__ = "0.1.6"
__license__ = "Apache-2.0" __license__ = "Apache-2.0"
from msgcenterpy.core.envelope import MessageEnvelope, create_envelope from msgcenterpy.core.envelope import MessageEnvelope, create_envelope

View File

@@ -9,6 +9,8 @@ class Properties(TypedDict, total=False):
ros_msg_cls_path: str ros_msg_cls_path: str
ros_msg_cls_namespace: str ros_msg_cls_namespace: str
json_schema: Dict[str, Any] json_schema: Dict[str, Any]
typed_dict_class_module: str
typed_dict_class_name: str
class FormatMetadata(TypedDict, total=False): class FormatMetadata(TypedDict, total=False):

View File

@@ -358,14 +358,14 @@ class TypeInfoProvider(ABC):
@abstractmethod @abstractmethod
def get_field_type_info( def get_field_type_info(
self, field_name: str, field_value: Any, field_accessor: "FieldAccessor" self, field_name: str, field_value: Any, parent_field_accessor: "FieldAccessor"
) -> Optional[TypeInfo]: ) -> Optional[TypeInfo]:
"""获取指定字段的类型信息 """获取指定字段的类型信息
Args: Args:
field_name: 字段名,简单字段名如 'field' field_name: 字段名,简单字段名如 'field'
field_value: 字段的当前值用于动态类型推断不能为None field_value: 字段的当前值用于动态类型推断不能为None
field_accessor: 字段访问器提供额外的上下文信息不能为None parent_field_accessor: 字段访问器提供额外的上下文信息不能为None
Returns: Returns:
字段的TypeInfo如果字段不存在则返回None 字段的TypeInfo如果字段不存在则返回None

View File

@@ -11,6 +11,7 @@ class MessageType(Enum):
JSON_SCHEMA = "json_schema" JSON_SCHEMA = "json_schema"
DICT = "dict" DICT = "dict"
YAML = "yaml" YAML = "yaml"
TYPED_DICT = "typed_dict" # Experimental
class ConversionError(Exception): class ConversionError(Exception):

View File

@@ -19,6 +19,87 @@ class JSONSchemaMessageInstance(MessageInstance[Dict[str, Any]]):
_json_schema: Dict[str, Any] = dict() _json_schema: Dict[str, Any] = dict()
_json_data: Dict[str, Any] = dict() _json_data: Dict[str, Any] = dict()
_JSON_TYPE_DEFAULTS: Dict[str, Any] = {
"string": "",
"integer": 0,
"number": 0.0,
"boolean": False,
"null": None,
}
@classmethod
def generate_default_from_schema(cls, schema: Dict[str, Any]) -> Dict[str, Any]:
"""根据JSON Schema生成包含所有字段默认值的数据字典
优先使用schema中显式声明的default否则根据类型生成零值。
支持嵌套object递归生成。
Args:
schema: JSON Schema定义根schema或子object schema
Returns:
包含所有字段默认值的数据字典
Examples:
>>> schema = {
... "type": "object",
... "properties": {
... "name": {"type": "string"},
... "age": {"type": "integer", "default": 18},
... "active": {"type": "boolean"},
... }
... }
>>> JSONSchemaMessageInstance.generate_default_from_schema(schema)
{'name': '', 'age': 18, 'active': False}
"""
properties = schema.get("properties", {})
result: Dict[str, Any] = {}
for field_name, field_schema in properties.items():
if isinstance(field_schema, dict):
result[field_name] = cls._generate_field_default(field_schema)
return result
@classmethod
def _generate_field_default(cls, field_schema: Dict[str, Any]) -> Any:
"""根据单个字段的schema生成默认值
Args:
field_schema: 字段的JSON Schema定义
Returns:
字段的默认值
"""
if "default" in field_schema:
return field_schema["default"]
if "const" in field_schema:
return field_schema["const"]
if "enum" in field_schema:
enum_values = field_schema["enum"]
if enum_values:
return enum_values[0]
json_type = field_schema.get("type")
if json_type is None:
return None
# 联合类型取第一个非null类型
if isinstance(json_type, list):
non_null = [t for t in json_type if t != "null"]
json_type = non_null[0] if non_null else "null"
if json_type == "object":
return cls.generate_default_from_schema(field_schema)
if json_type == "array":
return []
return cls._JSON_TYPE_DEFAULTS.get(json_type)
def __init__(self, inner_data: Dict[str, Any], schema: Dict[str, Any], **kwargs: Any) -> None: def __init__(self, inner_data: Dict[str, Any], schema: Dict[str, Any], **kwargs: Any) -> None:
""" """
初始化JSON Schema消息实例 初始化JSON Schema消息实例

View File

@@ -0,0 +1,375 @@
"""
TypedDict Message Instance - Experimental
This module provides support for TypedDict message instances with type information
extraction and field access capabilities.
WARNING: This implementation is EXPERIMENTAL and may change in future versions.
"""
import warnings
from typing import TYPE_CHECKING, Any, Dict, Optional, Type, get_type_hints
from msgcenterpy.core.envelope import MessageEnvelope, create_envelope
from msgcenterpy.core.message_instance import MessageInstance
from msgcenterpy.core.type_converter import TypeConverter
from msgcenterpy.core.type_info import ConstraintType, Consts, TypeInfo
from msgcenterpy.core.types import MessageType
if TYPE_CHECKING:
from msgcenterpy.core.field_accessor import FieldAccessor
class TypedDictMessageInstance(MessageInstance[Dict[str, Any]]):
"""TypedDict消息实例支持类型信息提取和字段访问器实验性
EXPERIMENTAL: This class is experimental and may change in future versions.
Attributes:
_typed_dict_class: TypedDict类型定义
_typed_dict_data: 实际的字典数据
_pydantic_model: 缓存的Pydantic模型懒加载
_json_schema: 缓存的JSON Schema懒加载
"""
_typed_dict_class: Type[Any]
_typed_dict_data: Dict[str, Any]
_pydantic_model: Optional[Any] = None
_json_schema: Optional[Dict[str, Any]] = None
def __init__(self, inner_data: Dict[str, Any], typed_dict: Type[Any], **kwargs: Any) -> None:
"""
初始化TypedDict消息实例
Args:
inner_data: 字典数据
typed_dict: TypedDict类型定义
**kwargs: 额外的关键字参数
"""
# 发出实验性警告
warnings.warn(
"TypedDictMessageInstance is experimental and may change in future versions",
FutureWarning,
stacklevel=2,
)
# 验证typed_dict是否为TypedDict类型
if not self._is_typed_dict(typed_dict):
raise TypeError(f"Expected a TypedDict class, got {type(typed_dict)}")
self._typed_dict_class = typed_dict
self._typed_dict_data = inner_data
self._pydantic_model = None
self._json_schema = None
super().__init__(inner_data, MessageType.TYPED_DICT)
@staticmethod
def _is_typed_dict(typed_dict_class: Type[Any]) -> bool:
"""检查给定的类是否为TypedDict"""
try:
# TypedDict类会有__annotations__和__total__等特殊属性
return (
hasattr(typed_dict_class, "__annotations__")
and hasattr(typed_dict_class, "__total__")
and hasattr(typed_dict_class, "__required_keys__")
and hasattr(typed_dict_class, "__optional_keys__")
)
except Exception:
return False
@property
def typed_dict_class(self) -> Type[Any]:
"""获取TypedDict类型定义"""
return self._typed_dict_class
@property
def typed_dict_class_module(self) -> str:
"""获取TypedDict类的模块路径"""
return self._typed_dict_class.__module__
@property
def typed_dict_class_name(self) -> str:
"""获取TypedDict类名"""
return self._typed_dict_class.__name__
@classmethod
def get_pydantic_model_from_typed_dict(cls, typed_dict: Type[Any]) -> Any:
"""从TypedDict类型创建Pydantic模型类方法版本
优先使用TypeAdapterPydantic V2如果不可用则使用create_model_from_typeddictPydantic V1
Args:
typed_dict: TypedDict类型定义
Returns:
Pydantic模型实例
Raises:
ImportError: 如果Pydantic未安装
RuntimeError: 如果无法创建Pydantic模型
"""
# 尝试使用Pydantic V2的TypeAdapter
try:
from pydantic import TypeAdapter
adapter = TypeAdapter(typed_dict)
return adapter
except ImportError:
pass # Pydantic V2不可用尝试V1
except Exception as e:
# TypeAdapter创建失败尝试V1方法
warnings.warn(f"Failed to create TypeAdapter: {e}, trying V1 approach", RuntimeWarning)
# 尝试使用Pydantic V1的create_model_from_typeddict
try:
from pydantic.main import create_model_from_typeddict
model = create_model_from_typeddict(typed_dict)
return model
except ImportError as e:
raise ImportError(
"Pydantic is required for get_pydantic_model_from_typed_dict(). "
"Please install it with: pip install pydantic"
) from e
except Exception as e:
raise RuntimeError(f"Failed to create Pydantic model from TypedDict: {e}") from e
def get_pydantic_model(self) -> Any:
"""获取或创建Pydantic模型实例方法版本
优先使用TypeAdapterPydantic V2如果不可用则使用create_model_from_typeddictPydantic V1
Returns:
Pydantic模型实例
Raises:
ImportError: 如果Pydantic未安装
RuntimeError: 如果无法创建Pydantic模型
"""
if self._pydantic_model is not None:
return self._pydantic_model
self._pydantic_model = self.get_pydantic_model_from_typed_dict(self._typed_dict_class)
return self._pydantic_model
@classmethod
def get_json_schema_from_typed_dict(cls, typed_dict: Type[Any]) -> Dict[str, Any]:
"""从TypedDict类型生成JSON Schema类方法版本
Args:
typed_dict: TypedDict类型定义
Returns:
JSON Schema字典
Raises:
ImportError: 如果Pydantic未安装
RuntimeError: 如果无法生成JSON Schema
"""
pydantic_model = cls.get_pydantic_model_from_typed_dict(typed_dict)
try:
schema: Dict[str, Any]
# Pydantic V2 API
if hasattr(pydantic_model, "json_schema"):
# TypeAdapter的情况
schema = pydantic_model.json_schema()
return schema
elif hasattr(pydantic_model, "model_json_schema"):
# BaseModel的情况V2
schema = pydantic_model.model_json_schema()
return schema
# Pydantic V1 API
elif hasattr(pydantic_model, "schema"):
schema = pydantic_model.schema()
return schema
else:
raise RuntimeError("Unable to extract JSON schema from Pydantic model")
except Exception as e:
raise RuntimeError(f"Failed to generate JSON schema: {e}") from e
def get_json_schema(self) -> Dict[str, Any]:
"""获取JSON Schema实例方法版本利用Pydantic转换
Returns:
JSON Schema字典
Raises:
ImportError: 如果Pydantic未安装
RuntimeError: 如果无法生成JSON Schema
"""
if self._json_schema is not None:
return self._json_schema
self._json_schema = self.get_json_schema_from_typed_dict(self._typed_dict_class)
return self._json_schema
def export_to_envelope(self, **kwargs: Any) -> MessageEnvelope:
"""导出为统一信封字典
将 typed_dict_class_module, typed_dict_class_name 和 json_schema 保存到 properties
确保 import_from_envelope 可以独立重建实例
"""
base_dict = self.get_python_dict()
# 尝试获取 json_schema如果 Pydantic 可用)
json_schema = None
try:
json_schema = self.get_json_schema()
except (ImportError, RuntimeError):
# Pydantic 不可用或生成失败,继续但不保存 schema
pass
envelope = create_envelope(
format_name=self.message_type.value,
content=base_dict,
metadata={
"current_format": self.message_type.value,
"source_cls_name": self.__class__.__name__,
"source_cls_module": self.__class__.__module__,
**self._metadata,
},
)
# 将 typed_dict_class_module, typed_dict_class_name 和 json_schema 保存到 properties
if "properties" not in envelope["metadata"]:
envelope["metadata"]["properties"] = {} # type: ignore[typeddict-item]
envelope["metadata"]["properties"]["typed_dict_class_module"] = self.typed_dict_class_module # type: ignore[typeddict-item]
envelope["metadata"]["properties"]["typed_dict_class_name"] = self.typed_dict_class_name # type: ignore[typeddict-item]
if json_schema is not None:
envelope["metadata"]["properties"]["json_schema"] = json_schema # type: ignore[typeddict-item]
return envelope
@classmethod
def import_from_envelope(cls, data: MessageEnvelope, **kwargs: Any) -> "TypedDictMessageInstance":
"""从规范信封创建TypedDict实例
优先从 envelope.metadata.properties 读取 json_schema
如果没有 json_schema则尝试从 typed_dict 参数或 typed_dict_class_path 恢复。
Args:
data: 消息信封
**kwargs: 可选的'typed_dict'参数
Returns:
TypedDict实例
Raises:
ValueError: 如果无法确定TypedDict类型
"""
content = data["content"]
properties = data.get("metadata", {}).get("properties", {})
# 优先从 kwargs 获取 typed_dict
typed_dict = kwargs.pop("typed_dict", None)
# 如果没有提供 typed_dict尝试从 properties 恢复
if typed_dict is None:
typed_dict_class_module = properties.get("typed_dict_class_module")
typed_dict_class_name = properties.get("typed_dict_class_name")
if typed_dict_class_module and typed_dict_class_name:
# 尝试从模块导入 TypedDict
try:
import importlib
module = importlib.import_module(typed_dict_class_module)
typed_dict = getattr(module, typed_dict_class_name)
except Exception as e:
raise ValueError(
f"Unable to import TypedDict '{typed_dict_class_name}' from module '{typed_dict_class_module}': {e}. "
"Please provide 'typed_dict' parameter explicitly."
) from e
if typed_dict is None:
raise ValueError(
"Unable to determine TypedDict type. "
"Please provide 'typed_dict' parameter or ensure envelope contains valid type information "
"(typed_dict_class_module and typed_dict_class_name)."
)
instance = cls(content, typed_dict, **kwargs)
return instance
def get_python_dict(self) -> Dict[str, Any]:
"""获取当前所有的字段名和对应的原始值"""
return self._typed_dict_data.copy()
def set_python_dict(self, value: Dict[str, Any], **kwargs: Any) -> bool:
"""设置所有字段的值,只做已有字段的更新"""
# 获取根访问器
root_accessor = self._field_accessor
if root_accessor is not None:
root_accessor.update_from_dict(source_data=value)
return True
# TypeInfoProvider 接口实现
def get_field_type_info(
self, field_name: str, field_value: Any, parent_field_accessor: "FieldAccessor"
) -> Optional[TypeInfo]:
"""从TypedDict定义中提取字段类型信息
Args:
field_name: 字段名
field_value: 字段值
parent_field_accessor: 父级字段访问器
Returns:
字段的类型信息
"""
# 构建完整路径
full_path = f"{parent_field_accessor.full_path_from_root}.{field_name}"
# 获取TypedDict的类型提示
try:
type_hints = get_type_hints(self._typed_dict_class)
except Exception:
type_hints = {}
# 获取字段的类型注解
field_type_annotation = type_hints.get(field_name)
# 确定类型信息
python_type = type(field_value)
if field_type_annotation is not None:
# 从类型注解推断标准类型
standard_type = TypeConverter.python_type_to_standard(field_type_annotation)
else:
# 如果没有类型注解,从值的类型推断
standard_type = TypeConverter.python_type_to_standard(python_type)
# 创建基础TypeInfo
type_info = TypeInfo(
field_name=field_name,
field_path=full_path,
standard_type=standard_type,
python_type=python_type,
original_type=field_type_annotation if field_type_annotation is not None else python_type,
current_value=field_value,
)
# 检查字段是否为必需字段
if hasattr(self._typed_dict_class, "__required_keys__"):
required_keys = getattr(self._typed_dict_class, "__required_keys__")
if field_name in required_keys:
type_info.add_constraint(
ConstraintType.REQUIRED,
True,
"Field is required by TypedDict definition",
)
# 处理列表/数组类型
if isinstance(field_value, list):
type_info.is_array = True
# 可以进一步提取元素类型信息,但这需要更复杂的类型解析
# 暂时留给后续版本实现
# 处理字典/对象类型
elif isinstance(field_value, dict):
type_info.is_object = True
# 可以进一步提取对象字段信息,但这需要递归解析
# 暂时留给后续版本实现
return type_info

View File

@@ -40,13 +40,17 @@ ros2 = [
] ]
dev = [ dev = [
"pytest>=7.0.0", "pytest>=7.0.0",
"black>=22.0.0", "pytest-asyncio>=0.21.0",
"isort>=5.0.0", "black>=23.0.0",
"mypy>=1.0.0", "isort>=5.13.0",
"flake8>=7.0.0",
"Flake8-pyproject>=1.2.0",
"mypy>=1.8.0",
"types-jsonschema>=4.0.0", "types-jsonschema>=4.0.0",
"types-PyYAML>=6.0.0", "types-PyYAML>=6.0.0",
"pre-commit>=2.20.0", "bandit[toml]>=1.7.0",
"bump2version>=1.0.0" "pre-commit>=3.5.0",
"bump-my-version>=0.28.0",
] ]
docs = [ docs = [
"sphinx>=5.0.0", "sphinx>=5.0.0",
@@ -70,14 +74,26 @@ include = ["msgcenterpy*"]
[tool.setuptools.dynamic] [tool.setuptools.dynamic]
version = {attr = "msgcenterpy.__version__"} version = {attr = "msgcenterpy.__version__"}
# ── Formatting ────────────────────────────────────────────────
[tool.black] [tool.black]
line-length = 120 line-length = 120
target-version = ['py310'] target-version = ["py310", "py311", "py312"]
[tool.isort] [tool.isort]
profile = "black" profile = "black"
line_length = 120
multi_line_output = 3 multi_line_output = 3
# ── Linting ───────────────────────────────────────────────────
[tool.flake8]
max-line-length = 200
extend-ignore = ["E203", "W503", "W291", "W293", "W391", "F401", "E402", "E721", "F841", "F541"]
exclude = ["build", "dist", "__pycache__", ".mypy_cache", ".pytest_cache", "htmlcov", ".vscode", "docs/_build", "msgcenterpy.egg-info"]
# ── Type checking ─────────────────────────────────────────────
[tool.mypy] [tool.mypy]
python_version = "3.10" python_version = "3.10"
plugins = ["pydantic.mypy"] plugins = ["pydantic.mypy"]
@@ -86,8 +102,11 @@ warn_unused_configs = true
disallow_untyped_defs = true disallow_untyped_defs = true
no_implicit_optional = true no_implicit_optional = true
warn_redundant_casts = true warn_redundant_casts = true
warn_unused_ignores = true warn_unused_ignores = false
strict_equality = true strict_equality = true
ignore_missing_imports = true
# ── Testing ───────────────────────────────────────────────────
[tool.pytest.ini_options] [tool.pytest.ini_options]
testpaths = ["tests"] testpaths = ["tests"]
@@ -95,12 +114,32 @@ python_files = "test_*.py"
python_classes = "Test*" python_classes = "Test*"
python_functions = "test_*" python_functions = "test_*"
addopts = "-v --tb=short --strict-markers --strict-config -ra --color=yes" addopts = "-v --tb=short --strict-markers --strict-config -ra --color=yes"
filterwarnings = [ filterwarnings = [
"ignore::DeprecationWarning", "ignore::DeprecationWarning",
"ignore::PendingDeprecationWarning" "ignore::PendingDeprecationWarning",
] ]
# ── Security ──────────────────────────────────────────────────
[tool.bandit] [tool.bandit]
exclude_dirs = ["tests", "build", "dist"] exclude_dirs = ["tests", "build", "dist"]
skips = ["B101", "B601"] skips = ["B101", "B601"]
# ── Version management ────────────────────────────────────────
[tool.bumpversion]
current_version = "0.1.6"
commit = true
tag = true
tag_name = "v{new_version}"
message = "release: bump version {current_version} → {new_version}"
[[tool.bumpversion.files]]
filename = "pyproject.toml"
search = 'current_version = "{current_version}"'
replace = 'current_version = "{new_version}"'
[[tool.bumpversion.files]]
filename = "msgcenterpy/__init__.py"
search = '__version__ = "{current_version}"'
replace = '__version__ = "{new_version}"'

View File

@@ -432,6 +432,159 @@ class TestJSONSchemaValidation:
assert len(invalid_instance._validation_errors) > 0 assert len(invalid_instance._validation_errors) > 0
class TestGenerateDefaultFromSchema:
"""generate_default_from_schema 默认值生成测试"""
def test_basic_types(self):
"""测试基础类型的零值生成"""
schema = {
"type": "object",
"properties": {
"s": {"type": "string"},
"i": {"type": "integer"},
"n": {"type": "number"},
"b": {"type": "boolean"},
"null_field": {"type": "null"},
},
}
result = JSONSchemaMessageInstance.generate_default_from_schema(schema)
assert result == {"s": "", "i": 0, "n": 0.0, "b": False, "null_field": None}
def test_explicit_default_takes_priority(self):
"""测试显式 default 优先于类型零值"""
schema = {
"type": "object",
"properties": {
"name": {"type": "string", "default": "anonymous"},
"age": {"type": "integer", "default": 18},
"score": {"type": "number", "default": 99.5},
},
}
result = JSONSchemaMessageInstance.generate_default_from_schema(schema)
assert result == {"name": "anonymous", "age": 18, "score": 99.5}
def test_enum_uses_first_value(self):
"""测试枚举类型使用第一个枚举值"""
schema = {
"type": "object",
"properties": {
"status": {"type": "string", "enum": ["active", "inactive", "pending"]},
},
}
result = JSONSchemaMessageInstance.generate_default_from_schema(schema)
assert result == {"status": "active"}
def test_const_value(self):
"""测试 const 关键字"""
schema = {
"type": "object",
"properties": {
"version": {"type": "string", "const": "v2"},
},
}
result = JSONSchemaMessageInstance.generate_default_from_schema(schema)
assert result == {"version": "v2"}
def test_array_type(self):
"""测试数组类型返回空列表"""
schema = {
"type": "object",
"properties": {
"tags": {"type": "array", "items": {"type": "string"}},
},
}
result = JSONSchemaMessageInstance.generate_default_from_schema(schema)
assert result == {"tags": []}
def test_nested_object(self):
"""测试嵌套对象递归生成"""
schema = {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"name": {"type": "string"},
"address": {
"type": "object",
"properties": {
"city": {"type": "string"},
"zip": {"type": "integer"},
},
},
},
},
},
}
result = JSONSchemaMessageInstance.generate_default_from_schema(schema)
assert result == {
"user": {
"name": "",
"address": {"city": "", "zip": 0},
}
}
def test_union_type(self):
"""测试联合类型取第一个非 null 类型"""
schema = {
"type": "object",
"properties": {
"nullable_str": {"type": ["string", "null"]},
"nullable_int": {"type": ["null", "integer"]},
},
}
result = JSONSchemaMessageInstance.generate_default_from_schema(schema)
assert result == {"nullable_str": "", "nullable_int": 0}
def test_no_type_field(self):
"""测试缺少 type 定义时返回 None"""
schema = {
"type": "object",
"properties": {
"unknown": {"description": "no type defined"},
},
}
result = JSONSchemaMessageInstance.generate_default_from_schema(schema)
assert result == {"unknown": None}
def test_empty_schema(self):
"""测试空 schema 返回空字典"""
result = JSONSchemaMessageInstance.generate_default_from_schema({})
assert result == {}
result2 = JSONSchemaMessageInstance.generate_default_from_schema({"type": "object"})
assert result2 == {}
def test_generated_default_creates_valid_instance(self):
"""测试生成的默认值可以直接创建有效的实例"""
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"count": {"type": "integer"},
"active": {"type": "boolean"},
"items": {"type": "array", "items": {"type": "string"}},
"meta": {
"type": "object",
"properties": {"version": {"type": "integer"}},
},
},
}
defaults = JSONSchemaMessageInstance.generate_default_from_schema(schema)
instance = JSONSchemaMessageInstance(defaults, schema)
assert len(instance._validation_errors) == 0
assert instance.get_python_dict() == defaults
# 运行测试的便捷函数 # 运行测试的便捷函数
def run_json_schema_tests(): def run_json_schema_tests():
"""运行 JSON Schema 相关测试""" """运行 JSON Schema 相关测试"""