xmlgenerator 0.4.0__tar.gz → 0.5.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xmlgenerator
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Generates XML documents from XSD schemas
5
5
  Home-page: https://github.com/lexakimov/xmlgenerator
6
6
  Author: Alexey Akimov
@@ -270,6 +270,7 @@ In the `value_override` sections, you can specify either a string value or speci
270
270
  | `ogrn_fl` | Primary State Registration Number (Physical Person) |
271
271
  | `kpp` | Reason Code for Registration |
272
272
  | `snils_formatted` | SNILS (Personal Insurance Account Number) in the format `123-456-789 90` |
273
+ | `email` | Random email address |
273
274
 
274
275
  **Configuration Examples:**
275
276
 
@@ -241,6 +241,7 @@ In the `value_override` sections, you can specify either a string value or speci
241
241
  | `ogrn_fl` | Primary State Registration Number (Physical Person) |
242
242
  | `kpp` | Reason Code for Registration |
243
243
  | `snils_formatted` | SNILS (Personal Insurance Account Number) in the format `123-456-789 90` |
244
+ | `email` | Random email address |
244
245
 
245
246
  **Configuration Examples:**
246
247
 
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='xmlgenerator',
5
- version='0.4.0',
5
+ version='0.5.0',
6
6
  packages=find_packages(exclude=("tests", "tests.*")),
7
7
  entry_points={
8
8
  'console_scripts': [
@@ -33,7 +33,7 @@ def _get_parser():
33
33
  dest="source_paths",
34
34
  help="paths to xsd schema(s) or directory with xsd schemas"
35
35
  )
36
- parser.add_argument(
36
+ config_arg = parser.add_argument(
37
37
  "-c", "--config",
38
38
  metavar="<config.yml>",
39
39
  dest="config_yaml",
@@ -94,6 +94,7 @@ def _get_parser():
94
94
  )
95
95
 
96
96
  # add shell completions
97
+ config_arg.complete = shtab.FILE
97
98
  source_arg.complete = shtab.FILE
98
99
  output_arg.complete = shtab.FILE
99
100
  shtab.add_argument_to(parser, ["-C", "--completion"], "print shell completion script (bash, zsh, tcsh)")
@@ -1,5 +1,7 @@
1
1
  import logging
2
+ from dataclasses import dataclass, replace
2
3
  from decimal import Decimal
4
+ from typing import Optional, Any, Callable, Dict
3
5
 
4
6
  from lxml import etree
5
7
  from xmlschema.validators import XsdComplexType, XsdAtomicRestriction, XsdTotalDigitsFacet, XsdElement, \
@@ -13,10 +15,75 @@ from xmlgenerator.substitution import Substitutor
13
15
  logger = logging.getLogger(__name__)
14
16
 
15
17
 
18
+ @dataclass
19
+ class TypeConstraints:
20
+ min_length: Optional[int] = None
21
+ max_length: Optional[int] = None
22
+ min_value: Optional[Any] = None
23
+ max_value: Optional[Any] = None
24
+ total_digits: Optional[int] = None
25
+ fraction_digits: Optional[int] = None
26
+ patterns: Optional[list] = None
27
+ rand_config: Optional[Any] = None
28
+
29
+
16
30
  class XmlGenerator:
17
31
  def __init__(self, randomizer: Randomizer, substitutor: Substitutor):
18
32
  self.randomizer = randomizer
19
33
  self.substitutor = substitutor
34
+ self.generators: Dict[str, Callable[[TypeConstraints], str]] = {
35
+ # primitive
36
+ 'boolean': self._generate_boolean,
37
+ 'string': self._generate_string,
38
+ 'decimal': self._generate_decimal,
39
+ 'float': self._generate_float,
40
+ 'double': self._generate_double,
41
+ 'duration': self._generate_duration,
42
+ 'dateTime': self._generate_datetime,
43
+ 'date': self._generate_date,
44
+ 'time': self._generate_time,
45
+ 'gYearMonth': self._generate_gyearmonth,
46
+ 'gYear': self._generate_gyear,
47
+ 'gMonthDay': self._generate_gmonthday,
48
+ 'gDay': self._generate_gday,
49
+ 'gMonth': self._generate_gmonth,
50
+ 'hexBinary': self._generate_hex_binary,
51
+ 'base64Binary': self._generate_base64_binary,
52
+ 'anyURI': self._generate_any_uri,
53
+ 'QName': self._generate_qname,
54
+ 'NOTATION': self._generate_notation,
55
+
56
+ # derived - from decimal
57
+ 'byte': self._generate_byte,
58
+ 'short': self._generate_short,
59
+ 'int': self._generate_int,
60
+ 'integer': self._generate_integer,
61
+ 'long': self._generate_long,
62
+
63
+ 'unsignedByte': self._generate_unsigned_byte,
64
+ 'unsignedShort': self._generate_unsigned_short,
65
+ 'unsignedInt': self._generate_unsigned_int,
66
+ 'unsignedLong': self._generate_unsigned_long,
67
+
68
+ 'positiveInteger': self._generate_positive_integer,
69
+ 'negativeInteger': self._generate_negative_integer,
70
+ 'nonPositiveInteger': self._generate_non_positive_integer,
71
+ 'nonNegativeInteger': self._generate_non_negative_integer,
72
+
73
+ # derived - from string
74
+ 'language': lambda c: (_ for _ in ()).throw(Exception('not yet implemented')),
75
+ 'Name': lambda c: (_ for _ in ()).throw(Exception('not yet implemented')),
76
+ 'NCName': lambda c: (_ for _ in ()).throw(Exception('not yet implemented')),
77
+ 'normalizedString': lambda c: (_ for _ in ()).throw(Exception('not yet implemented')),
78
+ 'token': lambda c: (_ for _ in ()).throw(Exception('not yet implemented')),
79
+ 'ID': lambda c: (_ for _ in ()).throw(Exception('not yet implemented')),
80
+ 'IDREF': lambda c: (_ for _ in ()).throw(Exception('not yet implemented')),
81
+ 'IDREFS': lambda c: (_ for _ in ()).throw(Exception('not yet implemented')),
82
+ 'ENTITY': lambda c: (_ for _ in ()).throw(Exception('not yet implemented')),
83
+ 'ENTITIES': lambda c: (_ for _ in ()).throw(Exception('not yet implemented')),
84
+ 'NMTOKEN': lambda c: (_ for _ in ()).throw(Exception('not yet implemented')),
85
+ 'NMTOKENS': lambda c: (_ for _ in ()).throw(Exception('not yet implemented')),
86
+ }
20
87
 
21
88
  def generate_xml(self, xsd_schema, local_config: GeneratorConfig) -> etree.Element:
22
89
  logger.debug('generate xml document...')
@@ -211,108 +278,14 @@ class XmlGenerator:
211
278
  # -------------------------------------------------------------------------------------------------------------
212
279
  # Генерируем значения для стандартных типов и типов с ограничениями
213
280
  if isinstance(xsd_type, XsdAtomicBuiltin) or isinstance(xsd_type, XsdAtomicRestriction):
214
- # Выясняем ограничения
215
- min_length = getattr(xsd_type, 'min_length', None) # None | int
216
- max_length = getattr(xsd_type, 'max_length', None) # None | int
217
-
218
- min_value = getattr(xsd_type, 'min_value', None) # None | int
219
- max_value = getattr(xsd_type, 'max_value', None) # None
220
-
221
- total_digits = None
222
- fraction_digits = None
223
- patterns = getattr(xsd_type, 'patterns', None)
224
-
225
- validators = getattr(xsd_type, 'validators', None)
226
- for validator in validators:
227
- if isinstance(validator, XsdMinExclusiveFacet):
228
- min_value = validator.value
229
- elif isinstance(validator, XsdMinInclusiveFacet):
230
- min_value = validator.value
231
- elif isinstance(validator, XsdMaxExclusiveFacet):
232
- max_value = validator.value
233
- elif isinstance(validator, XsdMaxInclusiveFacet):
234
- max_value = validator.value
235
- elif isinstance(validator, XsdLengthFacet):
236
- min_length = validator.value
237
- max_length = validator.value
238
- elif isinstance(validator, XsdMinLengthFacet):
239
- min_length = validator.value
240
- elif isinstance(validator, XsdMaxLengthFacet):
241
- max_length = validator.value
242
- elif isinstance(validator, XsdTotalDigitsFacet):
243
- total_digits = validator.value
244
- elif isinstance(validator, XsdFractionDigitsFacet):
245
- fraction_digits = validator.value
246
- elif isinstance(validator, XsdEnumerationFacets):
247
- pass
248
- elif callable(validator):
249
- pass
250
- else:
251
- raise RuntimeError(f"Unhandled validator: {validator}")
252
-
253
- if isinstance(min_value, Decimal):
254
- min_value = float(min_value)
255
- if isinstance(max_value, Decimal):
256
- max_value = float(max_value)
257
-
258
- rand_config = local_config.randomization
259
-
260
- logger.debug('bounds before adjust: min_length: %4s; max_length: %4s', min_length, max_length)
261
- min_length, max_length = merge_constraints(
262
- schema_min=min_length,
263
- schema_max=max_length,
264
- config_min=rand_config.min_length,
265
- config_max=rand_config.max_length
266
- )
267
- logger.debug('bounds after adjust: min_length: %4s; max_length: %4s', min_length, max_length)
268
-
281
+ constraints = extract_type_constraints(xsd_type, local_config)
269
282
  type_id = xsd_type.id or xsd_type.base_type.id or xsd_type.root_type.id
270
283
  logger.debug('generate value for type: "%s"', type_id)
284
+ generator = self.generators.get(type_id)
285
+ if generator is None:
286
+ raise RuntimeError(f"Generator not found for type: {type_id}")
287
+ generated_value = generator(constraints)
271
288
 
272
- match type_id:
273
- case 'boolean':
274
- result = self._generate_boolean()
275
- case 'string':
276
- result = self._generate_string(min_length, max_length, patterns)
277
- case 'integer':
278
- result = self._generate_integer(min_value, max_value, total_digits)
279
- case 'decimal':
280
- result = self._generate_decimal(rand_config, min_value, max_value, total_digits, fraction_digits)
281
- case 'float':
282
- result = self._generate_float(rand_config, min_value, max_value)
283
- case 'double':
284
- result = self._generate_double(rand_config, min_value, max_value)
285
- case 'duration':
286
- result = self._generate_duration()
287
- case 'dateTime':
288
- result = self._generate_datetime()
289
- case 'date':
290
- result = self._generate_date()
291
- case 'time':
292
- result = self._generate_time()
293
- case 'gYearMonth':
294
- result = self._generate_gyearmonth()
295
- case 'gYear':
296
- result = self._generate_gyear()
297
- case 'gMonthDay':
298
- result = self._generate_gmonthday()
299
- case 'gDay':
300
- result = self._generate_gday()
301
- case 'gMonth':
302
- result = self._generate_gmonth()
303
- case 'hexBinary':
304
- result = self._generate_hex_binary()
305
- case 'base64Binary':
306
- result = self._generate_base64_binary()
307
- case 'anyURI':
308
- result = self._generate_any_uri()
309
- case 'QName':
310
- result = self._generate_qname()
311
- case 'NOTATION':
312
- result = self._generate_notation()
313
- case _:
314
- raise RuntimeError(type_id)
315
- generated_value = result
316
289
  logger.debug('value generated: "%s"', generated_value)
317
290
  return generated_value
318
291
 
@@ -326,27 +299,27 @@ class XmlGenerator:
326
299
 
327
300
  raise RuntimeError(f"Can't generate value - unhandled type. Target name: {target_name}")
328
301
 
329
- def _generate_boolean(self):
302
+ # noinspection PyUnusedLocal
303
+ def _generate_boolean(self, constraints: TypeConstraints):
330
304
  return self.randomizer.any(['true', 'false'])
331
305
 
332
- def _generate_string(self, min_length, max_length, patterns):
333
- if patterns is not None:
306
+ def _generate_string(self, constraints: TypeConstraints):
307
+ if constraints.patterns is not None:
334
308
  # Генерация строки по regex
335
- random_enum = self.randomizer.any(patterns)
309
+ random_enum = self.randomizer.any(constraints.patterns)
336
310
  random_pattern = random_enum.attrib['value']
337
311
  return self.randomizer.regex(random_pattern)
338
312
 
339
313
  # Иначе генерируем случайную строку
340
- return self.randomizer.ascii_string(min_length, max_length)
314
+ return self.randomizer.ascii_string(constraints.min_length, constraints.max_length)
341
315
 
342
- def _generate_integer(self, min_value, max_value, total_digits):
343
- if total_digits:
344
- min_value = 10 ** (total_digits - 1)
345
- max_value = (10 ** total_digits) - 1
346
- rnd_int = self.randomizer.integer(min_value, max_value)
347
- return str(rnd_int)
316
+ def _generate_decimal(self, constraints: TypeConstraints):
317
+ rand_config = constraints.rand_config
318
+ min_value = constraints.min_value
319
+ max_value = constraints.max_value
320
+ total_digits = constraints.total_digits
321
+ fraction_digits = constraints.fraction_digits
348
322
 
349
- def _generate_decimal(self, rand_config, schema_min, schema_max, total_digits, fraction_digits):
350
323
  if fraction_digits is None:
351
324
  fraction_digits = self.randomizer.integer(1, 3)
352
325
 
@@ -367,81 +340,224 @@ class XmlGenerator:
367
340
  digit_max = 10 ** integer_digits - 1
368
341
  logger.debug("integer digits: %s; digit_min: %s; digit_max: %s", integer_digits, digit_min, digit_max)
369
342
 
370
- logger.debug('bounds before adjust: min_value: %4s; max_value: %4s', schema_min, schema_max)
343
+ logger.debug('bounds before adjust: min_value: %4s; max_value: %4s', min_value, max_value)
371
344
  config_min = rand_config.min_inclusive
372
345
  config_max = rand_config.max_inclusive
373
346
  effective_min, effective_max \
374
- = merge_constraints(digit_min, digit_max, schema_min, schema_max, config_min, config_max)
347
+ = merge_constraints(digit_min, digit_max, min_value, max_value, config_min, config_max)
375
348
  logger.debug('bounds after adjust: min_value: %4s; max_value: %4s', effective_min, effective_max)
376
349
 
377
- random_float = self.randomizer.float(effective_min, effective_max)
378
- return f"{random_float:.{fraction_digits}f}"
350
+ if fraction_digits == 0:
351
+ random_int = self.randomizer.integer(min_value, max_value)
352
+ return str(random_int)
353
+ else:
354
+ random_float = self.randomizer.float(effective_min, effective_max)
355
+ return f"{random_float:.{fraction_digits}f}"
379
356
 
380
- def _generate_float(self, rand_config, min_value, max_value):
381
- return self._generate_decimal(rand_config, min_value, max_value, None, 2)
357
+ def _generate_float(self, constraints: TypeConstraints):
358
+ decimal_constraints = replace(constraints, fraction_digits=2)
359
+ return self._generate_decimal(decimal_constraints)
382
360
 
383
- def _generate_double(self, rand_config, min_value, max_value):
384
- return self._generate_decimal(rand_config, min_value, max_value, None, 2)
361
+ def _generate_double(self, constraints: TypeConstraints):
362
+ decimal_constraints = replace(constraints, fraction_digits=2)
363
+ return self._generate_decimal(decimal_constraints)
385
364
 
386
- def _generate_duration(self):
365
+ def _generate_duration(self, constraints: TypeConstraints):
387
366
  raise RuntimeError("not yet implemented")
388
367
 
389
- def _generate_datetime(self):
368
+ # noinspection PyUnusedLocal
369
+ def _generate_datetime(self, constraints: TypeConstraints):
390
370
  random_datetime = self.randomizer.random_datetime()
391
371
  formatted = random_datetime.isoformat()
392
372
  return formatted
393
373
 
394
- def _generate_date(self):
374
+ # noinspection PyUnusedLocal
375
+ def _generate_date(self, constraints: TypeConstraints):
395
376
  random_date = self.randomizer.random_date()
396
377
  formatted = random_date.isoformat()
397
378
  return formatted
398
379
 
399
- def _generate_time(self):
380
+ # noinspection PyUnusedLocal
381
+ def _generate_time(self, constraints: TypeConstraints):
400
382
  random_time = self.randomizer.random_time()
401
383
  formatted = random_time.isoformat()
402
384
  return formatted
403
385
 
404
- def _generate_gyearmonth(self):
386
+ # noinspection PyUnusedLocal
387
+ def _generate_gyearmonth(self, constraints: TypeConstraints):
405
388
  random_date = self.randomizer.random_date()
406
389
  formatted = random_date.strftime('%Y-%m')
407
390
  return formatted
408
391
 
409
- def _generate_gyear(self):
392
+ # noinspection PyUnusedLocal
393
+ def _generate_gyear(self, constraints: TypeConstraints):
410
394
  return str(self.randomizer.integer(2000, 2050))
411
395
 
412
- def _generate_gmonthday(self):
396
+ # noinspection PyUnusedLocal
397
+ def _generate_gmonthday(self, constraints: TypeConstraints):
413
398
  random_date = self.randomizer.random_date()
414
399
  formatted = random_date.strftime('--%m-%d')
415
400
  return formatted
416
401
 
417
- def _generate_gday(self):
402
+ # noinspection PyUnusedLocal
403
+ def _generate_gday(self, constraints: TypeConstraints):
418
404
  random_date = self.randomizer.random_date()
419
405
  formatted = random_date.strftime('---%d')
420
406
  return formatted
421
407
 
422
- def _generate_gmonth(self):
408
+ # noinspection PyUnusedLocal
409
+ def _generate_gmonth(self, constraints: TypeConstraints):
423
410
  random_date = self.randomizer.random_date()
424
411
  formatted = random_date.strftime('--%m--')
425
412
  return formatted
426
413
 
427
- def _generate_hex_binary(self):
428
- raise RuntimeError("not yet implemented")
414
+ def _generate_hex_binary(self, constraints: TypeConstraints):
415
+ return self.randomizer.hex_string(constraints.min_length, constraints.max_length)
429
416
 
430
- def _generate_base64_binary(self):
417
+ # noinspection PyUnusedLocal
418
+ def _generate_base64_binary(self, constraints: TypeConstraints):
431
419
  raise RuntimeError("not yet implemented")
432
420
 
433
- def _generate_any_uri(self):
421
+ # noinspection PyUnusedLocal
422
+ def _generate_any_uri(self, constraints: TypeConstraints):
434
423
  raise RuntimeError("not yet implemented")
435
424
 
436
- def _generate_qname(self):
425
+ # noinspection PyUnusedLocal
426
+ def _generate_qname(self, constraints: TypeConstraints):
437
427
  raise RuntimeError("not yet implemented")
438
428
 
439
- def _generate_notation(self):
429
+ # noinspection PyUnusedLocal
430
+ def _generate_notation(self, constraints: TypeConstraints):
440
431
  raise RuntimeError("not yet implemented")
441
432
 
442
-
443
- def merge_constraints(digit_min=None, digit_max=None, schema_min=None, schema_max=None, config_min=None,
444
- config_max=None):
433
+ def _generate_byte(self, constraints: TypeConstraints):
434
+ constraints = replace(constraints, fraction_digits=0)
435
+ return self._generate_decimal(constraints)
436
+
437
+ def _generate_short(self, constraints: TypeConstraints):
438
+ constraints = replace(constraints, fraction_digits=0)
439
+ return self._generate_decimal(constraints)
440
+
441
+ def _generate_int(self, constraints: TypeConstraints):
442
+ constraints = replace(constraints, fraction_digits=0)
443
+ return self._generate_decimal(constraints)
444
+
445
+ def _generate_integer(self, constraints: TypeConstraints):
446
+ min_value = constraints.min_value if constraints.min_value is not None else -2147483648
447
+ max_value = constraints.max_value if constraints.max_value is not None else 2147483647
448
+ constraints = replace(constraints, min_value=min_value, max_value=max_value, fraction_digits=0)
449
+ return self._generate_decimal(constraints)
450
+
451
+ def _generate_long(self, constraints: TypeConstraints):
452
+ constraints = replace(constraints, fraction_digits=0)
453
+ return self._generate_decimal(constraints)
454
+
455
+ def _generate_unsigned_byte(self, constraints: TypeConstraints):
456
+ constraints = replace(constraints, fraction_digits=0)
457
+ return self._generate_decimal(constraints)
458
+
459
+ def _generate_unsigned_short(self, constraints: TypeConstraints):
460
+ constraints = replace(constraints, fraction_digits=0)
461
+ return self._generate_decimal(constraints)
462
+
463
+ def _generate_unsigned_int(self, constraints: TypeConstraints):
464
+ constraints = replace(constraints, fraction_digits=0)
465
+ return self._generate_decimal(constraints)
466
+
467
+ def _generate_unsigned_long(self, constraints: TypeConstraints):
468
+ constraints = replace(constraints, fraction_digits=0)
469
+ return self._generate_decimal(constraints)
470
+
471
+ def _generate_positive_integer(self, constraints: TypeConstraints):
472
+ min_value = constraints.min_value if constraints.min_value is not None else 1
473
+ max_value = constraints.max_value if constraints.max_value is not None else 2 ** 31 - 1
474
+ constraints = replace(constraints, min_value=min_value, max_value=max_value, fraction_digits=0)
475
+ return self._generate_decimal(constraints)
476
+
477
+ def _generate_negative_integer(self, constraints: TypeConstraints):
478
+ min_value = constraints.min_value if constraints.min_value is not None else -2 ** 31
479
+ max_value = constraints.max_value if constraints.max_value is not None else -1
480
+ constraints = replace(constraints, min_value=min_value, max_value=max_value, fraction_digits=0)
481
+ return self._generate_decimal(constraints)
482
+
483
+ def _generate_non_positive_integer(self, constraints: TypeConstraints):
484
+ min_value = constraints.min_value if constraints.min_value is not None else -2 ** 31
485
+ max_value = constraints.max_value if constraints.max_value is not None else 0
486
+ constraints = replace(constraints, min_value=min_value, max_value=max_value, fraction_digits=0)
487
+ return self._generate_decimal(constraints)
488
+
489
+ def _generate_non_negative_integer(self, constraints: TypeConstraints):
490
+ min_value = constraints.min_value if constraints.min_value is not None else 0
491
+ max_value = constraints.max_value if constraints.max_value is not None else 2 ** 31 - 1
492
+ constraints = replace(constraints, min_value=min_value, max_value=max_value, fraction_digits=0)
493
+ return self._generate_decimal(constraints)
494
+
495
+
496
+ def extract_type_constraints(xsd_type, local_config: GeneratorConfig) -> TypeConstraints:
497
+ min_length = getattr(xsd_type, 'min_length', None)
498
+ max_length = getattr(xsd_type, 'max_length', None)
499
+ min_value = getattr(xsd_type, 'min_value', None)
500
+ max_value = getattr(xsd_type, 'max_value', None)
501
+ total_digits = None
502
+ fraction_digits = None
503
+ patterns = getattr(xsd_type, 'patterns', None)
504
+ validators = getattr(xsd_type, 'validators', None)
505
+ for validator in validators:
506
+ if isinstance(validator, XsdMinExclusiveFacet):
507
+ min_value = validator.value
508
+ elif isinstance(validator, XsdMinInclusiveFacet):
509
+ min_value = validator.value
510
+ elif isinstance(validator, XsdMaxExclusiveFacet):
511
+ max_value = validator.value
512
+ elif isinstance(validator, XsdMaxInclusiveFacet):
513
+ max_value = validator.value
514
+ elif isinstance(validator, XsdLengthFacet):
515
+ min_length = validator.value
516
+ max_length = validator.value
517
+ elif isinstance(validator, XsdMinLengthFacet):
518
+ min_length = validator.value
519
+ elif isinstance(validator, XsdMaxLengthFacet):
520
+ max_length = validator.value
521
+ elif isinstance(validator, XsdTotalDigitsFacet):
522
+ total_digits = validator.value
523
+ elif isinstance(validator, XsdFractionDigitsFacet):
524
+ fraction_digits = validator.value
525
+ elif isinstance(validator, XsdEnumerationFacets):
526
+ pass
527
+ elif callable(validator):
528
+ pass
529
+ else:
530
+ raise RuntimeError(f"Unhandled validator: {validator}")
531
+
532
+ if isinstance(min_value, Decimal):
533
+ min_value = float(min_value)
534
+ if isinstance(max_value, Decimal):
535
+ max_value = float(max_value)
536
+
537
+ rand_config = local_config.randomization
538
+
539
+ logger.debug('bounds before adjust: min_length: %4s; max_length: %4s', min_length, max_length)
540
+ min_length, max_length = merge_constraints(
541
+ schema_min=min_length,
542
+ schema_max=max_length,
543
+ config_min=rand_config.min_length,
544
+ config_max=rand_config.max_length
545
+ )
546
+ logger.debug('bounds after adjust: min_length: %4s; max_length: %4s', min_length, max_length)
547
+
548
+ return TypeConstraints(
549
+ min_length=min_length,
550
+ max_length=max_length,
551
+ min_value=min_value,
552
+ max_value=max_value,
553
+ total_digits=total_digits,
554
+ fraction_digits=fraction_digits,
555
+ patterns=patterns,
556
+ rand_config=rand_config
557
+ )
558
+
559
+
560
+ def merge_constraints(digit_min=None, digit_max=None, schema_min=None, schema_max=None, config_min=None, config_max=None):
445
561
  logger.debug(
446
562
  "merge numeric constraints: "
447
563
  "digit_min: %s, digit_max: %s, schema_min: %s, schema_max: %s, config_min: %s, config_max: %s",
@@ -53,6 +53,16 @@ class Randomizer:
53
53
  letters = string.ascii_lowercase
54
54
  return ''.join(self._rnd.choice(letters) for _ in range(length)).capitalize()
55
55
 
56
+ def hex_string(self, min_length, max_length):
57
+ if min_length is None:
58
+ min_length = 1
59
+ if max_length is None:
60
+ max_length = 20
61
+
62
+ length = self._rnd.randint(min_length, max_length)
63
+ circumflexes = ''.join('^' for _ in range(length))
64
+ return self._fake.hexify(text=circumflexes, upper=True)
65
+
56
66
  def random_date(self, start_date: str = '1990-01-01', end_date: str = '2025-12-31') -> date:
57
67
  start = date.fromisoformat(start_date)
58
68
  end = date.fromisoformat(end_date)
@@ -133,3 +143,6 @@ class Randomizer:
133
143
  def snils_formatted(self):
134
144
  snils = self._fake.snils()
135
145
  return f"{snils[:3]}-{snils[3:6]}-{snils[6:9]} {snils[9:]}"
146
+
147
+ def email(self):
148
+ return self._fake.email()
@@ -46,6 +46,7 @@ class Substitutor:
46
46
  'ogrn_fl': lambda args: self.randomizer.ogrn_fl(),
47
47
  'kpp': lambda args: self.randomizer.kpp(),
48
48
  'snils_formatted': lambda args: self.randomizer.snils_formatted(),
49
+ 'email': lambda args: self.randomizer.email(),
49
50
  }
50
51
 
51
52
  def reset_context(self, xsd_filename, config_local):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xmlgenerator
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Generates XML documents from XSD schemas
5
5
  Home-page: https://github.com/lexakimov/xmlgenerator
6
6
  Author: Alexey Akimov
@@ -270,6 +270,7 @@ In the `value_override` sections, you can specify either a string value or speci
270
270
  | `ogrn_fl` | Primary State Registration Number (Physical Person) |
271
271
  | `kpp` | Reason Code for Registration |
272
272
  | `snils_formatted` | SNILS (Personal Insurance Account Number) in the format `123-456-789 90` |
273
+ | `email` | Random email address |
273
274
 
274
275
  **Configuration Examples:**
275
276
 
File without changes
File without changes