xmlgenerator 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
xmlgenerator/arguments.py CHANGED
@@ -1,9 +1,12 @@
1
+ import logging
1
2
  import sys
2
3
  from argparse import ArgumentParser, HelpFormatter
3
4
  from pathlib import Path
4
5
 
5
6
  import shtab
6
7
 
8
+ logger = logging.getLogger(__name__)
9
+
7
10
 
8
11
  class MyParser(ArgumentParser):
9
12
  def error(self, message):
@@ -99,6 +102,10 @@ def parse_args():
99
102
  parser = _get_parser()
100
103
  args = parser.parse_args()
101
104
 
105
+ # setup logger
106
+ log_level = logging.DEBUG if args.debug else logging.INFO
107
+ logger.setLevel(log_level)
108
+
102
109
  if args.config_yaml:
103
110
  config_path = Path(args.config_yaml)
104
111
  if not config_path.exists() or not config_path.is_file():
xmlgenerator/bootstrap.py CHANGED
@@ -1,39 +1,55 @@
1
+ import logging
2
+
1
3
  from lxml import etree
4
+ from xmlschema import XMLSchema
5
+
6
+ import xmlgenerator
7
+ from xmlgenerator import configuration, validation, randomization, substitution, generator
2
8
  from xmlgenerator.arguments import parse_args
3
9
  from xmlgenerator.configuration import load_config
4
10
  from xmlgenerator.generator import XmlGenerator
5
11
  from xmlgenerator.randomization import Randomizer
6
12
  from xmlgenerator.substitution import Substitutor
7
13
  from xmlgenerator.validation import XmlValidator
8
- from xmlschema import XMLSchema
9
-
10
14
 
11
15
  # TODO Generator - обработка стандартных xsd типов
12
16
  # TODO кастомные переменные для локального контекста
13
17
  # TODO валидация по Schematron
14
- # TODO debug logging
15
18
  # TODO типизировать
16
19
  # TODO Почистить и перевести комментарии
17
20
  # TODO Дописать тесты
18
- # TODO нативная сборка
19
- # TODO выкладка на github releases
20
- # TODO опубликовать https://pypi.org/
21
+
22
+ logging.basicConfig(level=logging.WARN, format='%(asctime)s [%(name)-26s] %(levelname)-6s - %(message)s')
23
+
24
+ logger = logging.getLogger('xmlgenerator.bootstrap')
21
25
 
22
26
 
23
27
  def main():
28
+ try:
29
+ _main()
30
+ except KeyboardInterrupt as ex:
31
+ logger.info('processing interrupted')
32
+
33
+
34
+ def _main():
24
35
  args, xsd_files, output_path = parse_args()
36
+ _setup_loggers(args)
25
37
 
26
- config = load_config(args.config_yaml)
38
+ if output_path:
39
+ logger.debug('specified output path: %s', output_path.absolute())
40
+ else:
41
+ logger.debug('output path is not specified. Generated xml will be written to stdout')
27
42
 
28
- print(f"Найдено схем: {len(xsd_files)}")
43
+ config = load_config(args.config_yaml)
29
44
 
30
45
  randomizer = Randomizer(args.seed)
31
46
  substitutor = Substitutor(randomizer)
32
47
  generator = XmlGenerator(randomizer, substitutor)
33
48
  validator = XmlValidator(args.validation, args.fail_fast)
34
49
 
50
+ logger.debug('found %s schemas', len(xsd_files))
35
51
  for xsd_file in xsd_files:
36
- print(f"Processing schema: {xsd_file.name}")
52
+ logger.info('processing schema: %s', xsd_file.name)
37
53
 
38
54
  # get configuration override for current schema
39
55
  local_config = config.get_for_file(xsd_file.name)
@@ -52,6 +68,7 @@ def main():
52
68
 
53
69
  # Print out to console
54
70
  if not output_path:
71
+ logger.debug('print xml document to stdout')
55
72
  print(decoded)
56
73
 
57
74
  # Validation (if enabled)
@@ -65,9 +82,20 @@ def main():
65
82
  output_file = output_path
66
83
  if output_path.is_dir():
67
84
  output_file = output_path / f'{xml_filename}.xml'
85
+ logger.debug('save xml document as %s', output_file.absolute())
68
86
  with open(output_file, 'wb') as f:
69
87
  f.write(xml_str)
70
- print(f"Saved document: {output_file.name}")
88
+
89
+
90
+ def _setup_loggers(args):
91
+ log_level = logging.DEBUG if args.debug else logging.INFO
92
+ logger.setLevel(log_level)
93
+ configuration.logger.setLevel(log_level)
94
+ validation.logger.setLevel(log_level)
95
+ xmlgenerator.generator.logger.setLevel(log_level)
96
+ substitution.logger.setLevel(log_level)
97
+ randomization.logger.setLevel(log_level)
98
+
71
99
 
72
100
  if __name__ == "__main__":
73
101
  main()
@@ -1,4 +1,5 @@
1
1
  import dataclasses
2
+ import logging
2
3
  import re
3
4
  import sys
4
5
  from dataclasses import dataclass, field, Field
@@ -6,6 +7,8 @@ from typing import Dict, get_args, get_origin, Any
6
7
 
7
8
  import yaml
8
9
 
10
+ logger = logging.getLogger(__name__)
11
+
9
12
 
10
13
  @dataclass
11
14
  class RandomizationConfig:
@@ -45,6 +48,7 @@ class Config:
45
48
  def get_for_file(self, xsd_name):
46
49
  for pattern, conf in self.specific.items():
47
50
  if re.match(pattern, xsd_name):
51
+ logger.debug("resolved configration with pattern '%s'", pattern)
48
52
  base_dict = dataclasses.asdict(self.global_)
49
53
  override_dict = dataclasses.asdict(conf, dict_factory=lambda x: {k: v for (k, v) in x if v is not None})
50
54
  updated_dict = _recursive_update(base_dict, override_dict)
@@ -52,17 +56,23 @@ class Config:
52
56
  local_override = conf.value_override
53
57
  global_override = self.global_.value_override
54
58
  merged_config.value_override = _merge_dicts(local_override, global_override)
59
+ _log_configration('using specific configuration:', merged_config)
55
60
  return merged_config
56
61
 
62
+ _log_configration('using global configration:', self.global_)
57
63
  return self.global_
58
64
 
59
65
 
60
66
  def load_config(file_path: str | None) -> "Config":
61
67
  if not file_path:
62
- return Config()
68
+ config = Config()
69
+ _log_configration("created default configuration:", config)
70
+ return config
63
71
  with open(file_path, 'r') as file:
64
72
  config_data: dict[str, str] = yaml.safe_load(file) or {}
65
- return _map_to_class(config_data, Config, "")
73
+ config = _map_to_class(config_data, Config, "")
74
+ _log_configration(f"configuration loaded from {file_path}:", config)
75
+ return config
66
76
 
67
77
 
68
78
  def _map_to_class(data_dict: dict, cls, parent_path: str):
@@ -80,7 +90,7 @@ def _map_to_class(data_dict: dict, cls, parent_path: str):
80
90
  for yaml_name, value in data_dict.items():
81
91
  class_field_name = yaml_name if yaml_name != "global" else "global_"
82
92
  if class_field_name not in class_fields:
83
- print(f"YAML parse error: unexpected property: {parent_path}.{yaml_name}", file=sys.stderr)
93
+ logger.error('YAML parse error: unexpected property: %s.%s', parent_path, yaml_name)
84
94
  sys.exit(1)
85
95
 
86
96
  # Определяем тип поля
@@ -90,10 +100,10 @@ def _map_to_class(data_dict: dict, cls, parent_path: str):
90
100
  # Проверка на отсутствие обязательных полей
91
101
  missing_fields = required_fields - yaml_items.keys()
92
102
  if missing_fields:
93
- print(f"YAML parse error: missing required properties in {parent_path}:", file=sys.stderr)
103
+ logger.error('YAML parse error: missing required properties in %s:', parent_path)
94
104
  for missing_field in missing_fields:
95
105
  yaml_field_name = missing_field if missing_field != "global_" else "global"
96
- print(yaml_field_name, file=sys.stderr)
106
+ logger.error(yaml_field_name)
97
107
  sys.exit(1)
98
108
 
99
109
  return cls(**yaml_items)
@@ -133,3 +143,12 @@ def _merge_dicts(base_dict, extra_dict):
133
143
  if key not in merged_dict:
134
144
  merged_dict[key] = value
135
145
  return merged_dict
146
+
147
+
148
+ def _log_configration(message, config):
149
+ if logger.isEnabledFor(logging.DEBUG):
150
+ logger.debug(message)
151
+ as_dict = dataclasses.asdict(config)
152
+ dumped = yaml.safe_dump(as_dict, allow_unicode=True, width=float("inf"), sort_keys=False, indent=4)
153
+ for line in dumped.splitlines():
154
+ logger.debug('|\t%s', line)
xmlgenerator/generator.py CHANGED
@@ -1,7 +1,6 @@
1
+ import logging
1
2
  import re
2
- import sys
3
3
 
4
- import rstr
5
4
  import xmlschema
6
5
  from lxml import etree
7
6
  from xmlschema.validators import XsdComplexType, XsdAtomicRestriction, XsdTotalDigitsFacet, XsdElement, \
@@ -12,6 +11,8 @@ from xmlgenerator.configuration import GeneratorConfig
12
11
  from xmlgenerator.randomization import Randomizer
13
12
  from xmlgenerator.substitution import Substitutor
14
13
 
14
+ logger = logging.getLogger(__name__)
15
+
15
16
 
16
17
  class XmlGenerator:
17
18
  def __init__(self, randomizer: Randomizer, substitutor: Substitutor):
@@ -28,21 +29,27 @@ class XmlGenerator:
28
29
  rnd = self.randomizer.rnd
29
30
 
30
31
  xsd_element_type = getattr(xsd_element, 'type', None)
32
+ logger.debug('fill down element "%s" with type %s', xsd_element.name, type(xsd_element_type).__name__)
31
33
 
32
34
  # Add attributes if they are
33
35
  attributes = getattr(xsd_element, 'attributes', dict())
34
36
  if len(attributes) > 0 and xsd_element_type.local_name != 'anyType':
37
+ logger.debug('add attributes to element %s', xsd_element.name)
35
38
  for attr_name, attr in attributes.items():
39
+ logger.debug('attribute: %s', attr_name)
36
40
  use = attr.use # optional | required | prohibited
37
41
  if use == 'prohibited':
42
+ logger.debug('skipped')
38
43
  continue
39
44
  elif use == 'optional':
40
45
  if rnd.random() > local_config.randomization.probability:
41
- continue # skip optional attribute
46
+ logger.debug('skipped')
47
+ continue # skip optional attribute
42
48
 
43
49
  attr_value = self._generate_value(attr.type, attr_name, local_config)
44
50
  if attr_value is not None:
45
51
  xml_element.set(attr_name, str(attr_value))
52
+ logger.debug(f'attribute %s set with value %s', attr_name, attr_value)
46
53
 
47
54
  # Process child elements --------------------------------------------------------------------------------------
48
55
  if isinstance(xsd_element, XsdElement):
@@ -69,7 +76,7 @@ class XmlGenerator:
69
76
  group_min_occurs = getattr(xsd_element, 'min_occurs', None)
70
77
  group_max_occurs = getattr(xsd_element, 'max_occurs', None)
71
78
  group_min_occurs = group_min_occurs if group_min_occurs is not None else 0
72
- group_max_occurs = group_max_occurs if group_max_occurs is not None else 10 # TODO externalize
79
+ group_max_occurs = group_max_occurs if group_max_occurs is not None else 10 # TODO externalize
73
80
  group_occurs = rnd.randint(group_min_occurs, group_max_occurs)
74
81
 
75
82
  if model == 'all':
@@ -80,7 +87,7 @@ class XmlGenerator:
80
87
  element_min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
81
88
  element_max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
82
89
  element_min_occurs = element_min_occurs if element_min_occurs is not None else 0
83
- element_max_occurs = element_max_occurs if element_max_occurs is not None else 10 # TODO externalize
90
+ element_max_occurs = element_max_occurs if element_max_occurs is not None else 10 # TODO externalize
84
91
  element_occurs = rnd.randint(element_min_occurs, element_max_occurs)
85
92
 
86
93
  for _ in range(element_occurs):
@@ -96,7 +103,7 @@ class XmlGenerator:
96
103
  element_min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
97
104
  element_max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
98
105
  element_min_occurs = element_min_occurs if element_min_occurs is not None else 0
99
- element_max_occurs = element_max_occurs if element_max_occurs is not None else 10 # TODO externalize
106
+ element_max_occurs = element_max_occurs if element_max_occurs is not None else 10 # TODO externalize
100
107
  element_occurs = rnd.randint(element_min_occurs, element_max_occurs)
101
108
 
102
109
  if isinstance(xsd_child_element_type, XsdElement):
@@ -123,7 +130,7 @@ class XmlGenerator:
123
130
  element_min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
124
131
  element_max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
125
132
  element_min_occurs = element_min_occurs if element_min_occurs is not None else 0
126
- element_max_occurs = element_max_occurs if element_max_occurs is not None else 10 # TODO externalize
133
+ element_max_occurs = element_max_occurs if element_max_occurs is not None else 10 # TODO externalize
127
134
  element_occurs = rnd.randint(element_min_occurs, element_max_occurs)
128
135
 
129
136
  for _ in range(element_occurs):
@@ -232,7 +239,6 @@ class XmlGenerator:
232
239
 
233
240
  raise RuntimeError(f"Can't generate value - unhandled type. Target name: {target_name}")
234
241
 
235
-
236
242
  def _generate_value_by_type(self, xsd_type, target_name, patterns, min_length, max_length, min_value, max_value,
237
243
  total_digits, fraction_digits) -> str | None:
238
244
 
@@ -289,17 +295,22 @@ class XmlGenerator:
289
295
 
290
296
  def _generate_string(self, target_name, patterns, min_length, max_length):
291
297
  rnd = self.randomizer.rnd
298
+ re_gen = self.randomizer.re_gen
292
299
  if patterns is not None:
293
300
  # Генерация строки по regex
294
301
  random_pattern = rnd.choice(patterns)
295
- xeger = rstr.xeger(random_pattern.attrib['value'])
302
+ xeger = re_gen.xeger(random_pattern.attrib['value'])
296
303
  xeger = re.sub(r'\s', ' ', xeger)
297
304
  if min_length > -1 and len(xeger) < min_length:
298
- print(
299
- f"Possible mistake in schema: {target_name} generated value '{xeger}' can't be shorter than {min_length}",
300
- file=sys.stderr)
305
+ logger.warning(
306
+ "Possible mistake in schema: %s generated value '%s' can't be shorter than %s",
307
+ target_name, xeger, min_length
308
+ )
301
309
  if -1 < max_length < len(xeger):
302
- print(f"Possible mistake in schema: {target_name} generated value '{xeger}' can't be longer than {max_length}", file=sys.stderr)
310
+ logger.warning(
311
+ "Possible mistake in schema: %s generated value '%s' can't be longer than %s",
312
+ target_name, xeger, max_length
313
+ )
303
314
  return xeger
304
315
 
305
316
  # Иначе генерируем случайную строку
@@ -347,29 +358,43 @@ class XmlGenerator:
347
358
  raise RuntimeError("not yet implemented")
348
359
 
349
360
  def _generate_datetime(self):
350
- raise RuntimeError("not yet implemented")
361
+ random_datetime = self.randomizer.random_datetime()
362
+ formatted = random_datetime.isoformat()
363
+ return formatted
351
364
 
352
365
  def _generate_date(self):
353
- raise RuntimeError("not yet implemented")
366
+ random_date = self.randomizer.random_date()
367
+ formatted = random_date.isoformat()
368
+ return formatted
354
369
 
355
370
  def _generate_time(self):
356
- raise RuntimeError("not yet implemented")
371
+ random_time = self.randomizer.random_time()
372
+ formatted = random_time.isoformat()
373
+ return formatted
357
374
 
358
375
  def _generate_gyearmonth(self):
359
- raise RuntimeError("not yet implemented")
376
+ random_date = self.randomizer.random_date()
377
+ formatted = random_date.strftime('%Y-%m')
378
+ return formatted
360
379
 
361
380
  def _generate_gyear(self):
362
381
  rnd = self.randomizer.rnd
363
- return rnd.randint(2000, 2050)
382
+ return str(rnd.randint(2000, 2050))
364
383
 
365
384
  def _generate_gmonthday(self):
366
- raise RuntimeError("not yet implemented")
385
+ random_date = self.randomizer.random_date()
386
+ formatted = random_date.strftime('--%m-%d')
387
+ return formatted
367
388
 
368
389
  def _generate_gday(self):
369
- raise RuntimeError("not yet implemented")
390
+ random_date = self.randomizer.random_date()
391
+ formatted = random_date.strftime('---%d')
392
+ return formatted
370
393
 
371
394
  def _generate_gmonth(self):
372
- raise RuntimeError("not yet implemented")
395
+ random_date = self.randomizer.random_date()
396
+ formatted = random_date.strftime('--%m--')
397
+ return formatted
373
398
 
374
399
  def _generate_hex_binary(self):
375
400
  raise RuntimeError("not yet implemented")
@@ -1,15 +1,27 @@
1
+ import logging
1
2
  import random
2
3
  import string
3
- from datetime import datetime, timedelta
4
+ import sys
5
+ from datetime import datetime, date, time, timedelta
4
6
 
7
+ import rstr
5
8
  from faker import Faker
6
9
 
10
+ logger = logging.getLogger(__name__)
11
+
7
12
 
8
13
  class Randomizer:
9
14
  def __init__(self, seed=None):
15
+ if not seed:
16
+ seed = random.randrange(sys.maxsize)
17
+ logger.debug('initialize with random seed: %s', seed)
18
+ else:
19
+ logger.debug('initialize with provided seed: %s', seed)
20
+
10
21
  self.rnd = random.Random(seed)
11
22
  self.fake = Faker(locale='ru_RU')
12
23
  self.fake.seed_instance(seed)
24
+ self.re_gen = rstr.Rstr(self.rnd)
13
25
 
14
26
  def ascii_string(self, min_length=-1, max_length=-1):
15
27
  min_length = min_length if min_length and min_length > -1 else 1
@@ -21,7 +33,31 @@ class Randomizer:
21
33
  letters = string.ascii_letters # Все буквы латиницы (a-z, A-Z)
22
34
  return ''.join(self.rnd.choice(letters) for _ in range(length))
23
35
 
24
- def random_date(self, start_date: str, end_date: str) -> datetime:
36
+ def random_date(self, start_date: str = '1990-01-01', end_date: str = '2025-12-31') -> date:
37
+ # Преобразуем строки в объекты datetime
38
+ start = date.fromisoformat(start_date)
39
+ end = date.fromisoformat(end_date)
40
+
41
+ # Вычисляем разницу в днях между начальной и конечной датой
42
+ delta = (end - start).days
43
+
44
+ # Генерируем случайное количество дней в пределах delta
45
+ random_days = self.rnd.randint(0, delta)
46
+
47
+ # Добавляем случайное количество дней к начальной дате
48
+ return start + timedelta(days=random_days)
49
+
50
+ def random_time(self, start_time: str = '00:00:00', end_time: str = '23:59:59') -> time:
51
+ start = time.fromisoformat(start_time)
52
+ end = time.fromisoformat(end_time)
53
+
54
+ random_h = self.rnd.randint(start.hour, end.hour)
55
+ random_m = self.rnd.randint(start.minute, end.minute)
56
+ random_s = self.rnd.randint(start.second, end.second)
57
+
58
+ return time(hour=random_h, minute=random_m, second=random_s)
59
+
60
+ def random_datetime(self, start_date: str = '1990-01-01', end_date: str = '2025-12-31') -> datetime:
25
61
  # Преобразуем строки в объекты datetime
26
62
  start = datetime.strptime(start_date, "%Y-%m-%d")
27
63
  end = datetime.strptime(end_date, "%Y-%m-%d")
@@ -1,13 +1,15 @@
1
+ import logging
1
2
  import re
2
- import uuid
3
-
4
- import rstr
5
3
 
6
4
  from xmlgenerator.randomization import Randomizer
7
5
 
8
6
  __all__ = ['Substitutor']
9
7
 
10
- _pattern = re.compile(pattern=r'\{\{\s*(?:(?P<function>\S*?)(?:\(\s*(?P<argument>[^)]*)\s*\))?\s*(?:\|\s*(?P<modifier>.*?))?)?\s*}}')
8
+ _pattern = re.compile(
9
+ r'\{\{\s*(?:(?P<function>\S*?)(?:\(\s*(?P<argument>[^)]*)\s*\))?\s*(?:\|\s*(?P<modifier>.*?))?)?\s*}}')
10
+
11
+ logger = logging.getLogger(__name__)
12
+
11
13
 
12
14
  class Substitutor:
13
15
  def __init__(self, randomizer: Randomizer):
@@ -17,18 +19,19 @@ class Substitutor:
17
19
  self._global_context = {}
18
20
  self.providers_dict = {
19
21
  # Функции локального контекста
20
- "source_filename": lambda: self._local_context["source_filename"],
21
- "source_extracted": lambda: self._local_context["source_extracted"],
22
- "output_filename": lambda: self.get_output_filename(),
23
-
24
- 'uuid': lambda: str(uuid.uuid4()),
25
- "regex": lambda a: rstr.xeger(a),
26
- "number": self._rand_int,
27
- "date": self._rand_date,
28
-
29
- "last_name": fake.last_name_male,
30
- "first_name": fake.first_name_male,
31
- "middle_name": fake.middle_name_male,
22
+ 'source_filename': lambda: self._local_context["source_filename"],
23
+ 'source_extracted': lambda: self._local_context["source_extracted"],
24
+ 'output_filename': lambda: self.get_output_filename(),
25
+
26
+ 'uuid': lambda: fake.uuid4(),
27
+ 'regex': self._rand_regex,
28
+ 'any': self._rand_any,
29
+ 'number': self._rand_int,
30
+ 'date': self._rand_date,
31
+
32
+ 'last_name': fake.last_name_male,
33
+ 'first_name': fake.first_name_male,
34
+ 'middle_name': fake.middle_name_male,
32
35
  'address_text': fake.address,
33
36
  'administrative_unit': fake.administrative_unit,
34
37
  'house_number': fake.building_number,
@@ -45,6 +48,16 @@ class Substitutor:
45
48
  'snils_formatted': randomizer.snils_formatted,
46
49
  }
47
50
 
51
+ def _rand_regex(self, a):
52
+ pattern = a.strip("'").strip('"')
53
+ return self.randomizer.re_gen.xeger(pattern)
54
+
55
+ def _rand_any(self, a):
56
+ args = str(a).split(sep=",")
57
+ value = self.randomizer.rnd.choice(args)
58
+ value = value.strip(' ').strip("'").strip('"')
59
+ return value
60
+
48
61
  def _rand_int(self, a):
49
62
  args = str(a).split(sep=",")
50
63
  return str(self.randomizer.rnd.randint(int(args[0]), int(args[1])))
@@ -53,8 +66,8 @@ class Substitutor:
53
66
  args = str(a).split(sep=",")
54
67
  date_from = args[0].strip(' ').strip("'").strip('"')
55
68
  date_until = args[1].strip(' ').strip("'").strip('"')
56
- random_date = self.randomizer.random_date(date_from, date_until)
57
- return random_date.strftime('%Y%m%d') # TODO externalize pattern
69
+ random_date = self.randomizer.random_datetime(date_from, date_until)
70
+ return random_date.strftime('%Y%m%d') # TODO externalize pattern
58
71
 
59
72
  def reset_context(self, xsd_filename, config_local):
60
73
  self._local_context.clear()
@@ -69,6 +82,11 @@ class Substitutor:
69
82
  resolved_value = self._process_expression(output_filename)
70
83
  self._local_context['output_filename'] = resolved_value
71
84
 
85
+ logger.debug('local_context reset')
86
+ logger.debug('local_context["source_filename"] = %s', xsd_filename)
87
+ logger.debug('local_context["source_extracted"] = %s (extracted with regexp %s)', source_extracted, source_filename)
88
+ logger.debug('local_context["output_filename"] = %s', resolved_value)
89
+
72
90
  def get_output_filename(self):
73
91
  return self._local_context.get("output_filename")
74
92
 
@@ -83,6 +101,7 @@ class Substitutor:
83
101
  return False, None
84
102
 
85
103
  def _process_expression(self, expression):
104
+ logger.debug('processing expression: %s', expression)
86
105
  global_context = self._global_context
87
106
  local_context = self._local_context
88
107
  result_value: str = expression
@@ -115,4 +134,5 @@ class Substitutor:
115
134
  for span, replacement in reversed(list(span_to_replacement.items())):
116
135
  result_value = result_value[:span[0]] + replacement + result_value[span[1]:]
117
136
 
137
+ logger.debug('expression resolved to value: %s', result_value)
118
138
  return result_value
@@ -1,7 +1,10 @@
1
+ import logging
1
2
  import sys
2
3
 
3
4
  from xmlschema import XMLSchemaValidationError
4
5
 
6
+ logger = logging.getLogger(__name__)
7
+
5
8
 
6
9
  class XmlValidator:
7
10
  def __init__(self, post_validate: str, fail_fast: bool):
@@ -11,11 +14,13 @@ class XmlValidator:
11
14
  self.validation_func = self._validate_with_schema
12
15
  case 'schematron':
13
16
  self.validation_func = self._validate_with_schematron
17
+ logger.debug("post validation: %s, fail fast: %s", post_validate, fail_fast)
14
18
 
15
19
  def validate(self, xsd_schema, document):
16
20
  self.validation_func(xsd_schema, document)
17
21
 
18
22
  def _validate_with_schema(self, xsd_schema, document):
23
+ logger.debug("validate generated xml with xsd schema")
19
24
  try:
20
25
  xsd_schema.validate(document)
21
26
  except XMLSchemaValidationError as err:
@@ -24,6 +29,7 @@ class XmlValidator:
24
29
  sys.exit(1)
25
30
 
26
31
  def _validate_with_schematron(self, xsd_schema, document):
32
+ logger.debug("validate generated xml with xsd schematron")
27
33
  raise RuntimeError("not yet implemented")
28
34
 
29
35
  # TODO
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xmlgenerator
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: Generates XML documents from XSD schemas
5
5
  Home-page: https://github.com/lexakimov/xmlgenerator
6
6
  Author: Alexey Akimov
@@ -247,6 +247,7 @@ In the `value_override` sections, you can specify either a string value or speci
247
247
  | `output_filename` | String described by the `output_filename_template` configuration parameter |
248
248
  | `uuid` | Random UUIDv4 |
249
249
  | `regex("pattern")` | Random string value matching the specified regular expression |
250
+ | `any('A', "B", C)` | Random value from enumeration |
250
251
  | `number(A, B)` | Random number between A and B |
251
252
  | `date("2010-01-01", "2025-01-01")` | Random date within the specified range |
252
253
  | `last_name` | Last Name |
@@ -0,0 +1,14 @@
1
+ xmlgenerator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ xmlgenerator/arguments.py,sha256=E0b5ndlGAxu39OgRatqmPdWSkS5EvCTLUEMKFgezi7c,4612
3
+ xmlgenerator/bootstrap.py,sha256=bPVHugpMSkv54_8Kxe0RPqyvIxC3cci-1yehntZm_YY,3510
4
+ xmlgenerator/configuration.py,sha256=DiYUpNCjkerjr73yD209q8FJwGWgipbwWXuLkt25rQM,5903
5
+ xmlgenerator/generator.py,sha256=Ci3xuOoG2jpTcoRa2c1BEDI_4hIfEd3iU05tcgB0paM,18877
6
+ xmlgenerator/randomization.py,sha256=LK4ZwSZdyks7qV0OOxKsGyKA0q660ID7x2DeU9gvL_I,3261
7
+ xmlgenerator/substitution.py,sha256=mD827EbgYg2mTp-PNcvsERJtVUKaLvaoOoQmGnq5zBo,5595
8
+ xmlgenerator/validation.py,sha256=uCJjS5YmRDlAp9C-5Rd4E2Brh6_3WOG2-dSGxDiaH14,2023
9
+ xmlgenerator-0.2.1.dist-info/licenses/LICENSE,sha256=QlXK8O3UcoAYUYwVJNgB9MSM7O94ogNo_1hd9GzznUQ,1070
10
+ xmlgenerator-0.2.1.dist-info/METADATA,sha256=zfA1y1vFm58sX2tRolF6G8LngaBGViSPp8C2Xp7_PvY,12801
11
+ xmlgenerator-0.2.1.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
12
+ xmlgenerator-0.2.1.dist-info/entry_points.txt,sha256=ly9hKr3o4AzFUkelBZNRzyKYf-Ld4kfcffvBu1oHq54,61
13
+ xmlgenerator-0.2.1.dist-info/top_level.txt,sha256=jr7FbMBm8MQ6j8I_-nWzQQEseXzwSCZNXgrkWuk9P4E,13
14
+ xmlgenerator-0.2.1.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- xmlgenerator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- xmlgenerator/arguments.py,sha256=0hbWgXOTj5GhXL3wHms8kHk5QAEzzN-1fBAc-lKAkBk,4446
3
- xmlgenerator/bootstrap.py,sha256=X5bN3jn6iWBtigzeGGgSzbVG0MGrHzdFrNOEf3MhHuQ,2598
4
- xmlgenerator/configuration.py,sha256=z3qbIL_2Lafp2bGTVWYPAAXzp-FuU_whQ7cz0RmbBKk,5101
5
- xmlgenerator/generator.py,sha256=QAKI36zqIYMjlrphKA8LlC0WWnF8M_6G-ZPWeZn31W4,17768
6
- xmlgenerator/randomization.py,sha256=_-PoMwSxAxI18x0_arQz0QkyZNUHtH8MB1tlgI3iOAE,1710
7
- xmlgenerator/substitution.py,sha256=Al4XFXdx8RI6V7nYDjfc2JEYFPbfmw5Mwet4sMCWdCU,4765
8
- xmlgenerator/validation.py,sha256=Q6sbIPqCargQ0YOJPuvcNdIohcR4yo8tJBLbuceQaag,1755
9
- xmlgenerator-0.1.0.dist-info/licenses/LICENSE,sha256=QlXK8O3UcoAYUYwVJNgB9MSM7O94ogNo_1hd9GzznUQ,1070
10
- xmlgenerator-0.1.0.dist-info/METADATA,sha256=6PqKFVwvR3NAOQUEQ8YRk1NN5n5ltHkxgIZp2RMRr8M,12653
11
- xmlgenerator-0.1.0.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
12
- xmlgenerator-0.1.0.dist-info/entry_points.txt,sha256=ly9hKr3o4AzFUkelBZNRzyKYf-Ld4kfcffvBu1oHq54,61
13
- xmlgenerator-0.1.0.dist-info/top_level.txt,sha256=jr7FbMBm8MQ6j8I_-nWzQQEseXzwSCZNXgrkWuk9P4E,13
14
- xmlgenerator-0.1.0.dist-info/RECORD,,