Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
/xmlgenerator.egg-info/
/_schemas/
/dist_native/
/.roo/
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
```
Expand Down Expand Up @@ -64,7 +64,7 @@ options:
-p, --pretty prettify the output XML
-n, --namespace alias=namespace define XML namespace alias (repeatable flag)
-v, --validation <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 <encoding> the output XML encoding (utf-8, windows-1251; default: utf-8)
-s, --seed <seed> set the randomization seed
-d, --debug enable debug mode
Expand Down Expand Up @@ -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`.

Expand Down
12 changes: 6 additions & 6 deletions README_RU.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
```
Expand Down Expand Up @@ -64,7 +64,7 @@ options:
-p, --pretty prettify the output XML
-n, --namespace alias=namespace define XML namespace alias (repeatable flag)
-v, --validation <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 <encoding> the output XML encoding (utf-8, windows-1251; default: utf-8)
-s, --seed <seed> set the randomization seed
-d, --debug enable debug mode
Expand Down Expand Up @@ -105,8 +105,8 @@ options:
Сгенерированные XML-документы проверяются на соответствие схеме, использованной для генерации.
По умолчанию используется валидация через исходную XSD-схему.

При несоответствии документа схеме, выполнение прекращается незамедлительно.
Это поведение можно отключить через флаг `-ff false` или `--fail-fast false`.
При несоответствии документа схеме выполнение прекращается незамедлительно.
Чтобы продолжить обработку несмотря на ошибки валидации, используйте флаг `-i`.

Чтобы отключить валидацию, укажите флаг `-v none` или `--validation none`.

Expand Down
4 changes: 0 additions & 4 deletions build_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@

from xmlgenerator import __version__

# TODO strip
# pyinstaller ?
# sudo pacman -S ccache

# --- Конфигурация сборки Nuitka ---

# Имя основного скрипта вашего приложения
Expand Down
2 changes: 1 addition & 1 deletion config_fns.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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') }}"
Expand Down
23 changes: 23 additions & 0 deletions config_mvd.yml
Original file line number Diff line number Diff line change
@@ -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 }}"
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
70 changes: 69 additions & 1 deletion tests/unit/test_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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:

Expand All @@ -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

Expand All @@ -124,14 +156,43 @@ 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()
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_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:

Expand All @@ -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]
Expand All @@ -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:
Expand Down
1 change: 0 additions & 1 deletion tests/unit/test_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ def config():


def log_xml(generated_xml):
"""Выводит сгенерированный XML в консоль для отладки."""
print(etree.tostring(generated_xml, pretty_print=True).decode('utf-8'))


Expand Down
10 changes: 8 additions & 2 deletions tests/unit/test_randomization.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from datetime import time

import pytest

Expand All @@ -24,17 +25,22 @@ 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)
for _ in range(5):
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')
45 changes: 45 additions & 0 deletions tests/unit/test_validation.py
Original file line number Diff line number Diff line change
@@ -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, "<xml/>")


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, "<xml/>")


def test_validator_skips_validation_when_disabled():
validator = XmlValidator('none', ignore_errors=False)
schema = DummySchema(Exception("should not raise"))

validator.validate(schema, "<xml/>")
2 changes: 1 addition & 1 deletion xmlgenerator/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.7.0"
__version__ = "0.8.0"
Loading