diff --git a/.gitignore b/.gitignore index 41956b8..1d4625f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ /xmlgenerator.egg-info/ /_schemas/ /dist_native/ +/.roo/ \ No newline at end of file diff --git a/README.md b/README.md index 6f175e4..8a2a7dd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # XML Generator -![PyPI - Version](https://img.shields.io/pypi/v/xmlgenerator) +[![PyPI - Version](https://img.shields.io/pypi/v/xmlgenerator)](https://pypi.org/project/xmlgenerator) [![PyPI - Downloads](https://img.shields.io/pypi/dm/xmlgenerator)](https://pypistats.org/packages/xmlgenerator) -[![DeepWiki](https://img.shields.io/badge/DeepWiki-lexakimov%2Fxmlgenerator-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==)](https://deepwiki.com/lexakimov/xmlgenerator) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/lexakimov/xmlgenerator) - [Русский 🇷🇺](README_RU.md) - [English 🇺🇸](README.md) @@ -29,7 +29,7 @@ pip install xmlgenerator ### Install executable file manually (linux) ```bash -curl -LO https://github.com/lexakimov/xmlgenerator/releases/download/v0.5.3/xmlgenerator-linux-amd64 +curl -LO https://github.com/lexakimov/xmlgenerator/releases/download/v0.8.0/xmlgenerator-linux-amd64 chmod +x xmlgenerator-linux-amd64 sudo install xmlgenerator-linux-amd64 /usr/local/bin/xmlgenerator ``` @@ -64,7 +64,7 @@ options: -p, --pretty prettify the output XML -n, --namespace alias=namespace define XML namespace alias (repeatable flag) -v, --validation validate the generated XML document (none, schema, schematron; default: schema) - -ff, --fail-fast terminate execution on a validation error (default: true) + -i continue execution when validation errors occur -e, --encoding the output XML encoding (utf-8, windows-1251; default: utf-8) -s, --seed set the randomization seed -d, --debug enable debug mode @@ -106,7 +106,7 @@ Generated XML documents are checked for conformance against the schema used for By default, validation against the source XSD schema is used. If a document does not conform to the schema, execution stops immediately. -This behavior can be disabled using the flag `-ff false` or `--fail-fast false`. +To keep processing despite validation errors, pass the `-i` flag. To disable validation, use the flag `-v none` or `--validation none`. diff --git a/README_RU.md b/README_RU.md index 92a8a26..22323e4 100644 --- a/README_RU.md +++ b/README_RU.md @@ -1,8 +1,8 @@ # XML Generator -![PyPI - Version](https://img.shields.io/pypi/v/xmlgenerator) +[![PyPI - Version](https://img.shields.io/pypi/v/xmlgenerator)](https://pypi.org/project/xmlgenerator) [![PyPI - Downloads](https://img.shields.io/pypi/dm/xmlgenerator)](https://pypistats.org/packages/xmlgenerator) -[![DeepWiki](https://img.shields.io/badge/DeepWiki-lexakimov%2Fxmlgenerator-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==)](https://deepwiki.com/lexakimov/xmlgenerator) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/lexakimov/xmlgenerator) - [Русский 🇷🇺](README_RU.md) - [English 🇺🇸](README.md) @@ -29,7 +29,7 @@ pip install xmlgenerator ### Ручная установка исполняемого файла (linux) ```bash -curl -LO https://github.com/lexakimov/xmlgenerator/releases/download/v0.5.3/xmlgenerator-linux-amd64 +curl -LO https://github.com/lexakimov/xmlgenerator/releases/download/v0.8.0/xmlgenerator-linux-amd64 chmod +x xmlgenerator-linux-amd64 sudo install xmlgenerator-linux-amd64 /usr/local/bin/xmlgenerator ``` @@ -64,7 +64,7 @@ options: -p, --pretty prettify the output XML -n, --namespace alias=namespace define XML namespace alias (repeatable flag) -v, --validation validate the generated XML document (none, schema, schematron; default: schema) - -ff, --fail-fast terminate execution on a validation error (default: true) + -i continue execution when validation errors occur -e, --encoding the output XML encoding (utf-8, windows-1251; default: utf-8) -s, --seed set the randomization seed -d, --debug enable debug mode @@ -105,8 +105,8 @@ options: Сгенерированные XML-документы проверяются на соответствие схеме, использованной для генерации. По умолчанию используется валидация через исходную XSD-схему. -При несоответствии документа схеме, выполнение прекращается незамедлительно. -Это поведение можно отключить через флаг `-ff false` или `--fail-fast false`. +При несоответствии документа схеме выполнение прекращается незамедлительно. +Чтобы продолжить обработку несмотря на ошибки валидации, используйте флаг `-i`. Чтобы отключить валидацию, укажите флаг `-v none` или `--validation none`. diff --git a/build_native.py b/build_native.py index ce82b35..620eca8 100644 --- a/build_native.py +++ b/build_native.py @@ -6,10 +6,6 @@ from xmlgenerator import __version__ -# TODO strip -# pyinstaller ? -# sudo pacman -S ccache - # --- Конфигурация сборки Nuitka --- # Имя основного скрипта вашего приложения diff --git a/config_fns.yml b/config_fns.yml index 32ef84f..42cdc78 100644 --- a/config_fns.yml +++ b/config_fns.yml @@ -14,7 +14,7 @@ global: value_override: "^(ИдФайл|FileID)$": "{{ output_filename }}" - "^(ВерсПрог|VersProg)$": "xmlgenerator 0.6.0" + "^(ВерсПрог|VersProg)$": "xmlgenerator 0.8.0" "Фамилия": "{{ last_name('ru_RU') }}" "Имя": "{{ first_name('ru_RU') }}" "Отчество": "{{ middle_name('ru_RU') }}" diff --git a/config_mvd.yml b/config_mvd.yml new file mode 100644 index 0000000..42acd1f --- /dev/null +++ b/config_mvd.yml @@ -0,0 +1,23 @@ +## Пример конфигурации для генерации документов по форматам МВД +global: + output_filename: "{{ root_element }}" + + randomization: + probability: 1 + + min_occurs: 1 + max_occurs: 2 + + min_length: 8 + max_length: 12 + + min_inclusive: 0 + max_inclusive: 100000 + + value_override: + "lastName": "{{ last_name('ru_RU') }}" + "firstName": "{{ first_name('ru_RU') }}" + "middleName": "{{ middle_name('ru_RU') }}" + "regAddressType": "{{ city('ru_RU') }}, {{ street('ru_RU') }}, д. {{ house_number('ru_RU') }}, кв. {{ number(1, 100) }}" + "fullAddress": "{{ city('ru_RU') }}, {{ street('ru_RU') }}, д. {{ house_number('ru_RU') }}, кв. {{ number(1, 100) }}" + "adressGUID": "{{ uuid }}" diff --git a/requirements.txt b/requirements.txt index b74cb85..e840c9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -lxml==6.0.0 +lxml==6.0.1 xmlschema==4.1.0 -Faker==37.4.2 +Faker==37.6.0 rstr==3.2.2 PyYAML==6.0.2 shtab==1.7.2 @@ -9,5 +9,5 @@ shtab==1.7.2 pytest==8.4.1 pytest-repeat==0.9.4 setuptools==80.9.0 -nuitka==2.7.12 +nuitka==2.7.13 twine==6.1.0 diff --git a/tests/unit/test_arguments.py b/tests/unit/test_arguments.py index 10f9e11..a545ad6 100644 --- a/tests/unit/test_arguments.py +++ b/tests/unit/test_arguments.py @@ -69,6 +69,7 @@ def test_parse_args__one_schema__exists(self, capsys): assert args.output_path is None assert args.pretty is False assert args.seed is None + assert args.ignore_validation_errors is False assert len(xsd_files) is 1 assert output_path is None @@ -85,6 +86,7 @@ def test_parse_args__one_schema__twice(self, capsys): assert args.output_path is None assert args.pretty is False assert args.seed is None + assert args.ignore_validation_errors is False assert len(xsd_files) is 1 assert output_path is None @@ -96,8 +98,37 @@ def test_parse_args__one_schema__check_output_is_file(self, capsys): args, xsd_files, output_path = parse('program -o out.xml data/simple_schemas/schema_1.xsd') assert output_path is not None + assert not output_path.exists() assert args.config_yaml is None + def test_parse_args__one_schema__output_points_to_existing_file(self, capsys, tmp_path): + existing_file = tmp_path / "existing" + existing_file.write_text("data") + + args, xsd_files, output_path = parse(f'program -o {existing_file} data/simple_schemas/schema_1.xsd') + + assert output_path == existing_file + assert existing_file.exists() + + def test_parse_args__one_schema__output_points_to_existing_directory(self, capsys, tmp_path): + existing_dir = tmp_path / "existing" + existing_dir.mkdir() + args, xsd_files, output_path = parse(f'program -o {existing_dir} data/simple_schemas/schema_1.xsd') + assert output_path == existing_dir + assert existing_dir.exists() + + def test_parse_args__one_schema__create_output_folder(self, capsys): + assert not Path("output_dir").exists() + parse('program -o output_dir/ data/simple_schemas/schema_1.xsd') + assert Path("output_dir").exists() + Path("output_dir").rmdir() + + def test_parse_args__one_schema__create_output_folder_without_trailing_slash(self, capsys): + assert not Path("output_dir").exists() + parse('program -o output_dir data/simple_schemas/schema_1.xsd') + assert Path("output_dir").exists() + Path("output_dir").rmdir() + class TestTwoSchemas: @@ -110,6 +141,7 @@ def test_parse_args__two_schemas__exists(self, capsys): assert args.output_path is None assert args.pretty is False assert args.seed is None + assert args.ignore_validation_errors is False assert len(xsd_files) is 2 assert output_path is None @@ -124,7 +156,7 @@ def test_parse_args__two_schemas__check_output_is_dir(self, capsys): captured = capsys.readouterr() assert excinfo.value.code == 2 assert 'usage: xmlgenerator [-h]' in captured.out - assert 'error: option -o/--output must be a directory when multiple source xsd schemas are provided.' in captured.err + assert 'error: option -o/--output must be a directory when multiple schemas are provided.' in captured.err def test_parse_args__two_schemas__create_output_folder(self, capsys): assert not Path("output_dir").exists() @@ -132,6 +164,35 @@ def test_parse_args__two_schemas__create_output_folder(self, capsys): assert Path("output_dir").exists() Path("output_dir").rmdir() + def test_parse_args__two_schemas__create_output_folder_without_trailing_slash(self, capsys): + assert not Path("output_dir").exists() + parse('program -o output_dir data/simple_schemas/schema_1.xsd data/simple_schemas/schema_2.xsd') + assert Path("output_dir").exists() + Path("output_dir").rmdir() + + def test_parse_args__two_schemas__create_output_folder_with_suffix_and_slash(self, capsys): + assert not Path("output.dir").exists() + parse('program -o output.dir/ data/simple_schemas/schema_1.xsd data/simple_schemas/schema_2.xsd') + assert Path("output.dir").exists() + Path("output.dir").rmdir() + + def test_parse_args__two_schemas__output_points_to_existing_directory(self, capsys, tmp_path): + existing_dir = tmp_path / "existing" + existing_dir.mkdir() + parse(f'program -o {existing_dir} data/simple_schemas/schema_1.xsd data/simple_schemas/schema_2.xsd') + assert existing_dir.exists() + + def test_parse_args__two_schemas__output_points_to_existing_file(self, capsys, tmp_path): + existing_file = tmp_path / "existing" + existing_file.write_text("data") + + with pytest.raises(SystemExit) as excinfo: + parse(f'program -o {existing_file} data/simple_schemas/schema_1.xsd data/simple_schemas/schema_2.xsd') + + captured = capsys.readouterr() + assert excinfo.value.code == 2 + assert f'error: option -o/--output points to existing file {existing_file}. It must be a directory when multiple schemas are provided.' in captured.err + class TestInputFolder: @@ -153,6 +214,7 @@ def test_parse_args__folder__not_empty(self, capsys): assert args.output_path is None assert args.pretty is False assert args.seed is None + assert args.ignore_validation_errors is False assert len(xsd_files) is 2 assert 'schema_1.xsd' in [v.name for v in xsd_files] assert 'schema_2.xsd' in [v.name for v in xsd_files] @@ -168,6 +230,12 @@ def test_parse_args__config_file_is_empty(self, capsys): args, xsd_files, output_path = parse('program -c data/config_empty.yaml data/simple_schemas/schema_1.xsd') assert args.config_yaml is not None + assert args.ignore_validation_errors is False + + def test_parse_args__ignore_validation_errors_flag(self, capsys): + args, xsd_files, output_path = parse('program -i data/simple_schemas/schema_1.xsd') + + assert args.ignore_validation_errors is True def test_parse_args__config_file_not_exists(self, capsys): with pytest.raises(SystemExit) as excinfo: diff --git a/tests/unit/test_generator.py b/tests/unit/test_generator.py index 19284d1..75059a6 100644 --- a/tests/unit/test_generator.py +++ b/tests/unit/test_generator.py @@ -38,7 +38,6 @@ def config(): def log_xml(generated_xml): - """Выводит сгенерированный XML в консоль для отладки.""" print(etree.tostring(generated_xml, pretty_print=True).decode('utf-8')) diff --git a/tests/unit/test_randomization.py b/tests/unit/test_randomization.py index 677db1f..77bef0c 100644 --- a/tests/unit/test_randomization.py +++ b/tests/unit/test_randomization.py @@ -1,4 +1,5 @@ import logging +from datetime import time import pytest @@ -24,7 +25,6 @@ def test_fake_no_seed(): assert randomizer1.email() != randomizer2.email() -@pytest.mark.repeat(10) def test_random_has_seed(): randomizer1 = Randomizer(seed=123) randomizer2 = Randomizer(seed=123) @@ -32,9 +32,15 @@ def test_random_has_seed(): assert randomizer1.integer(0, 1000000) == randomizer2.integer(0, 1000000) -@pytest.mark.repeat(10) def test_fake_has_seed(): randomizer1 = Randomizer(seed=123) randomizer2 = Randomizer(seed=123) for _ in range(5): assert randomizer1.email() == randomizer2.email() + + +def test_random_time_cross_hour(): + randomizer = Randomizer(seed=123) + generated_time = randomizer.random_time('09:45:00', '17:15:00') + + assert time.fromisoformat('09:45:00') <= generated_time <= time.fromisoformat('17:15:00') diff --git a/tests/unit/test_validation.py b/tests/unit/test_validation.py new file mode 100644 index 0000000..dee24fb --- /dev/null +++ b/tests/unit/test_validation.py @@ -0,0 +1,45 @@ +import pytest + +import xmlgenerator.validation as validation +from xmlgenerator.validation import XmlValidator + + +class DummySchema: + def __init__(self, exc): + self._exc = exc + + def validate(self, document): + if self._exc: + raise self._exc + + +def test_validator_exits_on_validation_error(monkeypatch): + class DummyError(Exception): + pass + + monkeypatch.setattr(validation, "XMLSchemaValidationError", DummyError) + + validator = XmlValidator('schema', ignore_errors=False) + schema = DummySchema(DummyError("boom")) + + with pytest.raises(SystemExit): + validator.validate(schema, "") + + +def test_validator_ignores_validation_error(monkeypatch): + class DummyError(Exception): + pass + + monkeypatch.setattr(validation, "XMLSchemaValidationError", DummyError) + + validator = XmlValidator('schema', ignore_errors=True) + schema = DummySchema(DummyError("boom")) + + validator.validate(schema, "") + + +def test_validator_skips_validation_when_disabled(): + validator = XmlValidator('none', ignore_errors=False) + schema = DummySchema(Exception("should not raise")) + + validator.validate(schema, "") diff --git a/xmlgenerator/__init__.py b/xmlgenerator/__init__.py index 49e0fc1..777f190 100644 --- a/xmlgenerator/__init__.py +++ b/xmlgenerator/__init__.py @@ -1 +1 @@ -__version__ = "0.7.0" +__version__ = "0.8.0" diff --git a/xmlgenerator/arguments.py b/xmlgenerator/arguments.py index 8e7b449..080af9d 100644 --- a/xmlgenerator/arguments.py +++ b/xmlgenerator/arguments.py @@ -79,10 +79,10 @@ def _get_parser(): help="validate the generated XML document (none, schema, schematron; default: %(default)s)" ) parser.add_argument( - "-ff", "--fail-fast", + "-i", + dest="ignore_validation_errors", action="store_true", - default="true", - help="terminate execution on a validation error (default: %(default)s)" + help="continue execution when validation errors occur" ) parser.add_argument( "-e", "--encoding", @@ -147,13 +147,30 @@ def parse_args(): # Обработка пути вывода output_path = Path(args.output_path) if args.output_path else None - # Проверка: если несколько XSD файлов, то output должен быть директорией - if len(xsd_files) > 1 and output_path and not (output_path.is_dir() or args.output_path.endswith(('/', '\\'))): - parser.error("option -o/--output must be a directory when multiple source xsd schemas are provided.") - - # Создание директории, если output указан как директория - if output_path and (output_path.is_dir() or args.output_path.endswith(('/', '\\'))): - output_path.mkdir(parents=True, exist_ok=True) + # Создание выходной директории если это необходимо + if output_path: + is_existing_dir = output_path.is_dir() if output_path.exists() else False + is_existing_file = output_path.is_file() if output_path.exists() else False + explicit_dir = args.output_path.endswith(('/', '\\')) + looks_like_dir = explicit_dir or not output_path.suffix + + if len(xsd_files) > 1: + if is_existing_file: + parser.error( + f"option -o/--output points to existing file {output_path}. " + "It must be a directory when multiple schemas are provided." + ) + + if not is_existing_dir: + if not looks_like_dir: + parser.error("option -o/--output must be a directory when multiple schemas are provided.") + + if not is_existing_dir: + output_path.mkdir(parents=True, exist_ok=True) + else: + if explicit_dir or (looks_like_dir and not is_existing_file): + if not is_existing_dir: + output_path.mkdir(parents=True, exist_ok=True) return args, xsd_files, output_path diff --git a/xmlgenerator/bootstrap.py b/xmlgenerator/bootstrap.py index 1efea16..324d712 100644 --- a/xmlgenerator/bootstrap.py +++ b/xmlgenerator/bootstrap.py @@ -63,7 +63,7 @@ def _main(): randomizer = Randomizer(args.seed) substitutor = Substitutor(randomizer) generator = XmlGenerator(randomizer, substitutor) - validator = XmlValidator(args.validation, args.fail_fast) + validator = XmlValidator(args.validation, args.ignore_validation_errors) total_count = len(xsd_files) logger.debug('found %s schema(s)', total_count) diff --git a/xmlgenerator/randomization.py b/xmlgenerator/randomization.py index c2e9bae..7cab631 100644 --- a/xmlgenerator/randomization.py +++ b/xmlgenerator/randomization.py @@ -122,11 +122,17 @@ def random_time(self, start_time: str = '00:00:00', end_time: str = '23:59:59') start = time.fromisoformat(start_time) end = time.fromisoformat(end_time) - random_h = self._rnd.randint(start.hour, end.hour) - random_m = self._rnd.randint(start.minute, end.minute) - random_s = self._rnd.randint(start.second, end.second) + start_seconds = start.hour * 3600 + start.minute * 60 + start.second + end_seconds = end.hour * 3600 + end.minute * 60 + end.second - return time(hour=random_h, minute=random_m, second=random_s) + if end_seconds < start_seconds: + raise ValueError('end_time must be greater than or equal to start_time') + + random_seconds = self._rnd.randint(start_seconds, end_seconds) + hour, rem = divmod(random_seconds, 3600) + minute, second = divmod(rem, 60) + + return time(hour=hour, minute=minute, second=second) def random_datetime(self, start_date: str = '1990-01-01', end_date: str = '2025-12-31') -> datetime: start = datetime.strptime(start_date, "%Y-%m-%d") diff --git a/xmlgenerator/validation.py b/xmlgenerator/validation.py index 26aa028..30faefb 100644 --- a/xmlgenerator/validation.py +++ b/xmlgenerator/validation.py @@ -7,25 +7,32 @@ class XmlValidator: - def __init__(self, post_validate: str, fail_fast: bool): - self.fail_fast = fail_fast + def __init__(self, post_validate: str, ignore_errors: bool): + self.ignore_errors = ignore_errors match post_validate: + case 'none': + self.validation_func = self._skip_validation case 'schema': self.validation_func = self._validate_with_schema case 'schematron': self.validation_func = self._validate_with_schematron - logger.debug("post validation: %s, fail fast: %s", post_validate, fail_fast) + case _: + raise ValueError(f"Unknown validation mode: {post_validate}") + logger.debug("post validation: %s, ignore errors: %s", post_validate, ignore_errors) def validate(self, xsd_schema, document): self.validation_func(xsd_schema, document) + def _skip_validation(self, *_): + logger.debug("validation skipped (mode 'none')") + def _validate_with_schema(self, xsd_schema, document): logger.debug("validate generated xml with xsd schema") try: xsd_schema.validate(document) except XMLSchemaValidationError as err: print(err, file=sys.stderr) - if self.fail_fast: + if not self.ignore_errors: sys.exit(1) def _validate_with_schematron(self, xsd_schema, document):