13 Commits
v0.1.5 ... main

Author SHA1 Message Date
Xuwznln
182c5187a0 refactor: simplify _extract_namespaced_type_fields, remove redundant try-except and post-processing
Made-with: Cursor
2026-03-22 01:33:01 +08:00
Xuwznln
9e6007eb60 release: bump version 0.1.7 → 0.1.8 2026-03-22 01:25:18 +08:00
Xuwznln
03b3b1144c fix: resolve nested ROS2 message types losing properties in JSON Schema generation
Array fields containing nested message types (e.g., Point[]) were serialized
as {type: object} without inner properties. Now correctly extracts and includes
all nested fields, required list, and title via NamespacedType introspection.

Made-with: Cursor
2026-03-22 01:25:04 +08:00
Xuwznln
97c76708cd release: bump version 0.1.6 → 0.1.7 2026-03-01 11:18:26 +08:00
Xuwznln
05d33086a9 ci: update Python version in CI workflows to 3.11
- Changed Python version from 3.10 to 3.11 in ci.yml and publish.yml workflows.
- Updated cache keys and build names accordingly.
- Simplified conditional logic in ros2_instance.py for interface determination.
2026-03-01 11:17:15 +08:00
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
11 changed files with 383 additions and 91 deletions

View File

@@ -1,10 +0,0 @@
[bumpversion]
current_version = 0.1.5
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

@@ -21,7 +21,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.10" # Use minimum version for consistency
python-version: "3.10"
- name: Install dependencies
run: |
@@ -33,27 +33,27 @@ jobs:
with:
extra_args: --all-files
# Step 2: Basic build and test with minimum Python version (3.10)
# Step 2: Basic build and test
basic-build:
name: Basic build (Python 3.10, Ubuntu)
name: Basic build (Python 3.11, Ubuntu)
runs-on: ubuntu-latest
needs: [code-format] # Only run after code formatting passes
steps:
- uses: actions/checkout@v6
- name: Set up Python 3.10
- name: Set up Python 3.11
uses: actions/setup-python@v6
with:
python-version: "3.10"
python-version: "3.11"
- name: Cache pip dependencies
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.cache/pip
key: ubuntu-pip-3.10-${{ hashFiles('**/pyproject.toml') }}
key: ubuntu-pip-3.11-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
ubuntu-pip-3.10-
ubuntu-pip-3.11-
- name: Install dependencies
run: |
@@ -122,7 +122,7 @@ jobs:
args: --detailed-output --output-format json
- name: Upload security reports
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: security-reports
path: |
@@ -155,7 +155,7 @@ jobs:
run: twine check dist/*
- name: Upload build artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: dist
path: dist/
@@ -173,7 +173,7 @@ jobs:
exclude:
# Skip the combination we already tested in basic-build
- os: ubuntu-latest
python-version: "3.10"
python-version: "3.11"
steps:
- uses: actions/checkout@v6
@@ -184,7 +184,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Cache pip dependencies
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }}

View File

@@ -34,7 +34,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.10" # Use minimum version for consistency
python-version: "3.10"
- name: Install dependencies
run: |
@@ -46,27 +46,27 @@ jobs:
with:
extra_args: --all-files
# Step 2: Basic build and test with minimum Python version (3.10)
# Step 2: Basic build and test
basic-build:
name: Basic build (Python 3.10, Ubuntu)
name: Basic build (Python 3.11, Ubuntu)
runs-on: ubuntu-latest
needs: [code-format] # Only run after code formatting passes
steps:
- uses: actions/checkout@v6
- name: Set up Python 3.10
- name: Set up Python 3.11
uses: actions/setup-python@v6
with:
python-version: "3.10"
python-version: "3.11"
- name: Cache pip dependencies
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.cache/pip
key: ubuntu-pip-3.10-${{ hashFiles('**/pyproject.toml') }}
key: ubuntu-pip-3.11-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
ubuntu-pip-3.10-
ubuntu-pip-3.11-
- name: Install dependencies
run: |
@@ -78,7 +78,7 @@ jobs:
run: |
pytest -v
# Step 3: ROS2 integration test
# Step 3: ROS2 integration test
test-with-ros2:
name: ROS2 integration test
runs-on: ubuntu-latest
@@ -135,7 +135,7 @@ jobs:
args: --detailed-output --output-format json
- name: Upload security reports
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: security-reports
path: |
@@ -197,7 +197,7 @@ jobs:
twine check dist/*
- name: Upload distributions
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: release-dists
path: dist/
@@ -217,7 +217,7 @@ jobs:
steps:
- name: Retrieve release distributions
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
name: release-dists
path: dist/
@@ -240,7 +240,7 @@ jobs:
steps:
- name: Retrieve release distributions
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
name: release-dists
path: dist/
@@ -260,7 +260,7 @@ jobs:
steps:
- name: Retrieve release distributions
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
name: release-dists
path: dist/

View File

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

View File

@@ -88,18 +88,18 @@ Testing
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
# Bug fixes (0.0.1 → 0.0.2)
bump2version patch
bump-my-version bump patch
# New features (0.0.2 → 0.1.0)
bump2version minor
bump-my-version bump minor
# Breaking changes (0.1.0 → 1.0.0)
bump2version major
bump-my-version bump major
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.
"""
__version__ = "0.1.5"
__version__ = "0.1.8"
__license__ = "Apache-2.0"
from msgcenterpy.core.envelope import MessageEnvelope, create_envelope

View File

@@ -277,9 +277,16 @@ class TypeInfo:
# Special handling for object types
if self.is_object and self.object_fields:
properties = {}
required_fields = []
for field_name, field_info in self.object_fields.items():
properties[field_name] = field_info.to_json_schema_property(include_constraints)
if field_info.has_constraint(ConstraintType.REQUIRED):
required_fields.append(field_name)
property_schema["properties"] = properties
if required_fields:
property_schema["required"] = required_fields
if self.field_name and self.field_name != Consts.ELEMENT_TYPE_INFO_SYMBOL:
property_schema["title"] = self.field_name
# Add description
if self.original_type:

View File

@@ -19,6 +19,87 @@ class JSONSchemaMessageInstance(MessageInstance[Dict[str, Any]]):
_json_schema: 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:
"""
初始化JSON Schema消息实例

View File

@@ -40,11 +40,7 @@ class ROS2MessageInstance(MessageInstance[Any]):
interface = (
"msg"
if ".msg" in module_name
else "srv"
if ".srv" in module_name
else "action"
if ".action" in module_name
else "msg"
else "srv" if ".srv" in module_name else "action" if ".action" in module_name else "msg"
)
return f"{package}/{interface}/{class_name}" if package and class_name else f"{module_name}.{class_name}"
@@ -221,10 +217,9 @@ class ROS2MessageInstance(MessageInstance[Any]):
# 基础类型的约束将在 field_accessor 中自动添加
pass
elif isinstance(definition_type, NamespacedType):
# 对象类型,标记为对象并提取字段信息
type_info.is_object = True
type_info.add_constraint(ConstraintType.TYPE_KEEP, True)
# 这里可以进一步扩展来提取对象字段信息
self._extract_namespaced_type_fields(type_info, definition_type)
# 提取元素类型信息
if get_element_type:
if not isinstance(definition_type, AbstractNestedType):
@@ -240,3 +235,33 @@ class ROS2MessageInstance(MessageInstance[Any]):
original_type=definition_type.value_type,
)
self._extract_from_rosidl_definition(type_info.element_type_info)
def _extract_namespaced_type_fields(self, type_info: TypeInfo, namespaced_type: "NamespacedType") -> None:
"""从 NamespacedType嵌套 ROS2 消息)中提取所有字段信息,填充 object_fields
递归处理嵌套的消息类型,确保多层嵌套的结构也能正确提取。
Args:
type_info: 要填充 object_fields 的 TypeInfo 对象
namespaced_type: rosidl NamespacedType 定义
"""
msg_cls = import_message_from_namespaced_type(namespaced_type)
msg_instance = msg_cls()
# noinspection PyProtectedMember
slots = msg_instance._fields_and_field_types
slot_types = msg_instance.SLOT_TYPES
for field_name, slot_type in zip(slots, slot_types):
std_type = TypeConverter.rosidl_definition_to_standard(slot_type)
python_type = TypeConverter.standard_to_python_type(std_type)
field_type_info = TypeInfo(
field_name=field_name,
field_path=f"{type_info.field_path}.{field_name}",
standard_type=std_type,
python_type=python_type,
original_type=slot_type,
)
self._extract_from_rosidl_definition(field_type_info)
field_type_info.add_constraint(ConstraintType.REQUIRED, True)
type_info.object_fields[field_name] = field_type_info

View File

@@ -40,13 +40,17 @@ ros2 = [
]
dev = [
"pytest>=7.0.0",
"black>=22.0.0",
"isort>=5.0.0",
"mypy>=1.0.0",
"pytest-asyncio>=0.21.0",
"black>=23.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-PyYAML>=6.0.0",
"pre-commit>=2.20.0",
"bump2version>=1.0.0"
"bandit[toml]>=1.7.0",
"pre-commit>=3.5.0",
"bump-my-version>=0.28.0",
]
docs = [
"sphinx>=5.0.0",
@@ -70,14 +74,26 @@ include = ["msgcenterpy*"]
[tool.setuptools.dynamic]
version = {attr = "msgcenterpy.__version__"}
# ── Formatting ────────────────────────────────────────────────
[tool.black]
line-length = 120
target-version = ['py310', 'py311', 'py312']
target-version = ["py310", "py311", "py312"]
[tool.isort]
profile = "black"
line_length = 120
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]
python_version = "3.10"
plugins = ["pydantic.mypy"]
@@ -86,8 +102,11 @@ warn_unused_configs = true
disallow_untyped_defs = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_unused_ignores = false
strict_equality = true
ignore_missing_imports = true
# ── Testing ───────────────────────────────────────────────────
[tool.pytest.ini_options]
testpaths = ["tests"]
@@ -95,12 +114,33 @@ python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"
addopts = "-v --tb=short --strict-markers --strict-config -ra --color=yes"
filterwarnings = [
"ignore::DeprecationWarning",
"ignore::PendingDeprecationWarning"
"ignore::PendingDeprecationWarning",
]
# ── Security ──────────────────────────────────────────────────
[tool.bandit]
exclude_dirs = ["tests", "build", "dist"]
skips = ["B101", "B601"]
# ── Version management ────────────────────────────────────────
[tool.bumpversion]
current_version = "0.1.8"
commit = true
tag = true
push = 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
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():
"""运行 JSON Schema 相关测试"""