xmlgenerator 0.4.0__tar.gz → 0.5.1__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.
- {xmlgenerator-0.4.0/xmlgenerator.egg-info → xmlgenerator-0.5.1}/PKG-INFO +2 -1
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/README.md +1 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/setup.py +1 -1
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator/arguments.py +2 -1
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator/generator.py +253 -137
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator/randomization.py +13 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator/substitution.py +1 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1/xmlgenerator.egg-info}/PKG-INFO +2 -1
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/LICENSE +0 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/setup.cfg +0 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator/__init__.py +0 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator/bootstrap.py +0 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator/configuration.py +0 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator/validation.py +0 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator.egg-info/SOURCES.txt +0 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator.egg-info/dependency_links.txt +0 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator.egg-info/entry_points.txt +0 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator.egg-info/requires.txt +0 -0
- {xmlgenerator-0.4.0 → xmlgenerator-0.5.1}/xmlgenerator.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: xmlgenerator
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.1
|
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
|
|
@@ -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
|
-
|
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,
|
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
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
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',
|
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,
|
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
|
-
|
378
|
-
|
350
|
+
if fraction_digits == 0:
|
351
|
+
random_int = self.randomizer.integer(effective_min, effective_max)
|
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,
|
381
|
-
|
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,
|
384
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
392
|
+
# noinspection PyUnusedLocal
|
393
|
+
def _generate_gyear(self, constraints: TypeConstraints):
|
410
394
|
return str(self.randomizer.integer(2000, 2050))
|
411
395
|
|
412
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
414
|
+
def _generate_hex_binary(self, constraints: TypeConstraints):
|
415
|
+
return self.randomizer.hex_string(constraints.min_length, constraints.max_length)
|
429
416
|
|
430
|
-
|
417
|
+
# noinspection PyUnusedLocal
|
418
|
+
def _generate_base64_binary(self, constraints: TypeConstraints):
|
431
419
|
raise RuntimeError("not yet implemented")
|
432
420
|
|
433
|
-
|
421
|
+
# noinspection PyUnusedLocal
|
422
|
+
def _generate_any_uri(self, constraints: TypeConstraints):
|
434
423
|
raise RuntimeError("not yet implemented")
|
435
424
|
|
436
|
-
|
425
|
+
# noinspection PyUnusedLocal
|
426
|
+
def _generate_qname(self, constraints: TypeConstraints):
|
437
427
|
raise RuntimeError("not yet implemented")
|
438
428
|
|
439
|
-
|
429
|
+
# noinspection PyUnusedLocal
|
430
|
+
def _generate_notation(self, constraints: TypeConstraints):
|
440
431
|
raise RuntimeError("not yet implemented")
|
441
432
|
|
442
|
-
|
443
|
-
|
444
|
-
|
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.
|
3
|
+
Version: 0.5.1
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|