Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/faker/providers/misc/__init__.py: 18%

232 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import csv 

2import hashlib 

3import io 

4import json 

5import os 

6import re 

7import string 

8import tarfile 

9import uuid 

10import zipfile 

11 

12from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Union 

13 

14from faker.exceptions import UnsupportedFeature 

15 

16from .. import BaseProvider 

17 

18localized = True 

19 

20csv.register_dialect("faker-csv", csv.excel, quoting=csv.QUOTE_ALL) 

21 

22 

23class Provider(BaseProvider): 

24 def boolean(self, chance_of_getting_true: int = 50) -> bool: 

25 """Generate a random boolean value based on ``chance_of_getting_true``. 

26 

27 :sample: chance_of_getting_true=25 

28 :sample: chance_of_getting_true=50 

29 :sample: chance_of_getting_true=75 

30 """ 

31 return self.generator.random.randint(1, 100) <= chance_of_getting_true 

32 

33 def null_boolean(self) -> Optional[bool]: 

34 """Generate ``None``, ``True``, or ``False``, each with equal probability.""" 

35 

36 return { 

37 0: None, 

38 1: True, 

39 -1: False, 

40 }[self.generator.random.randint(-1, 1)] 

41 

42 def binary(self, length: int = (1 * 1024 * 1024)) -> bytes: 

43 """Generate a random binary blob of ``length`` bytes. 

44 

45 If this faker instance has been seeded, performance will be signficiantly reduced, to conform 

46 to the seeding. 

47 

48 :sample: length=64 

49 """ 

50 # If the generator has already been seeded, urandom can't be used 

51 if self.generator._is_seeded: 

52 blob = [self.generator.random.randrange(256) for _ in range(length)] 

53 return bytes(blob) 

54 

55 # Generator is unseeded anyway, just use urandom 

56 return os.urandom(length) 

57 

58 def md5(self, raw_output: bool = False) -> Union[bytes, str]: 

59 """Generate a random MD5 hash. 

60 

61 If ``raw_output`` is ``False`` (default), a hexadecimal string representation of the MD5 hash 

62 will be returned. If ``True``, a ``bytes`` object representation will be returned instead. 

63 

64 :sample: raw_output=False 

65 :sample: raw_output=True 

66 """ 

67 res: hashlib._Hash = hashlib.md5(str(self.generator.random.random()).encode()) 

68 if raw_output: 

69 return res.digest() 

70 return res.hexdigest() 

71 

72 def sha1(self, raw_output: bool = False) -> Union[bytes, str]: 

73 """Generate a random SHA-1 hash. 

74 

75 If ``raw_output`` is ``False`` (default), a hexadecimal string representation of the SHA-1 hash 

76 will be returned. If ``True``, a ``bytes`` object representation will be returned instead. 

77 

78 :sample: raw_output=False 

79 :sample: raw_output=True 

80 """ 

81 res: hashlib._Hash = hashlib.sha1(str(self.generator.random.random()).encode()) 

82 if raw_output: 

83 return res.digest() 

84 return res.hexdigest() 

85 

86 def sha256(self, raw_output: bool = False) -> Union[bytes, str]: 

87 """Generate a random SHA-256 hash. 

88 

89 If ``raw_output`` is ``False`` (default), a hexadecimal string representation of the SHA-256 hash 

90 will be returned. If ``True``, a ``bytes`` object representation will be returned instead. 

91 

92 :sample: raw_output=False 

93 :sample: raw_output=True 

94 """ 

95 res: hashlib._Hash = hashlib.sha256(str(self.generator.random.random()).encode()) 

96 if raw_output: 

97 return res.digest() 

98 return res.hexdigest() 

99 

100 def uuid4( 

101 self, 

102 cast_to: Optional[Union[Callable[[uuid.UUID], str], Callable[[uuid.UUID], bytes]]] = str, 

103 ) -> Union[bytes, str, uuid.UUID]: 

104 """Generate a random UUID4 object and cast it to another type if specified using a callable ``cast_to``. 

105 

106 By default, ``cast_to`` is set to ``str``. 

107 

108 May be called with ``cast_to=None`` to return a full-fledged ``UUID``. 

109 

110 :sample: 

111 :sample: cast_to=None 

112 """ 

113 # Based on http://stackoverflow.com/q/41186818 

114 generated_uuid: uuid.UUID = uuid.UUID(int=self.generator.random.getrandbits(128), version=4) 

115 if cast_to is not None: 

116 return cast_to(generated_uuid) 

117 return generated_uuid 

118 

119 def password( 

120 self, 

121 length: int = 10, 

122 special_chars: bool = True, 

123 digits: bool = True, 

124 upper_case: bool = True, 

125 lower_case: bool = True, 

126 ) -> str: 

127 """Generate a random password of the specified ``length``. 

128 

129 The arguments ``special_chars``, ``digits``, ``upper_case``, and ``lower_case`` control 

130 what category of characters will appear in the generated password. If set to ``True`` 

131 (default), at least one character from the corresponding category is guaranteed to appear. 

132 Special characters are characters from ``!@#$%^&*()_+``, digits are characters from 

133 ``0123456789``, and uppercase and lowercase characters are characters from the ASCII set of 

134 letters. 

135 

136 :sample: length=12 

137 :sample: length=40, special_chars=False, upper_case=False 

138 """ 

139 choices = "" 

140 required_tokens = [] 

141 if special_chars: 141 ↛ 142line 141 didn't jump to line 142, because the condition on line 141 was never true

142 required_tokens.append(self.generator.random.choice("!@#$%^&*()_+")) 

143 choices += "!@#$%^&*()_+" 

144 if digits: 144 ↛ 147line 144 didn't jump to line 147, because the condition on line 144 was never false

145 required_tokens.append(self.generator.random.choice(string.digits)) 

146 choices += string.digits 

147 if upper_case: 147 ↛ 150line 147 didn't jump to line 150, because the condition on line 147 was never false

148 required_tokens.append(self.generator.random.choice(string.ascii_uppercase)) 

149 choices += string.ascii_uppercase 

150 if lower_case: 150 ↛ 154line 150 didn't jump to line 154, because the condition on line 150 was never false

151 required_tokens.append(self.generator.random.choice(string.ascii_lowercase)) 

152 choices += string.ascii_lowercase 

153 

154 assert len(required_tokens) <= length, "Required length is shorter than required characters" 

155 

156 # Generate a first version of the password 

157 chars: str = self.random_choices(choices, length=length) # type: ignore 

158 

159 # Pick some unique locations 

160 random_indexes: Set[int] = set() 

161 while len(random_indexes) < len(required_tokens): 

162 random_indexes.add(self.generator.random.randint(0, len(chars) - 1)) 

163 

164 # Replace them with the required characters 

165 for i, index in enumerate(random_indexes): 

166 chars[index] = required_tokens[i] # type: ignore 

167 

168 return "".join(chars) 

169 

170 def zip( 

171 self, 

172 uncompressed_size: int = 65536, 

173 num_files: int = 1, 

174 min_file_size: int = 4096, 

175 compression: Optional[str] = None, 

176 ) -> bytes: 

177 """Generate a bytes object containing a random valid zip archive file. 

178 

179 The number and sizes of files contained inside the resulting archive can be controlled 

180 using the following arguments: 

181 

182 - ``uncompressed_size`` - the total size of files before compression, 16 KiB by default 

183 - ``num_files`` - the number of files archived in resulting zip file, 1 by default 

184 - ``min_file_size`` - the minimum size of each file before compression, 4 KiB by default 

185 

186 No compression is used by default, but setting ``compression`` to one of the values listed 

187 below will use the corresponding compression type. 

188 

189 - ``'bzip2'`` or ``'bz2'`` for BZIP2 

190 - ``'lzma'`` or ``'xz'`` for LZMA 

191 - ``'deflate'``, ``'gzip'``, or ``'gz'`` for GZIP 

192 

193 :sample: uncompressed_size=256, num_files=4, min_file_size=32 

194 :sample: uncompressed_size=256, num_files=32, min_file_size=4, compression='bz2' 

195 """ 

196 if any( 

197 [ 

198 not isinstance(num_files, int) or num_files <= 0, 

199 not isinstance(min_file_size, int) or min_file_size <= 0, 

200 not isinstance(uncompressed_size, int) or uncompressed_size <= 0, 

201 ] 

202 ): 

203 raise ValueError( 

204 "`num_files`, `min_file_size`, and `uncompressed_size` must be positive integers", 

205 ) 

206 if min_file_size * num_files > uncompressed_size: 

207 raise AssertionError( 

208 "`uncompressed_size` is smaller than the calculated minimum required size", 

209 ) 

210 if compression in ["bzip2", "bz2"]: 

211 compression_ = zipfile.ZIP_BZIP2 

212 elif compression in ["lzma", "xz"]: 

213 compression_ = zipfile.ZIP_LZMA 

214 elif compression in ["deflate", "gzip", "gz"]: 

215 compression_ = zipfile.ZIP_DEFLATED 

216 else: 

217 compression_ = zipfile.ZIP_STORED 

218 

219 zip_buffer = io.BytesIO() 

220 remaining_size = uncompressed_size 

221 with zipfile.ZipFile(zip_buffer, mode="w", compression=compression_) as zip_handle: 

222 for file_number in range(1, num_files + 1): 

223 filename = self.generator.pystr() + str(file_number) 

224 

225 max_allowed_size = remaining_size - (num_files - file_number) * min_file_size 

226 if file_number < num_files: 

227 file_size = self.generator.random.randint(min_file_size, max_allowed_size) 

228 remaining_size = remaining_size - file_size 

229 else: 

230 file_size = remaining_size 

231 

232 data = self.generator.binary(file_size) 

233 zip_handle.writestr(filename, data) 

234 return zip_buffer.getvalue() 

235 

236 def tar( 

237 self, 

238 uncompressed_size: int = 65536, 

239 num_files: int = 1, 

240 min_file_size: int = 4096, 

241 compression: Optional[str] = None, 

242 ) -> bytes: 

243 """Generate a bytes object containing a random valid tar file. 

244 

245 The number and sizes of files contained inside the resulting archive can be controlled 

246 using the following arguments: 

247 

248 - ``uncompressed_size`` - the total size of files before compression, 16 KiB by default 

249 - ``num_files`` - the number of files archived in resulting zip file, 1 by default 

250 - ``min_file_size`` - the minimum size of each file before compression, 4 KiB by default 

251 

252 No compression is used by default, but setting ``compression`` to one of the values listed 

253 below will use the corresponding compression type. 

254 

255 - ``'bzip2'`` or ``'bz2'`` for BZIP2 

256 - ``'lzma'`` or ``'xz'`` for LZMA 

257 - ``'gzip'`` or ``'gz'`` for GZIP 

258 

259 :sample: uncompressed_size=256, num_files=4, min_file_size=32 

260 :sample: uncompressed_size=256, num_files=32, min_file_size=4, compression='bz2' 

261 """ 

262 if any( 

263 [ 

264 not isinstance(num_files, int) or num_files <= 0, 

265 not isinstance(min_file_size, int) or min_file_size <= 0, 

266 not isinstance(uncompressed_size, int) or uncompressed_size <= 0, 

267 ] 

268 ): 

269 raise ValueError( 

270 "`num_files`, `min_file_size`, and `uncompressed_size` must be positive integers", 

271 ) 

272 if min_file_size * num_files > uncompressed_size: 

273 raise AssertionError( 

274 "`uncompressed_size` is smaller than the calculated minimum required size", 

275 ) 

276 if compression in ["gzip", "gz"]: 

277 mode = "w:gz" 

278 elif compression in ["bzip2", "bz2"]: 

279 mode = "w:bz2" 

280 elif compression in ["lzma", "xz"]: 

281 mode = "w:xz" 

282 else: 

283 mode = "w" 

284 

285 tar_buffer = io.BytesIO() 

286 remaining_size = uncompressed_size 

287 with tarfile.open(mode=mode, fileobj=tar_buffer) as tar_handle: 

288 for file_number in range(1, num_files + 1): 

289 file_buffer = io.BytesIO() 

290 filename = self.generator.pystr() + str(file_number) 

291 

292 max_allowed_size = remaining_size - (num_files - file_number) * min_file_size 

293 if file_number < num_files: 

294 file_size = self.generator.random.randint(min_file_size, max_allowed_size) 

295 remaining_size = remaining_size - file_size 

296 else: 

297 file_size = remaining_size 

298 

299 tarinfo = tarfile.TarInfo(name=filename) 

300 data = self.generator.binary(file_size) 

301 file_buffer.write(data) 

302 tarinfo.size = len(file_buffer.getvalue()) 

303 file_buffer.seek(0) 

304 tar_handle.addfile(tarinfo, file_buffer) 

305 file_buffer.close() 

306 return tar_buffer.getvalue() 

307 

308 def image( 

309 self, 

310 size: Tuple[int, int] = (256, 256), 

311 image_format: str = "png", 

312 hue: Optional[Union[int, Sequence[int], str]] = None, 

313 luminosity: Optional[str] = None, 

314 ) -> bytes: 

315 """Generate an image and draw a random polygon on it using the Python Image Library. 

316 Without it installed, this provider won't be functional. Returns the bytes representing 

317 the image in a given format. 

318 

319 The argument ``size`` must be a 2-tuple containing (width, height) in pixels. Defaults to 256x256. 

320 

321 The argument ``image_format`` can be any valid format to the underlying library like ``'tiff'``, 

322 ``'jpeg'``, ``'pdf'`` or ``'png'`` (default). Note that some formats need present system libraries 

323 prior to building the Python Image Library. 

324 Refer to https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html for details. 

325 

326 The arguments ``hue`` and ``luminosity`` are the same as in the color provider and are simply forwarded to 

327 it to generate both the background and the shape colors. Therefore, you can ask for a "dark blue" image, etc. 

328 

329 :sample: size=(2, 2), hue='purple', luminosity='bright', image_format='pdf' 

330 :sample: size=(16, 16), hue=[90,270], image_format='ico' 

331 """ 

332 try: 

333 import PIL.Image 

334 import PIL.ImageDraw 

335 except ImportError: 

336 raise UnsupportedFeature("`image` requires the `Pillow` python library.", "image") 

337 

338 (width, height) = size 

339 image = PIL.Image.new("RGB", size, self.generator.color(hue=hue, luminosity=luminosity)) 

340 draw = PIL.ImageDraw.Draw(image) 

341 draw.polygon( 

342 [(self.random_int(0, width), self.random_int(0, height)) for _ in range(self.random_int(3, 12))], 

343 fill=self.generator.color(hue=hue, luminosity=luminosity), 

344 outline=self.generator.color(hue=hue, luminosity=luminosity), 

345 ) 

346 with io.BytesIO() as fobj: 

347 image.save(fobj, format=image_format) 

348 fobj.seek(0) 

349 return fobj.read() 

350 

351 def dsv( 

352 self, 

353 dialect: str = "faker-csv", 

354 header: Optional[Sequence[str]] = None, 

355 data_columns: Tuple[str, str] = ("{{name}}", "{{address}}"), 

356 num_rows: int = 10, 

357 include_row_ids: bool = False, 

358 **fmtparams: Any, 

359 ) -> str: 

360 """Generate random delimiter-separated values. 

361 

362 This method's behavior share some similarities with ``csv.writer``. The ``dialect`` and 

363 ``**fmtparams`` arguments are the same arguments expected by ``csv.writer`` to control its 

364 behavior, and instead of expecting a file-like object to where output will be written, the 

365 output is controlled by additional keyword arguments and is returned as a string. 

366 

367 The ``dialect`` argument defaults to ``'faker-csv'`` which is the name of a ``csv.excel`` 

368 subclass with full quoting enabled. 

369 

370 The ``header`` argument expects a list or a tuple of strings that will serve as the header row 

371 if supplied. The ``data_columns`` argument expects a list or a tuple of string tokens, and these 

372 string tokens will be passed to :meth:`pystr_format() <faker.providers.python.Provider.pystr_format>` 

373 for data generation. Argument Groups are used to pass arguments to the provider methods. 

374 Both ``header`` and ``data_columns`` must be of the same length. 

375 

376 Example: 

377 fake.set_arguments('top_half', {'min_value': 50, 'max_value': 100}) 

378 fake.dsv(data_columns=('{{ name }}', '{{ pyint:top_half }}')) 

379 

380 The ``num_rows`` argument controls how many rows of data to generate, and the ``include_row_ids`` 

381 argument may be set to ``True`` to include a sequential row ID column. 

382 

383 :sample: dialect='excel', data_columns=('{{name}}', '{{address}}') 

384 :sample: dialect='excel-tab', data_columns=('{{name}}', '{{address}}'), include_row_ids=True 

385 :sample: data_columns=('{{name}}', '{{address}}'), num_rows=5, delimiter='$' 

386 """ 

387 

388 if not isinstance(num_rows, int) or num_rows <= 0: 

389 raise ValueError("`num_rows` must be a positive integer") 

390 if not isinstance(data_columns, (list, tuple)): 

391 raise TypeError("`data_columns` must be a tuple or a list") 

392 if header is not None: 

393 if not isinstance(header, (list, tuple)): 

394 raise TypeError("`header` must be a tuple or a list") 

395 if len(header) != len(data_columns): 

396 raise ValueError("`header` and `data_columns` must have matching lengths") 

397 

398 dsv_buffer = io.StringIO() 

399 writer = csv.writer(dsv_buffer, dialect=dialect, **fmtparams) 

400 

401 if header: 

402 if include_row_ids: 

403 header = list(header) 

404 header.insert(0, "ID") 

405 writer.writerow(header) 

406 

407 for row_num in range(1, num_rows + 1): 

408 row = [self.generator.pystr_format(column) for column in data_columns] 

409 if include_row_ids: 

410 row.insert(0, str(row_num)) 

411 

412 writer.writerow(row) 

413 

414 return dsv_buffer.getvalue() 

415 

416 def csv( 

417 self, 

418 header: Optional[Sequence[str]] = None, 

419 data_columns: Tuple[str, str] = ("{{name}}", "{{address}}"), 

420 num_rows: int = 10, 

421 include_row_ids: bool = False, 

422 ) -> str: 

423 """Generate random comma-separated values. 

424 

425 For more information on the different arguments of this method, please refer to 

426 :meth:`dsv() <faker.providers.misc.Provider.dsv>` which is used under the hood. 

427 

428 :sample: data_columns=('{{name}}', '{{address}}'), num_rows=10, include_row_ids=False 

429 :sample: header=('Name', 'Address', 'Favorite Color'), 

430 data_columns=('{{name}}', '{{address}}', '{{safe_color_name}}'), 

431 num_rows=10, include_row_ids=True 

432 """ 

433 return self.dsv( 

434 header=header, 

435 data_columns=data_columns, 

436 num_rows=num_rows, 

437 include_row_ids=include_row_ids, 

438 delimiter=",", 

439 ) 

440 

441 def tsv( 

442 self, 

443 header: Optional[Sequence[str]] = None, 

444 data_columns: Tuple[str, str] = ("{{name}}", "{{address}}"), 

445 num_rows: int = 10, 

446 include_row_ids: bool = False, 

447 ) -> str: 

448 """Generate random tab-separated values. 

449 

450 For more information on the different arguments of this method, please refer to 

451 :meth:`dsv() <faker.providers.misc.Provider.dsv>` which is used under the hood. 

452 

453 :sample: data_columns=('{{name}}', '{{address}}'), num_rows=10, include_row_ids=False 

454 :sample: header=('Name', 'Address', 'Favorite Color'), 

455 data_columns=('{{name}}', '{{address}}', '{{safe_color_name}}'), 

456 num_rows=10, include_row_ids=True 

457 """ 

458 return self.dsv( 

459 header=header, 

460 data_columns=data_columns, 

461 num_rows=num_rows, 

462 include_row_ids=include_row_ids, 

463 delimiter="\t", 

464 ) 

465 

466 def psv( 

467 self, 

468 header: Optional[Sequence[str]] = None, 

469 data_columns: Tuple[str, str] = ("{{name}}", "{{address}}"), 

470 num_rows: int = 10, 

471 include_row_ids: bool = False, 

472 ) -> str: 

473 """Generate random pipe-separated values. 

474 

475 For more information on the different arguments of this method, please refer to 

476 :meth:`dsv() <faker.providers.misc.Provider.dsv>` which is used under the hood. 

477 

478 :sample: data_columns=('{{name}}', '{{address}}'), num_rows=10, include_row_ids=False 

479 :sample: header=('Name', 'Address', 'Favorite Color'), 

480 data_columns=('{{name}}', '{{address}}', '{{safe_color_name}}'), 

481 num_rows=10, include_row_ids=True 

482 """ 

483 return self.dsv( 

484 header=header, 

485 data_columns=data_columns, 

486 num_rows=num_rows, 

487 include_row_ids=include_row_ids, 

488 delimiter="|", 

489 ) 

490 

491 def json(self, data_columns: Optional[List] = None, num_rows: int = 10, indent: Optional[int] = None) -> str: 

492 """ 

493 Generate random JSON structure values. 

494 

495 Using a dictionary or list of records that is passed as ``data_columns``, 

496 define the structure that is used to build JSON structures. For complex 

497 data structures it is recommended to use the dictionary format. 

498 

499 Data Column Dictionary format: 

500 {'key name': 'definition'} 

501 

502 The definition can be 'provider', 'provider:argument_group', tokenized 

503 'string {{ provider:argument_group }}' that is passed to the python 

504 provider method pystr_format() for generation, or a fixed '@word'. 

505 Using Lists, Tuples, and Dicts as a definition for structure. 

506 

507 Example: 

508 fake.set_arguments('top_half', {'min_value': 50, 'max_value': 100}) 

509 fake.json(data_columns={'Name': 'name', 'Score': 'pyint:top_half'}) 

510 

511 Data Column List format: 

512 [('key name', 'definition', {'arguments'})] 

513 

514 With the list format the definition can be a list of records, to create 

515 a list within the structure data. For literal entries within the list, 

516 set the 'field_name' to None. 

517 

518 :param data_columns: specification for the data structure 

519 :type data_columns: dict 

520 :param num_rows: number of rows the returned 

521 :type num_rows: int 

522 :param indent: number of spaces to indent the fields 

523 :type indent: int 

524 :return: Serialized JSON data 

525 :rtype: str 

526 

527 :sample: data_columns={'Spec': '@1.0.1', 'ID': 'pyint', 

528 'Details': {'Name': 'name', 'Address': 'address'}}, num_rows=2 

529 :sample: data_columns={'Candidates': ['name', 'name', 'name']}, 

530 num_rows=1 

531 :sample: data_columns=[('Name', 'name'), ('Points', 'pyint', 

532 {'min_value': 50, 'max_value': 100})], num_rows=1 

533 """ 

534 default_data_columns = { 

535 "name": "{{name}}", 

536 "residency": "{{address}}", 

537 } 

538 data_columns: Union[List, Dict] = data_columns if data_columns else default_data_columns 

539 

540 def process_list_structure(data: Sequence[Any]) -> Any: 

541 entry: Dict[str, Any] = {} 

542 

543 for name, definition, *arguments in data: 

544 kwargs = arguments[0] if arguments else {} 

545 

546 if not isinstance(kwargs, dict): 

547 raise TypeError("Invalid arguments type. Must be a dictionary") 

548 

549 if name is None: 

550 return self._value_format_selection(definition, **kwargs) 

551 

552 if isinstance(definition, tuple): 

553 entry[name] = process_list_structure(definition) 

554 elif isinstance(definition, (list, set)): 

555 entry[name] = [process_list_structure([item]) for item in definition] 

556 else: 

557 entry[name] = self._value_format_selection(definition, **kwargs) 

558 return entry 

559 

560 def process_dict_structure(data: Union[int, float, bool, Dict[str, Any]]) -> Any: 

561 entry: Dict[str, Any] = {} 

562 

563 if isinstance(data, str): 

564 return self._value_format_selection(data) 

565 

566 if isinstance(data, dict): 

567 for name, definition in data.items(): 

568 if isinstance(definition, (tuple, list, set)): 

569 entry[name] = [process_dict_structure(item) for item in definition] 

570 elif isinstance(definition, (dict, int, float, bool)): 

571 entry[name] = process_dict_structure(definition) 

572 else: 

573 entry[name] = self._value_format_selection(definition) 

574 return entry 

575 

576 return data 

577 

578 def create_json_structure(data_columns: Union[Dict, List]) -> dict: 

579 if isinstance(data_columns, dict): 

580 return process_dict_structure(data_columns) 

581 

582 if isinstance(data_columns, list): 

583 return process_list_structure(data_columns) 

584 

585 raise TypeError("Invalid data_columns type. Must be a dictionary or list") 

586 

587 if num_rows == 1: 

588 return json.dumps(create_json_structure(data_columns), indent=indent) 

589 

590 data = [create_json_structure(data_columns) for _ in range(num_rows)] 

591 return json.dumps(data, indent=indent) 

592 

593 def fixed_width(self, data_columns: Optional[list] = None, num_rows: int = 10, align: str = "left") -> str: 

594 """ 

595 Generate random fixed width values. 

596 

597 Using a list of tuple records that is passed as ``data_columns``, that 

598 defines the structure that will be generated. Arguments within the 

599 record are provider specific, and should be a dictionary that will be 

600 passed to the provider method. 

601 

602 Data Column List format 

603 [('field width', 'definition', {'arguments'})] 

604 

605 The definition can be 'provider', 'provider:argument_group', tokenized 

606 'string {{ provider:argument_group }}' that is passed to the python 

607 provider method pystr_format() for generation, or a fixed '@word'. 

608 Using Lists, Tuples, and Dicts as a definition for structure. 

609 

610 Argument Groups can be used to pass arguments to the provider methods, 

611 but will override the arguments supplied in the tuple record. 

612 

613 Example: 

614 fake.set_arguments('top_half', {'min_value': 50, 'max_value': 100}) 

615 fake.fixed_width(data_columns=[(20, 'name'), (3, 'pyint:top_half')]) 

616 

617 :param data_columns: specification for the data structure 

618 :type data_columns: list 

619 :param num_rows: number of rows the generator will yield 

620 :type num_rows: int 

621 :param align: positioning of the value. (left, middle, right) 

622 :type align: str 

623 :return: Serialized Fixed Width data 

624 :rtype: str 

625 

626 :sample: data_columns=[(20, 'name'), (3, 'pyint', {'min_value': 50, 

627 'max_value': 100})], align='right', num_rows=2 

628 """ 

629 default_data_columns = [ 

630 (20, "name"), 

631 (3, "pyint", {"max_value": 20}), 

632 ] 

633 data_columns = data_columns if data_columns else default_data_columns 

634 align_map = { 

635 "left": "<", 

636 "middle": "^", 

637 "right": ">", 

638 } 

639 data = [] 

640 

641 for _ in range(num_rows): 

642 row = [] 

643 

644 for width, definition, *arguments in data_columns: 

645 kwargs = arguments[0] if arguments else {} 

646 

647 if not isinstance(kwargs, dict): 

648 raise TypeError("Invalid arguments type. Must be a dictionary") 

649 

650 result = self._value_format_selection(definition, **kwargs) 

651 row.append(f'{result:{align_map.get(align, "<")}{width}}'[:width]) 

652 

653 data.append("".join(row)) 

654 return "\n".join(data) 

655 

656 def _value_format_selection(self, definition: str, **kwargs: Any) -> Union[int, str]: 

657 """ 

658 Formats the string in different ways depending on it's contents. 

659 

660 The return can be the '@word' itself, a '{{ token }}' passed to PyStr, 

661 or a 'provider:argument_group' format field that returns potentially 

662 a non-string type. 

663 

664 This ensures that Numbers, Boolean types that are generated in the 

665 JSON structures in there proper type, and not just strings. 

666 """ 

667 

668 # Check for PyStr first as complex strings may start with @ 

669 if re.match(r".*\{\{.*\}\}.*", definition): 

670 return self.generator.pystr_format(definition) 

671 

672 # Check for fixed @words that won't be generated 

673 if re.match(r"^@.*", definition): 

674 return definition.lstrip("@") 

675 

676 # Check if a argument group has been supplied 

677 if re.match(r"^[a-zA-Z0-9_-]*:\w", definition): 

678 definition, argument_group = definition.split(":") 

679 arguments = self.generator.get_arguments(argument_group.strip()) 

680 

681 return self.generator.format(definition.strip(), **arguments) 

682 

683 # Assume the string is refering to a provider 

684 return self.generator.format(definition, **kwargs)