xmlgenerator 0.3.0__tar.gz → 0.4.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.3.0
3
+ Version: 0.4.0
4
4
  Summary: Generates XML documents from XSD schemas
5
5
  Home-page: https://github.com/lexakimov/xmlgenerator
6
6
  Author: Alexey Akimov
@@ -173,6 +173,8 @@ global:
173
173
  # Probability of adding optional elements (0.0-1.0)
174
174
  # Default value: 0.5
175
175
  probability: 1
176
+ # Limit for the minimal number of elements
177
+ min_occurs: 0
176
178
  # Limit for the maximum number of elements
177
179
  max_occurs: 5
178
180
  # Minimum string length
@@ -144,6 +144,8 @@ global:
144
144
  # Probability of adding optional elements (0.0-1.0)
145
145
  # Default value: 0.5
146
146
  probability: 1
147
+ # Limit for the minimal number of elements
148
+ min_occurs: 0
147
149
  # Limit for the maximum number of elements
148
150
  max_occurs: 5
149
151
  # Minimum string length
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='xmlgenerator',
5
- version='0.3.0',
5
+ version='0.4.0',
6
6
  packages=find_packages(exclude=("tests", "tests.*")),
7
7
  entry_points={
8
8
  'console_scripts': [
@@ -12,7 +12,6 @@ from xmlgenerator.randomization import Randomizer
12
12
  from xmlgenerator.substitution import Substitutor
13
13
  from xmlgenerator.validation import XmlValidator
14
14
 
15
- # TODO конфигурация ограничений - occurs
16
15
  # TODO Generator - обработка стандартных xsd типов
17
16
  # TODO кастомные переменные для локального контекста
18
17
  # TODO валидация по Schematron
@@ -90,6 +89,7 @@ def _main():
90
89
 
91
90
 
92
91
  def _setup_loggers(args):
92
+ logging.addLevelName(logging.WARNING, 'WARN')
93
93
  log_level = logging.DEBUG if args.debug else logging.INFO
94
94
  logger.setLevel(log_level)
95
95
  configuration.logger.setLevel(log_level)
@@ -1,6 +1,6 @@
1
1
  import logging
2
+ from decimal import Decimal
2
3
 
3
- import xmlschema
4
4
  from lxml import etree
5
5
  from xmlschema.validators import XsdComplexType, XsdAtomicRestriction, XsdTotalDigitsFacet, XsdElement, \
6
6
  XsdGroup, XsdFractionDigitsFacet, XsdLengthFacet, XsdMaxLengthFacet, XsdMinExclusiveFacet, XsdMinInclusiveFacet, \
@@ -18,7 +18,8 @@ class XmlGenerator:
18
18
  self.randomizer = randomizer
19
19
  self.substitutor = substitutor
20
20
 
21
- def generate_xml(self, xsd_schema: xmlschema.XMLSchema, local_config: GeneratorConfig) -> etree.Element:
21
+ def generate_xml(self, xsd_schema, local_config: GeneratorConfig) -> etree.Element:
22
+ logger.debug('generate xml document...')
22
23
  ns_map = {None if k == '' else k: v for k, v in xsd_schema.namespaces.items() if v != ''}
23
24
  xsd_root_element = xsd_schema.root_elements[0]
24
25
  xml_root_element = etree.Element(xsd_root_element.name, nsmap=ns_map)
@@ -26,7 +27,11 @@ class XmlGenerator:
26
27
  self._add_elements(xml_tree, xml_root_element, xsd_root_element, local_config)
27
28
  return xml_root_element
28
29
 
29
- def _add_elements(self, xml_tree, xml_element: etree.Element, xsd_element, local_config: GeneratorConfig) -> None:
30
+ def _add_elements(self, xml_tree, xml_element, xsd_element, local_config: GeneratorConfig) -> None:
31
+ rand_config = local_config.randomization
32
+ min_occurs_conf = rand_config.min_occurs
33
+ max_occurs_conf = rand_config.max_occurs
34
+
30
35
  # Process child elements --------------------------------------------------------------------------------------
31
36
  if isinstance(xsd_element, XsdElement):
32
37
  element_xpath = xml_tree.getpath(xml_element)
@@ -38,14 +43,14 @@ class XmlGenerator:
38
43
  attributes = getattr(xsd_element, 'attributes', dict())
39
44
  if len(attributes) > 0 and xsd_element_type.local_name != 'anyType':
40
45
  for attr_name, attr in attributes.items():
41
- logger.debug('element: %s; attribute "%s" [processing]', element_xpath, attr_name)
46
+ logger.debug('element: %s; attribute: "%s" - [processing]', element_xpath, attr_name)
42
47
  use = attr.use # optional | required | prohibited
43
48
  if use == 'prohibited':
44
- logger.debug('element: %s; attribute: "%s" [skipped]', element_xpath, attr_name)
49
+ logger.debug('element: %s; attribute: "%s" - [skipped]', element_xpath, attr_name)
45
50
  continue
46
51
  elif use == 'optional':
47
- if self.randomizer.random() > local_config.randomization.probability:
48
- logger.debug('element: %s; attribute: "%s" [skipped]', element_xpath, attr_name)
52
+ if self.randomizer.random() > rand_config.probability:
53
+ logger.debug('element: %s; attribute: "%s" - [skipped]', element_xpath, attr_name)
49
54
  continue
50
55
 
51
56
  attr_value = self._generate_value(attr.type, attr_name, local_config)
@@ -57,58 +62,82 @@ class XmlGenerator:
57
62
  text = self._generate_value(xsd_element_type, xsd_element.name, local_config)
58
63
  xml_element.text = text
59
64
  logger.debug('element: %s = "%s"', element_xpath, text)
60
- return
65
+
61
66
  elif isinstance(xsd_element_type, XsdAtomicRestriction):
62
67
  text = self._generate_value(xsd_element_type, xsd_element.name, local_config)
63
68
  xml_element.text = text
64
69
  logger.debug('element: %s = "%s"', element_xpath, text)
65
- return
70
+
66
71
  elif isinstance(xsd_element_type, XsdComplexType):
67
72
  xsd_element_type_content = xsd_element_type.content
68
73
  if isinstance(xsd_element_type_content, XsdGroup):
69
74
  self._add_elements(xml_tree, xml_element, xsd_element_type_content, local_config)
70
75
  else:
71
76
  raise RuntimeError()
77
+
72
78
  else:
73
79
  raise RuntimeError()
74
80
 
75
81
  elif isinstance(xsd_element, XsdGroup):
76
82
  model = xsd_element.model
77
83
 
78
- group_min_occurs = getattr(xsd_element, 'min_occurs', None)
79
- group_max_occurs = getattr(xsd_element, 'max_occurs', None)
80
- group_min_occurs = group_min_occurs if group_min_occurs is not None else 0 # TODO externalize
81
- group_max_occurs = group_max_occurs if group_max_occurs is not None else 10 # TODO externalize
82
- group_occurs = self.randomizer.integer(group_min_occurs, group_max_occurs)
84
+ min_occurs = getattr(xsd_element, 'min_occurs', None)
85
+ max_occurs = getattr(xsd_element, 'max_occurs', None)
86
+ min_occurs, max_occurs = merge_constraints(
87
+ schema_min=min_occurs,
88
+ schema_max=max_occurs,
89
+ config_min=min_occurs_conf,
90
+ config_max=max_occurs_conf
91
+ )
92
+ if max_occurs is None:
93
+ max_occurs = 10
94
+ group_occurs = self.randomizer.integer(min_occurs, max_occurs)
95
+ logger.debug('add %s (random between %s and %s) groups of type "%s"',
96
+ group_occurs, min_occurs, max_occurs, model)
83
97
 
84
98
  if model == 'all':
85
99
  for _ in range(group_occurs):
86
100
  xsd_group_content = xsd_element.content
87
101
  for xsd_child_element_type in xsd_group_content:
88
102
 
89
- element_min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
90
- element_max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
91
- element_min_occurs = element_min_occurs if element_min_occurs is not None else 0 # TODO externalize
92
- element_max_occurs = element_max_occurs if element_max_occurs is not None else 10 # TODO externalize
93
- element_occurs = self.randomizer.integer(element_min_occurs, element_max_occurs)
103
+ min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
104
+ max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
105
+ min_occurs, max_occurs = merge_constraints(
106
+ schema_min=min_occurs,
107
+ schema_max=max_occurs,
108
+ config_min=min_occurs_conf,
109
+ config_max=max_occurs_conf
110
+ )
111
+ if max_occurs is None:
112
+ max_occurs = 10
113
+ element_occurs = self.randomizer.integer(min_occurs, max_occurs)
114
+ logger.debug('element_occurs: %s (random between %s and %s)', element_occurs, min_occurs,
115
+ max_occurs)
94
116
 
95
117
  for _ in range(element_occurs):
96
118
  xml_child_element = etree.SubElement(xml_element, xsd_child_element_type.name)
97
119
  self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
98
- return
99
120
 
100
121
  elif model == 'sequence':
101
122
  for _ in range(group_occurs):
102
123
  xsd_group_content = xsd_element.content
103
124
  for xsd_child_element_type in xsd_group_content:
125
+ if isinstance(xsd_child_element_type, XsdElement):
104
126
 
105
- element_min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
106
- element_max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
107
- element_min_occurs = element_min_occurs if element_min_occurs is not None else 0 # TODO externalize
108
- element_max_occurs = element_max_occurs if element_max_occurs is not None else 10 # TODO externalize
109
- element_occurs = self.randomizer.integer(element_min_occurs, element_max_occurs)
127
+ min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
128
+ max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
129
+ min_occurs, max_occurs = merge_constraints(
130
+ schema_min=min_occurs,
131
+ schema_max=max_occurs,
132
+ config_min=min_occurs_conf,
133
+ config_max=max_occurs_conf
134
+ )
135
+ if max_occurs is None:
136
+ max_occurs = 10
137
+ element_occurs = self.randomizer.integer(min_occurs, max_occurs)
138
+ logger.debug('element_occurs: %s (random between %s and %s)', element_occurs, min_occurs,
139
+ max_occurs)
110
140
 
111
- if isinstance(xsd_child_element_type, XsdElement):
112
141
  for _ in range(element_occurs):
113
142
  xml_child_element = etree.SubElement(xml_element, xsd_child_element_type.name)
114
143
  self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
@@ -123,22 +152,28 @@ class XmlGenerator:
123
152
 
124
153
  else:
125
154
  raise RuntimeError(xsd_child_element_type)
126
- return
127
155
 
128
156
  elif model == 'choice':
129
157
  for _ in range(group_occurs):
130
158
  xsd_child_element_type = self.randomizer.any(xsd_element)
131
159
 
132
- element_min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
133
- element_max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
134
- element_min_occurs = element_min_occurs if element_min_occurs is not None else 0 # TODO externalize
135
- element_max_occurs = element_max_occurs if element_max_occurs is not None else 10 # TODO externalize
136
- element_occurs = self.randomizer.integer(element_min_occurs, element_max_occurs)
160
+ min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
161
+ max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
162
+ min_occurs, max_occurs = merge_constraints(
163
+ schema_min=min_occurs,
164
+ schema_max=max_occurs,
165
+ config_min=min_occurs_conf,
166
+ config_max=max_occurs_conf
167
+ )
168
+ if max_occurs is None:
169
+ max_occurs = 10
170
+ element_occurs = self.randomizer.integer(min_occurs, max_occurs)
171
+ logger.debug('element_occurs: %s (random between %s and %s)', element_occurs, min_occurs,
172
+ max_occurs)
137
173
 
138
174
  for _ in range(element_occurs):
139
175
  xml_child_element = etree.SubElement(xml_element, xsd_child_element_type.name)
140
176
  self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
141
- return
142
177
 
143
178
  else:
144
179
  raise RuntimeError()
@@ -215,32 +250,69 @@ class XmlGenerator:
215
250
  else:
216
251
  raise RuntimeError(f"Unhandled validator: {validator}")
217
252
 
218
- rand_config = local_config.randomization
253
+ if isinstance(min_value, Decimal):
254
+ min_value = float(min_value)
255
+ if isinstance(max_value, Decimal):
256
+ max_value = float(max_value)
219
257
 
220
- logger.debug(
221
- 'restrictions before override: min_length: %4s; max_length: %4s; min_value: %4s; max_value: %4s',
222
- min_length, max_length, min_value, max_value
223
- )
224
-
225
- min_length, max_length = calculate_bounds_1(
226
- min_length, max_length, rand_config.min_length, rand_config.max_length
227
- )
228
-
229
- min_value, max_value = calculate_bounds_1(
230
- min_value, max_value, rand_config.min_inclusive, rand_config.max_inclusive
231
- )
232
-
233
- logger.debug(
234
- 'restrictions after override: min_length: %4s; max_length: %4s; min_value: %4s; max_value: %4s',
235
- min_length, max_length, min_value, max_value
236
- )
258
+ rand_config = local_config.randomization
237
259
 
238
- generated_value = self._generate_value_by_type(
239
- xsd_type, patterns,
240
- min_length, max_length,
241
- min_value, max_value,
242
- total_digits, fraction_digits
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
243
266
  )
267
+ logger.debug('bounds after adjust: min_length: %4s; max_length: %4s', min_length, max_length)
268
+
269
+ type_id = xsd_type.id or xsd_type.base_type.id or xsd_type.root_type.id
270
+ logger.debug('generate value for type: "%s"', type_id)
271
+
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
244
316
  logger.debug('value generated: "%s"', generated_value)
245
317
  return generated_value
246
318
 
@@ -254,63 +326,10 @@ class XmlGenerator:
254
326
 
255
327
  raise RuntimeError(f"Can't generate value - unhandled type. Target name: {target_name}")
256
328
 
257
- def _generate_value_by_type(self, xsd_type, patterns, min_length, max_length, min_value, max_value,
258
- total_digits, fraction_digits) -> str | None:
259
-
260
- type_id = xsd_type.id
261
- base_type = xsd_type.base_type
262
- if not type_id:
263
- type_id = base_type.id
264
- if not type_id:
265
- type_id = xsd_type.root_type.id
266
-
267
- logger.debug('generate value for type: "%s"', type_id)
268
-
269
- match type_id:
270
- case 'string':
271
- return self._generate_string(patterns, min_length, max_length)
272
- case 'boolean':
273
- return self._generate_boolean()
274
- case 'integer':
275
- return self._generate_integer(total_digits, min_value, max_value)
276
- case 'decimal':
277
- return self._generate_decimal(total_digits, fraction_digits, min_value, max_value)
278
- case 'float':
279
- return self._generate_float(min_value, max_value)
280
- case 'double':
281
- return self._generate_double(min_value, max_value)
282
- case 'duration':
283
- return self._generate_duration()
284
- case 'dateTime':
285
- return self._generate_datetime()
286
- case 'date':
287
- return self._generate_date()
288
- case 'time':
289
- return self._generate_time()
290
- case 'gYearMonth':
291
- return self._generate_gyearmonth()
292
- case 'gYear':
293
- return self._generate_gyear()
294
- case 'gMonthDay':
295
- return self._generate_gmonthday()
296
- case 'gDay':
297
- return self._generate_gday()
298
- case 'gMonth':
299
- return self._generate_gmonth()
300
- case 'hexBinary':
301
- return self._generate_hex_binary()
302
- case 'base64Binary':
303
- return self._generate_base64_binary()
304
- case 'anyURI':
305
- return self._generate_any_uri()
306
- case 'QName':
307
- return self._generate_qname()
308
- case 'NOTATION':
309
- return self._generate_notation()
310
- case _:
311
- raise RuntimeError(type_id)
312
-
313
- def _generate_string(self, patterns, min_length, max_length):
329
+ def _generate_boolean(self):
330
+ return self.randomizer.any(['true', 'false'])
331
+
332
+ def _generate_string(self, min_length, max_length, patterns):
314
333
  if patterns is not None:
315
334
  # Генерация строки по regex
316
335
  random_enum = self.randomizer.any(patterns)
@@ -320,17 +339,14 @@ class XmlGenerator:
320
339
  # Иначе генерируем случайную строку
321
340
  return self.randomizer.ascii_string(min_length, max_length)
322
341
 
323
- def _generate_boolean(self):
324
- return self.randomizer.any(['true', 'false'])
325
-
326
- def _generate_integer(self, total_digits, min_value, max_value):
342
+ def _generate_integer(self, min_value, max_value, total_digits):
327
343
  if total_digits:
328
344
  min_value = 10 ** (total_digits - 1)
329
345
  max_value = (10 ** total_digits) - 1
330
346
  rnd_int = self.randomizer.integer(min_value, max_value)
331
347
  return str(rnd_int)
332
348
 
333
- def _generate_decimal(self, total_digits, fraction_digits, min_value, max_value):
349
+ def _generate_decimal(self, rand_config, schema_min, schema_max, total_digits, fraction_digits):
334
350
  if fraction_digits is None:
335
351
  fraction_digits = self.randomizer.integer(1, 3)
336
352
 
@@ -345,22 +361,27 @@ class XmlGenerator:
345
361
 
346
362
  integer_digits = total_digits - fraction_digits
347
363
 
348
- # negative
349
- min_value_fact = -(10 ** integer_digits - 1)
350
-
351
- # positive
352
- max_value_fact = 10 ** integer_digits - 1
364
+ # negative bound
365
+ digit_min = -(10 ** integer_digits - 1)
366
+ # positive bound
367
+ digit_max = 10 ** integer_digits - 1
368
+ logger.debug("integer digits: %s; digit_min: %s; digit_max: %s", integer_digits, digit_min, digit_max)
353
369
 
354
- min_value_fact, max_value_fact = calculate_bounds_2(min_value_fact, max_value_fact, min_value, max_value)
370
+ logger.debug('bounds before adjust: min_value: %4s; max_value: %4s', schema_min, schema_max)
371
+ config_min = rand_config.min_inclusive
372
+ config_max = rand_config.max_inclusive
373
+ effective_min, effective_max \
374
+ = merge_constraints(digit_min, digit_max, schema_min, schema_max, config_min, config_max)
375
+ logger.debug('bounds after adjust: min_value: %4s; max_value: %4s', effective_min, effective_max)
355
376
 
356
- random_float = self.randomizer.float(min_value_fact, max_value_fact)
377
+ random_float = self.randomizer.float(effective_min, effective_max)
357
378
  return f"{random_float:.{fraction_digits}f}"
358
379
 
359
- def _generate_float(self, min_value, max_value):
360
- return self._generate_double(min_value, max_value)
380
+ def _generate_float(self, rand_config, min_value, max_value):
381
+ return self._generate_decimal(rand_config, min_value, max_value, None, 2)
361
382
 
362
- def _generate_double(self, min_value, max_value):
363
- return self._generate_decimal(None, 2, min_value, max_value)
383
+ def _generate_double(self, rand_config, min_value, max_value):
384
+ return self._generate_decimal(rand_config, min_value, max_value, None, 2)
364
385
 
365
386
  def _generate_duration(self):
366
387
  raise RuntimeError("not yet implemented")
@@ -419,38 +440,41 @@ class XmlGenerator:
419
440
  raise RuntimeError("not yet implemented")
420
441
 
421
442
 
422
- def calculate_bounds_1(fact_min, fact_max, config_min, config_max):
423
- if config_min:
424
- if fact_min is None:
425
- fact_min = config_min
426
- else:
427
- new_min = max(fact_min, config_min)
428
- if fact_max and new_min <= fact_max:
429
- fact_min = new_min
430
-
431
- if config_max:
432
- if fact_max is None:
433
- fact_max = config_max
434
- else:
435
- new_max = min(fact_max, config_max)
436
- if new_max >= fact_min:
437
- fact_max = new_max
438
-
439
- if fact_max and fact_min and fact_max < fact_min:
440
- fact_max = fact_min = min(fact_max, fact_min)
443
+ def merge_constraints(digit_min=None, digit_max=None, schema_min=None, schema_max=None, config_min=None,
444
+ config_max=None):
445
+ logger.debug(
446
+ "merge numeric constraints: "
447
+ "digit_min: %s, digit_max: %s, schema_min: %s, schema_max: %s, config_min: %s, config_max: %s",
448
+ digit_min, digit_max, schema_min, schema_max, config_min, config_max)
441
449
 
442
- return fact_min, fact_max
450
+ # За основу берем цифровые ограничения (они самые нестрогие)
451
+ effective_min, effective_max = digit_min, digit_max
443
452
 
453
+ # Применяем схемные ограничения
454
+ if schema_min is not None:
455
+ effective_min = max(effective_min, schema_min) if effective_min is not None else schema_min
456
+ if schema_max is not None:
457
+ effective_max = min(effective_max, schema_max) if effective_max is not None else schema_max
444
458
 
445
- def calculate_bounds_2(fact_min, fact_max, config_min, config_max):
459
+ # Применяем конфигурационные ограничения с проверкой на конфликт
446
460
  if config_min is not None:
447
- new_min = max(fact_min, config_min)
448
- if fact_max and new_min <= fact_max:
449
- fact_min = new_min
461
+ if effective_max is not None and config_min > effective_max:
462
+ logger.warning("can't apply bound from configuration: config_min (%s) > effective_max (%s)",
463
+ config_min, effective_max)
464
+ else:
465
+ effective_min = max(effective_min, config_min) if effective_min is not None else config_min
450
466
 
451
467
  if config_max is not None:
452
- new_max = min(fact_max, config_max)
453
- if new_max >= fact_min:
454
- fact_max = new_max
468
+ if effective_min is not None and config_max < effective_min:
469
+ logger.warning("can't apply bound from configuration: config_max (%s) < effective_min (%s)",
470
+ config_max, effective_min)
471
+ else:
472
+ effective_max = min(effective_max, config_max) if effective_max is not None else config_max
473
+
474
+ # Проверяем на конфликт
475
+ if effective_min is not None and effective_max is not None and effective_min > effective_max:
476
+ logger.warning("constrains conflict: effective_min (%s) > effective_max (%s). Swap values.",
477
+ effective_min, effective_max)
478
+ effective_min, effective_max = effective_max, effective_min
455
479
 
456
- return fact_min, fact_max
480
+ return effective_min, effective_max
@@ -4,7 +4,6 @@ import re
4
4
  import string
5
5
  import sys
6
6
  from datetime import datetime, date, time, timedelta
7
- from decimal import Decimal
8
7
 
9
8
  import rstr
10
9
  from faker import Faker
@@ -42,10 +41,6 @@ class Randomizer:
42
41
  return self._rnd.randint(min_value, max_value)
43
42
 
44
43
  def float(self, min_value, max_value):
45
- if isinstance(min_value, Decimal):
46
- min_value = float(min_value)
47
- if isinstance(max_value, Decimal):
48
- max_value = float(max_value)
49
44
  return self._rnd.uniform(min_value, max_value)
50
45
 
51
46
  def ascii_string(self, min_length, max_length):
@@ -61,7 +61,7 @@ class Substitutor:
61
61
  resolved_value = self._process_expression(output_filename)
62
62
  self._local_context['output_filename'] = resolved_value
63
63
 
64
- logger.debug('local_context reset')
64
+ logger.debug('reset local context...')
65
65
  logger.debug('local_context["source_filename"] = %s', xsd_filename)
66
66
  logger.debug('local_context["source_extracted"] = %s (extracted with regexp %s)', source_extracted, source_filename)
67
67
  logger.debug('local_context["output_filename"] = %s', resolved_value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xmlgenerator
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Generates XML documents from XSD schemas
5
5
  Home-page: https://github.com/lexakimov/xmlgenerator
6
6
  Author: Alexey Akimov
@@ -173,6 +173,8 @@ global:
173
173
  # Probability of adding optional elements (0.0-1.0)
174
174
  # Default value: 0.5
175
175
  probability: 1
176
+ # Limit for the minimal number of elements
177
+ min_occurs: 0
176
178
  # Limit for the maximum number of elements
177
179
  max_occurs: 5
178
180
  # Minimum string length
File without changes
File without changes