Source code for fake

"""
https://github.com/barseghyanartur/fake.py/
"""

import array
import ast
import asyncio
import contextlib
import io
import locale
import logging
import math
import mimetypes
import os
import random
import re
import secrets
import string
import struct
import subprocess
import tarfile
import unittest
import uuid
import wave
import zipfile
import zlib
import zoneinfo
from abc import abstractmethod
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
from calendar import monthrange
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor
from copy import deepcopy
from dataclasses import dataclass, field, fields, is_dataclass
from datetime import date, datetime, time, timedelta, timezone
from decimal import Decimal
from email.message import EmailMessage
from email.policy import Policy, default
from email.utils import parseaddr
from functools import partial
from inspect import signature
from io import BytesIO
from pathlib import Path
from tempfile import gettempdir, mktemp
from textwrap import wrap
from threading import Lock
from typing import (
    Any,
    Awaitable,
    Callable,
    Coroutine,
    Dict,
    Iterable,
    List,
    Literal,
    Optional,
    Sequence,
    Set,
    TextIO,
    Tuple,
    Type,
    TypeVar,
    Union,
    get_args,
    get_origin,
    get_type_hints,
)
from unittest.mock import MagicMock, patch
from uuid import UUID

__title__ = "fake.py"
__version__ = "0.13.1"
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
__copyright__ = "2023-2025 Artur Barseghyan"
__license__ = "MIT"
__all__ = (
    "AuthorshipData",
    "BaseStorage",
    "CLI",
    "DjangoModelFactory",
    "DocxGenerator",
    "EpubGenerator",
    "FACTORY",
    "FAKER",
    "FILE_REGISTRY",
    "Factory",
    "FactoryMethod",
    "Faker",
    "FileRegistry",
    "FileSystemStorage",
    "GraphicPdfGenerator",
    "LazyAttribute",
    "LazyFunction",
    "LazyStringTemplate",
    "MetaData",
    "ModelFactory",
    "OdtGenerator",
    "PROVIDER_REGISTRY",
    "PostSave",
    "PreInit",
    "PreSave",
    "PydanticModelFactory",
    "RtfGenerator",
    "SQLAlchemyModelFactory",
    "StringTemplate",
    "StringValue",
    "SubFactory",
    "TestCLI",
    "TestFaker",
    "TestLazyStringTemplate",
    "TestOrganiseProviders",
    "TestScript",
    "TestSlugify",
    "TestStringTemplate",
    "TextPdfGenerator",
    "TortoiseModelFactory",
    "create_inner_bmp_file",
    "create_inner_docx_file",
    "create_inner_eml_file",
    "create_inner_gif_file",
    "create_inner_generic_file",
    "create_inner_jpg_file",
    "create_inner_odt_file",
    "create_inner_pdf_file",
    "create_inner_png_file",
    "create_inner_ppm_file",
    "create_inner_rtf_file",
    "create_inner_svg_file",
    "create_inner_tar_file",
    "create_inner_text_pdf_file",
    "create_inner_tif_file",
    "create_inner_txt_file",
    "create_inner_wav_file",
    "create_inner_zip_file",
    "fill_dataclass",
    "fill_pydantic_model",
    "format_type_hint",
    "fuzzy_choice_create_inner_file",
    "get_argparse_type",
    "get_provider_args",
    "get_provider_defaults",
    "is_optional_type",
    "list_create_inner_file",
    "main",
    "organise_providers",
    "post_save",
    "pre_init",
    "pre_save",
    "provider",
    "run_async_in_thread",
    "resolve_inner_directory",
    "slugify",
    "trait",
    "wrap_text",
    "xor_transform",
)

LOGGER = logging.getLogger(__name__)
T = TypeVar("T")
ElementType = Sequence[T]

# ************************************************
# ******************* Public *********************
# ************************************************

IMAGE_SERVICES = (
    "https://picsum.photos/{width}/{height}",
    "https://dummyimage.com/{width}x{height}",
    "https://placekitten.com/{width}/{height}",
    "https://loremflickr.com/{width}/{height}",
)

FREE_EMAIL_DOMAINS = (
    "gmail.com",
    "hotmail.com",
    "mail.com",
    "outlook.com",
    "proton.me",
    "protonmail.com",
    "yahoo.com",
)

TLDS = (
    "com",
    "org",
    "net",
    "io",
)

URL_PROTOCOLS = (
    "http",
    "https",
)

URL_SUFFIXES = (
    ".html",
    ".php",
    ".go",
    "",
    "/",
)

FILE_TYPES = mimetypes.types_map
FILE_EXTENSIONS = [__v[1:] for __v in FILE_TYPES.keys()]  # noqa: SIM118
MIME_TYPES = list(FILE_TYPES.values())

UNWANTED_GEO_PATTERN = re.compile(
    r"^([A-Z0-9-+]+$|GB.*|localtime|Universal|Etc|Factory)"
)

TAR_COMPRESSION_OPTIONS = {"gz", "bz2", "xz"}

PDF_TEXT_TPL_PAGE_OBJECT = """{page_num} 0 obj
<</Type /Page
/Parent 3 0 R
/Resources 2 0 R
/Contents {content_obj_num} 0 R
>>
endobj
"""

PDF_TEXT_TPL_CONTENT_OBJECT = """{obj_num} 0 obj
<</Length {stream_length}>>
stream
{content}
endstream
6 0 obj
<</Type /Font /Subtype /Type1 /BaseFont /Helvetica>>
endobj
"""

PDF_GRAPHIC_TPL_IMAGE_OBJECT = """{obj_num} 0 obj
<</Type /XObject
/Subtype /Image
/Width {width}
/Height {height}
/ColorSpace /DeviceRGB
/BitsPerComponent 8
/Filter /FlateDecode
/Length {stream_length}>>
stream
"""

PDF_GRAPHIC_TPL_PAGE_OBJECT = """{page_obj_num} 0 obj
<</Type /Page
/Parent 3 0 R
/Resources <</XObject <</Im{image_obj_num} {image_obj_num} 0 R>> >>
/Contents {content_obj_num} 0 R
>>
endobj
"""

PDF_GRAPHIC_TPL_CONTENT_OBJECT = """{content_obj_num} 0 obj
<</Length 44>>
stream
q
100 0 0 100 0 0 cm
/Im{image_obj_num} Do
Q
endstream
endobj
"""

PDF_GRAPHIC_TPL_PAGES_OBJECT = """3 0 obj
<</Type /Pages
/Kids [{pages_kids}]
/Count {num_pages}
>>
endobj
"""

PDF_GRAPHIC_TPL_CATALOG_OBJECT = """1 0 obj
<</Type /Catalog
/Pages 3 0 R
>>
endobj
"""

PDF_GRAPHIC_TPL_TRAILER_OBJECT = """trailer
<</Size 6
/Root 1 0 R>>
startxref
"""

SVG_TPL = """
<svg width="{width}px" height="{height}px" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="rgb{color}" />
</svg>"""

DOCX_TPL_DOC_HEADER = (
    '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
    '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'  # noqa
    "<w:body>"
)

DOCX_TPL_DOC_FOOTER = "</w:body></w:document>"

DOC_TPL_DOC_STRUCTURE_RELS = (
    b"<?xml version='1.0' encoding='UTF-8' standalone='yes'?>"
    b"<Relationships xmlns='http://schemas.openxmlformats.org/package/2006/relationships'>"  # noqa
    b"<Relationship Id='rId1' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument' Target='word/document.xml'/>"  # noqa
    b"</Relationships>"
)

DOC_TPL_DOC_STRUCTURE_WORD_RELS = (
    b"<?xml version='1.0' encoding='UTF-8' standalone='yes'?>"
    b"<Relationships xmlns='http://schemas.openxmlformats.org/package/2006/relationships'>"  # noqa
    b"<Relationship Id='rId1' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles' Target='styles.xml'/>"  # noqa
    b"</Relationships>"
)

DOC_TPL_DOC_STRUCTURE_WORD_STYLES = (
    b"<?xml version='1.0' encoding='UTF-8' standalone='yes'?>"
    b"<w:styles xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>"  # noqa
    b"<w:style w:type='paragraph' w:default='1' w:styleId='Normal'>"
    b"<w:name w:val='Normal'/><w:qFormat/></w:style></w:styles>"
)

DOC_TPL_DOC_STRUCTURE_CONTENT_TYPES = (
    b"<?xml version='1.0' encoding='UTF-8' standalone='yes'?>"
    b"<Types xmlns='http://schemas.openxmlformats.org/package/2006/content-types'>"  # noqa
    b"<Default Extension='rels' ContentType='application/vnd.openxmlformats-package.relationships+xml'/>"  # noqa
    b"<Default Extension='xml' ContentType='application/xml'/>"
    b"<Override PartName='/word/document.xml' ContentType='application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml'/>"  # noqa
    b"<Override PartName='/word/styles.xml' ContentType='application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml'/>"  # noqa
    b"</Types>"
)

RTF_HEADER = r"{\rtf1\ansi\deff0"
RTF_FONT_TABLE = r"{\fonttbl{\f0\fnil\fcharset0 Arial;}}"
RTF_COLOR_TABLE = r"{\colortbl ;\red0\green0\blue0;}"
RTF_DOCUMENT_START = "\n"
RTF_DOCUMENT_END = "\n}"

EPUB_MIMETYPE = b"application/epub+zip"
EPUB_CONTAINER_XML = b"""<?xml version="1.0"?>
<container version="1.0"
           xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
  <rootfiles>
    <rootfile full-path="OEBPS/content.opf"
              media-type="application/oebps-package+xml"/>
  </rootfiles>
</container>"""

# Missing/specific custom types
MISSING_MIMETYPES = {
    ".docx": "application/"
    "vnd.openxmlformats-officedocument.wordprocessingml.document",
    ".pptx": "application/"
    "vnd.openxmlformats-officedocument.presentationml.presentation",
    ".xlsx": "application/"
    "vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    ".epub": "application/epub+zip",
    ".odt": "application/vnd.oasis.opendocument.text",
    ".rtf": "application/rtf",
}

SLUGIFY_RE = re.compile(r"[^a-zA-Z0-9]")

TEMP_DIR = gettempdir()


[docs] def slugify(value: str, separator: str = "") -> str: """Slugify.""" return SLUGIFY_RE.sub(separator, value).lower()
[docs] def resolve_inner_directory(file: "StringValue", directory: str) -> Path: """Resolve the in-archive path for an inner file. Combines three components: 1. *directory* — the static ``options["directory"]`` prefix supplied by the caller of ``zip()`` / ``tar()``. 2. ``file.data.get("directory", "")`` — an optional subdirectory set by the creator function via its ``dir_path`` parameter. 3. ``Path(file).name`` — the bare filename of the generated file, derived from the real on-disk path as always. When ``data["directory"]`` is absent (i.e. ``dir_path`` was not passed to the creator), this returns the same result as the current ``Path(directory) / Path(file).name`` expression, preserving full backwards compatibility. :param file: The ``StringValue`` returned by a creator function. :param directory: The ``options["directory"]`` prefix (may be ``""``). :return: The resolved in-archive path. :rtype: Path """ inner_dir = file.data.get("directory", "") safe_components = [] for part in inner_dir.replace("\\", "/").split("/"): if part and part != "." and part != "..": if len(part) == 2 and part[1] == ":": continue safe_components.append(part) safe_inner = "/".join(safe_components) safe_dir_parts = [] for part in directory.replace("\\", "/").split("/"): if part and part != "." and part != "..": if len(part) == 2 and part[1] == ":": continue safe_dir_parts.append(part) safe_dir = "/".join(safe_dir_parts) if safe_inner: return Path(safe_dir) / safe_inner / Path(file).name return Path(safe_dir) / Path(file).name
def get_mime_maintype_subtype(path: str) -> Tuple[str, str]: """ Determine the MIME maintype and subtype for the given file path, using Python's built-in mimetypes plus a few custom entries. :param path: Path to the file (extension is used for lookup). :return: (maintype, subtype) tuple, for example ("application", "pdf") :rtype: Tuple[str, str] """ # Initialize the system-wide mappings mimetypes.init() for ext, ctype in MISSING_MIMETYPES.items(): mimetypes.add_type(ctype, ext, strict=True) # Prefer `guess_file_type` on Python 3.13+, else `guess_type` if hasattr(mimetypes, "guess_file_type"): ctype, encoding = mimetypes.guess_file_type(path) else: ctype, encoding = mimetypes.guess_type(path) # If unknown or if an encoding hint was returned, fall back safely if ctype is None or encoding is not None: ctype = "application/octet-stream" # Split into maintype/subtype maintype, subtype = ctype.split("/", 1) return maintype, subtype
[docs] class MetaData: __slots__ = ("content",) def __init__(self): self.content: Optional[str] = None
[docs] def add_content(self, content: Union[str, List[str]]) -> None: if isinstance(content, list): self.content = "\n".join(content) else: self.content = content
[docs] class StringValue(str): __slots__ = ("data",) data: Dict[str, Any] def __new__( cls: Type["StringValue"], value: str, *args, **kwargs, ) -> "StringValue": obj = str.__new__(cls, value) obj.data = {} return obj
class BytesValue(bytes): data: Dict[str, Any] def __new__( cls: Type["BytesValue"], value: bytes, *args, **kwargs, ) -> "BytesValue": obj = bytes.__new__(cls, value) obj.data = {} return obj def returns_list(func: Callable) -> bool: """Checks if callable returns a list of `StringValue`. Returns True if it's a List. Returns False otherwise. """ try: return_type = get_type_hints(func).get("return", None) except Exception: return False if not return_type: return False return_origin = getattr(return_type, "__origin__", None) if return_origin is list or return_origin is List: # If it's a list, check the type of its elements element_type = getattr(return_type, "__args__", [None])[0] if element_type in {StringValue, BytesValue}: return True element_origin = getattr(element_type, "__origin__", None) if element_origin is Union and set( getattr(element_type, "__args__", []) ) == { BytesValue, StringValue, }: return True return False
[docs] def wrap_text(text: str, wrap_chars_after: int) -> str: return "\n".join( wrap( text=text, width=wrap_chars_after, replace_whitespace=False, # drop_whitespace=False, ) )
class StringTemplateMixin: template: str wrap_chars_after: Optional[int] faker: Optional["Faker"] # Regular expression to match placeholders with optional arguments placeholder_pattern = re.compile(r"\{(\w+)(?:\((.*?)\))?}") def render(self) -> str: """Substitute all placeholders in the template with corresponding method calls on the object. :rtype: str :return: The formatted string with all placeholders replaced. """ content = self.placeholder_pattern.sub(self.replacer, self.template) if self.wrap_chars_after: content = wrap_text(content, self.wrap_chars_after) return content def replacer(self, match: re.Match) -> str: """Replacement method to process each regex match. :param match: The regex match object. :raises ValueError: If there is an error parsing arguments or calling the method. :raises AttributeError: If the method does not exist on the object. :return: The replacement string. :rtype: str """ method_name = match.group(1) args_str = match.group(2) # Parse arguments if any args = [] kwargs = {} if args_str: try: # Safely parse the arguments using ast.literal_eval # We wrap the arguments in a dummy function to parse them # Example: f('morning', format='%A, %B %d, %Y') parsed_args = ast.parse(f"f({args_str})", mode="eval").body for arg in parsed_args.args: args.append(ast.literal_eval(arg)) # noqa: PERF401 for kw in parsed_args.keywords: kwargs[kw.arg] = ast.literal_eval(kw.value) except Exception as err: raise ValueError( f"Error parsing arguments for '{method_name}': {err}" ) from err # Get the method from the object method = getattr(self.faker, method_name, None) if callable(method): try: return str(method(*args, **kwargs)) except Exception as err: raise ValueError( f"Error calling method '{method_name}': {err}" ) from err else: raise AttributeError( f"Method '{method_name}' not found in the " f"object '{self.faker.__class__.__name__}'." ) def __str__(self) -> str: return self.render() def __repr__(self) -> str: return self.render() def __iter__(self): # Allows iteration as a string return iter(str(self)) def __getitem__(self, index): # Allows indexing like a string return str(self)[index]
[docs] class StringTemplate(str, StringTemplateMixin): """LazyStringTemplate. Usage example: .. code-block:: python from fake import FAKER, StringTemplate template = ''' {date(start_date='-7d')}, Dear {name}, {sentence(nb_words=25)} Best regards, {name} ''' string_template = StringTemplate(template) print(string_template) Integration with providers: .. code-block:: python from fake import FAKER, StringTemplate template = ''' {date(start_date='-7d')}, Dear {name}, {sentence(nb_words=25)} Best regards, {name} ''' string_template = StringTemplate(template) FAKER.docx_file( texts=[StringTemplate(template) for _ in range(10)] ) FAKER.eml_file(content=string_template) FAKER.txt_file(text=string_template) FAKER.text_pdf_file( texts=[StringTemplate(template) for _ in range(10)] ) """ def __new__( cls, template: str, wrap_chars_after: Optional[int] = None, faker: Optional["Faker"] = None, ) -> "StringTemplate": # Create a temporary instance to use render instance = super().__new__(cls, "") instance.faker = faker or FAKER instance.template = template instance.wrap_chars_after = wrap_chars_after # Render the content rendered = instance.render() # Return the new string instance with the rendered content return super().__new__(cls, rendered)
[docs] class LazyStringTemplate(StringTemplateMixin): """LazyStringTemplate. Usage example: .. code-block:: python from fake import FAKER, LazyStringTemplate template = ''' {date(start_date='-7d')}, Dear {name}, {sentence(nb_words=25)} Best regards, {name} ''' string_template = LazyStringTemplate(template) print(string_template.render()) Integration with providers: .. code-block:: python from fake import FAKER, LazyStringTemplate template = ''' {date(start_date='-7d')}, Dear {name}, {sentence(nb_words=25)} Best regards, {name} ''' string_template = LazyStringTemplate(template) FAKER.docx_file(texts=[str(string_template)]) FAKER.eml_file(content=str(string_template)) FAKER.txt_file(text=str(string_template)) FAKER.text_pdf_file(texts=[str(string_template) for _ in range(10)]) """ def __init__( self, template: str, wrap_chars_after: Optional[int] = None, faker: Optional["Faker"] = None, ) -> None: self.faker = faker or FAKER self.template = template self.wrap_chars_after = wrap_chars_after
[docs] class FileRegistry: """Stores list `StringValue` instances. .. code-block:: python from fake import FAKER, FILE_REGISTRY txt_file_1 = FAKER.txt_file() txt_file_2 = FAKER.txt_file() ... txt_file_n = FAKER.txt_file() # The FileRegistry._registry would then contain this: { txt_file_1, txt_file_2, ..., txt_file_n, } # Clean up created files as follows: FILE_REGISTRY.clean_up() """ def __init__(self) -> None: self._registry: Set[StringValue] = set() self._lock = Lock()
[docs] def add(self, string_value: StringValue) -> None: with self._lock: self._registry.add(string_value)
[docs] def remove(self, string_value: Union[StringValue, str]) -> bool: if not isinstance(string_value, StringValue): string_value = self.search(string_value) # type: ignore if not string_value: return False with self._lock: # No error if the element doesn't exist self._registry.discard(string_value) # type: ignore try: string_value.data["storage"].unlink( # type: ignore string_value.data["filename"] # type: ignore ) return True except Exception as e: LOGGER.error( f"Failed to unlink file " f"{string_value.data['filename']}: {e}" # type: ignore ) return False
[docs] def search(self, value: str) -> Optional[StringValue]: with self._lock: for string_value in self._registry: if string_value == value: return string_value return None
[docs] def clean_up(self) -> None: with self._lock: while self._registry: file = self._registry.pop() try: file.data["storage"].unlink(file.data["filename"]) except Exception as err: LOGGER.error( f"Failed to unlink file {file.data['filename']}: {err}" )
FILE_REGISTRY = FileRegistry()
[docs] class BaseStorage: """Base storage.""" def __init__(self, *args, **kwargs) -> None: self.args = args self.kwargs = kwargs
[docs] def generate_basename( self: "BaseStorage", prefix: str = "tmp", length: int = 8, ) -> str: """Generate a random alphanumeric sequence.""" if not prefix: prefix = "tmp" # Use lowercase letters, digits and underscore characters = string.ascii_lowercase + string.digits + "_" return prefix + "".join(random.choices(characters, k=length))
[docs] @abstractmethod def generate_filename( self: "BaseStorage", extension: str, prefix: Optional[str] = None, basename: Optional[str] = None, ) -> Any: """Generate filename."""
[docs] @abstractmethod def write_text( self: "BaseStorage", filename: Any, data: str, encoding: Optional[str] = None, ) -> int: """Write text."""
[docs] @abstractmethod def write_bytes(self: "BaseStorage", filename: Any, data: bytes) -> int: """Write bytes."""
[docs] @abstractmethod def exists(self: "BaseStorage", filename: Any) -> bool: """Check if file exists."""
[docs] @abstractmethod def relpath(self: "BaseStorage", filename: Any) -> str: """Return relative path."""
[docs] @abstractmethod def abspath(self: "BaseStorage", filename: Any) -> str: """Return absolute path."""
[docs] class FileSystemStorage(BaseStorage): """File storage class using pathlib for path handling. Usage example: .. code-block:: python from fake import FAKER, FileSystemStorage storage = FileSystemStorage() docx_file = storage.generate_filename(prefix="zzz_", extension="docx") storage.write_bytes(docx_file, FAKER.docx()) Initialization with params: .. code-block:: python from fake import FAKER, FileSystemStorage storage = FileSystemStorage() docx_file = FAKER.docx_file(storage=storage) """ def __init__( self: "FileSystemStorage", root_path: Optional[Union[str, Path]] = TEMP_DIR, rel_path: Optional[str] = "tmp", *args, **kwargs, ) -> None: """ :param root_path: Path of your files root directory (e.g., Django's `settings.MEDIA_ROOT`). :param rel_path: Relative path (from root directory). :param *args: :param **kwargs: """ self.root_path = Path(root_path or "") self.rel_path = Path(rel_path or "") super().__init__(*args, **kwargs)
[docs] def generate_filename( self: "FileSystemStorage", extension: str, prefix: Optional[str] = None, basename: Optional[str] = None, ) -> str: """Generate filename.""" dir_path = self.root_path / self.rel_path dir_path.mkdir(parents=True, exist_ok=True) if not extension: raise ValueError("Extension shall be given!") if not basename: basename = self.generate_basename(prefix) return str(dir_path / f"{basename}.{extension}")
[docs] def write_text( self: "FileSystemStorage", filename: str, data: str, encoding: Optional[str] = None, ) -> int: """Write text.""" path = Path(filename) path.write_text(data, encoding=encoding or "utf-8") return len(data)
[docs] def write_bytes( self: "FileSystemStorage", filename: str, data: bytes, ) -> int: """Write bytes.""" path = Path(filename) path.write_bytes(data) return len(data)
[docs] def exists(self: "FileSystemStorage", filename: str) -> bool: """Check if file exists.""" file_path = Path(filename) if file_path.is_absolute(): return file_path.exists() return (self.root_path / file_path).exists()
[docs] def relpath(self: "FileSystemStorage", filename: str) -> str: """Return relative path.""" return str(Path(filename).relative_to(self.root_path))
[docs] def abspath(self: "FileSystemStorage", filename: str) -> str: """Return absolute path.""" file_path = Path(filename) if file_path.is_absolute(): return str(file_path.resolve()) return str((self.root_path / file_path).resolve())
[docs] class TextPdfGenerator: """Text PDF generatr. Usage example: .. code-block:: python from pathlib import Path from fake import FAKER, TextPdfGenerator Path("/tmp/text_example.pdf").write_bytes( FAKER.pdf(nb_pages=100, generator=TextPdfGenerator) ) """ nb_pages: int texts: List[str] def __init__(self, faker: "Faker") -> None: self.faker = faker def _add_page_object(self, page_num, content_obj_num): return PDF_TEXT_TPL_PAGE_OBJECT.format( page_num=page_num, content_obj_num=content_obj_num, ) def _add_content_object(self, obj_num, page_text): content = f"BT /F1 24 Tf 100 700 Td ({page_text}) Tj ET" stream_length = len(content) return PDF_TEXT_TPL_CONTENT_OBJECT.format( obj_num=obj_num, stream_length=stream_length, content=content, )
[docs] def create( self, nb_pages: Optional[int] = None, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, **kwargs, ) -> bytes: # Initialization if not nb_pages and not texts: raise ValueError( "Either `nb_pages` or `texts` arguments shall be given." ) if texts: self.nb_pages: int = len(texts) self.texts = texts else: self.nb_pages: int = nb_pages or 1 self.texts = self.faker.sentences(nb=self.nb_pages) if metadata: metadata.add_content(self.texts) # Construction pdf_bytes = io.BytesIO() pdf_bytes.write(b"%PDF-1.4\n") pdf_bytes.write(b"1 0 obj\n<</Type /Catalog/Pages 3 0 R>>\nendobj\n") pdf_bytes.write(b"2 0 obj\n<</Font <</F1 6 0 R>>>>\nendobj\n") pdf_bytes.write(b"3 0 obj\n<</Type /Pages/Kids [") page_objs = [] content_objs = [] for i, page_text in enumerate(self.texts): page_obj_num = 4 + 2 * i content_obj_num = page_obj_num + 1 page_objs.append( self._add_page_object(page_obj_num, content_obj_num) ) content_objs.append( self._add_content_object(content_obj_num, page_text) ) pdf_bytes.write(f"{page_obj_num} 0 R ".encode()) pdf_bytes.write(f"] /Count {str(self.nb_pages)}>>\nendobj\n".encode()) for page_obj in page_objs: pdf_bytes.write(page_obj.encode()) for content_obj in content_objs: pdf_bytes.write(content_obj.encode()) pdf_bytes.write(f"xref\n0 {str(4 + 2 * self.nb_pages)}\n".encode()) pdf_bytes.write(b"0000000000 65535 f \n") pdf_bytes.write( b"0000000010 00000 n \n0000000057 00000 n \n0000000103 00000 n \n" ) offset = 149 for _ in range(self.nb_pages): pdf_bytes.write(f"{offset:010} 00000 n \n".encode()) offset += 78 pdf_bytes.write(f"{offset:010} 00000 n \n".encode()) offset += 73 pdf_bytes.write( f"trailer\n<</Size {str(4 + 2 * self.nb_pages)}/Root 1 0 R>>\n" f"".encode() ) pdf_bytes.write(b"startxref\n564\n%%EOF") return pdf_bytes.getvalue()
[docs] class GraphicPdfGenerator: """Graphic PDF generatr. Usage example: .. code-block:: python from pathlib import Path from fake import FAKER, GraphicPdfGenerator Path("/tmp/graphic_example.pdf").write_bytes( FAKER.pdf(nb_pages=100, generator=GraphicPdfGenerator) ) """ nb_pages: int image_size: Tuple[int, int] image_color: Tuple[int, int, int] def __init__(self, faker: "Faker") -> None: self.faker = faker def _create_raw_image_data(self): width, height = self.image_size # Create uncompressed raw RGB data raw_data = bytes(self.image_color) * width * height return zlib.compress(raw_data) def _add_image_object(self, pdf_bytes, obj_num): width, height = self.image_size image_stream = self._create_raw_image_data() stream_length = len(image_stream) pdf_bytes.write( PDF_GRAPHIC_TPL_IMAGE_OBJECT.format( obj_num=obj_num, width=width, height=height, stream_length=stream_length, ).encode() ) pdf_bytes.write(image_stream) pdf_bytes.write(b"\nendstream\nendobj\n")
[docs] def create( self, nb_pages: int = 1, image_size: Tuple[int, int] = (100, 100), image_color: Tuple[int, int, int] = (255, 0, 0), **kwargs, ) -> bytes: # Initialization self.nb_pages = nb_pages self.image_size = image_size self.image_color = image_color # Construction pdf_bytes = io.BytesIO() pdf_bytes.write(b"%PDF-1.0\n") # Image object number image_obj_num = 4 # Positions in the PDF for the xref table positions = [pdf_bytes.tell()] # Add image object self._add_image_object(pdf_bytes, image_obj_num) positions.append(pdf_bytes.tell()) # Add pages for i in range(self.nb_pages): page_obj_num = 5 + i content_obj_num = page_obj_num + self.nb_pages pdf_bytes.write( PDF_GRAPHIC_TPL_PAGE_OBJECT.format( page_obj_num=page_obj_num, image_obj_num=image_obj_num, content_obj_num=content_obj_num, ).encode() ) positions.append(pdf_bytes.tell()) # Content stream that uses the image pdf_bytes.write( PDF_GRAPHIC_TPL_CONTENT_OBJECT.format( content_obj_num=content_obj_num, image_obj_num=image_obj_num, ).encode() ) positions.append(pdf_bytes.tell()) # Pages object pages_kids = " ".join([f"{5 + i} 0 R" for i in range(self.nb_pages)]) pdf_bytes.write( PDF_GRAPHIC_TPL_PAGES_OBJECT.format( pages_kids=pages_kids, num_pages=self.nb_pages, ).encode() ) positions.append(pdf_bytes.tell()) # Catalog object pdf_bytes.write(PDF_GRAPHIC_TPL_CATALOG_OBJECT.encode()) positions.append(pdf_bytes.tell()) # xref table pdf_bytes.write(b"xref\n0 1\n0000000000 65535 f \n") for pos in positions: pdf_bytes.write(f"{pos:010} 00000 n \n".encode()) # Trailer pdf_bytes.write(PDF_GRAPHIC_TPL_TRAILER_OBJECT.encode()) pdf_bytes.write(f"{positions[-1]}\n".encode()) pdf_bytes.write(b"%%EOF") return pdf_bytes.getvalue()
[docs] class AuthorshipData: _authorship_data: Dict[str, List[str]] = {} first_names: Set[str] = set() last_names: Set[str] = set() def _extract_info(self, file: TextIO) -> List[str]: return [ line.strip() for line in file if "__author__" in line or "Author:" in line ] def _find_authorship_info(self, file_path: str) -> List[str]: try: with open(file_path, "r", encoding="utf-8") as file: return self._extract_info(file) except UnicodeDecodeError: with open(file_path, "r", encoding="latin-1") as file: return self._extract_info(file) def _extract_authorship_info_from_stdlib(self) -> None: stdlib_path = os.path.dirname(os.__file__) for root, _, files in os.walk(stdlib_path): for file in files: if file.endswith(".py"): file_path = os.path.join(root, file) authorship_info = self._find_authorship_info(file_path) if authorship_info: self._authorship_data[file_path] = authorship_info def _extract_names(self) -> None: """Extract first and last names from authorship information. Ensures that multi-part last names are treated as a single entity. """ # Patterns for different cases patterns = { # For simple cases like '# Author: <author>' "simple": r"# Author: ([\w\s\-\']+)", # For cases like '__author__ = "<author> <email>"' "email_in_brackets": r'__author__\s*=\s*"([\w\s\-\']+)', # For multiple authors like '# Author: <author>, <author>' "multiple_authors": r"# Author: ([\w\s\-\']+), ([\w\s\-\']+)", # For cases like '# Author: <author>, <email>' "author_with_email": r"# Author: ([\w\s\-\']+), \w+@[\w\.-]+", } for info_list in self._authorship_data.values(): for info in info_list: # Ignoring anything after '--', emails, and dates info = re.sub( ( r"--.*|<[\w\.-]+@[\w\.-]+>|\b\d{4}\b|\bJanuary\b|" r"\bFebruary\b|\bMarch\b|\bApril\b|\bMay\b|\bJune\b|" r"\bJuly\b|\bAugust\b|\bSeptember\b|\bOctober\b|" r"\bNovember\b|\bDecember\b" ), "", info, ) for pattern in patterns.values(): found_names = re.findall(pattern, info) for name in found_names: if isinstance(name, tuple): # In case of multiple authors for n in name: split_name = n.strip().split() if len(split_name) >= 2: if split_name[0] not in {"The"}: self.first_names.add(split_name[0]) self.last_names.add( " ".join(split_name[1:]) ) # Joining multi-part last names else: split_name = name.strip().split() if len(split_name) >= 2: if split_name[0] not in {"The"}: self.first_names.add(split_name[0]) self.last_names.add( " ".join(split_name[1:]) ) # Joining multi-part last names def __init__(self): self._extract_authorship_info_from_stdlib() self._extract_names()
[docs] class DocxGenerator: """DocxGenerator - generates a DOCX file with text. Usage example: .. code-block:: python from pathlib import Path from fake import FAKER Path("/tmp/example.docx").write_bytes( DocxGenerator(FAKER).create(nb_pages=100) ) """ def __init__(self, faker: "Faker") -> None: self.faker = faker def _create_page(self, text: str, is_last_page: bool) -> str: # Split the text into lines by \n lines = text.split("\n") # Wrap each line in the required XML and add a line break between them page_content = "<w:p>" for line in lines: page_content += f"<w:r><w:t>{line}</w:t></w:r><w:br/>" page_content += "</w:p>" # Add a page break if this is not the last page if not is_last_page: page_content += '<w:p><w:r><w:br w:type="page"/></w:r></w:p>' return page_content
[docs] def create( self, nb_pages: Optional[int] = None, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, ) -> bytes: if not nb_pages and not texts: raise ValueError( "Either `nb_pages` or `texts` arguments shall be given." ) if texts: nb_pages = len(texts) else: texts = self.faker.sentences(nb=nb_pages) if metadata: metadata.add_content(texts) # type: ignore # Construct the main document content document_content = DOCX_TPL_DOC_HEADER for i, page_text in enumerate(texts): # type: ignore document_content += self._create_page( page_text, i == nb_pages - 1, # type: ignore ) document_content += DOCX_TPL_DOC_FOOTER # Basic structure of a DOCX file docx_structure = { "word/document.xml": document_content.encode(), "_rels/.rels": DOC_TPL_DOC_STRUCTURE_RELS, "word/_rels/document.xml.rels": DOC_TPL_DOC_STRUCTURE_WORD_RELS, "word/styles.xml": DOC_TPL_DOC_STRUCTURE_WORD_STYLES, "[Content_Types].xml": DOC_TPL_DOC_STRUCTURE_CONTENT_TYPES, } # Create the DOCX file (ZIP archive) docx_bytes = io.BytesIO() with zipfile.ZipFile(docx_bytes, "w") as docx: for path, content in docx_structure.items(): docx.writestr(path, content) return docx_bytes.getvalue()
[docs] class OdtGenerator: """OdtGenerator - generates an ODT file with text. Usage example: .. code-block:: python from pathlib import Path from fake import FAKER Path("/tmp/example.odt").write_bytes( OdtGenerator(FAKER).create(nb_pages=100) ) """ def __init__(self, faker: "Faker") -> None: self.faker = faker def _escape_xml(self, text: str) -> str: """Escapes XML special characters in the given text. :param text: The text to escape. :return: Escaped text. :rtype: str """ return ( text.replace("&", "&amp;") .replace("<", "&lt;") .replace(">", "&gt;") .replace('"', "&quot;") .replace("'", "&apos;") ) def _create_page(self, text: str) -> str: """Creates a paragraph with line breaks based on '\n' in the text. :param text: The text content with '\n' indicating where line breaks should be. :return: A string representing the XML of the paragraph with line breaks. :rtype: str """ # Split the text by '\n' to determine where to insert line breaks lines = text.split("\n") # Initialize the paragraph XML with the desired style paragraph_xml = '<text:p text:style-name="P1">' # Iterate over each line and insert <text:line-break/> where needed for i, line in enumerate(lines): # Escape XML special characters in the line escaped_line = self._escape_xml(line) paragraph_xml += escaped_line if i < len(lines) - 1: paragraph_xml += "<text:line-break/>" # Close the paragraph tag paragraph_xml += "</text:p>" return paragraph_xml
[docs] def create( self, nb_pages: Optional[int] = None, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, ) -> bytes: if not nb_pages and not texts: raise ValueError("Either `nb_pages` or `texts` must be provided.") if texts: nb_pages = len(texts) else: texts = self.faker.sentences(nb=nb_pages) if metadata: metadata.add_content(texts) # type: ignore # Prepare the XML content with page breaks content_xml = f"""<?xml version="1.0" encoding="UTF-8"?> <office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"> <office:body> <office:text> {"".join(self._create_page(text) for text in texts)} </office:text> </office:body> </office:document-content>""" # Prepare the XML for styles (including style for page break) styles_xml = """<?xml version="1.0" encoding="UTF-8"?> <office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"> <office:styles> <style:style style:name="P1" style:family="paragraph"> <style:paragraph-properties fo:break-before="page"/> </style:style> </office:styles> </office:document-styles>""" # Create the ODT file (ZIP archive) odt_bytes = io.BytesIO() with zipfile.ZipFile(odt_bytes, "w") as odt: # Add the mimetype file (needs to be the first file in the # archive and uncompressed) odt.writestr( "mimetype", "application/vnd.oasis.opendocument.text", zipfile.ZIP_STORED, ) # Add the content and styles files odt.writestr("content.xml", content_xml.encode("utf-8")) odt.writestr("styles.xml", styles_xml.encode("utf-8")) # Add the manifest file odt.writestr( "META-INF/manifest.xml", """<?xml version="1.0" encoding="UTF-8"?> <manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"> <manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.text" manifest:full-path="/"/> <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/> <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="styles.xml"/> </manifest:manifest>""".encode("utf-8"), ) return odt_bytes.getvalue()
[docs] class RtfGenerator: """RtfGenerator - generates an RTF file with text. Usage example: .. code-block:: python from pathlib import Path from fake import FAKER Path("/tmp/example.rtf").write_bytes( RtfGenerator(FAKER).create(nb_pages=5) ) """ def __init__(self, faker: "Faker") -> None: self.faker = faker def _escape(self, text: str) -> str: """Escape backslashes, braces and unicode characters for RTF.""" # Basic escaping of backslash and braces; extend as needed. return ( text.replace("\\", r"\\").replace("{", r"\{").replace("}", r"\}") # TODO: for full unicode support \uXXXX escapes are needed ) def _create_page(self, text: str, is_last_page: bool) -> str: # Split into lines, wrap each as a paragraph paragraphs = [] for line in text.split("\n"): esc = self._escape(line) paragraphs.append(rf"\pard\sa200\sl276\slmult1 {esc}\par") page_rtf = "\n".join(paragraphs) # Insert a page break if not the last page if not is_last_page: page_rtf += r"\page" return page_rtf
[docs] def create( self, nb_pages: Optional[int] = None, texts: Optional[List[str]] = None, metadata: Optional["MetaData"] = None, ) -> bytes: if not nb_pages and not texts: raise ValueError("Either `nb_pages` or `texts` must be provided.") if texts: nb_pages = len(texts) else: texts = self.faker.sentences(nb=nb_pages) # generate dummy texts if metadata: metadata.add_content(texts) # type: ignore # Build the full RTF document content = [ RTF_HEADER, RTF_FONT_TABLE, RTF_COLOR_TABLE, RTF_DOCUMENT_START, ] for i, page_text in enumerate(texts): # type: ignore content.append(self._create_page(page_text, i == nb_pages - 1)) # type: ignore content.append(RTF_DOCUMENT_END) # Return bytes return "\n".join(content).encode("utf-8")
[docs] class EpubGenerator: """EpubGenerator - generates a minimal EPUB file with text pages.""" def __init__(self, faker: "Faker") -> None: self.faker = faker def _escape(self, text: str) -> str: """Basic escape for XML.""" return ( text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;") ) def _create_xhtml(self, text: str, index: int) -> bytes: escaped = self._escape(text) xhtml = f"""<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Page {index}</title></head> <body><div>{escaped.replace(chr(10), "<br/>")}</div></body> </html>""" return xhtml.encode("utf-8") def _create_opf(self, nb_pages: int) -> bytes: manifest_items = [] spine_items = [] for i in range(1, nb_pages + 1): manifest_items.append( f"<item " f'id="page{i}" ' f'href="page{i}.xhtml" ' f'media-type="application/xhtml+xml"/>' ) spine_items.append(f'<itemref idref="page{i}"/>') manifest = "\n ".join(manifest_items) spine = "\n ".join(spine_items) opf = f"""<?xml version="1.0" encoding="utf-8"?> <package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="BookId"> <metadata xmlns:dc="http://purl.org/dc/elements/1.1/"> <dc:title>Fake Document</dc:title> <dc:language>en</dc:language> <dc:identifier id="BookId">fake-book-id</dc:identifier> </metadata> <manifest> <item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/> {manifest} </manifest> <spine toc="ncx"> {spine} </spine> </package>""" return opf.encode("utf-8") def _create_ncx(self, nb_pages: int) -> bytes: navpoints = [ f"""<navPoint id="navPoint-{i}" playOrder="{i}"> <navLabel><text>Page {i}</text></navLabel> <content src="page{i}.xhtml"/> </navPoint>""" for i in range(1, nb_pages + 1) ] navbody = "\n ".join(navpoints) ncx = f"""<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd"> <ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1"> <head> <meta name="dtb:uid" content="fake-book-id"/> </head> <docTitle><text>Fake Document</text></docTitle> <navMap> {navbody} </navMap> </ncx>""" return ncx.encode("utf-8")
[docs] def create( self, nb_pages: Optional[int] = None, texts: Optional[List[str]] = None, metadata: Optional["MetaData"] = None, ) -> bytes: if not nb_pages and not texts: raise ValueError("Either `nb_pages` or `texts` must be provided.") if texts: nb_pages = len(texts) else: texts = self.faker.sentences(nb=nb_pages) # type: ignore if metadata: metadata.add_content(texts) # type: ignore buf = io.BytesIO() # EPUB requires mimetype as first file, uncompressed with zipfile.ZipFile(buf, "w") as epub: epub.writestr( "mimetype", EPUB_MIMETYPE, compress_type=zipfile.ZIP_STORED ) # Container epub.writestr("META-INF/container.xml", EPUB_CONTAINER_XML) # XHTML pages for idx, page in enumerate(texts, start=1): # type: ignore epub.writestr( f"OEBPS/page{idx}.xhtml", self._create_xhtml(page, idx) ) # OPF package epub.writestr("OEBPS/content.opf", self._create_opf(nb_pages)) # NCX epub.writestr("OEBPS/toc.ncx", self._create_ncx(nb_pages)) return buf.getvalue()
class JpgGenerator: GRAY_1_PX_JPG = ( b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00" b"\xff\xdb\x00C\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xc0\x00\x0b\x08\x00\x01\x00\x01\x01\x01\x11\x00\xff\xc4\x00\x14" b"\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x00?\x00?\xff\xd9" ) GRAY_1_PX_JPG_BYTEARRAY = bytearray(GRAY_1_PX_JPG) RED_1_PX_JPG = ( b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00" b"\xff\xdb\x00C\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xdb\x00C\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b'\xff\xc0\x00\x11\x08\x00\x01\x00\x01\x03\x01"\x00\x02\x11\x01\x03\x11' b"\x01\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x02\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x15\x01" b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" b"\x03\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11" b"\x00?\x00\x90\x02\x8f\xff\xd9" ) RED_1_PX_JPG_BYTEARRAY = bytearray(RED_1_PX_JPG) YELLOW_1_PX_JPG = ( b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00" b"\xff\xdb\x00C\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xdb\x00C\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b'\xff\xc0\x00\x11\x08\x00\x01\x00\x01\x03\x01"\x00\x02\x11\x01\x03\x11' b"\x01\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x02\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x15\x01" b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" b"\x03\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11" b"\x00?\x00\xb0\x13/\xff\xd9" ) YELLOW_1_PX_JPG_BYTEARRAY = bytearray(YELLOW_1_PX_JPG) BLUE_1_PX_JPG = ( b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00" b"\xff\xdb\x00C\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xdb\x00C\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b'\xff\xc0\x00\x11\x08\x00\x01\x00\x01\x03\x01"\x00\x02\x11\x01\x03\x11' b"\x01\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x02\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x15\x01" b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" b"\x03\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11" b"\x00?\x00\x80\x14\x0f\xff\xd9" ) BLUE_1_PX_JPG_BYTEARRAY = bytearray(BLUE_1_PX_JPG) GREEN_1_PX_JPG = ( b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00" b"\xff\xdb\x00C\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xdb\x00C\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b'\xff\xc0\x00\x11\x08\x00\x01\x00\x01\x03\x01"\x00\x02\x11\x01\x03\x11' b"\x01\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x01\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x01" b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" b"\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?" b"\x00\xa0\x00?\xff\xd9" ) GREEN_1_PX_JPG_BYTEARRAY = bytearray(GREEN_1_PX_JPG) BLACK_1_PX_JPG = ( b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00" b"\xff\xdb\x00C\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xdb\x00C\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" b'\xff\xc0\x00\x11\x08\x00\x01\x00\x01\x03\x01"\x00\x02\x11\x01\x03\x11' b"\x01\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x03\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x01" b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?" b"\x00\x98\x00\xff\xd9" ) BLACK_1_PX_JPG_BYTEARRAY = bytearray(BLACK_1_PX_JPG) COLORS = { (0, 255, 0): GREEN_1_PX_JPG_BYTEARRAY, (255, 255, 0): YELLOW_1_PX_JPG_BYTEARRAY, (255, 0, 0): RED_1_PX_JPG_BYTEARRAY, (0, 0, 255): BLUE_1_PX_JPG_BYTEARRAY, (0, 0, 0): BLACK_1_PX_JPG_BYTEARRAY, (128, 128, 128): GRAY_1_PX_JPG_BYTEARRAY, } @classmethod def euclidean_distance(cls, color1, color2): return math.sqrt(sum((a - b) ** 2 for a, b in zip(color1, color2))) @classmethod def detect_closest_color(cls, color: Tuple[int, int, int]) -> bytearray: closest = None min_distance = float("inf") for ref_color, color_name in cls.COLORS.items(): distance = cls.euclidean_distance(color, ref_color) if distance < min_distance: min_distance = distance closest = color_name return closest @classmethod def find_marker(cls, jpeg_bytes, marker) -> int: """Helper function to find a marker.""" marker_bytes = marker index = jpeg_bytes.find(marker_bytes) if index == -1: raise ValueError(f"Marker {marker} not found.") return index @classmethod def generate( cls, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (128, 128, 128), ) -> bytes: """Create a JPG image of a specified size and colour. :param size: Tuple of width and height of the image in pixels. :param color: Colour of the image in RGB format (tuple of three integers). :return: Byte content of the JPG image. :rtype: bytes """ original = cls.detect_closest_color(color) width, height = size jpeg = original.copy() # Step 1: Update the SOF0 marker with new dimensions sof0 = b"\xff\xc0" sof0_index = cls.find_marker(jpeg, sof0) # SOF0 structure: # [Marker] # [Length] # [Precision] # [Height] # [Width] # [Components] # [Component Spec...] # Length is 2 bytes, Precision is 1 byte, Height and Width are 2 bytes # each. # Components: 1 byte ID, 1 byte Sampling factors, 1 byte Quantization # table number. # Extract current height and width (for 1x1 image) # Height is at sof0_index + 5 and sof0_index + 6 # Width is at sof0_index + 7 and sof0_index + 8 # We need to modify these to new_height and new_width # Pack new height and width as big-endian unsigned shorts new_height_bytes = struct.pack(">H", height) new_width_bytes = struct.pack(">H", width) # Replace height and width in the JPEG bytes jpeg[sof0_index + 5] = new_height_bytes[0] jpeg[sof0_index + 6] = new_height_bytes[1] jpeg[sof0_index + 7] = new_width_bytes[0] jpeg[sof0_index + 8] = new_width_bytes[1] # Step 2: Locate the Start of Scan (SOS) marker sos = b"\xff\xda" sos_index = cls.find_marker(jpeg, sos) # SOS structure: # [Marker] # [Length] # [Components] # [Component Spec...] # [Start & End of Spectral Selection] # [Successive Approximation] # Extract the length of the SOS segment to find where image data starts sos_length = struct.unpack(">H", jpeg[sos_index + 2 : sos_index + 4])[0] image_data_start = sos_index + 2 + sos_length # Locate the End of Image (EOI) marker eoi = b"\xff\xd9" eoi_index = jpeg.find(eoi, image_data_start) if eoi_index == -1: eoi_index = 0 # raise ValueError("EOI marker not found.") # Step 3: Extract the original image data (between SOS and EOI) original_image_data = jpeg[image_data_start:eoi_index] # Step 4: Determine the number of 8x8 blocks needed blocks_per_row = (width + 7) // 8 # Ceiling division blocks_per_col = (height + 7) // 8 total_blocks = blocks_per_row * blocks_per_col # Step 5: Replicate the image data for each block # For a constant gray image, each block's data is identical replicated_image_data = original_image_data * total_blocks # Optional: If the image size isn't a multiple of 8, padding might be # necessary. # However, JPEG decoders typically ignore the extra padding, so we can # proceed. # Step 6: Assemble the new JPEG bytes new_jpeg = ( jpeg[:image_data_start] + replicated_image_data + jpeg[eoi_index:] ) return bytes(new_jpeg) class ProviderRegistryItem(str): __slots__ = ("tags",) tags: Optional[Tuple[str, ...]] def __new__(cls, value, *args, **kwargs): obj = str.__new__(cls, value) obj.tags = () return obj # Global registry for provider methods UID_REGISTRY: Dict[str, "Faker"] = {} ALIAS_REGISTRY: Dict[str, "Faker"] = {} PROVIDER_REGISTRY: Dict[str, Set[ProviderRegistryItem]] = defaultdict(set) class Provider: def __init__( self, func: Callable, tags: Optional[Tuple[str, ...]] = None, ) -> None: self.func = func self.is_provider = True self.registered_name = None self.tags = tags def __set_name__(self, owner, name): module = owner.__module__ class_name = owner.__name__ class_qualname = f"{module}.{class_name}" self.registered_name = f"{module}.{class_name}.{name}" func_name = ProviderRegistryItem(self.func.__name__) func_name.tags = self.tags PROVIDER_REGISTRY[class_qualname].add(func_name) def __get__(self, instance, owner): # Return a method bound to the instance or the unbound function return self.func.__get__(instance, owner)
[docs] def provider(*args: Any, tags: Optional[Tuple[str, ...]] = None) -> Callable: # Decorator is used without arguments if args and callable(args[0]): return Provider(args[0]) # Decorator is used with arguments def wrapper(func: Callable) -> Provider: return Provider(func, tags=tags) return wrapper
[docs] class Faker: """fake.py - simplified, standalone alternative with no dependencies. ---- Usage example: .. code-block:: python from fake import FAKER print(FAKER.first_name()) # Random first name print(FAKER.last_name()) # Random last name print(FAKER.name()) # Random name print(FAKER.word()) # Random word from the Zen of Python print(FAKER.words(nb=3)) # List of 3 random words from Zen of Python print(FAKER.sentence()) # Random sentence (5 random words by default) print(FAKER.paragraph()) # Paragraph (5 random sentences by default) print(FAKER.paragraphs()) # 3 random paragraphs print(FAKER.text()) # Random text up to 200 characters print(FAKER.file_name()) # Random filename with '.txt' extension print(FAKER.email()) # Random email print(FAKER.url()) # Random URL print(FAKER.pyint()) # Random integer print(FAKER.pybool()) # Random boolean print(FAKER.pystr()) # Random string print(FAKER.pyfloat()) # Random float ---- PDF: .. code-block:: python from pathlib import Path from fake import FAKER, TextPdfGenerator, GraphicPdfGenerator Path("/tmp/graphic_pdf.pdf").write_bytes( FAKER.pdf(nb_pages=100, generator=GraphicPdfGenerator) ) Path("/tmp/text_pdf.pdf").write_bytes( FAKER.pdf(nb_pages=100, generator=TextPdfGenerator) ) ---- Various image formats: .. code-block:: python from pathlib import Path from fake import FAKER Path("/tmp/image.png").write_bytes(FAKER.png()) Path("/tmp/image.svg").write_bytes(FAKER.svg()) Path("/tmp/image.bmp").write_bytes(FAKER.bmp()) Path("/tmp/image.gif").write_bytes(FAKER.gif()) Note, that all image formats accept `size` (default: `(100, 100)`) and `color`(default: `(255, 0, 0)`) arguments. """ def __init__( self, alias: Optional[str] = None, seed: Optional[int] = None, ) -> None: self._words: List[str] = [] self._first_names: List[str] = [] self._last_names: List[str] = [] self._cities: List[str] = [] self._countries: List[str] = [] self._geo_locations: List[str] = [] self._country_codes: List[str] = [] self._locales: List[str] = [] self.random = random.Random(seed) self.uid = f"{self.__class__.__module__}.{self.__class__.__name__}" if alias and alias in ALIAS_REGISTRY: LOGGER.warning( f"Alias '{alias}' already registered. " f"Using '{self.uid}' as alias instead." ) alias = None self.alias = alias or self.uid if self.uid not in UID_REGISTRY: UID_REGISTRY[self.uid] = self if self.alias not in ALIAS_REGISTRY: ALIAS_REGISTRY[self.alias] = self self.load_data()
[docs] def seed(self, seed: Optional[int]) -> None: """Reset the RNG state with a new seed.""" self.random.seed(seed)
[docs] def load_data(self): self.load_words() self.load_names() self.load_geo_locations() self.load_locales_and_country_codes()
[docs] @staticmethod def get_by_uid(uid: str) -> Union["Faker", None]: return UID_REGISTRY.get(uid)
[docs] @staticmethod def get_by_alias(alias: str) -> Union["Faker", None]: return ALIAS_REGISTRY.get(alias)
[docs] def load_words(self) -> None: with contextlib.redirect_stdout(io.StringIO()): # Dynamically import 'this' module this = __import__("this") zen_encoded: str = this.s translation_map: Dict[str, str] = {v: k for k, v in this.d.items()} zen: str = self._rot13_translate(zen_encoded, translation_map) self._words = sorted( zen.translate(str.maketrans("", "", string.punctuation)) .lower() .split() )
[docs] def load_names(self) -> None: authorship_data = AuthorshipData() self._first_names = sorted(authorship_data.first_names) self._last_names = sorted(authorship_data.last_names)
[docs] def load_geo_locations(self) -> None: cities: Set[str] = set() countries: Set[str] = set() geo_locations: Set[str] = set() add_city = cities.add add_country = countries.add add_geo_location = geo_locations.add for tz in zoneinfo.available_timezones(): parts = tz.split("/") _parts = [ part.replace("_", " ") for part in parts if part and not UNWANTED_GEO_PATTERN.match(part) ] if _parts: add_geo_location("/".join(_parts)) # Ignore single-part entries that match our exclusion pattern if len(parts) == 1: if not UNWANTED_GEO_PATTERN.match(parts[0]): country = parts[0].replace("_", "") add_country(country) # Extract cities for Asia and Europe elif ( parts[0] in ["Asia", "Europe"] and len(parts) > 1 # Check to ensure there is a second part ): city = parts[1].replace("_", " ") add_city(city) self._cities = sorted(cities) self._countries = sorted(countries) self._geo_locations = sorted(geo_locations)
[docs] def load_locales_and_country_codes(self) -> None: # Fetch all available locales from the system _available_locales = locale.locale_alias # Filter and clean the list to show more standardized locale names _locales = { __value.split(".")[0] for __key, __value in _available_locales.items() if "_" in __value } # Extract country codes from the locale keys _country_codes: Set[str] = set() add_country_code = _country_codes.add for __key in _locales: __parts = __key.split("_") if len(__parts) > 1: # Get the first two characters of the second part, # typically the country code __country_code = __parts[1][:2] # Add to set to ensure uniqueness add_country_code(__country_code.upper()) self._country_codes = sorted(_country_codes) self._locales = sorted(_locales)
@staticmethod def _rot13_translate(text: str, translation_map: Dict[str, str]) -> str: return "".join([translation_map.get(c, c) for c in text])
[docs] @provider(tags=("Unique",)) def uuid(self) -> UUID: """Generate a UUID. :return: UUID :rtype: UUID """ return uuid.uuid4()
[docs] @provider(tags=("Unique",)) def uuids(self, nb: int = 5) -> List[UUID]: """Generate a list of UUIDs. :param nb: Number of UUIDs to generate. :return: List of UUIDs :rtype: List[UUID] """ return [uuid.uuid4() for _ in range(nb)]
[docs] @provider(tags=("Person",)) def first_name(self) -> str: """Generate a first name. :return: First name. :rtype: str """ return self.random.choice(self._first_names)
[docs] @provider(tags=("Person",)) def first_names(self, nb: int = 5) -> List[str]: """Generate a list of first names. :param nb: Number of first names to generate. :return: List of first names. :rtype: List[str] """ return [self.first_name() for _ in range(nb)]
[docs] @provider(tags=("Person",)) def last_name(self) -> str: """Generate a last name. :return: Last name. :rtype: str """ return self.random.choice(self._last_names)
[docs] @provider(tags=("Person",)) def last_names(self, nb: int = 5) -> List[str]: """Generate a list of last names. :param nb: Number of last names to generate. :return: List of last names. :rtype: List[str] """ return [self.last_name() for _ in range(nb)]
[docs] @provider(tags=("Person",)) def name(self) -> str: """Generate a name. :return: Name. :rtype: str """ return f"{self.first_name()} {self.last_name()}"
[docs] @provider(tags=("Person",)) def names(self, nb: int = 5) -> List[str]: """Generate a list of names. :param nb: Number of names to generate. :return: List of names. :rtype: List[str] """ return [self.name() for _ in range(nb)]
[docs] @provider(tags=("Person",)) def username(self) -> str: """Generate a username. :return: Username. :rtype: str """ return ( f"{self.word()}_{self.word()}_{self.word()}_{self.pystr()}" ).lower()
[docs] @provider(tags=("Person",)) def usernames(self, nb: int = 5) -> List[str]: """Generate a list of usernames. :param nb: Number of usernames to generate. :return: List of usernames. :rtype: List[str] """ return [self.username() for _ in range(nb)]
[docs] @provider(tags=("Text",)) def slug(self) -> str: """Generate a slug. :return: Slug. :rtype: str """ return ( f"{self.word()}-{self.word()}-{self.word()}-{self.pystr()}" ).lower()
[docs] @provider(tags=("Text",)) def slugs(self, nb: int = 5) -> List[str]: """Generate a list of slugs. :param nb: Number of slugs to generate. :return: List of slugs. :rtype: List[str] """ return [self.slug() for _ in range(nb)]
[docs] @provider(tags=("Text",)) def word(self) -> str: """Generate a word. :return: Word. :rtype: str """ return self.random.choice(self._words).capitalize()
[docs] @provider(tags=("Text",)) def words(self, nb: int = 5) -> List[str]: """Generate a list of words. :param nb: Number of words to generate. :return: List of words. :rtype: List[str] """ return list(self.random.choices(self._words, k=nb))
[docs] @provider(tags=("Text",)) def sentence(self, nb_words: int = 5, suffix: str = ".") -> str: """Generate a sentence. :param nb_words: Number of words to generate. :param suffix: Suffix to add to sentence. Defaults to `.`. :return: Sentence. :rtype: str """ return ( f"{' '.join([self.word() for _ in range(nb_words)]).capitalize()}" f"{suffix}" )
[docs] @provider(tags=("Text",)) def sentences(self, nb: int = 3) -> List[str]: """Generate a list of sentences. :param nb: Number of sentences to generate. :return: List of sentences. :rtype: List[str] """ return [self.sentence() for _ in range(nb)]
[docs] @provider(tags=("Text",)) def paragraph(self, nb_sentences: int = 5) -> str: """Generate a paragraph. :param nb_sentences: Number of sentences to generate. :return: Paragraph. :rtype: str """ return " ".join([self.sentence() for _ in range(nb_sentences)])
[docs] @provider(tags=("Text",)) def paragraphs(self, nb: int = 3) -> List[str]: """Generate a list of paragraphs. :param nb: Number of paragraphs to generate. :return: List of paragraphs. :rtype: List[str] """ return [self.paragraph() for _ in range(nb)]
[docs] @provider(tags=("Text",)) def text( self, nb_chars: int = 200, suffix: str = ".", allow_overflow: bool = False, ) -> str: """Generate a text. :param nb_chars: Number of characters to generate. :param suffix: Suffix to add to text. Defaults to `.`. :param allow_overflow: Allow overflow. Defaults to False. :return: Text. :rtype: str """ current_text: str = "" while len(current_text) < nb_chars: sentence: str = self.sentence() current_text += f" {sentence}" if current_text else sentence if allow_overflow: return current_text return current_text[: (nb_chars - len(suffix))] + suffix
[docs] @provider(tags=("Text",)) def texts(self, nb: int = 3) -> List[str]: """Generate a list of texts. :param nb: Number of texts to generate. :return: List of texts. :rtype: List[str] """ return [self.text() for _ in range(nb)]
[docs] @provider(tags=("Filename",)) def file_name( self, prefix: Optional[str] = "", extension: str = "txt", ) -> str: """Generate a random filename. :param prefix: Prefix to add to filename. Defaults to empty string. :param extension: Extension to add to filename. Defaults to '.txt'. :return: Filename. :rtype: str """ return os.path.basename( mktemp( suffix=f".{extension}" if extension else "", prefix=prefix or "", ) )
[docs] @provider(tags=("Filename",)) def file_path( self, prefix: Optional[str] = "", extension: str = "txt", ) -> str: """Generate a random file path. :param prefix: Prefix to add to file path. Defaults to empty string. :param extension: Extension to add to file path. Defaults to '.txt'. :return: File path. :rtype: str """ return mktemp( suffix=f".{extension}" if extension else "", prefix=prefix or "", )
[docs] @provider(tags=("Filename",)) def dir_path( self, depth: int = 1, ) -> str: """Generate a random dir path. :param depth: Depth to add to dir path. Defaults to 1. :return: Dir path. :rtype: str """ _path_parts = [ self.random.choice(self._words).lower() for _ in range(depth) ] return os.path.join(*([os.path.sep] + _path_parts))
[docs] @provider(tags=("Filename",)) def file_extension(self) -> str: """Generate a random extension. :return: File extension. :rtype: str """ return self.random.choice(FILE_EXTENSIONS)
[docs] @provider(tags=("Filename",)) def mime_type(self) -> str: """Generate a random mime type. :return: Mime type. :rtype: str """ return self.random.choice(MIME_TYPES)
[docs] @provider(tags=("Internet",)) def tld(self, tlds: Optional[Tuple[str, ...]] = None) -> str: """Generate a random TLD. :param tlds: List of TLDs to use. If not given or None, a default list of TLDs is used. :return: TLD. :rtype: str """ return self.random.choice(tlds or TLDS)
[docs] @provider(tags=("Internet",)) def domain_name(self, tlds: Optional[Tuple[str, ...]] = None) -> str: """Generate a random domain name. :param tlds: List of TLDs to use. If not given or None, a default list of TLDs is used. :return: Domain name. :rtype: str """ domain = self.word().lower() tld = self.tld(tlds) return f"{domain}.{tld}"
[docs] @provider(tags=("Internet",)) def free_email_domain(self) -> str: """Generate a random free email domain. :return: Free email domain. :rtype: str """ return self.random.choice(FREE_EMAIL_DOMAINS)
[docs] @provider(tags=("Internet",)) def email(self, domain_names: Optional[Tuple[str, ...]] = None) -> str: """Generate a random email. :param domain_names: List of domains to randomly choose from. If not given or None, random words are used. :return: Email. :rtype: str """ domain = self.random.choice(domain_names) if domain_names else None return ( f"{'.'.join(_w.lower() for _w in self.words(3))}" f"@{domain or self.domain_name()}" )
[docs] @provider(tags=("Internet",)) def emails( self, nb: int = 5, domain_names: Optional[Tuple[str, ...]] = None, ) -> List[str]: """Generate a list of random emails. :param nb: Number of emails to generate. :param domain_names: List of domains to randomly choose from. If not given or None, random words are used. :return: List of emails. :rtype: List[str] """ return [self.email(domain_names=domain_names) for _ in range(nb)]
[docs] @provider(tags=("Internet",)) def company_email( self, domain_names: Optional[Tuple[str, ...]] = None, ) -> str: """Generate a random company email. :param domain_names: List of domains to randomly choose from. If not given or None, random words are used. :return: Company email. :rtype: str """ domain = self.random.choice(domain_names) if domain_names else None return f"{slugify(self.name())}@{domain or self.domain_name()}"
[docs] @provider(tags=("Internet",)) def company_emails( self, nb: int = 5, domain_names: Optional[Tuple[str, ...]] = None, ) -> List[str]: """Generate a list of random company emails. :param nb: Number of emails to generate. :param domain_names: List of domains to randomly choose from. If not given or None, random words are used. :return: List of emails. :rtype: List[str] """ return [ self.company_email(domain_names=domain_names) for _ in range(nb) ]
[docs] @provider(tags=("Internet",)) def free_email( self, domain_names: Optional[Tuple[str, ...]] = None, ) -> str: """Generate a random free email. :param domain_names: List of domains to randomly choose from. If not given or None, random words are used. :return: Free email. :rtype: str """ domain = self.random.choice(domain_names) if domain_names else None return f"{slugify(self.name())}@{domain or self.free_email_domain()}"
[docs] @provider(tags=("Internet",)) def free_emails( self, nb: int = 5, domain_names: Optional[Tuple[str, ...]] = None, ) -> List[str]: """Generate a list of random free emails. :param nb: Number of emails to generate. :param domain_names: List of domains to randomly choose from. If not given or None, random words are used. :return: List of emails. :rtype: List[str] """ return [self.free_email(domain_names=domain_names) for _ in range(nb)]
[docs] @provider(tags=("Internet",)) def url( self, protocols: Optional[Tuple[str, ...]] = None, tlds: Optional[Tuple[str, ...]] = None, suffixes: Optional[Tuple[str, ...]] = None, ) -> str: """Generate a random URL. Example without params: .. code-block:: python FAKER.url() Example with params: .. code-block:: python FAKER.url( protocols=("https",), tlds=("com",), suffixes=(".py",), ) :param protocols: List of protocols to use. If not given or None, default protocols are used. :param tlds: List of TLDs to use. If not given or None, default TLDs are used. :param suffixes: List of suffixes to use. If not given or None, default suffixes are used. :return: URL. :rtype: str """ protocol = self.random.choice(protocols or URL_PROTOCOLS) suffix = self.random.choice(suffixes or URL_SUFFIXES) return ( f"{protocol}://" f"{self.domain_name(tlds)}" f"/{self.word().lower()}" f"{suffix}" )
[docs] @provider(tags=("Internet",)) def image_url( self, width: int = 800, height: int = 600, service_url: Optional[str] = None, ) -> str: """Generate a random image URL. :param width: Width of the image. Defaults to 800. :param height: Height of the image. Defaults to 600. :param service_url: Service URL. If not given or None, randomly picked service URL is used. :return: Image URL. :rtype: str """ if service_url is None: service_url = self.random.choice(IMAGE_SERVICES) return service_url.format(width=width, height=height)
[docs] @provider(tags=("Python",)) def pyint(self, min_value: int = 0, max_value: int = 9999) -> int: """Generate a random integer. :param min_value: Min value of the generated integer. Defaults to 0. :param max_value: Max value of the generated integer. Defaults to 9999. :return: Random integer within the range [min_value, max_value]. :rtype: int """ return self.random.randint(min_value, max_value)
[docs] @provider(tags=("Python",)) def pybool(self) -> bool: """Generate a random boolean. :return: Random boolean. :rtype: bool """ return self.random.choice( ( True, False, ) )
[docs] @provider(tags=("Python",)) def pystr(self, nb_chars: int = 20) -> str: """Generate a random string. :param nb_chars: Number of characters to generate. :return: Random string. :rtype: str """ return "".join(self.random.choices(string.ascii_letters, k=nb_chars))
[docs] @provider(tags=("Text",)) def password( self, length: int = 10, min_lower: int = 1, min_upper: int = 1, min_digits: int = 3, min_special: int = 0, ) -> str: """Generate a random password. :param length: Total length of the password. Default is 10. :param min_lower: Minimum number of lowercase letters. Defaults to 1. :param min_upper: Minimum number of uppercase letters. Defaults to 1. :param min_digits: Minimum number of digits. Default is 3. :param min_special: Minimum number of special characters. Defaults to 0. :return: Random password string. :rtype: str :raises ValueError: if the total ``length`` is less than the sum of all minimum character constraints. """ if length < min_lower + min_upper + min_digits + min_special: raise ValueError("Length is too short for the given constraints.") rng = secrets.SystemRandom() password_chars = ( [rng.choice(string.ascii_lowercase) for _ in range(min_lower)] + [rng.choice(string.ascii_uppercase) for _ in range(min_upper)] + [rng.choice(string.digits) for _ in range(min_digits)] + [rng.choice(string.punctuation) for _ in range(min_special)] ) remaining_length = length - ( min_lower + min_upper + min_digits + min_special ) if remaining_length > 0: all_chars = ( string.ascii_letters + string.digits + string.punctuation ) password_chars += [ rng.choice(all_chars) for _ in range(remaining_length) ] rng.shuffle(password_chars) return "".join(password_chars)
[docs] @provider(tags=("Python",)) def pyfloat( self, min_value: float = 0.0, max_value: float = 10.0, ) -> float: """Generate a random float number. :param min_value: Min value of the generated number. Defaults to 0. :param max_value: Max value of the generated number. Defaults to 10. :return: Random float number. :rtype: float """ return self.random.uniform(min_value, max_value)
[docs] @provider(tags=("Python",)) def pydecimal( self, left_digits: int = 5, right_digits: int = 2, positive: bool = True, ) -> Decimal: """Generate a random Decimal number. :param left_digits: Number of digits to the left of the decimal point. :param right_digits: Number of digits to the right of the decimal point. :param positive: If True, the number will be positive, otherwise it can be negative. :return: Random Decimal number. :rtype: Decimal :raises ValueError: if ``left_digits`` or ``right_digits`` is less than 0. """ if left_digits < 0: raise ValueError("`left_digits` must be at least 0") if right_digits < 0: raise ValueError("`right_digits` must be at least 0") if left_digits > 0: # Generate the integer part __lower = 10 ** (left_digits - 1) __upper = (10**left_digits) - 1 int_part = self.random.randint(__lower, __upper) else: int_part = 0 if right_digits > 0: # Generate the fractional part __lower = 10 ** (right_digits - 1) __upper = (10**right_digits) - 1 fractional_part = self.random.randint(__lower, __upper) else: fractional_part = 0 # Combine both parts number = Decimal(f"{int_part}.{fractional_part}") # Make the number negative if needed if not positive: number = -number return number
[docs] @provider(tags=("Internet",)) def ipv4(self) -> str: """Generate a random IP v4. :return: Random IP v4. :rtype: str """ return ".".join(str(self.random.randint(0, 255)) for _ in range(4))
def _add_months(self, dt: datetime, months: int) -> datetime: """Add months to dt, clamping day to valid range. :param dt: Base datetime. :param months: Number of months (can be negative). :return: New datetime shifted by months. :rtype: datetime """ month = dt.month - 1 + months year = dt.year + month // 12 month = month % 12 + 1 day = min(dt.day, monthrange(year, month)[1]) return dt.replace(year=year, month=month, day=day) def _add_years(self, dt: datetime, years: int) -> datetime: """Add years to dt, handling Feb 29 leap year clamping. :param dt: Base datetime. :param years: Number of years (can be negative). :return: New datetime shifted by years. :rtype: datetime """ target_year = dt.year + years day = min(dt.day, monthrange(target_year, dt.month)[1]) return dt.replace(year=target_year, day=day) def _parse_date_string( self, date_str: str, tzinfo: timezone = timezone.utc, _now: Optional[datetime] = None, ) -> datetime: """Parse a date string into a datetime. Supports: - ISO format: 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS' - Relative: [+/-][num][unit] where unit is d, h, m, w, mo, y - Special: 'now', 'today' for current datetime Units: - d days - h hours - m minutes - w weeks - mo months - y years Deprecated (still work, emit warnings): H -> h, M -> m :param date_str: Date string (ISO or relative shorthand). :param tzinfo: Timezone. Defaults to UTC. :param _now: Optional reference point for testing. :return: Parsed datetime. :rtype: datetime :raises ValueError: if format is incorrect. """ now = _now if _now is not None else datetime.now(tzinfo) if date_str in ("now", "today"): return now iso_match = re.match( r"^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?$", date_str, ) if iso_match: year, month, day = ( int(iso_match.group(1)), int(iso_match.group(2)), int(iso_match.group(3)), ) hour = int(iso_match.group(4) or 0) minute = int(iso_match.group(5) or 0) second = int(iso_match.group(6) or 0) return datetime( year, month, day, hour, minute, second, tzinfo=tzinfo ) match = re.fullmatch(r"([+-]?\d+)(mo|[dhmwMyHM])", date_str) if not match: raise ValueError( "Date string format is incorrect. Expected formats like " "'-7d', '+2w', '-3mo', '+1y', '-24h', '+30m' or " "'YYYY-MM-DD', 'YYYY-MM-DD HH:MM:SS'." ) value_str, unit = match.groups() value = int(value_str) if unit == "H": import warnings warnings.warn( "Date unit 'H' is deprecated. Use 'h' instead.", DeprecationWarning, stacklevel=2, ) unit = "h" elif unit == "M": import warnings warnings.warn( "Date unit 'M' is deprecated. Use 'm' instead.", DeprecationWarning, stacklevel=2, ) unit = "m" if unit == "d": return now + timedelta(days=value) elif unit == "h": return now + timedelta(hours=value) elif unit == "m": return now + timedelta(minutes=value) elif unit == "w": return now + timedelta(weeks=value) elif unit == "mo": return self._add_months(now, value) return self._add_years(now, value)
[docs] @provider(tags=("Date/Time",)) def date( self, start_date: str = "-7d", end_date: str = "+0d", tzinfo: timezone = timezone.utc, ) -> date: """Generate random date between `start_date` and `end_date`. Both `start_date` and `end_date` support: 1. ISO format dates: - 'YYYY-MM-DD' (e.g., '2000-01-01') - 'YYYY-MM-DD HH:MM:SS' (e.g., '2000-01-01 12:00:00') 2. Relative shorthand notation: [+/-][number][unit] Units: - d days - h hours - m minutes - w weeks - mo months - y years Deprecated (still work, emit warnings): H -> h, M -> m Sign: '+' for future, '-' for past, or omit '+' for future Special: 'now', 'today' for current datetime Shorthand notation examples: - '-7d': 7 days ago - '+3d' or '3d': 3 days from now - '-2w': 2 weeks ago - '+1mo': 1 month from now - '-1y': 1 year ago - '-24h': 24 hours ago - '+2h': 2 hours from now - '-30m': 30 minutes ago - 'today' or 'now': current datetime ISO format examples: - '2000-01-01': January 1, 2000 - '2025-12-31': December 31, 2025 - '2000-01-01 00:00:00': with time component Usage example: .. code-block:: python FAKER.date() # random date between 7 days ago and today FAKER.date('-7d', '+0d') # same as above (explicit) FAKER.date('-30d', '-1d') # between 30 days ago and yesterday FAKER.date('+1d', '+7d') # between tomorrow and next week FAKER.date('today', '+3d') # between today and 3 days from now FAKER.date('-2w', '+1w') # within 3-week window around now FAKER.date( '-3mo', '+6mo' ) # between 3 months ago and 6 months from now FAKER.date('-1y', '+1y') # within 1 year window around now FAKER.date('2000-01-01', '2025-12-31') # ISO date range FAKER.date('-1d', '-1d') # yesterday only FAKER.date('-365d', 'today') # in the past year FAKER.date('+1d', '+30d') # in the next month :param start_date: The start date (ISO format or shorthand notation). :param end_date: The end date (ISO format or shorthand notation). :param tzinfo: The timezone. :return: A string representing the formatted date. :rtype: date """ start_datetime = self._parse_date_string(start_date, tzinfo) end_datetime = self._parse_date_string(end_date, tzinfo) time_between_dates = (end_datetime - start_datetime).days random_days = self.random.randrange( time_between_dates + 1 ) # Include the end date random_date = start_datetime + timedelta(days=random_days) return random_date.date()
[docs] @provider(tags=("Date/Time",)) def date_time( self, start_date: str = "-7d", end_date: str = "+0d", tzinfo: timezone = timezone.utc, ) -> datetime: """Generate a random datetime between `start_date` and `end_date`. Both `start_date` and `end_date` support: 1. ISO format dates: - 'YYYY-MM-DD' (e.g., '2000-01-01') - 'YYYY-MM-DD HH:MM:SS' (e.g., '2000-01-01 12:00:00') 2. Relative shorthand notation: [+/-][number][unit] Units: - d days - h hours - m minutes - w weeks - mo months - y years Deprecated (still work, emit warnings): H -> h, M -> m Sign: '+' for future, '-' for past, or omit '+' for future Special: 'now', 'today' for current datetime Shorthand notation examples: - '-7d': 7 days ago - '+3d' or '3d': 3 days from now - '-2w': 2 weeks ago - '+1mo': 1 month from now - '-1y': 1 year ago - '-24h': 24 hours ago - '+2h': 2 hours from now - '-30m': 30 minutes ago - 'today' or 'now': current datetime ISO format examples: - '2000-01-01': January 1, 2000 - '2025-12-31 23:59:59': with time component Usage example: .. code-block:: python FAKER.date_time() # random datetime between 7 days ago and now FAKER.date_time('-7d', '+0d') # same as above (explicit) FAKER.date_time('-24h', '+0h') # in the past 24 hours FAKER.date_time('-30m', '+30m') # within 1-hour window around now FAKER.date_time('-2w', '+1w') # within 3-week window FAKER.date_time('-3mo', '+6mo') # within 9-month window FAKER.date_time('-1y', '+1y') # within 1 year window FAKER.date_time('today', '+1d') # between now and tomorrow FAKER.date_time('+1h', '+4h') # between 1-4 hours from now FAKER.date_time('-1d', 'now') # since yesterday FAKER.date_time('-2h', '-1h') # between 2-1 hours ago FAKER.date_time('+15m', '+45m') # in 15-45 minutes FAKER.date_time('-365d', 'today') # in the past year FAKER.date_time( '2000-01-01', '2025-12-31' ) # ISO date range :param start_date: The start datetime (ISO format or shorthand notation). Defaults to `-7d`. :param end_date: The end datetime (ISO format or shorthand notation). Defaults to `+0d`. :param tzinfo: The timezone. Defaults to `timezone.utc`. :return: A string representing the formatted datetime. :rtype: datetime """ start_datetime = self._parse_date_string(start_date, tzinfo) end_datetime = self._parse_date_string(end_date, tzinfo) time_between_datetimes = int( (end_datetime - start_datetime).total_seconds() ) random_seconds = self.random.randrange( time_between_datetimes + 1 ) # Include the end date time random_date_time = start_datetime + timedelta(seconds=random_seconds) return random_date_time
[docs] @provider(tags=("Date/Time",)) def year(self, start_year: int = 1900, end_year: int = 2100) -> int: """Generate a random year between start_year and end_year (inclusive). :param start_year: The lower bound for the random year. Defaults to 1900. :param end_year: The upper bound for the random year. Defaults to 2100. :return: A random year as an integer. :rtype: int """ return self.random.randint(start_year, end_year)
[docs] @provider(tags=("Date/Time",)) def time(self, fmt: str = "%H:%M:%S") -> str: """Generate a random time string formatted according to `fmt`. :param fmt: The format to use. Defaults to `%H:%M:%S`. :return: A random time string formatted according to `fmt`. :rtype: str """ hour = self.pyint(min_value=0, max_value=23) minute = self.pyint(min_value=0, max_value=59) second = self.pyint(min_value=0, max_value=59) return time(hour, minute, second).strftime(fmt)
[docs] @provider( tags=( "Binary", "Document", ) ) def pdf( self, nb_pages: int = 1, generator: Union[ Type[TextPdfGenerator], Type[GraphicPdfGenerator] ] = GraphicPdfGenerator, metadata: Optional[MetaData] = None, **kwargs, ) -> bytes: """Create a PDF document of a given size. :param nb_pages: The number of pages to generate. Defaults to 1. :param generator: The PDF generator to use. Defaults to `GraphicPdfGenerator`. :param metadata: The metadata to use. Defaults to `None`. :param **kwargs: Additional arguments to pass to `generator`. :return: A bytes object representing the PDF document. :rtype: bytes """ _pdf = generator(faker=self) return _pdf.create(nb_pages=nb_pages, metadata=metadata, **kwargs)
[docs] @provider( tags=( "Binary", "Document", ) ) def text_pdf( self, nb_pages: int = 1, generator: Type[TextPdfGenerator] = TextPdfGenerator, metadata: Optional[MetaData] = None, **kwargs, ) -> bytes: """Create a PDF document of a given size. :param nb_pages: The number of pages to generate. Defaults to 1. :param generator: The PDF generator to use. Defaults to `TextPdfGenerator`. :param metadata: The metadata to use. Defaults to `None`. :param **kwargs: Additional arguments to pass to `generator`. :return: A bytes object representing the PDF document. :rtype: bytes """ return self.pdf( nb_pages=nb_pages, generator=generator, metadata=metadata, **kwargs, )
[docs] @provider( tags=( "Binary", "Image", ) ) def png( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), ) -> bytes: """Create a PNG image of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format (tuple of three integers). Defaults to (0, 0, 255). :return: Byte content of the PNG image. :rtype: bytes """ width, height = size # PNG file format header png_header = b"\x89PNG\r\n\x1a\n" # IHDR chunk: width, height, bit depth, colour type, compression, # filter, interlace ihdr_content = ( width.to_bytes(4, byteorder="big") + height.to_bytes(4, byteorder="big") + b"\x08\x02\x00\x00\x00" ) ihdr = b"IHDR" + ihdr_content ihdr_chunk = ( len(ihdr_content).to_bytes(4, byteorder="big") + ihdr + zlib.crc32(ihdr).to_bytes(4, byteorder="big") ) # IDAT chunk: image data raw_data = ( b"\x00" + bytes(color) * width ) # No filter, and RGB data for each pixel compressed_data = zlib.compress(raw_data * height) # Compress the data idat_chunk = ( len(compressed_data).to_bytes(4, byteorder="big") + b"IDAT" + compressed_data + zlib.crc32(b"IDAT" + compressed_data).to_bytes( length=4, byteorder="big", ) ) # IEND chunk: marks the image end iend_chunk = b"\x00\x00\x00\x00IEND\xae\x42\x60\x82" # Combine all chunks png_data = png_header + ihdr_chunk + idat_chunk + iend_chunk return png_data
[docs] @provider( tags=( "Binary", "Image", ) ) def svg( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), ) -> bytes: """Create an SVG image of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format (tuple of three integers). Defaults to (0, 0, 255). :return: Byte content of the SVG image. :rtype: bytes """ width, height = size return SVG_TPL.format(width=width, height=height, color=color).encode()
[docs] @provider( tags=( "Binary", "Image", ) ) def bmp( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), ) -> bytes: """Create a BMP image of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format (tuple of three integers). Defaults to (0, 0, 255). :return: Byte content of the BMP image. :rtype: bytes """ width, height = size # BMP Header and DIB Header (BITMAPINFOHEADER format) file_header = b"BM" # Signature dib_header = b"\x28\x00\x00\x00" # DIB Header size (40 bytes) # Image width and height width_bytes = width.to_bytes(4, byteorder="little") height_bytes = height.to_bytes(4, byteorder="little") # Image pixel data # BMP files are padded to be a multiple of 4 bytes wide row_padding = (4 - (3 * width) % 4) % 4 pixel_data = bytes(color[::-1]) * width + b"\x00" * row_padding image_data = pixel_data * height # File size file_size = ( 14 + 40 + len(image_data) ) # 14 bytes file header, 40 bytes DIB header file_size_bytes = file_size.to_bytes(4, byteorder="little") # Final assembly of the BMP file return ( file_header + file_size_bytes + b"\x00\x00\x00\x00" + b"\x36\x00\x00\x00" # Reserved 4 bytes # Pixel data offset (54 bytes: 14 for file header, 40 for DIB # header) + dib_header + width_bytes + height_bytes + b"\x01\x00" + b"\x18\x00" # Number of colour planes + b"\x00\x00\x00\x00" # Bits per pixel (24 for RGB) + len(image_data).to_bytes( # Compression method (0 for none) 4, byteorder="little" ) + b"\x13\x0b\x00\x00" # Size of the raw bitmap data # Print resolution of the image (2835 pixels/meter) + b"\x13\x0b\x00\x00" + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00" # Number of colours in the palette + image_data # Important colours )
[docs] @provider( tags=( "Binary", "Image", ) ) def gif( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), ) -> bytes: """Create a GIF image of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format (tuple of three integers). Defaults to (0, 0, 255). :return: Byte content of the GIF image. :rtype: bytes """ width, height = size # Header header = b"GIF89a" # Logical Screen Descriptor screen_width = width.to_bytes(2, byteorder="little") screen_height = height.to_bytes(2, byteorder="little") # Global Colour Table Flag set to 1, Colour resolution, and Sort Flag # to 0 packed_field = b"\xf7" bg_color_index = b"\x00" # Background Colour Index pixel_aspect_ratio = b"\x00" # No aspect ratio information # Global Colour Table. # Since it's a single colour, we only need one entry in our table, # rest are black. # Each colour is 3 bytes (RGB). color_table = bytes(color) + b"\x00" * (3 * 255) # Image Descriptor image_descriptor = ( b"\x2c" + b"\x00\x00\x00\x00" + screen_width + screen_height + b"\x00" ) # Image Data lzw_min_code_size = b"\x08" # Set to 8 for no compression # Image Data Blocks for a single colour. # Simplest LZW encoding for a single colour: clear code, followed # by colour index, end code. image_data_blocks = bytearray( [0x02, 0x4C, 0x01, 0x00] ) # Compressed data # Footer footer = b"\x3b" # Combine all parts return ( header + screen_width + screen_height + packed_field + bg_color_index + pixel_aspect_ratio + color_table + image_descriptor + lzw_min_code_size + image_data_blocks + footer )
[docs] @provider( tags=( "Binary", "Image", ) ) def tif( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), ) -> bytes: """Create a TIF image of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format (tuple of three integers). Defaults to (0, 0, 255). :return: Byte content of the TIF image. :rtype: bytes """ width, height = size r, g, b = color # TIFF Header # Byte order indication ('II' for little endian) # Version number (42) # Offset to the first IFD (8 bytes from the beginning) tiff_header = b"II\x2a\x00\x08\x00\x00\x00" # IFD setup num_entries = 12 ifd_offset = 8 next_ifd = 0 # No next IFD # Image data starting just after IFD # (8 bytes for header + 2 + 12*num_entries + 4 for next IFD) data_offset = ifd_offset + 2 + 12 * num_entries + 4 # Entries in IFD entries = [ (256, 4, 1, width), # Image width (257, 4, 1, height), # Image height (258, 3, 3, data_offset + width * height * 3), # Bits per sample (259, 3, 1, 1), # Compression (1 = no compression) (262, 3, 1, 2), # Photometric interpretation (2 = RGB) (273, 4, 1, data_offset), # Offset to image data (277, 3, 1, 3), # Samples per pixel (278, 4, 1, height), # Rows per strip (279, 4, 1, width * height * 3), # Strip byte counts (image data size) (282, 5, 1, data_offset + width * height * 3 + 6), # XResolution placeholder offset (283, 5, 1, data_offset + width * height * 3 + 14), # YResolution placeholder offset (284, 3, 1, 1), # Planar configuration (1 = chunky) ] # Write IFD ifd = bytearray() ifd += int.to_bytes(num_entries, 2, "little") for entry in entries: tag, type_, count, value = entry ifd += int.to_bytes(tag, 2, "little") ifd += int.to_bytes(type_, 2, "little") ifd += int.to_bytes(count, 4, "little") ifd += int.to_bytes(value, 4, "little") ifd += int.to_bytes(next_ifd, 4, "little") # Image data image_data = bytes([r, g, b] * width * height) # Bits per sample values (8 bits per channel) bits_per_sample = bytes([8, 0, 8, 0, 8, 0]) # Resolution (72 dpi, stored as a rational number 72/1) resolution = (72, 1) res_bytes = int.to_bytes(resolution[0], 4, "little") res_bytes += int.to_bytes(resolution[1], 4, "little") res_bytes += res_bytes # For both X and Y resolutions # Complete TIFF file return tiff_header + ifd + image_data + bits_per_sample + res_bytes
[docs] @provider( tags=( "Binary", "Image", ) ) def ppm( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), ) -> bytes: """Create a PPM image of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format, tuple of three integers: (0-255, 0-255, 0-255). Defaults to (0, 0, 255). :return: Byte content of the PPM image. :rtype: bytes """ width, height = size # PPM Header ppm_header = f"P6\n{width} {height}\n255\n".encode() # Image data image_data = bytearray() for _ in range(height): for _ in range(width): image_data.extend(color) # Complete PPM file return ppm_header + bytes(image_data)
[docs] @provider( tags=( "Binary", "Image", ) ) def jpg( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (128, 128, 128), ) -> bytes: """Create a JPG image of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format, tuple of three integers: (0-255, 0-255, 0-255). Defaults to (128, 128, 128). :return: Byte content of the JPG image. :rtype: bytes """ return JpgGenerator.generate(size=size, color=color)
[docs] @provider( tags=( "Binary", "Image", ) ) def image( self, image_format: Literal[ "png", "svg", "bmp", "gif", "tif", "ppm", "jpg", ] = "png", size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), ) -> bytes: """Create an image of a specified format, size and colour. :param image_format: Format of the image. Defaults to "png". :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format, tuple of three integers: (0-255, 0-255, 0-255). Defaults to (128, 128, 128). :return: Byte content of the image. :rtype: bytes :raises ValueError: If the image format is not supported. """ if image_format not in { "png", "svg", "bmp", "gif", "tif", "ppm", "jpg", }: raise ValueError() image_func = getattr(self, image_format) return image_func(size=size, color=color)
[docs] @provider( tags=( "Binary", "Audio", ) ) def wav( self, frequency: int = 440, duration: int = 1, volume: Union[float, int] = 0.5, sample_rate: int = 44100, ) -> bytes: """Create a WAV audio. :param frequency: The frequency of the tone in Hz. Defaults to 440. :param duration: Duration of the tone in seconds. Defaults to 1. :param volume: Volume of the tone, scale between 0.0 and 1.0. Defaults to 0.5. :param sample_rate: Sampling rate in Hz. Defaults to 44100. :return: Byte content of the WAV audio. :rtype: bytes """ num_samples = int(sample_rate * duration) samples_per_cycle = int(sample_rate / frequency) cycle = array.array( "h" ) # 'h' is the typecode for signed short integers # Precompute one cycle of the sine wave for i in range(samples_per_cycle): sample = ( volume * 32767 * math.sin(2 * math.pi * i / samples_per_cycle) ) cycle.append(int(sample)) # Generate full data by repeating the cycle data = (cycle * (1 + num_samples // samples_per_cycle))[:num_samples] # Prepare WAV file structure in a BytesIO stream buffer = io.BytesIO() with wave.open(buffer, "w") as _wav_file: _wav_file.setnchannels(1) # Mono _wav_file.setsampwidth(2) # 2 bytes per sample (16-bit) _wav_file.setframerate(sample_rate) _wav_file.writeframes(data.tobytes()) # Get the bytes wav_bytes = buffer.getvalue() buffer.close() return wav_bytes
[docs] @provider( tags=( "Binary", "Document", ) ) def docx( self, nb_pages: Optional[int] = 1, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, ) -> bytes: """Create a DOCX document. Usage example without params: .. code-block:: python FAKER.docx() Usage example with params: .. code-block:: python FAKER.docx(texts=("Page 1", "Page 2", "Page 3")) FAKER.docx(nb_pages=5) :param nb_pages: Number of pages to show. Defaults to 1. :param texts: List of texts to show. Defaults to None. :param metadata: Metadata to show. Defaults to None. :return: Byte content of the DOCX document. :rtype: bytes """ _docx = DocxGenerator(faker=self) return _docx.create(nb_pages=nb_pages, texts=texts, metadata=metadata)
[docs] @provider( tags=( "Binary", "Document", ) ) def rtf( self, nb_pages: Optional[int] = 1, texts: Optional[List[str]] = None, metadata: Optional["MetaData"] = None, ) -> bytes: """Create an RTF document. Usage example without params: .. code-block:: python FAKER.rtf() Usage example with params: .. code-block:: python FAKER.rtf(texts=("Page 1", "Page 2", "Page 3")) FAKER.rtf(nb_pages=5) :param nb_pages: Number of pages to show. Defaults to 1. :param texts: List of texts to show. Defaults to None. :param metadata: Metadata to show. Defaults to None. :return: Byte content of the RTF document. :rtype: bytes """ _rtf = RtfGenerator(faker=self) return _rtf.create(nb_pages=nb_pages, texts=texts, metadata=metadata)
[docs] @provider( tags=( "Binary", "Document", ) ) def epub( self, nb_pages: Optional[int] = 1, texts: Optional[List[str]] = None, metadata: Optional["MetaData"] = None, ) -> bytes: """Create a minimal EPUB document. Usage example without params: .. code-block:: python FAKER.epub() Usage example with params: .. code-block:: python FAKER.epub(texts=("Page 1", "Page 2", "Page 3")) FAKER.epub(nb_pages=5) :param nb_pages: Number of pages to show. Defaults to 1. :param texts: List of texts to show. Defaults to None. :param metadata: Metadata to show. Defaults to None. :return: Byte content of the minimal EPUB document. :rtype: bytes """ _epub = EpubGenerator(faker=self) return _epub.create(nb_pages=nb_pages, texts=texts, metadata=metadata)
[docs] @provider( tags=( "Binary", "Document", ) ) def odt( self, nb_pages: Optional[int] = 1, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, ) -> bytes: """Create a ODT document. Usage example without params: .. code-block:: python FAKER.odt() Usage example with params: .. code-block:: python FAKER.odt(texts=("Page 1", "Page 2", "Page 3")) FAKER.odt(nb_pages=5) :param nb_pages: Number of pages to show. Defaults to 1. :param texts: List of texts to show. Defaults to None. :param metadata: Metadata to show. Defaults to None. :return: Byte content of the ODT document. :rtype: bytes """ _odt = OdtGenerator(faker=self) return _odt.create(nb_pages=nb_pages, texts=texts, metadata=metadata)
[docs] @provider(tags=("Binary",)) def bin( self, length: int = 16, ) -> bytes: """Create random bytes. :param length: Length of the bytes to generate. Defaults to 16. :return: Byte content of the random bytes. :rtype: bytes """ return os.urandom(length)
[docs] @provider( tags=( "Binary", "Archive", ) ) def zip(self, options: Optional[Dict[str, Any]] = None, **kwargs): """Create a ZIP archive file as bytes. Usage example. A complex case. .. code-block:: python from fake import create_inner_txt_file, FAKER zip_file = FAKER.zip( options={ "count": 5, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": { "prefix": "zzz_file_", }, "directory": "zzz", }, ) """ data: Dict[str, Any] = { "inner": {}, "files": [], } fs_storage = FileSystemStorage() # Specific if options: # Complex case _count = options.get("count", 1) _create_inner_file_func = options.get( "create_inner_file_func", create_inner_txt_file ) _create_inner_file_args = options.get("create_inner_file_args", {}) _dir_path = Path("") _directory = options.get("directory", "") else: # Defaults _count = 1 _create_inner_file_func = create_inner_txt_file _create_inner_file_args = {} _dir_path = Path("") _directory = "" _zip_content = BytesIO() with zipfile.ZipFile(_zip_content, "w") as __fake_file: _kwargs = {} _kwargs.update(_create_inner_file_args) # If _create_inner_file_func returns a list of values if returns_list(_create_inner_file_func): _files = _create_inner_file_func( storage=fs_storage, **_kwargs, ) for __file in _files: data["inner"][str(__file)] = __file __file_abs_path = fs_storage.abspath(__file) _inner_path = resolve_inner_directory(__file, _directory) __fake_file.write( __file_abs_path, arcname=_inner_path, ) os.remove(__file_abs_path) # Clean up temporary files data["files"].append(_inner_path) # If _create_inner_file_func returns a single value else: for __i in range(_count): __file = _create_inner_file_func( storage=fs_storage, **_kwargs, ) data["inner"][str(__file)] = __file __file_abs_path = fs_storage.abspath(__file) _inner_path = resolve_inner_directory(__file, _directory) __fake_file.write( __file_abs_path, arcname=_inner_path, ) os.remove(__file_abs_path) # Clean up temporary files data["files"].append(_inner_path) raw_content = BytesValue(_zip_content.getvalue()) raw_content.data = data return raw_content
[docs] @provider( tags=( "Binary", "Archive", ) ) def tar( self, options: Optional[Dict[str, Any]] = None, compression: Optional[Literal["gz", "bz2", "xz"]] = None, **kwargs, ) -> BytesValue: """Generate a TAR archive file as bytes. :param options: Options (non-structured) for complex types, such as ZIP. :param compression: Desired compression. Can be None or `gz`, `bz2` or `xz`. :param **kwargs: Additional keyword arguments to pass to the function. :rtype: BytesValue :return: Relative path (from root directory) of the generated file or raw content of the file. Usage example. Complex case. .. code-block:: python from fake import create_inner_txt_file, FAKER tar_file = FAKER.tar( prefix="ttt_archive_", options={ "count": 5, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": { "prefix": "ttt_file_", }, "directory": "ttt", }, ) """ data: Dict[str, Any] = { "inner": {}, "files": [], } fs_storage = FileSystemStorage() # Specific if options: # Complex case _count = options.get("count", 1) _create_inner_file_func = options.get( "create_inner_file_func", create_inner_txt_file ) _create_inner_file_args = options.get("create_inner_file_args", {}) _dir_path = Path("") _directory = options.get("directory", "") else: # Defaults _count = 1 _create_inner_file_func = create_inner_txt_file _create_inner_file_args = {} _dir_path = Path("") _directory = "" _tar_content = BytesIO() _mode = "w" if compression and compression in TAR_COMPRESSION_OPTIONS: _mode += f":{compression}" with tarfile.open(fileobj=_tar_content, mode=_mode) as __fake_file: _kwargs = {} _kwargs.update(_create_inner_file_args) # If _create_inner_file_func returns a list of values if returns_list(_create_inner_file_func): _files = _create_inner_file_func( storage=fs_storage, **_kwargs, ) for __file in _files: data["inner"][str(__file)] = __file __file_abs_path = fs_storage.abspath(__file) _inner_path = resolve_inner_directory(__file, _directory) __fake_file.add( __file_abs_path, arcname=_inner_path, ) os.remove(__file_abs_path) # Clean up temporary files data["files"].append(_inner_path) # If _create_inner_file_func returns a single value else: for __i in range(_count): __file = _create_inner_file_func( storage=fs_storage, **_kwargs, ) data["inner"][str(__file)] = __file __file_abs_path = fs_storage.abspath(__file) _inner_path = resolve_inner_directory(__file, _directory) __fake_file.add( __file_abs_path, arcname=_inner_path, ) os.remove(__file_abs_path) # Clean up temporary files data["files"].append(_inner_path) raw_content = BytesValue(_tar_content.getvalue()) raw_content.data = data return raw_content
[docs] @provider(tags=("Archive", "Binary", "Email")) def eml( self, options: Optional[Dict[str, Any]] = None, content: Optional[str] = None, subject: Optional[str] = None, cte_type: Optional[str] = None, policy: Optional[Policy] = None, **kwargs, ) -> BytesValue: """Generate an EML file bytes. :param options: Options (non-structured) for complex types, such as ZIP. :param content: Email body text. :param subject: Email subject. :param cte_type: Content-Transfer-Encoding (CTE) type. :param policy: Email message policy. :param **kwargs: Additional keyword arguments to pass to the function. :rtype: BytesValue :return: Relative path (from root directory) of the generated file or raw content of the file. Usage example. A complex case. .. code-block:: python from fake import create_inner_docx_file, FAKER eml_file = FAKER.eml_file( prefix="zzz_email_", options={ "count": 5, "create_inner_file_func": create_inner_docx_file, "create_inner_file_args": { "prefix": "zzz_file_", }, } ) """ fs_storage = FileSystemStorage() if not content: content = self.text() if not subject: subject = self.sentence() data: Dict[str, Any] = { "content": f"{subject}\n {content}", "inner": {}, } # Choose the policy to use for creating and serializing if policy is None: # Clone the `default` and apply `cte_type` if given policy = default.clone(cte_type=cte_type) if cte_type else default msg = EmailMessage(policy=policy) msg["To"] = self.email() msg["From"] = self.email() msg["Subject"] = subject msg.set_content(content, cte=policy.cte_type) data.update( { "to": msg["To"], "from": msg["From"], "subject": msg["Subject"], "body": content, } ) # Specific if options: # Complex case _count = options.get("count", 1) _create_inner_file_func = options.get("create_inner_file_func") _create_inner_file_args = options.get("create_inner_file_args", {}) else: # Defaults _count = 1 _create_inner_file_func = None _create_inner_file_args = {} _kwargs = {} _kwargs.update(_create_inner_file_args) if _create_inner_file_func and callable(_create_inner_file_func): # If _create_inner_file_func returns a list of values if returns_list(_create_inner_file_func): _files = _create_inner_file_func( storage=fs_storage, **_kwargs, ) for __file in _files: data["inner"][str(__file)] = __file __file_abs_path = fs_storage.abspath(__file) _maintype, _subtype = get_mime_maintype_subtype( path=__file_abs_path, ) with open(__file_abs_path, "rb") as _fp: _file_data = _fp.read() msg.add_attachment( _file_data, maintype=_maintype, subtype=_subtype, filename=os.path.basename(__file), ) os.remove(__file_abs_path) # Clean up temporary files # If _create_inner_file_func returns a single value else: for __i in range(_count): __file = _create_inner_file_func( storage=fs_storage, **_kwargs, ) data["inner"][str(__file)] = __file __file_abs_path = fs_storage.abspath(__file) _maintype, _subtype = get_mime_maintype_subtype( path=__file_abs_path, ) with open(__file_abs_path, "rb") as _fp: _file_data = _fp.read() msg.add_attachment( _file_data, maintype=_maintype, subtype=_subtype, filename=os.path.basename(__file), ) os.remove(__file_abs_path) # Clean up temporary files raw_content = BytesValue(msg.as_bytes(policy=policy)) raw_content.data = data return raw_content
[docs] @provider(tags=("Document", "File")) def pdf_file( self, nb_pages: int = 1, generator: Union[ Type[TextPdfGenerator], Type[GraphicPdfGenerator] ] = GraphicPdfGenerator, metadata: Optional[MetaData] = None, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, **kwargs, ) -> StringValue: """Create a PDF file. Usage example without params: .. code-block:: python FAKER.pdf_file() Usage example with params: .. code-block:: python FAKER.pdf_file(nb_pages=5, prefix="custom_pdf_") :param nb_pages: Number of pages in the PDF. Defaults to 1. :param generator: PDF generator class to use. Defaults to GraphicPdfGenerator. :param metadata: Metadata for the PDF. Defaults to None. :param storage: Storage backend to use. Defaults to None, in which case FileSystemStorage is used. :param basename: Base name for the file. Can also be a :class:`StringTemplate` or :class:`LazyStringTemplate`. Defaults to None. :param prefix: Prefix for the file name. Can also be a :class:`StringTemplate` or :class:`LazyStringTemplate`. Defaults to None. :param **kwargs: Additional keyword arguments to pass to the PDF generator. :return: StringValue containing the relative path of the generated PDF file. :rtype: StringValue """ if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") filename = storage.generate_filename( extension="pdf", prefix=prefix, basename=basename, ) if not metadata: metadata = MetaData() data = self.pdf( nb_pages=nb_pages, generator=generator, metadata=metadata, **kwargs ) storage.write_bytes(filename=filename, data=data) file = StringValue(storage.relpath(filename)) file.data = { "storage": storage, "filename": filename, "content": metadata.content, } FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("Document", "File")) def text_pdf_file( self, nb_pages: int = 1, generator: Type[TextPdfGenerator] = TextPdfGenerator, metadata: Optional[MetaData] = None, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, **kwargs, ) -> StringValue: """Create a text PDF file. Usage example without params: .. code-block:: python FAKER.text_pdf_file() Usage example with params: .. code-block:: python FAKER.text_pdf_file(nb_pages=5, prefix="custom_text_pdf_") :param nb_pages: Number of pages in the PDF. Defaults to 1. :param generator: PDF generator class to use. Defaults to TextPdfGenerator. :param metadata: Metadata for the PDF. Defaults to None. :param storage: Storage backend to use. Defaults to None, in which case FileSystemStorage is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :param **kwargs: Additional keyword arguments to pass to the PDF generator. :return: StringValue containing the relative path of the generated PDF file. :rtype: StringValue """ return self.pdf_file( nb_pages=nb_pages, generator=generator, metadata=metadata, storage=storage, basename=basename, prefix=prefix, **kwargs, )
def _image_file( self, image_format: Literal[ "png", "svg", "bmp", "gif", "tif", "ppm", "jpg", ] = "png", size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), extension: Optional[str] = None, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, ) -> StringValue: if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") if extension is None: extension = image_format filename = storage.generate_filename( extension=extension, prefix=prefix, basename=basename, ) data = self.image(image_format=image_format, size=size, color=color) storage.write_bytes(filename=filename, data=data) file = StringValue(storage.relpath(filename)) file.data = {"storage": storage, "filename": filename} FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("Image", "File")) def png_file( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, extension: Optional[str] = None, ) -> StringValue: """Create a PNG image file of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format (tuple of three integers). Defaults to (0, 0, 255). :param storage: Storage backend to use. Defaults to None, in which case FileSystemStorage is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :param extension: File extension. Defaults to None, in which case the `image_format` value is used. :return: StringValue containing the relative path of the generated PNG file. :rtype: StringValue """ return self._image_file( image_format="png", size=size, color=color, storage=storage, basename=basename, prefix=prefix, extension=extension, )
[docs] @provider(tags=("Image", "File")) def svg_file( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, extension: Optional[str] = None, ) -> StringValue: """Create an SVG image file of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format (tuple of three integers). Defaults to (0, 0, 255). :param storage: Storage backend to use. Defaults to None, in which case `FileSystemStorage` is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :param extension: File extension. Defaults to None, in which case the `image_format` value is used. :return: `StringValue` containing the relative path of the generated SVG file. :rtype: StringValue """ return self._image_file( image_format="svg", size=size, color=color, storage=storage, basename=basename, prefix=prefix, extension=extension, )
[docs] @provider(tags=("Image", "File")) def bmp_file( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, extension: Optional[str] = None, ) -> StringValue: """Create a BMP image file of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format (tuple of three integers). Defaults to (0, 0, 255). :param storage: Storage backend to use. Defaults to None, in which case `FileSystemStorage` is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :param extension: File extension. Defaults to None, in which case the `image_format` value is used. :return: `StringValue` containing the relative path of the generated BMP file. :rtype: StringValue """ return self._image_file( image_format="bmp", size=size, color=color, storage=storage, basename=basename, prefix=prefix, extension=extension, )
[docs] @provider(tags=("Image", "File")) def gif_file( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, extension: Optional[str] = None, ) -> StringValue: """Create a GIF image file of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format (tuple of three integers). Defaults to (0, 0, 255). :param storage: Storage backend to use. Defaults to None, in which case `FileSystemStorage` is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :param extension: File extension. Defaults to None, in which case the `image_format` value is used. :return: StringValue containing the relative path of the generated GIF file. :rtype: StringValue """ return self._image_file( image_format="gif", size=size, color=color, storage=storage, basename=basename, prefix=prefix, extension=extension, )
[docs] @provider(tags=("Image", "File")) def tif_file( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, extension: Optional[str] = None, ) -> StringValue: """Create a TIF image file of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format (tuple of three integers). Defaults to (0, 0, 255). :param storage: Storage backend to use. Defaults to None, in which case `FileSystemStorage` is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :param extension: File extension. Defaults to None, in which case the `image_format` value is used. :return: `StringValue` containing the relative path of the generated TIF file. :rtype: StringValue """ return self._image_file( image_format="tif", size=size, color=color, extension=extension, storage=storage, basename=basename, prefix=prefix, )
[docs] @provider(tags=("Image", "File")) def ppm_file( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, extension: Optional[str] = None, ) -> StringValue: """Create a PPM image file of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format (tuple of three integers). Defaults to (0, 0, 255). :param storage: Storage backend to use. Defaults to None, in which case `FileSystemStorage` is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :param extension: File extension. Defaults to None, in which case the `image_format` value is used. :return: `StringValue` containing the relative path of the generated PPM file. :rtype: StringValue """ return self._image_file( image_format="ppm", size=size, color=color, extension=extension, storage=storage, basename=basename, prefix=prefix, )
[docs] @provider(tags=("Image", "File")) def jpg_file( self, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (128, 128, 128), storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, extension: Optional[str] = None, ) -> StringValue: """Create a JPG image file of a specified size and colour. :param size: Tuple of width and height of the image in pixels. Defaults to (100, 100). :param color: Colour of the image in RGB format (tuple of three integers). Defaults to (128, 128, 128). :param storage: Storage backend to use. Defaults to None, in which case `FileSystemStorage` is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :param extension: File extension. Defaults to None, in which case the `image_format` value is used. :return: `StringValue` containing the relative path of the generated JPG file. :rtype: StringValue """ return self._image_file( image_format="jpg", size=size, color=color, extension=extension, storage=storage, basename=basename, prefix=prefix, )
[docs] @provider(tags=("Audio", "File")) def wav_file( self, frequency: int = 440, duration: int = 1, volume: Union[float, int] = 0.5, sample_rate: int = 44100, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, ) -> StringValue: """Create a WAV audio file. :param frequency: Frequency of the audio in Hertz. Defaults to 440. :param duration: Duration of the audio in seconds. Defaults to 1. :param volume: Volume of the audio (0.0 to 1.0). Defaults to 0.5. :param sample_rate: Sample rate of the audio in samples per second. Defaults to 44100. :param storage: Storage backend to use. Defaults to None, in which case `FileSystemStorage` is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :return: `StringValue` containing the relative path of the generated WAV file. :rtype: StringValue """ if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") filename = storage.generate_filename( extension="wav", prefix=prefix, basename=basename, ) data = self.wav( frequency=frequency, duration=duration, volume=volume, sample_rate=sample_rate, ) storage.write_bytes(filename=filename, data=data) file = StringValue(storage.relpath(filename)) file.data = { "storage": storage, "filename": filename, } FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("Document", "File")) def docx_file( self, nb_pages: int = 1, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, ) -> StringValue: """Create a DOCX document file. Usage example without params: .. code-block:: python FAKER.docx_file() Usage example with params: .. code-block:: python FAKER.docx_file(nb_pages=5, prefix="custom_docx_") FAKER.docx_file(texts=["First page", "Second page", "Third page"]) :param nb_pages: Number of pages to show. Defaults to 1. :param texts: List of texts to show. Defaults to None. :param metadata: Metadata to show. Defaults to None. :param storage: Storage backend to use. Defaults to None, in which case `FileSystemStorage` is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :return: `StringValue` containing the relative path of the generated DOCX file. :rtype: StringValue """ if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") filename = storage.generate_filename( extension="docx", prefix=prefix, basename=basename, ) if not metadata: metadata = MetaData() data = self.docx(nb_pages=nb_pages, texts=texts, metadata=metadata) storage.write_bytes(filename=filename, data=data) file = StringValue(storage.relpath(filename)) file.data = { "storage": storage, "filename": filename, "content": metadata.content, } FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("Document", "File")) def rtf_file( self, nb_pages: int = 1, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, ) -> StringValue: """Create an RTF document file. Usage example without params: .. code-block:: python FAKER.rtf_file() Usage example with params: .. code-block:: python FAKER.rtf_file(nb_pages=5, prefix="custom_rtf_") FAKER.rtf_file(texts=["First page", "Second page", "Third page"]) :param nb_pages: Number of pages to show. Defaults to 1. :param texts: List of texts to show. Defaults to None. :param metadata: Metadata to show. Defaults to None. :param storage: Storage backend to use. Defaults to None, in which case `FileSystemStorage` is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :return: `StringValue` containing the relative path of the generated RTF file. :rtype: StringValue """ if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") filename = storage.generate_filename( extension="rtf", prefix=prefix, basename=basename, ) if not metadata: metadata = MetaData() data = self.rtf(nb_pages=nb_pages, texts=texts, metadata=metadata) storage.write_bytes(filename=filename, data=data) file = StringValue(storage.relpath(filename)) file.data = { "storage": storage, "filename": filename, "content": metadata.content, } FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("Document", "File")) def epub_file( self, nb_pages: int = 1, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, ) -> StringValue: """Create a EPUB document file. Usage example without params: .. code-block:: python FAKER.epub_file() Usage example with params: .. code-block:: python FAKER.epub_file(nb_pages=5, prefix="custom_epub_") FAKER.epub_file(texts=["First page", "Second page", "Third page"]) :param nb_pages: Number of pages to show. Defaults to 1. :param texts: List of texts to show. Defaults to None. :param metadata: Metadata to show. Defaults to None. :param storage: Storage backend to use. Defaults to None, in which case `FileSystemStorage` is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :return: `StringValue` containing the relative path of the generated EPUB file. :rtype: StringValue """ if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") filename = storage.generate_filename( extension="epub", prefix=prefix, basename=basename, ) if not metadata: metadata = MetaData() data = self.epub(nb_pages=nb_pages, texts=texts, metadata=metadata) storage.write_bytes(filename=filename, data=data) file = StringValue(storage.relpath(filename)) file.data = { "storage": storage, "filename": filename, "content": metadata.content, } FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("Document", "File")) def odt_file( self, nb_pages: int = 1, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, ) -> StringValue: """Create a ODT document file. Usage example without params: .. code-block:: python FAKER.odt_file() Usage example with params: .. code-block:: python FAKER.odt_file(nb_pages=5, prefix="custom_odt_") FAKER.odt_file(texts=["First page", "Second page", "Third page"]) :param nb_pages: Number of pages to show. Defaults to 1. :param texts: List of texts to show. Defaults to None. :param metadata: Metadata to show. Defaults to None. :param storage: Storage backend to use. Defaults to None, in which case `FileSystemStorage` is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :return: `StringValue` containing the relative path of the generated ODT file. :rtype: StringValue """ if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") filename = storage.generate_filename( extension="odt", prefix=prefix, basename=basename, ) if not metadata: metadata = MetaData() data = self.odt(nb_pages=nb_pages, texts=texts, metadata=metadata) storage.write_bytes(filename=filename, data=data) file = StringValue(storage.relpath(filename)) file.data = { "storage": storage, "filename": filename, "content": metadata.content, } FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("File",)) def bin_file( self, length: int = 16, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, **kwargs, ) -> StringValue: """Create a BIN file.""" if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") filename = storage.generate_filename( extension="bin", prefix=prefix, basename=basename, ) data = self.bin(length=length) storage.write_bytes(filename=filename, data=data) file = StringValue(storage.relpath(filename)) file.data = { "storage": storage, "filename": filename, } FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("Archive", "File")) def zip_file( self, metadata: Optional[MetaData] = None, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, options: Optional[Dict[str, Any]] = None, **kwargs, ) -> StringValue: """Create a ZIP archive file. Usage example. A simple case. .. code-block:: python from fake import FAKER zip_archive = FAKER.zip() Usage example. A complex case. .. code-block:: python from fake import create_inner_txt_file, FAKER zip_file = FAKER.zip( prefix="zzz_archive_", options={ "count": 5, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": { "prefix": "zzz_file_", }, "directory": "zzz", }, ) """ if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") filename = storage.generate_filename( extension="zip", prefix=prefix, basename=basename, ) if not metadata: metadata = MetaData() data = self.zip(metadata=metadata, options=options) storage.write_bytes(filename=filename, data=data) file = StringValue(storage.relpath(filename)) file.data = { "storage": storage, "filename": filename, "content": metadata.content, } FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("Archive", "File")) def tar_file( self, metadata: Optional[MetaData] = None, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, options: Optional[Dict[str, Any]] = None, compression: Optional[Literal["gz", "bz2", "xz"]] = None, **kwargs, ) -> StringValue: """Create a TAR archive file.""" if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") filename = storage.generate_filename( extension="tar", prefix=prefix, basename=basename, ) if not metadata: metadata = MetaData() data = self.tar( metadata=metadata, options=options, compression=compression, ) storage.write_bytes(filename=filename, data=data) file = StringValue(storage.relpath(filename)) file.data = { "storage": storage, "filename": filename, "content": metadata.content, } FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("Archive", "Email", "File")) def eml_file( self, metadata: Optional[MetaData] = None, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, options: Optional[Dict[str, Any]] = None, content: Optional[str] = None, subject: Optional[str] = None, cte_type: Optional[str] = None, policy: Optional[Policy] = None, **kwargs, ) -> StringValue: """Create an EML file. :param metadata: Metadata for the EML file. Defaults to None. :param storage: Storage backend to use. Defaults to None, in which case FileSystemStorage is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :param options: Additional options for EML generation. Defaults to None. :param content: Content of the email. Defaults to None. :param subject: Subject of the email. Defaults to None. :param cte_type: Content-Transfer-Encoding type. Defaults to None. :param policy: Email policy to use. Defaults to None. :param **kwargs: Additional keyword arguments. :return: StringValue containing the relative path of the generated EML file. :rtype: StringValue """ if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") filename = storage.generate_filename( extension="eml", prefix=prefix, basename=basename, ) if not metadata: metadata = MetaData() data = self.eml( metadata=metadata, options=options, content=content, subject=subject, cte_type=cte_type, policy=policy, ) storage.write_bytes(filename=filename, data=data) file = StringValue(storage.relpath(filename)) file.data = { "storage": storage, "filename": filename, "content": metadata.content, } FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("File", "Text")) def txt_file( self, nb_chars: Optional[int] = 200, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, text: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, ) -> StringValue: """Create a text document file. :param nb_chars: Number of characters in the text file. Defaults to 200. :param storage: Storage backend to use. Defaults to None, in which case FileSystemStorage is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :param text: Text content to write to the file. If None, random text will be generated. Can also be a :class:`StringTemplate` or :class:`LazyStringTemplate`. Defaults to None. :return: StringValue containing the relative path of the generated text file. :rtype: StringValue """ if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if isinstance(text, (StringTemplate, LazyStringTemplate)): text = str(text) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") filename = storage.generate_filename( extension="txt", prefix=prefix, basename=basename, ) if not text: if not nb_chars: nb_chars = 200 text = self.text(nb_chars=nb_chars) storage.write_text(filename=filename, data=text) # type: ignore file = StringValue(storage.relpath(filename)) file.data = { "storage": storage, "filename": filename, "content": text, } FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("File",)) def generic_file( self, content: Union[bytes, str, StringTemplate, LazyStringTemplate], extension: str, storage: Optional[BaseStorage] = None, basename: Optional[ Union[str, StringTemplate, LazyStringTemplate] ] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, ) -> StringValue: """Create a generic file. :param content: Content to write to the file. Can be bytes, string, :class:`StringTemplate`, or :class:`LazyStringTemplate`. Templates are rendered fresh on each call before writing. :param extension: File extension. :param storage: Storage backend to use. Defaults to None, in which case FileSystemStorage is used. :param basename: Base name for the file. Defaults to None. :param prefix: Prefix for the file name. Defaults to None. :return: StringValue containing the relative path of the generated file. :rtype: StringValue """ if storage is None: storage = FileSystemStorage() if isinstance(basename, (StringTemplate, LazyStringTemplate)): basename = str(basename) if isinstance(prefix, (StringTemplate, LazyStringTemplate)): prefix = str(prefix) if basename: _validate_filename_component(basename, "basename") if prefix: _validate_filename_component(prefix, "prefix") filename = storage.generate_filename( extension=extension, prefix=prefix, basename=basename, ) if isinstance(content, (StringTemplate, LazyStringTemplate)): content = str(content) if isinstance(content, bytes): storage.write_bytes(filename, content) else: storage.write_text(filename, content) file = StringValue(storage.relpath(filename)) file.data = { "content": content, "filename": filename, "storage": storage, } FILE_REGISTRY.add(file) return file
[docs] @provider(tags=("Geographic",)) def city(self) -> str: """Get a random city. :return: A random city name. :rtype: str """ return self.random.choice(self._cities)
[docs] @provider(tags=("Geographic",)) def country(self) -> str: """Get a random country. :return: A random country name. :rtype: str """ return self.random.choice(self._countries)
[docs] @provider(tags=("Geographic",)) def geo_location(self) -> str: """Get a random geo-location. :return: A random geo-location name. :rtype: str """ return self.random.choice(self._geo_locations)
[docs] @provider(tags=("Geographic",)) def country_code(self) -> str: """Get a random country code. :return: A random country code. :rtype: str """ return self.random.choice(self._country_codes)
[docs] @provider(tags=("Geographic",)) def locale(self) -> str: """Get a random locale. :return: A random locale. :rtype: str """ return self.random.choice(self._locales)
[docs] @provider(tags=("Geographic",)) def latitude(self) -> float: """Generate a random latitude. :return: A random latitude. :rtype: float """ return self.random.uniform(-90, 90)
lat = latitude # noqa
[docs] @provider(tags=("Geographic",)) def longitude(self) -> float: """Generate a random longitude. :return: A random longitude. :rtype: float """ return self.random.uniform(-180, 180)
lng = lon = longitude # noqa
[docs] @provider(tags=("Geographic",)) def latitude_longitude(self) -> Tuple[float, float]: """Generate a random (latitude, longitude) pair. :return: A tuple containing a random latitude and longitude. :rtype: Tuple[float, float] """ return ( self.random.uniform(-90, 90), self.random.uniform(-180, 180), )
latlng = latlon = latitude_longitude # noqa
[docs] @provider(tags=("Banking",)) def iban( self, country_code: Optional[str] = None, bank_length: int = 8, account_length: int = 10, ) -> str: """Generate a random valid IBAN number. Usage example without params: .. code-block:: python from fake import FAKER FAKER.iban() # 'BA36243707341183493142' Usage example with params: .. code-block:: python from fake import FAKER FAKER.iban(country_code="DE", bank_length=8, account_length=10) # 'DE45984597116997556579' :param country_code: Optional country code. If None, a random country code is selected. Defaults to None. :param bank_length: Length of the bank identifier part. Defaults to 8. :param account_length: Length of the account number part. Defaults to 10. :return: A random valid IBAN number. :rtype: str """ if not country_code: country_code = self.random.choice(self._country_codes) # Generate the bank number and account number bank_number = "".join( str(self.random.randint(0, 9)) for _ in range(bank_length) ) account_number = "".join( str(self.random.randint(0, 9)) for _ in range(account_length) ) # Basic Bank Account Number (BBAN) bban = bank_number + account_number # Convert country code and calculate checksum country_number = "".join( str(ord(c) - 55) for c in country_code ) # Convert letters to numbers # Temporary IBAN for checksum calculation temporary_iban = bban + country_number + "00" # Calculate the checksum using modulo 97 checksum = 98 - (int(temporary_iban) % 97) # Format checksum to be two digits checksum_str = f"{checksum:02d}" # Construct the actual IBAN iban = f"{country_code}{checksum_str}{bban}" return iban
def _calculate_isbn10_checksum(self, digits: str) -> str: """Calculate ISBN-10 checksum using correct modulo 11 calculation.""" weights = range(10, 1, -1) # Weights from 10 to 2 total = sum(w * int(d) for w, d in zip(weights, digits)) remainder = total % 11 checksum = "X" if remainder == 1 else str((11 - remainder) % 11) return checksum
[docs] @provider(tags=("Book",)) def isbn10(self) -> str: """Generate a random valid ISBN-10. :return: A random valid ISBN-10 number in the format 'XXX-XXX-XXX-X'. :rtype: str """ # Randomly generate the first 9 digits as a string digits = "".join(str(self.random.randint(0, 9)) for _ in range(9)) # Calculate the checksum digit checksum = self._calculate_isbn10_checksum(digits) # Form the full ISBN-10 number isbn10 = digits + checksum # Example format: 'XXX-XXX-XXX-X', commonly used in ISBN return f"{isbn10[:3]}-{isbn10[3:6]}-{isbn10[6:9]}-{isbn10[9]}"
def _isbn13_checksum(self, digits: List[str]) -> str: """Calculate the ISBN-13 checksum digit.""" total = sum( (3 if i % 2 else 1) * int(digit) for i, digit in enumerate(digits) ) checksum = (10 - (total % 10)) % 10 return str(checksum)
[docs] @provider(tags=("Book",)) def isbn13(self) -> str: """Generate a random valid ISBN-13, starting with 978 or 979. :return: A random valid ISBN-13 number in the format 'XXX-X-XXX-XXXXX-X'. :rtype: str """ prefix = self.random.choice(["978", "979"]) digits = [str(self.random.randint(0, 9)) for _ in range(9)] full_digits = list(prefix) + digits checksum = self._isbn13_checksum(full_digits) isbn = "".join(full_digits) + checksum return f"{isbn[0:3]}-{isbn[3:4]}-{isbn[4:7]}-{isbn[7:12]}-{isbn[12]}"
[docs] @provider(tags=("Choice",)) def random_choice(self, elements: ElementType[T]) -> T: """Random choice: pick a single element from the given list. Example usage: .. code-block:: python from fake import FAKER FAKER.random_choice(("Art", "Photography", "Generative AI")) # 'Photography' :param elements: A list or tuple of elements to choose from. :return: A randomly selected element from the input list. :rtype: T """ return self.random.choice(elements)
random_element = random_choice # noqa
[docs] @provider(tags=("Choice",)) def random_sample(self, elements: ElementType[T], length: int) -> List[T]: """Random sample: pick random `length` elements from the given list. Example usage: .. code-block:: python from fake import FAKER FAKER.random_sample(("Art", "Photography", "Generative AI"), 2) # ['Photography', 'Art'] """ return self.random.sample(elements, length)
random_elements = random_sample # noqa
[docs] @provider(tags=("Text",)) def randomise_string( self, value: str, letters: str = string.ascii_uppercase, digits: str = string.digits, ) -> str: """Randomise string: `?` is replaced with letter, `#` with number. Usage example: .. code-block:: python from fake import FAKER FAKER.randomise_string(value="10.####/####-####") # '10.4613/4636-8596' FAKER.randomise_string(value="???? ##") # '1234 AB' :param value: Input string containing `?` and `#` placeholders. :param letters: String of characters to use for `?` replacement. Defaults to uppercase ASCII letters. :param digits: String of characters to use for `#` replacement. Defaults to digits 0-9. :returns: String with `?` replaced by random letters and `#` by random digits. :rtype: str """ result = "" for char in value: if char == "?": result += self.random.choice(letters) elif char == "#": result += self.random.choice(digits) else: result += char return result
randomize_string = bothify = randomise_string # noqa
[docs] @provider(tags=("Text",)) def string_template( self, template: str, wrap_chars_after: Optional[int] = None, faker: Optional["Faker"] = None, ) -> str: """String template. Generate a string by substituting placeholders in a template with fake data or provided values. Usage example: .. code-block:: python from fake import FAKER FAKER.string_template( "Hello {first_name}! I have two words for you: {words(nb=2)}" ) # "Hello Mike! I have two words for you: ['idea', 'first']" :param template: Template string containing placeholders in curly braces (e.g., '{name}', '{words(nb=2)}'). Placeholders should match Faker method names or be overridden by kwargs. :param wrap_chars_after: Number of characters to wrap around. :param faker: Faker instance to use. :rtype: str :returns: String with placeholders replaced with correspondent fake values. """ return StringTemplate( template=template, wrap_chars_after=wrap_chars_after, faker=faker or self, )
[docs] @provider(tags=("Text",)) def lazy_string_template( self, template: str, wrap_chars_after: Optional[int] = None, faker: Optional["Faker"] = None, ) -> "LazyStringTemplate": """Lazy string template. Generate a string by substituting placeholders in a template with fake data or provided values. Usage example: .. code-block:: python from fake import FAKER tpl = FAKER.lazy_string_template( "Hello {first_name}! I have two words for you: {words(nb=2)}" ) print(tpl) # "Hello Mike! I have two words for you: ['idea', 'first']" print(tpl) # Hello Lars! I have two words for you: ['be', 'if'] :param template: Template string containing placeholders in curly braces (e.g., '{name}', '{words(nb=2)}'). Placeholders should match Faker method names or be overridden by kwargs. :param wrap_chars_after: Number of characters to wrap around. :param faker: Faker instance to use. :rtype: LazyStringTemplate :returns: String with placeholders replaced with correspondent fake values. """ return LazyStringTemplate( template=template, wrap_chars_after=wrap_chars_after, faker=faker or self, )
def _validate_filename_component(value: str, param_name: str) -> None: """Validate filename component has no path traversal or invalid chars. :param value: The value to validate. :param param_name: The parameter name for error messages. :raises ValueError: If the value contains path traversal or invalid chars. """ if not value: return if os.path.isabs(value): raise ValueError( f"Invalid {param_name}: '{value}' must be a relative filename, " f"not an absolute path." ) if ".." in value or value.startswith("/") or value.startswith("\\"): raise ValueError( f"Invalid {param_name}: '{value}' has path traversal characters." ) invalid_chars = set(r'<>"|?*') if any(c in value for c in invalid_chars): raise ValueError( f"Invalid {param_name}: '{value}' contains invalid characters. " f"Cannot contain: {invalid_chars}" ) FAKER = Faker(alias="default")
[docs] def create_inner_pdf_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, nb_pages: Optional[int] = 1, generator: Union[ Type[TextPdfGenerator], Type[GraphicPdfGenerator] ] = GraphicPdfGenerator, metadata: Optional[MetaData] = None, dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner PDF file.""" sv = FAKER.pdf_file( storage=storage, basename=basename, prefix=prefix, nb_pages=nb_pages, generator=generator, metadata=metadata, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_text_pdf_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, nb_pages: Optional[int] = 1, generator: Type[TextPdfGenerator] = TextPdfGenerator, metadata: Optional[MetaData] = None, dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner text PDF file.""" sv = FAKER.text_pdf_file( storage=storage, basename=basename, prefix=prefix, nb_pages=nb_pages, generator=generator, metadata=metadata, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_png_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner PNG file.""" sv = FAKER.png_file( storage=storage, basename=basename, prefix=prefix, size=size, color=color, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_svg_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner SVG file.""" sv = FAKER.svg_file( storage=storage, basename=basename, prefix=prefix, size=size, color=color, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_bmp_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner BMP file.""" sv = FAKER.bmp_file( storage=storage, basename=basename, prefix=prefix, size=size, color=color, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_gif_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner GIF file.""" sv = FAKER.gif_file( storage=storage, basename=basename, prefix=prefix, size=size, color=color, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_tif_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner TIF file.""" sv = FAKER.tif_file( storage=storage, basename=basename, prefix=prefix, size=size, color=color, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_ppm_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (0, 0, 255), dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner PPM file.""" sv = FAKER.ppm_file( storage=storage, basename=basename, prefix=prefix, size=size, color=color, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_jpg_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, size: Tuple[int, int] = (100, 100), color: Tuple[int, int, int] = (128, 128, 128), dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner JPG file.""" sv = FAKER.jpg_file( storage=storage, basename=basename, prefix=prefix, size=size, color=color, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_wav_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, frequency: int = 440, duration: int = 1, volume: Union[float, int] = 0.5, sample_rate: int = 44100, dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner WAV file.""" sv = FAKER.wav_file( storage=storage, basename=basename, prefix=prefix, frequency=frequency, duration=duration, volume=volume, sample_rate=sample_rate, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_docx_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, nb_pages: Optional[int] = 1, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner DOCX file.""" sv = FAKER.docx_file( storage=storage, basename=basename, prefix=prefix, nb_pages=nb_pages, texts=texts, metadata=metadata, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_rtf_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, nb_pages: Optional[int] = 1, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner RTF file.""" sv = FAKER.rtf_file( storage=storage, basename=basename, prefix=prefix, nb_pages=nb_pages, texts=texts, metadata=metadata, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
def create_inner_epub_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, nb_pages: Optional[int] = 1, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner EPUB file.""" sv = FAKER.epub_file( storage=storage, basename=basename, prefix=prefix, nb_pages=nb_pages, texts=texts, metadata=metadata, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_odt_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, nb_pages: Optional[int] = 1, texts: Optional[List[str]] = None, metadata: Optional[MetaData] = None, dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner ODT file.""" sv = FAKER.odt_file( storage=storage, basename=basename, prefix=prefix, nb_pages=nb_pages, texts=texts, metadata=metadata, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_zip_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, options: Optional[Dict[str, Any]] = None, metadata: Optional[MetaData] = None, dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner ZIP file.""" sv = FAKER.zip_file( storage=storage, basename=basename, prefix=prefix, options=options, metadata=metadata, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_tar_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, options: Optional[Dict[str, Any]] = None, compression: Optional[Literal["gz", "bz2", "xz"]] = None, metadata: Optional[MetaData] = None, dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner TAR file.""" sv = FAKER.tar_file( storage=storage, basename=basename, prefix=prefix, options=options, compression=compression, metadata=metadata, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_eml_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, options: Optional[Dict[str, Any]] = None, content: Optional[str] = None, subject: Optional[str] = None, cte_type: Optional[str] = None, policy: Optional[Policy] = None, metadata: Optional[MetaData] = None, dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner EML file.""" sv = FAKER.eml_file( storage=storage, basename=basename, prefix=prefix, options=options, content=content, subject=subject, cte_type=cte_type, policy=policy, metadata=metadata, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_txt_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, text: Optional[str] = None, dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create inner TXT file.""" if not text: text = FAKER.text() sv = FAKER.txt_file( storage=storage, basename=basename, prefix=prefix, text=text, **kwargs, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def create_inner_generic_file( storage: Optional[BaseStorage] = None, basename: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, prefix: Optional[Union[str, StringTemplate, LazyStringTemplate]] = None, content: Union[bytes, str, StringTemplate, LazyStringTemplate] = "", extension: str = "txt", dir_path: Optional[str] = None, **kwargs, ) -> StringValue: """Create an inner file of any format with arbitrary content. Unlike the other ``create_inner_*`` functions which each target a specific file format, this function accepts any ``extension`` and any ``content`` — including a :class:`StringTemplate` or :class:`LazyStringTemplate`, which is rendered fresh on each call. Usage examples:: # Plain string content create_inner_generic_file( content="<root/>", extension="xml", ) # StringTemplate — rendered once at call-site construction time create_inner_generic_file( content=StringTemplate("<title>{sentence(nb_words=4)}</title>"), extension="xml", ) # LazyStringTemplate — rendered fresh on every call, useful with # count > 1 so each file gets independently generated content create_inner_generic_file( content=LazyStringTemplate("<title>{sentence(nb_words=4)}</title>"), extension="xml", dir_path="{dir_path(depth=2)}", ) :param storage: Storage backend to use. Defaults to ``FileSystemStorage``. :param basename: Base name for the file. Defaults to ``None``. :param prefix: Prefix for the file name. Defaults to ``None``. :param content: File content. Accepts ``bytes``, ``str``, :class:`StringTemplate`, or :class:`LazyStringTemplate`. Defaults to ``""``. :param extension: File extension without leading dot. Defaults to ``"txt"``. :param dir_path: Optional in-archive subdirectory. Resolved through ``FAKER.string_template()`` so template expressions such as ``"{dir_path(depth=3)}"`` are supported. Defaults to ``None``. :param **kwargs: Reserved for API compatibility. Currently ignored. :return: ``StringValue`` containing the relative path of the generated file. :rtype: StringValue """ sv = FAKER.generic_file( content=content, extension=extension, storage=storage, basename=basename, prefix=prefix, ) if dir_path: sv.data["directory"] = FAKER.string_template(dir_path).lstrip("/") return sv
[docs] def fuzzy_choice_create_inner_file( func_choices: List[Tuple[Callable[..., StringValue], Dict[str, Any]]], **kwargs, ) -> StringValue: """Create inner file from given list of function choices. :param func_choices: List of functions to choose from. :param **kwargs: Additional keyword arguments to pass to the function. :rtype: StringValue :return: StringValue instance. Usage example: .. code-block:: python from fake import ( FAKER, FileSystemStorage, create_inner_docx_file, create_inner_png_file, create_inner_txt_file, fuzzy_choice_create_inner_file, ) STORAGE = FileSystemStorage() kwargs = {"storage": STORAGE} file = fuzzy_choice_create_inner_file( [ (create_inner_docx_file, kwargs), (create_inner_png_file, kwargs), (create_inner_txt_file, kwargs), ] ) You could use it in archives to make a variety of different file types within the archive. .. code-block:: python from fake import ( FAKER, FileSystemStorage, create_inner_docx_file, create_inner_png_file, create_inner_txt_file, fuzzy_choice_create_inner_file, ) STORAGE = FileSystemStorage() kwargs = {"storage": STORAGE} file = FAKER.zip_file( prefix="zzz_archive_", options={ "count": 50, "create_inner_file_func": fuzzy_choice_create_inner_file, "create_inner_file_args": { "func_choices": [ (create_inner_docx_file, kwargs), (create_inner_png_file, kwargs), (create_inner_txt_file, kwargs), ], }, "directory": "zzz", } ) """ _func, _kwargs = random.choice(func_choices) return _func(**_kwargs)
[docs] def list_create_inner_file( func_list: List[Tuple[Callable[..., StringValue], Dict[str, Any]]], **kwargs, ) -> List[StringValue]: """Generates multiple files based on the provided list of functions and arguments. :param func_list: List of tuples, each containing a function to generate a file and its arguments. :param **kwargs: Additional keyword arguments to pass to the functions. :rtype: List[StringValue] :return: List of generated file names. Usage example: .. code-block:: python from fake import ( FAKER, FileSystemStorage, create_inner_docx_file, create_inner_txt_file, list_create_inner_file, ) file = FAKER.zip_file( basename="alice-looking-through-the-glass", options={ "create_inner_file_func": list_create_inner_file, "create_inner_file_args": { "func_list": [ (create_inner_docx_file, {"basename": "doc"}), (create_inner_txt_file, {"basename": "doc_metadata"}), (create_inner_txt_file, {"basename": "doc_isbn"}), ], }, } ) Note, that while all other inner functions return back `StringValue` value, `list_create_inner_file` returns back a `List[StringValue]` value. Notably, all inner functions were designed to support archives (such as ZIP, TAR and EML, but the list may grow in the future). If the inner function passed in the `create_inner_file_func` argument returns a List of `StringValue` values, the `option` argument is being ignored and generated files are simply limited to what has been passed in the `func_list` list of tuples. """ created_files = [] for func, func_kwargs in func_list: file = func(**func_kwargs) created_files.append(file) return created_files
[docs] class FactoryMethod: def __init__( self, method_name: str, faker: Optional[Faker] = None, **kwargs, ): self.method_name = method_name self.kwargs = kwargs self.faker = faker or FAKER def __call__(self): method = getattr(self.faker, self.method_name) return method(**self.kwargs)
def create_factory_method(method_name): def method(self, **kwargs): return FactoryMethod(method_name, faker=self.faker, **kwargs) return method
[docs] class SubFactory: def __init__(self, factory_class, **kwargs): self.factory_class = factory_class self.factory_kwargs = kwargs def __call__(self): # Initialize the specified factory class and create an instance return self.factory_class.create(**self.factory_kwargs)
[docs] class Factory: """Factory.""" def __init__(self, faker: Optional[Faker] = None) -> None: # Directly use the setter to ensure provider methods are added self.faker = faker or FAKER @property def faker(self): return self._faker @faker.setter def faker(self, value): self._faker = value self._add_provider_methods(value) def _add_provider_methods(self, faker_instance): for class_name, methods in PROVIDER_REGISTRY.items(): if ( class_name == f"{__name__}.{Faker.__name__}" or class_name == self.faker.uid ): for method_name in methods: if hasattr(faker_instance, method_name): bound_method = create_factory_method(method_name) setattr(self, method_name, bound_method.__get__(self))
FACTORY = Factory(faker=FAKER)
[docs] def pre_init(func): func.is_pre_init = True return func
[docs] def pre_save(func): func.is_pre_save = True return func
[docs] def post_save(func): func.is_post_save = True return func
[docs] def trait(func): func.is_trait = True return func
[docs] class LazyAttribute: def __init__(self, func): self.func = func def __get__(self, obj, objtype=None): if obj is None: return self value = self.func(obj) setattr(obj, self.func.__name__, value) return value
[docs] class LazyFunction: def __init__(self, func): self.func = func def __call__(self): return self.func() def __get__(self, obj, objtype=None): if obj is None: return self return self.func()
[docs] class PreInit: def __init__(self, func, *args, **kwargs): self.func = func self.args = args self.kwargs = kwargs
[docs] def execute(self, data: Dict[str, Any]) -> None: self.func(data, *self.args, **self.kwargs)
[docs] class PreSave: def __init__(self, func, *args, **kwargs): self.func = func self.args = args self.kwargs = kwargs
[docs] def execute(self, instance): self.func(instance, *self.args, **self.kwargs)
[docs] class PostSave: def __init__(self, func, *args, **kwargs): self.func = func self.args = args self.kwargs = kwargs
[docs] def execute(self, instance): self.func(instance, *self.args, **self.kwargs)
[docs] class ModelFactory: """ModelFactory."""
[docs] class Meta: get_or_create = ("id",) # Default fields for get_or_create
def __init_subclass__(cls, **kwargs): base_meta = getattr( cls.__bases__[0], "_meta", { attr: getattr(cls.__bases__[0].Meta, attr) # type: ignore for attr in dir(cls.__bases__[0].Meta) # type: ignore if not attr.startswith("_") }, ) cls_meta = { attr: getattr(cls.Meta, attr) for attr in dir(cls.Meta) if not attr.startswith("_") } cls._meta = {**base_meta, **cls_meta} # type: ignore @classmethod def _run_hooks(cls, hooks, instance): for method in hooks: getattr(cls, method)(cls, instance) @classmethod def _apply_traits(cls, instance, **kwargs) -> None: for name, method in cls.__dict__.items(): if getattr(method, "is_trait", False) and kwargs.get(name, False): method(cls, instance) @classmethod def _apply_lazy_attributes(cls, instance, model_data): for _field, value in model_data.items(): if isinstance(value, LazyAttribute): # Trigger computation and setting of the attribute setattr(instance, _field, value.__get__(instance, cls))
[docs] @classmethod def create(cls, **kwargs): model = cls.Meta.model # type: ignore trait_keys = { name for name, method in cls.__dict__.items() if getattr(method, "is_trait", False) } # Collect PreInit, PreSave, PostSave methods and prepare model data pre_init_methods = {} pre_save_methods = {} post_save_methods = {} model_data = {} for _field, value in cls.__dict__.items(): # Do not process any fields that have been otherwise # provided directly using keyword arguments. if _field in kwargs: continue if isinstance(value, PreInit): pre_init_methods[_field] = value elif isinstance(value, PreSave): pre_save_methods[_field] = value elif isinstance(value, PostSave): post_save_methods[_field] = value elif not _field.startswith( ( "_", "Meta", ) ) and ( not getattr(value, "is_trait", False) and not getattr(value, "is_pre_init", False) and not getattr(value, "is_pre_save", False) and not getattr(value, "is_post_save", False) ): model_data[_field] = ( value() if isinstance( value, (FactoryMethod, SubFactory, LazyFunction), ) else value ) # Update model_data with non-trait kwargs and collect PreSave from # kwargs. for key, value in kwargs.items(): if isinstance(value, PreInit): pre_init_methods[key] = value elif isinstance(value, PreSave): pre_save_methods[key] = value elif isinstance(value, PostSave): post_save_methods[key] = value elif key not in trait_keys and key not in pre_save_methods: model_data[key] = value # Execute pre-init methods for pre_init_method in pre_init_methods.values(): pre_init_method.execute(model_data) # Pre-init hooks pre_init_hooks = [ method for method in dir(cls) if getattr(getattr(cls, method), "is_pre_init", False) ] cls._run_hooks(pre_init_hooks, model_data) # Create a new instance instance = model(**model_data) # Apply traits cls._apply_traits(instance, **kwargs) # Apply LazyAttribute values cls._apply_lazy_attributes(instance, model_data) # Execute PreSave methods for __pre_save_method in pre_save_methods.values(): __pre_save_method.execute(instance) # Pre-save hooks pre_save_hooks = [ method for method in dir(cls) if getattr(getattr(cls, method), "is_pre_save", False) ] cls._run_hooks(pre_save_hooks, instance) # Save the instance cls.save(instance) # Execute PostSave methods for __post_save_method in post_save_methods.values(): __post_save_method.execute(instance) # Post-save hooks post_save_hooks = [ method for method in dir(cls) if getattr(getattr(cls, method), "is_post_save", False) ] cls._run_hooks(post_save_hooks, instance) return instance
[docs] @classmethod def create_batch(cls, count, **kwargs): return [cls.create(**kwargs) for _ in range(count)]
def __new__(cls, **kwargs): return cls.create(**kwargs)
[docs] @classmethod def save(cls, instance): """Save the instance."""
[docs] class PydanticModelFactory(ModelFactory): """Pydantic ModelFactory."""
[docs] class DjangoModelFactory(ModelFactory): """Django ModelFactory."""
[docs] @classmethod def save(cls, instance): instance.save()
[docs] @classmethod def create(cls, **kwargs): model = cls.Meta.model # type: ignore unique_fields = cls._meta.get("get_or_create", ["id"]) # type: ignore trait_keys = { name for name, method in cls.__dict__.items() if getattr(method, "is_trait", False) } # Construct a query for unique fields query = { _field: kwargs[_field] for _field in unique_fields if _field in kwargs } # Try to get an existing instance if query: instance = model.objects.filter(**query).first() if instance: return instance # Collect PreInit, PreSave, PostSave methods and prepare model data pre_init_methods = {} pre_save_methods = {} post_save_methods = {} model_data = {} for _field, value in cls.__dict__.items(): # Do not process any fields that have been otherwise # provided directly using keyword arguments. if _field in kwargs: continue if isinstance(value, PreInit): pre_init_methods[_field] = value elif isinstance(value, PreSave): pre_save_methods[_field] = value elif isinstance(value, PostSave): post_save_methods[_field] = value elif not _field.startswith( ( "_", "Meta", ) ) and ( not getattr(value, "is_trait", False) and not getattr(value, "is_pre_init", False) and not getattr(value, "is_pre_save", False) and not getattr(value, "is_post_save", False) ): model_data[_field] = ( value() if isinstance( value, (FactoryMethod, SubFactory, LazyFunction) ) else value ) # TODO: Check if this block is really needed now, that # nested_attrs and direct_attrs are already handled separately # later on. # Update model_data with non-trait kwargs and collect PreSave # from kwargs. for key, value in kwargs.items(): if isinstance(value, PreInit): pre_init_methods[key] = value elif isinstance(value, PreSave): pre_save_methods[key] = value elif isinstance(value, PostSave): post_save_methods[key] = value # Separate nested attributes and direct attributes nested_attrs = {k: v for k, v in kwargs.items() if "__" in k} direct_attrs = {k: v for k, v in kwargs.items() if "__" not in k} # Update direct attributes with callable results for _field, value in model_data.items(): if isinstance(value, (FactoryMethod, SubFactory)): model_data[_field] = ( value() if _field not in direct_attrs else direct_attrs[_field] ) # Update model_data with non-trait kwargs and collect PreSave # and PostSave from direct_attrs. for key, value in direct_attrs.items(): if isinstance(value, PreInit): pre_init_methods[key] = value elif isinstance(value, PreSave): pre_save_methods[key] = value elif isinstance(value, PostSave): post_save_methods[key] = value elif key not in trait_keys and key not in pre_save_methods: model_data[key] = value # Execute pre-init methods for pre_init_method in pre_init_methods.values(): pre_init_method.execute(model_data) # Pre-init hooks pre_init_hooks = [ method for method in dir(cls) if getattr(getattr(cls, method), "is_pre_init", False) ] cls._run_hooks(pre_init_hooks, model_data) # Create a new instance if none found instance = model(**model_data) # Apply traits cls._apply_traits(instance, **kwargs) # Apply LazyAttribute values cls._apply_lazy_attributes(instance, model_data) # Handle nested attributes for attr, value in nested_attrs.items(): field_name, nested_attr = attr.split("__", 1) if isinstance(getattr(cls, field_name, None), SubFactory): related_instance = getattr( cls, field_name ).factory_class.create(**{nested_attr: value}) setattr(instance, field_name, related_instance) # Execute PreSave methods for __pre_save_method in pre_save_methods.values(): __pre_save_method.execute(instance) # Run pre-save hooks pre_save_hooks = [ method for method in dir(cls) if getattr(getattr(cls, method), "is_pre_save", False) ] cls._run_hooks(pre_save_hooks, instance) # Save instance cls.save(instance) # Execute PostSave methods for __post_save_method in post_save_methods.values(): __post_save_method.execute(instance) # Run post-save hooks post_save_hooks = [ method for method in dir(cls) if getattr(getattr(cls, method), "is_post_save", False) ] cls._run_hooks(post_save_hooks, instance) return instance
[docs] def run_async_in_thread(coroutine: Coroutine) -> Awaitable: """Run an asynchronous coroutine in a separate thread. :param coroutine: An asyncio coroutine to be run. :return: The result of the coroutine. :rtype: Awaitable """ def thread_target(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) return loop.run_until_complete(coroutine) with ThreadPoolExecutor() as executor: future = executor.submit(thread_target) return future.result()
[docs] class TortoiseModelFactory(ModelFactory): """Tortoise ModelFactory."""
[docs] @classmethod def save(cls, instance): async def async_save(): await instance.save() run_async_in_thread(async_save())
[docs] @classmethod def create(cls, **kwargs): model = cls.Meta.model # type: ignore unique_fields = cls._meta.get("get_or_create", ["id"]) # type: ignore trait_keys = { name for name, method in cls.__dict__.items() if getattr(method, "is_trait", False) } # Construct a query for unique fields query = { _field: kwargs[_field] for _field in unique_fields if _field in kwargs } # Try to get an existing instance if query: async def async_filter(): return await model.filter(**query).first() instance = run_async_in_thread(async_filter()) if instance: return instance # Collect PreInit, PreSave, PostSave methods and prepare model data pre_init_methods = {} pre_save_methods = {} post_save_methods = {} model_data = {} for _field, value in cls.__dict__.items(): # Do not process any fields that have been otherwise # provided directly using keyword arguments. if _field in kwargs: continue if isinstance(value, PreInit): pre_init_methods[_field] = value elif isinstance(value, PreSave): pre_save_methods[_field] = value elif isinstance(value, PostSave): post_save_methods[_field] = value elif not _field.startswith( ( "_", "Meta", ) ) and ( not getattr(value, "is_trait", False) and not getattr(value, "is_pre_init", False) and not getattr(value, "is_pre_save", False) and not getattr(value, "is_post_save", False) ): model_data[_field] = ( value() if isinstance( value, (FactoryMethod, SubFactory, LazyFunction) ) else value ) # TODO: Check is this block is needed now that kwargs are split # into nested_attrs and direct_attrs later on. # Update model_data with non-trait kwargs and collect PreSave # and PostSave from kwargs. for key, value in kwargs.items(): if isinstance(value, PreSave): pre_save_methods[key] = value elif isinstance(value, PostSave): post_save_methods[key] = value # Separate nested attributes and direct attributes nested_attrs = {k: v for k, v in kwargs.items() if "__" in k} direct_attrs = {k: v for k, v in kwargs.items() if "__" not in k} # Update direct attributes with callable results for _field, value in model_data.items(): if isinstance(value, (FactoryMethod, SubFactory)): model_data[_field] = ( value() if _field not in direct_attrs else direct_attrs[_field] ) # Update model_data with non-trait kwargs and collect PreSave # from direct_attrs. for key, value in direct_attrs.items(): if isinstance(value, PreInit): pre_init_methods[key] = value elif isinstance(value, PreSave): pre_save_methods[key] = value elif isinstance(value, PostSave): post_save_methods[key] = value elif key not in trait_keys and key not in pre_save_methods: model_data[key] = value # Execute pre-init methods for pre_init_method in pre_init_methods.values(): pre_init_method.execute(model_data) # Pre-init hooks pre_init_hooks = [ method for method in dir(cls) if getattr(getattr(cls, method), "is_pre_init", False) ] cls._run_hooks(pre_init_hooks, model_data) # Create a new instance if none found instance = model(**model_data) # Apply traits cls._apply_traits(instance, **kwargs) # Apply LazyAttribute values cls._apply_lazy_attributes(instance, model_data) # Handle nested attributes for _attr, _value in nested_attrs.items(): _field_name, _nested_attr = _attr.split("__", 1) if isinstance(getattr(cls, _field_name, None), SubFactory): async def async_related_instance( field_name_=_field_name, nested_attr_=_nested_attr, value_=_value, ): return getattr(cls, field_name_).factory_class.create( **{nested_attr_: value_} ) related_instance = run_async_in_thread(async_related_instance()) setattr(instance, _field_name, related_instance) # Execute PreSave methods for __pre_save_method in pre_save_methods.values(): __pre_save_method.execute(instance) # Run pre-save hooks pre_save_hooks = [ method for method in dir(cls) if getattr(getattr(cls, method), "is_pre_save", False) ] cls._run_hooks(pre_save_hooks, instance) # Save instance cls.save(instance) # Execute PostSave methods for __post_save_method in post_save_methods.values(): __post_save_method.execute(instance) # Run post-save hooks post_save_hooks = [ method for method in dir(cls) if getattr(getattr(cls, method), "is_post_save", False) ] cls._run_hooks(post_save_hooks, instance) return instance
[docs] class SQLAlchemyModelFactory(ModelFactory): """SQLAlchemy ModelFactory."""
[docs] @classmethod def save(cls, instance): session = cls.MetaSQLAlchemy.get_session() # type: ignore session.add(instance) session.commit()
[docs] @classmethod def create(cls, **kwargs): session = cls.MetaSQLAlchemy.get_session() # type: ignore model = cls.Meta.model # type: ignore unique_fields = cls._meta.get("get_or_create", ["id"]) # type: ignore trait_keys = { name for name, method in cls.__dict__.items() if getattr(method, "is_trait", False) } # Check for existing instance if unique_fields: query_kwargs = { _field: kwargs.get(_field) for _field in unique_fields } instance = session.query(model).filter_by(**query_kwargs).first() if instance: return instance # Collect PreInit, PreSave, PostSave methods and prepare model data pre_init_methods = {} pre_save_methods = {} post_save_methods = {} model_data = {} for _field, value in cls.__dict__.items(): # Do not process any fields that have been otherwise # provided directly using keyword arguments. if _field in kwargs: continue if isinstance(value, PreInit): pre_init_methods[_field] = value elif isinstance(value, PreSave): pre_save_methods[_field] = value elif isinstance(value, PostSave): post_save_methods[_field] = value elif not _field.startswith( ( "_", "Meta", ) ) and ( not getattr(value, "is_trait", False) and not getattr(value, "is_pre_init", False) and not getattr(value, "is_pre_save", False) and not getattr(value, "is_post_save", False) ): model_data[_field] = ( value() if isinstance( value, (FactoryMethod, SubFactory, LazyFunction) ) else value ) # TODO: Check if this is really needed now that kwargs are # handled in direct_attrs later on. # Update model_data with non-trait kwargs and collect PreSave # from kwargs. for key, value in kwargs.items(): if isinstance(value, PreSave): pre_save_methods[key] = value elif isinstance(value, PostSave): post_save_methods[key] = value # Separate nested attributes and direct attributes nested_attrs = {k: v for k, v in kwargs.items() if "__" in k} direct_attrs = {k: v for k, v in kwargs.items() if "__" not in k} # Update direct attributes with callable results for _field, value in model_data.items(): if isinstance(value, (FactoryMethod, SubFactory)): model_data[_field] = ( value() if _field not in direct_attrs else direct_attrs[_field] ) # Update model_data with non-trait kwargs and collect PreSave # from direct_attrs. for key, value in direct_attrs.items(): if isinstance(value, PreInit): pre_init_methods[key] = value elif isinstance(value, PreSave): pre_save_methods[key] = value elif isinstance(value, PostSave): post_save_methods[key] = value elif key not in trait_keys and key not in pre_save_methods: model_data[key] = value # Execute pre-init methods for pre_init_method in pre_init_methods.values(): pre_init_method.execute(model_data) # Pre-init hooks pre_init_hooks = [ method for method in dir(cls) if getattr(getattr(cls, method), "is_pre_init", False) ] cls._run_hooks(pre_init_hooks, model_data) # Create a new instance instance = model(**model_data) # Apply traits cls._apply_traits(instance, **kwargs) # Apply LazyAttribute values cls._apply_lazy_attributes(instance, model_data) # Handle nested attributes for attr, value in nested_attrs.items(): field_name, nested_attr = attr.split("__", 1) if isinstance(getattr(cls, field_name, None), SubFactory): related_instance = getattr( cls, field_name ).factory_class.create(**{nested_attr: value}) setattr(instance, field_name, related_instance) # Execute PreSave methods for __pre_save_method in pre_save_methods.values(): __pre_save_method.execute(instance) # Run pre-save hooks pre_save_hooks = [ method for method in dir(cls) if getattr(getattr(cls, method), "is_pre_save", False) ] cls._run_hooks(pre_save_hooks, instance) # Save instance cls.save(instance) # Execute PostSave methods for __post_save_method in post_save_methods.values(): __post_save_method.execute(instance) # Run post-save hooks post_save_hooks = [ method for method in dir(cls) if getattr(getattr(cls, method), "is_post_save", False) ] cls._run_hooks(post_save_hooks, instance) return instance
# ************************************************ # ******************* Internal ******************* # ************************************************ class ClassProperty(property): """ClassProperty.""" def __get__(self, cls, owner): """Get.""" return classmethod(self.fget).__get__(None, owner)() # type: ignore classproperty = ClassProperty
[docs] def xor_transform(val: str, key: int = 10) -> str: """Simple, deterministic string encoder/decoder. Usage example: .. code-block:: python val = "abcd" encoded_val = xor_transform(val) decoded_val = xor_transform(encoded_val) """ return "".join(chr(ord(__c) ^ key) for __c in val)
class BaseDataFiller: TYPE_TO_PROVIDER = { bool: FAKER.pybool, int: FAKER.pyint, str: FAKER.pystr, datetime: FAKER.date_time, date: FAKER.date, float: FAKER.pyfloat, Decimal: FAKER.pydecimal, } FIELD_NAME_TO_PROVIDER = { "name": FAKER.word, "title": FAKER.sentence, "slug": FAKER.slug, "content": FAKER.text, "category": FAKER.word, "username": FAKER.username, "email": FAKER.email, "headline": FAKER.sentence, "first_name": FAKER.first_name, "last_name": FAKER.last_name, "uuid": FAKER.uuid, "body": FAKER.text, "summary": FAKER.paragraph, "date_of_birth": FAKER.date, "dob": FAKER.date, "age": partial(FAKER.pyint, min_value=1, max_value=100), "url": FAKER.url, } @classmethod def get_provider_for_field_name(cls, field_name) -> Optional[Callable]: return BaseDataFiller.FIELD_NAME_TO_PROVIDER.get(field_name) class DataclassDataFiller(BaseDataFiller): @classmethod def get_provider_for_type(cls, field_type) -> Optional[Callable]: """Get provider function for the type given.""" # Extract the base type from Optional if get_origin(field_type) is Optional: field_type = get_args(field_type)[0] return cls.TYPE_TO_PROVIDER.get(field_type) @classmethod def fill(cls, dataclass_type: Type) -> Any: """Fill dataclass with data.""" if not is_dataclass(dataclass_type): raise ValueError("The provided type must be a dataclass") kwargs = {} for _field in fields(dataclass_type): provider_func = cls.get_provider_for_field_name(_field.name) if not provider_func: if is_dataclass(_field.type): # Recursive call for nested dataclass def provider_func(field_=_field): return cls.fill(field_.type) else: provider_func = cls.get_provider_for_type(_field.type) if provider_func: kwargs[_field.name] = provider_func() else: # Skip if no provider function is defined continue return dataclass_type(**kwargs) fill_dataclass = DataclassDataFiller.fill class PydanticDataFiller(BaseDataFiller): @classmethod def get_provider_for_type(cls, field_type) -> Optional[Callable]: if isinstance(field_type, type) and issubclass( field_type, (list, dict, set) ): return None if ( hasattr(field_type, "__origin__") and field_type.__origin__ is Optional # noqa ): field_type = field_type.__args__[0] # noqa return cls.TYPE_TO_PROVIDER.get(field_type) @classmethod def is_class_type(cls, type_hint): return isinstance(type_hint, type) and not any( issubclass(type_hint, primitive) for primitive in (int, str, float, bool, Decimal) ) @classmethod def fill(cls, object_type: Type) -> Any: if not ( hasattr(object_type, "__fields__") and hasattr(object_type, "Config") ): raise ValueError("The provided type must be a Pydantic model") type_hints = get_type_hints(object_type) kwargs = {} for field_name, field_type in type_hints.items(): # Check for Pydantic's default_factory default_factory = getattr( object_type.__fields__[field_name], "default_factory", None ) if default_factory is not None: kwargs[field_name] = default_factory() continue provider_func = cls.get_provider_for_field_name(field_name) if not provider_func: if cls.is_class_type(field_type): kwargs[field_name] = cls.fill(field_type) else: provider_func = cls.get_provider_for_type(field_type) if provider_func: kwargs[field_name] = provider_func() else: continue else: kwargs[field_name] = provider_func() return object_type(**kwargs) fill_pydantic_model = PydanticDataFiller.fill # ************************************************ # ********************* CLI ********************** # ************************************************
[docs] def get_provider_args(func: Callable) -> Dict[str, Any]: """Retrieve the argument list and types for a provider function by inspecting its signature. """ sig = signature(func) return {param.name: param.annotation for param in sig.parameters.values()}
[docs] def get_provider_defaults(func: Callable) -> Dict[str, Any]: """Retrieve the argument list and defaults for a provider function by inspecting its signature. """ sig = signature(func) return {param.name: param.default for param in sig.parameters.values()}
[docs] def is_optional_type(type_hint) -> bool: """Check if the type hint is an Optional.""" origin = get_origin(type_hint) args = get_args(type_hint) return origin is Optional or (origin is Union and type(None) in args)
[docs] def get_argparse_type(param_type) -> Any: """Get the corresponding argparse type for a given parameter type.""" origin = get_origin(param_type) args = get_args(param_type) if origin is Union: if type(None) in args: non_none_types = [arg for arg in args if arg is not type(None)] if len(non_none_types) == 1: return get_argparse_type(non_none_types[0]) return str # Default to string if multiple types are present return str # Default to string if it's a non-optional Union elif origin in [list, tuple, set]: return lambda x: [get_argparse_type(args[0])(i) for i in x.split(",")] elif param_type is int: return int elif param_type is float: return float elif param_type is bool: return lambda x: x.lower() in ("true", "1", "yes") else: return str
[docs] def organise_providers(provider_tags) -> Dict[str, Any]: """Organise providers by category for easier navigation.""" categories: Dict[str, Any] = {} for _provider, tags in provider_tags: for tag in tags: if tag not in categories: categories[tag] = [] categories[tag].append(_provider) # Sort the providers within each category for category in categories: categories[category] = sorted(categories[category]) # Return categories sorted by the category names return dict(sorted(categories.items()))
organize_providers = organise_providers # For backwards compatibility
[docs] def format_type_hint(type_hint) -> str: """Format the type hint for display.""" origin = get_origin(type_hint) _args = get_args(type_hint) _type = ", ".join( [format_type_hint(arg) for arg in _args if arg is not type(None)] ) if is_optional_type(type_hint): return f"Optional[{_type}]" elif origin is Tuple: formatted_args = [] for arg in _args: if arg is Ellipsis: formatted_args.append("...") else: formatted_args.append(format_type_hint(arg)) return f"Tuple[{', '.join(formatted_args)}]" elif _args: return ( f"{getattr(origin, '__name__', str(origin))}" f"[{', '.join([format_type_hint(arg) for arg in _args])}]" ) elif isinstance(type_hint, type) and type_hint.__module__ == "builtins": return type_hint.__name__ elif type_hint is Ellipsis: return "..." elif isinstance(type_hint, str): return type_hint else: try: return f"{type_hint.__module__}.{type_hint.__name__}" except Exception: return str(type_hint)
[docs] class CLI: """CLI.""" def __init__(self, faker: Optional[Faker] = None) -> None: if faker: self.faker = faker else: self.faker = FAKER faker_id = f"{self.faker.__module__}.{self.faker.__class__.__name__}" self.provider_list = sorted(PROVIDER_REGISTRY[faker_id]) self.provider_tags = [ (_provider, _provider.tags) for _provider in self.provider_list ] self.parser = self.setup_parser() self.args = self.parser.parse_args()
[docs] def setup_parser(self) -> ArgumentParser: _parser = ArgumentParser( description=f"CLI for fake.py (version {__version__})", formatter_class=ArgumentDefaultsHelpFormatter, ) subparsers = _parser.add_subparsers( dest="command", help="Available commands" ) for provider_name in self.provider_list: provider_func = getattr(self.faker, provider_name) doc_string = ( provider_func.__doc__.split("\n")[0] if provider_func.__doc__ else None ) subparser = subparsers.add_parser(provider_name, help=doc_string) provider_args = get_provider_args(provider_func) provider_defaults = get_provider_defaults(provider_func) for param_name, param_type in provider_args.items(): formatted_type = format_type_hint(param_type) default_value = provider_defaults.get(param_name, None) argparse_type = get_argparse_type(param_type) if is_optional_type(param_type) or default_value is not None: subparser.add_argument( f"--{param_name}", help=f"{param_name} (type: {formatted_type})", type=argparse_type, default=default_value, ) else: subparser.add_argument( param_name, help=( f"{param_name} (type: {formatted_type}, " f"default value: {default_value})" ), type=argparse_type, ) return _parser
[docs] def execute_command(self) -> None: command = self.args.command if not command: self.parser.print_help() return provider_func = getattr(self.faker, command) provider_params = signature(provider_func).parameters kwargs = {} for param_name in provider_params: if hasattr(self.args, param_name): param_value = getattr(self.args, param_name) if param_value is not None: kwargs[param_name] = param_value result = provider_func(**kwargs) print(result)
[docs] def main() -> None: cli = CLI() cli.execute_command()
if __name__ == "__main__": main() # ************************************************ # ******************** Tests ********************* # ************************************************
[docs] class TestScript(unittest.TestCase):
[docs] def test_main(self): # Call the script directly and check that it executes without errors result = subprocess.run( ["python", "fake.py"], capture_output=True, text=True, ) self.assertEqual(result.returncode, 0) result.stdout.startswith("usage:")
[docs] class TestOrganiseProviders(unittest.TestCase):
[docs] def setUp(self): self.provider_list = sorted(PROVIDER_REGISTRY[f"{__name__}.Faker"]) self.provider_tags = [ (_provider, _provider.tags) for _provider in self.provider_list ]
[docs] def test_organise_providers_empty(self): provider_tags: List[Tuple[str, Iterable[str]]] = [] result = organise_providers(provider_tags) expected: Dict[str, Any] = {} self.assertEqual(result, expected)
[docs] def test_organise_providers_single_tag(self): provider_tags = [(ProviderRegistryItem("provider1"), ("Tag1",))] result = organise_providers(provider_tags) expected = {"Tag1": [ProviderRegistryItem("provider1")]} self.assertEqual(result, expected)
[docs] def test_organise_providers_multiple_tags(self): provider_tags = [(ProviderRegistryItem("provider1"), ("Tag1", "Tag2"))] result = organise_providers(provider_tags) expected = { "Tag1": [ProviderRegistryItem("provider1")], "Tag2": [ProviderRegistryItem("provider1")], } self.assertEqual(result, expected)
[docs] def test_organise_providers_multiple_providers(self): provider_tags = [ (ProviderRegistryItem("provider1"), ("Tag1",)), (ProviderRegistryItem("provider2"), ("Tag1", "Tag2")), (ProviderRegistryItem("provider3"), ("Tag3",)), ] result = organise_providers(provider_tags) expected = { "Tag1": [ ProviderRegistryItem("provider1"), ProviderRegistryItem("provider2"), ], "Tag2": [ProviderRegistryItem("provider2")], "Tag3": [ProviderRegistryItem("provider3")], } self.assertEqual(result, expected)
[docs] def test_organise_providers_sorting(self): provider_tags = [ (ProviderRegistryItem("provider3"), ("Tag1",)), (ProviderRegistryItem("provider1"), ("Tag1",)), (ProviderRegistryItem("provider2"), ("Tag1",)), ] result = organise_providers(provider_tags) expected = { "Tag1": [ ProviderRegistryItem("provider1"), ProviderRegistryItem("provider2"), ProviderRegistryItem("provider3"), ] } self.assertEqual(result, expected)
[docs] def test_organise_providers_category_sorting(self): provider_tags = [ (ProviderRegistryItem("provider1"), ("TagB",)), (ProviderRegistryItem("provider2"), ("TagA",)), ] result = organise_providers(provider_tags) expected = { "TagA": [ProviderRegistryItem("provider2")], "TagB": [ProviderRegistryItem("provider1")], } self.assertEqual(result, expected)
[docs] class TestCLI(unittest.TestCase):
[docs] def setUp(self): self.provider_list = sorted(PROVIDER_REGISTRY[f"{__name__}.Faker"])
[docs] @patch("sys.argv", ["fake-py"]) def test_provider_list(self): cli = CLI() self.assertEqual(cli.provider_list, self.provider_list)
[docs] @patch("sys.argv", ["fake-py", "pyint"]) def test_pyint_provider(self): with patch("sys.stdout", new=io.StringIO()) as fake_out: main() self.assertTrue(fake_out.getvalue().strip().isdigit())
[docs] @patch( "sys.argv", [ "fake-py", "generic_file", "--content", "MyContent", "--extension", "txt", "--basename", "my_file", ], ) def test_generic_file_provider(self): with patch("sys.stdout", new=io.StringIO()) as fake_out: main() self.assertEqual( fake_out.getvalue().strip(), "tmp/my_file.txt", )
[docs] @patch("sys.argv", ["fake-py", "date"]) def test_date_provider(self): with patch("sys.stdout", new=io.StringIO()) as fake_out: main() self.assertRegex( fake_out.getvalue().strip(), r"\d{4}-\d{2}-\d{2}", )
[docs] @patch("sys.argv", ["fake-py", "date", "--start_date=-2d"]) def test_date_provider_with_args(self): with patch("sys.stdout", new=io.StringIO()) as fake_out: main() self.assertRegex( fake_out.getvalue().strip(), r"\d{4}-\d{2}-\d{2}", )
[docs] @patch("sys.argv", ["fake-py", "docx_file", "--nb_pages=1"]) def test_docx_file_provider(self): with patch("sys.stdout", new=io.StringIO()) as fake_out: main() self.assertTrue(fake_out.getvalue().strip().endswith(".docx"))
[docs] @patch("sys.argv", ["fake-py"]) def test_no_command(self): with patch("sys.stdout", new=io.StringIO()) as fake_out: main() self.assertIn( "usage: fake-py", fake_out.getvalue().strip(), )
[docs] class TestFaker(unittest.TestCase):
[docs] def setUp(self) -> None: self.faker = FAKER
[docs] def tearDown(self): FILE_REGISTRY.clean_up()
[docs] @classmethod def is_valid_email(cls, email: str) -> bool: parsed_address = parseaddr(email) return "@" in parsed_address[1]
[docs] def test_seed(self) -> None: _string_template = """ {date(start_date="-7d")} # Title: {sentence(nb_words=6, suffix="")} ## Authors: {name}, {name}, {name} ## Abstract ### Introduction {text(nb_chars=200, allow_overflow=True)} ### Objective {text(nb_chars=200, allow_overflow=True)} ### Methods {text(nb_chars=200, allow_overflow=True)} ### Results {text(nb_chars=200, allow_overflow=True)} ### Conclusion {text(nb_chars=200, allow_overflow=True)} Keywords: {word}, {word}, {word} """ _providers = [ (self.faker.first_name, {}), (self.faker.first_names, {}), (self.faker.last_name, {}), (self.faker.last_names, {}), (self.faker.name, {}), (self.faker.names, {}), (self.faker.username, {}), (self.faker.usernames, {}), (self.faker.slug, {}), (self.faker.slugs, {}), (self.faker.word, {}), (self.faker.words, {}), (self.faker.sentence, {}), (self.faker.sentences, {}), (self.faker.paragraph, {}), (self.faker.paragraphs, {}), (self.faker.text, {}), (self.faker.texts, {}), (self.faker.dir_path, {}), (self.faker.file_extension, {}), (self.faker.mime_type, {}), (self.faker.tld, {}), (self.faker.domain_name, {}), (self.faker.free_email_domain, {}), (self.faker.email, {}), (self.faker.emails, {}), (self.faker.company_email, {}), (self.faker.company_emails, {}), (self.faker.free_email, {}), (self.faker.free_emails, {}), (self.faker.url, {}), (self.faker.image_url, {}), (self.faker.pyint, {}), (self.faker.pybool, {}), (self.faker.pystr, {}), (self.faker.pyfloat, {}), (self.faker.pydecimal, {}), (self.faker.ipv4, {}), (self.faker.date, {}), # (self.faker.date_time, {}), # TODO: Find out why inconsistent (self.faker.year, {}), (self.faker.time, {}), (self.faker.city, {}), (self.faker.country, {}), (self.faker.geo_location, {}), (self.faker.country_code, {}), (self.faker.locale, {}), (self.faker.latitude, {}), (self.faker.longitude, {}), (self.faker.latitude_longitude, {}), (self.faker.iban, {}), (self.faker.isbn10, {}), (self.faker.isbn13, {}), ( self.faker.random_choice, {"elements": ("Art", "Photography", "Generative AI")}, ), ( self.faker.random_sample, { "elements": ("Art", "Photography", "Generative AI"), "length": 2, }, ), (self.faker.randomise_string, {"value": "???? ##"}), (self.faker.string_template, {"template": _string_template}), # ( # self.faker.lazy_string_template, # {"template": _string_template} # ), # TODO: Implement test ] for _provider, _kwargs in _providers: if _provider.__name__ not in ("pybool"): self.faker.seed(None) list_1 = [ _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), ] self.faker.seed(1) list_2 = [ _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), ] self.assertNotEqual(list_1, list_2) self.faker.seed(42) list_1 = [ _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), ] self.faker.seed(42) list_2 = [ _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), _provider(**_kwargs), ] self.assertEqual(list_1, list_2)
[docs] def test_uuid(self) -> None: uuid_value = self.faker.uuid() self.assertIsInstance(uuid_value, uuid.UUID)
[docs] def test_uuids(self) -> None: uuids = self.faker.uuids() for uuid_value in uuids: self.assertIsInstance(uuid_value, uuid.UUID)
[docs] def test_first_name(self) -> None: first_name: str = self.faker.first_name() self.assertIsInstance(first_name, str) self.assertGreater(len(first_name), 0) self.assertIn(first_name, self.faker._first_names)
[docs] def test_first_names(self) -> None: first_names: List[str] = self.faker.first_names() for first_name in first_names: self.assertIsInstance(first_name, str) self.assertGreater(len(first_name), 0) self.assertIn(first_name, self.faker._first_names)
[docs] def test_last_name(self) -> None: last_name: str = self.faker.last_name() self.assertIsInstance(last_name, str) self.assertGreater(len(last_name), 0) self.assertIn(last_name, self.faker._last_names)
[docs] def test_last_names(self) -> None: last_names: List[str] = self.faker.last_names() for last_name in last_names: self.assertIsInstance(last_name, str) self.assertGreater(len(last_name), 0) self.assertIn(last_name, self.faker._last_names)
[docs] def test_name(self) -> None: name: str = self.faker.name() self.assertIsInstance(name, str) self.assertGreater(len(name), 0) parts = name.split(" ") first_name = parts[0] last_name = " ".join(parts[1:]) self.assertIn(first_name, self.faker._first_names) self.assertIn(last_name, self.faker._last_names)
[docs] def test_names(self) -> None: names: List[str] = self.faker.names() for name in names: self.assertIsInstance(name, str) self.assertGreater(len(name), 0) parts = name.split(" ") first_name = parts[0] last_name = " ".join(parts[1:]) self.assertIn(first_name, self.faker._first_names) self.assertIn(last_name, self.faker._last_names)
[docs] def test_username(self) -> None: username: str = self.faker.username() self.assertIsInstance(username, str)
[docs] def test_usernames(self) -> None: usernames: List[str] = self.faker.usernames() for username in usernames: self.assertIsInstance(username, str)
[docs] def test_slug(self) -> None: slug: str = self.faker.slug() self.assertIsInstance(slug, str)
[docs] def test_slugs(self) -> None: slugs: List[str] = self.faker.slugs() for slug in slugs: self.assertIsInstance(slug, str)
[docs] def test_word(self) -> None: word: str = self.faker.word() self.assertIsInstance(word, str) self.assertGreater(len(word), 0)
[docs] def test_words(self) -> None: words: List[str] = self.faker.words(nb=3) self.assertIsInstance(words, list) self.assertEqual(len(words), 3)
[docs] def test_sentence(self) -> None: sentence: str = self.faker.sentence() self.assertIsInstance(sentence, str) self.assertGreaterEqual(len(sentence.split()), 5) self.assertTrue(sentence.endswith("."))
[docs] def test_sentences(self) -> None: sentences: List[str] = self.faker.sentences(nb=3) self.assertIsInstance(sentences, list) self.assertEqual(len(sentences), 3)
[docs] def test_paragraph(self) -> None: paragraph: str = self.faker.paragraph() self.assertIsInstance(paragraph, str) self.assertGreaterEqual(len(paragraph.split(".")), 5)
[docs] def test_paragraphs(self) -> None: paragraphs: List[str] = self.faker.paragraphs(nb=3) self.assertIsInstance(paragraphs, list) self.assertEqual(len(paragraphs), 3)
[docs] def test_text(self) -> None: text: str = self.faker.text(nb_chars=100) self.assertIsInstance(text, str) self.assertLessEqual(len(text), 100)
[docs] def test_texts(self) -> None: texts: List[str] = self.faker.texts(nb=3) self.assertIsInstance(texts, list) self.assertEqual(len(texts), 3)
[docs] def test_file_name(self) -> None: extensions = [(None, "txt"), ("txt", "txt"), ("jpg", "jpg")] for extension, expected_extension in extensions: with self.subTest( extension=extension, expected_extension=expected_extension ): kwargs = {} if extension is not None: kwargs["extension"] = extension file_name: str = self.faker.file_name(**kwargs) self.assertIsInstance(file_name, str) self.assertTrue(file_name.endswith(f".{expected_extension}"))
[docs] def test_file_path(self) -> None: extensions = [(None, "txt"), ("txt", "txt"), ("jpg", "jpg")] for extension, expected_extension in extensions: with self.subTest( extension=extension, expected_extension=expected_extension ): kwargs = {} if extension is not None: kwargs["extension"] = extension file_path: str = self.faker.file_path(**kwargs) self.assertIsInstance(file_path, str) self.assertTrue(file_path.endswith(f".{expected_extension}"))
[docs] def test_dir_path(self) -> None: with self.subTest("test_default_depth"): # Test with default depth (should be 1) self.faker.seed(0) # Set seed for reproducibility path = self.faker.dir_path() self.assertTrue( path.startswith(os.path.sep), "Path should start with os.path.sep", ) # Split path and remove empty parts caused by leading separator parts = [part for part in path.split(os.path.sep) if part] self.assertEqual( len(parts), 1, "There should be exactly one directory name appended", ) # Check that the generated folder is one of the expected words # in lowercase self.assertIn( parts[0], [word.lower() for word in self.faker._words], ) with self.subTest("test_depth_three"): # Test with depth set to 3 depth = 3 self.faker.seed(1) # Set seed for reproducibility path = self.faker.dir_path(depth) self.assertTrue( path.startswith(os.path.sep), "Path should start with os.path.sep", ) parts = [part for part in path.split(os.path.sep) if part] self.assertEqual( len(parts), depth, f"There should be exactly {depth} directory names appended", ) for part in parts: self.assertIn( part, [word.lower() for word in self.faker._words], ) with self.subTest(" test_randomness_reproducibility"): # Given a fixed seed, the output should be reproducible. self.faker.seed(123) path1 = self.faker.dir_path(2) self.faker.seed(123) path2 = self.faker.dir_path(2) self.assertEqual( path1, path2, "Paths should be the same for the same random seed", )
[docs] def test_file_extension(self) -> None: with self.subTest("Return type"): _ext = self.faker.file_extension() self.assertIsInstance(_ext, str) with self.subTest("Extension validity"): _ext = self.faker.file_extension() self.assertIn(_ext, FILE_EXTENSIONS)
[docs] def test_mime_type(self) -> None: with self.subTest("Return type"): _mime_type = self.faker.mime_type() self.assertIsInstance(_mime_type, str) with self.subTest("Mime type validity"): _mime_type = self.faker.mime_type() self.assertIn(_mime_type, MIME_TYPES)
[docs] def test_tld_with_defaults(self) -> None: for _ in range(20): result = self.faker.tld() self.assertIn(result, TLDS)
[docs] def test_tld_with_custom_tlds(self) -> None: custom_tlds = ("edu", "gov", "mil") for _ in range(20): result = self.faker.tld(custom_tlds) self.assertIn(result, custom_tlds)
[docs] def test_domain_name_with_defaults(self) -> None: result = self.faker.domain_name() parts = result.split(".") self.assertEqual(len(parts), 2) domain, tld = parts self.assertTrue(domain.islower()) self.assertIn(tld, TLDS)
[docs] def test_domain_name_custom_domain_names(self) -> None: custom_tlds = ("edu", "gov", "mil") for _ in range(20): result = self.faker.domain_name(custom_tlds) parts = result.split(".") self.assertEqual(len(parts), 2) domain, tld = parts self.assertTrue(domain.islower()) self.assertIn(tld, custom_tlds)
[docs] def test_free_email_domain(self): for _ in range(20): result = self.faker.free_email_domain() self.assertIn(result, FREE_EMAIL_DOMAINS)
[docs] def test_email(self) -> None: email: str = self.faker.email() self.assertIsInstance(email, str) self.assertTrue(self.is_valid_email(email))
[docs] def test_emails(self) -> None: emails: List[str] = self.faker.emails(nb=3) self.assertIsInstance(emails, list) self.assertEqual(len(emails), 3)
[docs] def test_email_custom_domain_names(self) -> None: domains = [ ("example.com", "example.com"), ("gmail.com", "gmail.com"), ] for domain, expected_domain in domains: with self.subTest(domain=domain, expected_domain=expected_domain): kwargs = {"domain_names": [domain]} email: str = self.faker.email(**kwargs) self.assertIsInstance(email, str) self.assertTrue(self.is_valid_email(email)) self.assertTrue(email.endswith(f"@{expected_domain}"))
[docs] def test_company_email(self) -> None: email: str = self.faker.company_email() self.assertIsInstance(email, str) self.assertTrue(self.is_valid_email(email))
[docs] def test_company_emails(self) -> None: emails: List[str] = self.faker.company_emails(nb=3) self.assertIsInstance(emails, list) self.assertEqual(len(emails), 3)
[docs] def test_company_email_custom_domain_names(self) -> None: domains = [ ("microsoft.com", "microsoft.com"), ("google.com", "google.com"), ] for domain, expected_domain in domains: with self.subTest(domain=domain, expected_domain=expected_domain): kwargs = {"domain_names": [domain]} email: str = self.faker.company_email(**kwargs) self.assertIsInstance(email, str) self.assertTrue(self.is_valid_email(email)) self.assertTrue(email.endswith(f"@{expected_domain}"))
[docs] def test_free_email(self) -> None: email: str = self.faker.free_email() self.assertIsInstance(email, str) self.assertTrue(self.is_valid_email(email))
[docs] def test_free_emails(self) -> None: emails: List[str] = self.faker.free_emails(nb=3) self.assertIsInstance(emails, list) self.assertEqual(len(emails), 3)
[docs] def test_url(self) -> None: protocols = ("http", "https") tlds = ("com", "org", "net", "io") suffixes = (".html", ".php", ".go", "", "/") for protocol in protocols: for tld in tlds: for suffix in suffixes: with self.subTest( protocol=protocol, tld=tld, suffix=suffix ): url: str = self.faker.url( protocols=(protocol,), tlds=(tld,), suffixes=(suffix,), ) self.assertIsInstance(url, str) self.assertTrue(url.startswith(f"{protocol}://")) self.assertIn(f".{tld}/", url) self.assertTrue( url.endswith(suffix) or url.endswith(f"{suffix}/") )
[docs] def test_image_url(self) -> None: params = ( (None, None, None, {"width": 800, "height": 600}), (640, 480, None, {"width": 640, "height": 480}), ( None, None, "https://example.com/{width}x{height}", {"width": 800, "height": 600}, ), ) for width, height, service_url, expected in params: kwargs: Dict[str, Union[str, int, None]] = {} if width: kwargs["width"] = width if height: kwargs["height"] = height if service_url: kwargs["service_url"] = service_url image_url = self.faker.image_url(**kwargs) self.assertIn(str(expected["width"]), image_url) self.assertIn(str(expected["height"]), image_url) self.assertTrue(image_url.startswith("https://"))
[docs] def test_pyint(self) -> None: ranges = [ (None, None, 0, 9999), (0, 5, 0, 5), (-5, 0, -5, 0), ] for min_val, max_val, expected_min_val, expected_max_val in ranges: with self.subTest( min_value=min_val, max_value=max_val, expected_min_value=expected_min_val, expected_max_value=expected_max_val, ): kwargs = {} if min_val is not None: kwargs["min_value"] = min_val if max_val is not None: kwargs["max_value"] = max_val val: int = self.faker.pyint(**kwargs) self.assertIsInstance(val, int) self.assertGreaterEqual(val, expected_min_val) self.assertLessEqual(val, expected_max_val)
[docs] def test_pybool(self) -> None: value: bool = self.faker.pybool() self.assertIsInstance(value, bool)
[docs] def test_pystr(self) -> None: ranges = [ (None, 20), (0, 0), (1, 1), (5, 5), (10, 10), (100, 100), ] valid_characters = set(string.ascii_letters) # ASCII letters for nb_chars, expected_nb_chars in ranges: with self.subTest( nb_chars=nb_chars, expected_nb_chars=expected_nb_chars, ): kwargs = {} if nb_chars is not None: kwargs["nb_chars"] = nb_chars val: str = self.faker.pystr(**kwargs) # Check if the output is a string self.assertIsInstance(val, str) # Check if the string has the correct length self.assertEqual(len(val), expected_nb_chars) # Check if all characters are from the valid set self.assertTrue(all(c in valid_characters for c in val))
[docs] def test_password(self): """Test password.""" with self.subTest("That has the correct length."): lengths = [10, 12, 15, 20] for length in lengths: with self.subTest(length=length): pwd = self.faker.password(length=length, min_digits=3) self.assertEqual( len(pwd), length, f"Password length should be {length}", ) with self.subTest("Test contains at least 1 lowercase letter."): pwd = self.faker.password() self.assertTrue( any(c.islower() for c in pwd), "Password must contain at least 1 lowercase letter.", ) with self.subTest("Test contains at least 1 uppercase letter."): pwd = self.faker.password() self.assertTrue( any(c.isupper() for c in pwd), "Password must contain at least 1 uppercase letter.", ) with self.subTest("Test contains at least the min number of digits."): min_digits = 3 pwd = self.faker.password(min_digits=min_digits) digit_count = sum(c.isdigit() for c in pwd) self.assertGreaterEqual( digit_count, min_digits, f"Password must contain at least {min_digits} digits.", ) with self.subTest("Test generator with custom constraints."): length = 15 min_digits = 5 pwd = self.faker.password(length=length, min_digits=min_digits) self.assertEqual(len(pwd), length, "Password length mismatch.") self.assertTrue( any(c.islower() for c in pwd), "Password must contain at least 1 lowercase letter.", ) self.assertTrue( any(c.isupper() for c in pwd), "Password must contain at least 1 uppercase letter.", ) digit_count = sum(c.isdigit() for c in pwd) self.assertGreaterEqual( digit_count, min_digits, f"Password must contain at least {min_digits} digits.", ) with ( self.subTest("Test raises ValueError when length too short."), self.assertRaises(ValueError), ): self.faker.password( length=4, min_digits=3 ) # 2 required characters + 3 digits > 4 with self.subTest("Test multiple generated passwords are unique."): passwords = {self.faker.password() for _ in range(25)} self.assertEqual(len(passwords), 25, "Passwords should be unique.")
[docs] def test_pyfloat(self) -> None: ranges = [ (None, None, 0.0, 10.0), (0.0, 5.0, 0.0, 5.0), (-5.0, 0.0, -5.0, 0.0), ] for min_val, max_val, expected_min_val, expected_max_val in ranges: with self.subTest( min_value=min_val, max_value=max_val, expected_min_value=expected_min_val, expected_max_value=expected_max_val, ): kwargs = {} if min_val is not None: kwargs["min_value"] = min_val if max_val is not None: kwargs["max_value"] = max_val val: float = self.faker.pyfloat(**kwargs) self.assertIsInstance(val, float) self.assertGreaterEqual(val, expected_min_val) self.assertLessEqual(val, expected_max_val)
[docs] def test_pydecimal(self): with self.subTest("With positive=True"): for __ in range(100): decimal_number = self.faker.pydecimal( left_digits=3, right_digits=2, positive=True, ) self.assertIsInstance(decimal_number, Decimal) self.assertTrue(1 <= decimal_number < 1000) # Check if right digits are 2 self.assertEqual(decimal_number.as_tuple().exponent, -2) with self.subTest("With positive=False"): for __ in range(100): negative_decimal_number = self.faker.pydecimal( left_digits=2, right_digits=2, positive=False, ) self.assertTrue(-100 <= negative_decimal_number <= 100) with self.subTest("With right_digits=0"): for __ in range(100): decimal_number = self.faker.pydecimal( left_digits=2, right_digits=0, positive=True, ) self.assertIsInstance(decimal_number, Decimal) # Check if there is no fractional part self.assertEqual(decimal_number % 1, 0) # Check if it's a 3-digit number self.assertTrue(10 <= decimal_number < 100) with self.subTest("With left_digits=0"): for __ in range(100): decimal_number = self.faker.pydecimal( left_digits=0, right_digits=2, positive=True ) self.assertIsInstance(decimal_number, Decimal) self.assertTrue(0 <= decimal_number < 1) self.assertTrue( 10 <= decimal_number * 100 < 100 ) # Check that the fractional part is correct # Test for zero left digits with negative numbers decimal_number_neg = self.faker.pydecimal( left_digits=0, right_digits=2, positive=False ) self.assertTrue(-1 < decimal_number_neg <= 0) with ( self.subTest("Fail on `left_digits` < 0"), self.assertRaises(ValueError), ): self.faker.pydecimal(left_digits=-1) with ( self.subTest("Fail on `right_digits` < 0"), self.assertRaises(ValueError), ): self.faker.pydecimal(right_digits=-1)
[docs] def test_ipv4(self) -> None: # Test a large number of IPs to ensure randomness and correctness for _ in range(1000): ip = self.faker.ipv4() self.assertIsNotNone(ip) self.assertIsInstance(ip, str) parts = ip.split(".") self.assertEqual(len(parts), 4) for part in parts: self.assertTrue(part.isdigit()) self.assertTrue(0 <= int(part) <= 255)
[docs] def test_parse_date_string(self) -> None: import warnings # Test 'now' and 'today' special keywords self.assertAlmostEqual( self.faker._parse_date_string("now"), datetime.now(timezone.utc), delta=timedelta(seconds=1), ) self.assertAlmostEqual( self.faker._parse_date_string("today"), datetime.now(timezone.utc), delta=timedelta(seconds=1), ) # Test days, hours, and minutes self.assertAlmostEqual( self.faker._parse_date_string("1d"), datetime.now(timezone.utc) + timedelta(days=1), delta=timedelta(seconds=1), ) # Test deprecated H and M units (with warnings) with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) self.assertAlmostEqual( self.faker._parse_date_string("-1H"), datetime.now(timezone.utc) - timedelta(hours=1), delta=timedelta(seconds=1), ) self.assertAlmostEqual( self.faker._parse_date_string("30M"), datetime.now(timezone.utc) + timedelta(minutes=30), delta=timedelta(seconds=1), ) # Test new units (w, mo, y) with self.subTest("weeks"): result = self.faker._parse_date_string("+2w") self.assertIsInstance(result, datetime) with self.subTest("months"): result = self.faker._parse_date_string("-3mo") self.assertIsInstance(result, datetime) with self.subTest("years"): result = self.faker._parse_date_string("+1y") self.assertIsInstance(result, datetime) # Test invalid format with self.assertRaises(ValueError): self.faker._parse_date_string("1x")
[docs] def test_date(self) -> None: # Test the same date for start and end start_date = "now" end_date = "+0d" random_date = self.faker.date(start_date, end_date) self.assertIsInstance(random_date, date) self.assertEqual(random_date, datetime.now(timezone.utc).date()) # Test date range start_date = "-2d" end_date = "+2d" random_date = self.faker.date(start_date, end_date) self.assertIsInstance(random_date, date) self.assertTrue( datetime.now(timezone.utc).date() - timedelta(days=2) <= random_date <= datetime.now(timezone.utc).date() + timedelta(days=2) )
[docs] def test_date_time(self) -> None: # Test the same datetime for start and end start_date = "now" end_date = "+0d" random_datetime = self.faker.date_time(start_date, end_date) self.assertIsInstance(random_datetime, datetime) self.assertAlmostEqual( random_datetime, datetime.now(timezone.utc), delta=timedelta(seconds=1), ) # Test datetime range start_date = "-2H" end_date = "+2H" random_datetime = self.faker.date_time(start_date, end_date) self.assertIsInstance(random_datetime, datetime) self.assertTrue( datetime.now(timezone.utc) - timedelta(hours=2) <= random_datetime <= datetime.now(timezone.utc) + timedelta(hours=2) )
[docs] def test_date_iso_range(self) -> None: """Test ISO date format support.""" start_date = "2000-01-01" end_date = "2025-12-31" random_date = self.faker.date(start_date, end_date) self.assertIsInstance(random_date, date) self.assertGreaterEqual(random_date, date(2000, 1, 1)) self.assertLessEqual(random_date, date(2025, 12, 31))
[docs] def test_date_time_iso_range(self) -> None: """Test ISO datetime format with time component.""" start_date = "2000-01-01 00:00:00" end_date = "2000-01-01 23:59:59" random_datetime = self.faker.date_time(start_date, end_date) self.assertIsInstance(random_datetime, datetime) self.assertTrue( datetime(2000, 1, 1, 0, 0, 0, tzinfo=timezone.utc) <= random_datetime <= datetime(2000, 1, 1, 23, 59, 59, tzinfo=timezone.utc) )
[docs] def test_date_relative_weeks(self) -> None: """Test weeks unit.""" random_date = self.faker.date("-2w", "+1w") self.assertIsInstance(random_date, date) now = datetime.now(timezone.utc).date() self.assertGreaterEqual(random_date, now - timedelta(weeks=2)) self.assertLessEqual(random_date, now + timedelta(weeks=1))
[docs] def test_date_relative_months(self) -> None: """Test months unit.""" random_date = self.faker.date("-3mo", "+6mo") self.assertIsInstance(random_date, date)
[docs] def test_date_relative_years(self) -> None: """Test years unit.""" random_date = self.faker.date("-1y", "+1y") self.assertIsInstance(random_date, date)
[docs] def test_date_time_relative_weeks(self) -> None: """Test weeks unit for datetime.""" random_datetime = self.faker.date_time("-1w", "+1w") self.assertIsInstance(random_datetime, datetime)
[docs] def test_date_time_relative_months(self) -> None: """Test months unit for datetime.""" random_datetime = self.faker.date_time("-2mo", "+2mo") self.assertIsInstance(random_datetime, datetime)
[docs] def test_date_time_relative_years(self) -> None: """Test years unit for datetime.""" random_datetime = self.faker.date_time("-1y", "+1y") self.assertIsInstance(random_datetime, datetime)
[docs] def test_date_deprecated_units(self) -> None: """Test deprecated H and M units still work but emit warnings.""" import warnings with ( self.subTest("deprecated H"), warnings.catch_warnings(record=True) as w, ): warnings.simplefilter("always") self.faker._parse_date_string("-1H") self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[0].category, DeprecationWarning)) self.assertIn("'H'", str(w[0].message)) with ( self.subTest("deprecated M"), warnings.catch_warnings(record=True) as w, ): warnings.simplefilter("always") self.faker._parse_date_string("+30M") self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[0].category, DeprecationWarning)) self.assertIn("'M'", str(w[0].message))
[docs] def test_year(self) -> None: """Test that the default year is between 1900 and 2100.""" _year = self.faker.year() self.assertGreaterEqual(_year, 1900) self.assertLessEqual(_year, 2100)
[docs] def test_time(self) -> None: # Test default format with self.subTest("test_time_default_format"): t = self.faker.time() # HH:MM:SS self.assertRegex( t, r"^[0-1]\d:[0-5]\d:[0-5]\d$|^2[0-3]:[0-5]\d:[0-5]\d$", ) # Test custom format with self.subTest("test_time_custom_format"): t2 = self.faker.time(fmt="%H:%M") # HH:MM self.assertRegex(t2, r"^[0-1]\d:[0-5]\d$|^2[0-3]:[0-5]\d$")
[docs] def test_text_pdf(self) -> None: with ( self.subTest("All params None, should fail"), self.assertRaises(ValueError), ): self.faker.pdf( nb_pages=None, texts=None, generator=TextPdfGenerator, ) with self.subTest("Without params"): pdf = self.faker.pdf(generator=TextPdfGenerator) self.assertTrue(pdf) self.assertIsInstance(pdf, bytes) with self.subTest("With `texts` provided"): texts = self.faker.sentences() pdf = self.faker.pdf(texts=texts, generator=TextPdfGenerator) self.assertTrue(pdf) self.assertIsInstance(pdf, bytes) with self.subTest("With `metadata` provided"): metadata = MetaData() pdf = self.faker.pdf( generator=TextPdfGenerator, metadata=metadata, ) self.assertTrue(pdf) self.assertIsInstance(pdf, bytes) with self.subTest("text_pdf shortcut"): metadata = MetaData() pdf = self.faker.text_pdf( metadata=metadata, ) self.assertTrue(pdf) self.assertIsInstance(pdf, bytes)
[docs] def test_graphic_pdf(self) -> None: pdf = self.faker.pdf(generator=GraphicPdfGenerator) self.assertTrue(pdf) self.assertIsInstance(pdf, bytes)
[docs] def test_png(self) -> None: png = self.faker.png() self.assertTrue(png) self.assertIsInstance(png, bytes)
[docs] def test_svg(self) -> None: svg = self.faker.svg() self.assertTrue(svg) self.assertIsInstance(svg, bytes)
[docs] def test_bmp(self) -> None: bmp = self.faker.bmp() self.assertTrue(bmp) self.assertIsInstance(bmp, bytes)
[docs] def test_gif(self) -> None: gif = self.faker.gif() self.assertTrue(gif) self.assertIsInstance(gif, bytes)
[docs] def test_tif(self) -> None: tif = self.faker.tif() self.assertTrue(tif) self.assertIsInstance(tif, bytes)
[docs] def test_ppm(self) -> None: ppm = self.faker.ppm() self.assertTrue(ppm) self.assertIsInstance(ppm, bytes)
[docs] def test_jpg(self) -> None: jpg = self.faker.jpg() self.assertTrue(jpg) self.assertIsInstance(jpg, bytes)
[docs] def test_image(self): for image_format in {"png", "svg", "bmp", "gif", "tif", "ppm", "jpg"}: with self.subTest(image_format=image_format): image = self.faker.image( image_format=image_format, ) self.assertTrue(image) self.assertIsInstance(image, bytes) for image_format in {"bin"}: with ( self.subTest(image_format=image_format), self.assertRaises(ValueError), ): self.faker.image(image_format=image_format)
[docs] def test_wav(self) -> None: wav = self.faker.wav() self.assertTrue(wav) self.assertIsInstance(wav, bytes)
[docs] def test_docx(self) -> None: with ( self.subTest("All params None, should fail"), self.assertRaises(ValueError), ): self.faker.docx(nb_pages=None, texts=None) with self.subTest("Without params"): docx = self.faker.docx() self.assertTrue(docx) self.assertIsInstance(docx, bytes) with self.subTest("With `texts` provided"): texts = self.faker.sentences() docx = self.faker.docx(texts=texts) self.assertTrue(docx) self.assertIsInstance(docx, bytes)
[docs] def test_rtf(self) -> None: with ( self.subTest("All params None, should fail"), self.assertRaises(ValueError), ): self.faker.rtf(nb_pages=None, texts=None) with self.subTest("Without params"): rtf = self.faker.rtf() self.assertTrue(rtf) self.assertIsInstance(rtf, bytes) with self.subTest("With `texts` provided"): texts = self.faker.sentences() rtf = self.faker.rtf(texts=texts) self.assertTrue(rtf) self.assertIsInstance(rtf, bytes)
[docs] def test_epub(self) -> None: with ( self.subTest("All params None, should fail"), self.assertRaises(ValueError), ): self.faker.epub(nb_pages=None, texts=None) with self.subTest("Without params"): epub = self.faker.epub() self.assertTrue(epub) self.assertIsInstance(epub, bytes) with self.subTest("With `texts` provided"): texts = self.faker.sentences() epub = self.faker.epub(texts=texts) self.assertTrue(epub) self.assertIsInstance(epub, bytes)
[docs] def test_odt(self) -> None: with ( self.subTest("All params None, should fail"), self.assertRaises(ValueError), ): self.faker.odt(nb_pages=None, texts=None) with self.subTest("Without params"): odt = self.faker.odt() self.assertTrue(odt) self.assertIsInstance(odt, bytes) with self.subTest("With `texts` provided"): texts = self.faker.sentences() odt = self.faker.odt(texts=texts) self.assertTrue(odt) self.assertIsInstance(odt, bytes)
[docs] def test_bin(self) -> None: value = self.faker.bin() self.assertTrue(value) self.assertIsInstance(value, bytes)
[docs] def test_zip(self) -> None: value = self.faker.zip() self.assertTrue(value) self.assertIsInstance(value, bytes)
[docs] def test_tar(self) -> None: value = self.faker.tar() self.assertTrue(value) self.assertIsInstance(value, bytes)
[docs] def test_eml(self) -> None: value = self.faker.eml() self.assertTrue(value) self.assertIsInstance(value, bytes)
[docs] def test_pdf_file(self) -> None: file = self.faker.pdf_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_pdf_file_basename_string_template(self): """pdf_file accepts StringTemplate as basename.""" sv = self.faker.pdf_file( basename=StringTemplate("custom_pdf_{word}"), ) filename = Path(sv.data["storage"].abspath(sv.data["filename"])) basename = filename.stem self.assertIn("custom_pdf_", basename) self.assertNotIn("{word}", basename)
[docs] def test_pdf_file_prefix_lazy_string_template(self): """pdf_file accepts LazyStringTemplate as prefix.""" template = LazyStringTemplate("lazy_prefix_{word}") sv = self.faker.pdf_file(prefix=template) filename = Path(sv.data["storage"].abspath(sv.data["filename"])) basename = filename.stem self.assertTrue(basename.startswith("lazy_prefix_")) self.assertNotIn("{word}", basename)
[docs] def test_text_pdf_file(self) -> None: file = self.faker.text_pdf_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_png_file(self) -> None: file = self.faker.png_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_svg_file(self) -> None: file = self.faker.svg_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_bmp_file(self) -> None: file = self.faker.bmp_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_gif_file(self) -> None: file = self.faker.gif_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_tif_file(self) -> None: file = self.faker.tif_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_ppm_file(self) -> None: file = self.faker.ppm_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_jpg_file(self) -> None: file = self.faker.jpg_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_wav_file(self) -> None: file = self.faker.wav_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_docx_file(self) -> None: file = self.faker.docx_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_odt_file(self) -> None: file = self.faker.odt_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_bin_file(self) -> None: file = self.faker.bin_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_zip_file(self) -> None: file = self.faker.zip_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_tar_file(self) -> None: file = self.faker.tar_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_eml_file(self) -> None: file = self.faker.eml_file() self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_txt_file(self) -> None: with self.subTest("Without arguments"): file = self.faker.txt_file() self.assertTrue(os.path.exists(file.data["filename"])) with self.subTest("nb_chars=None"): file = self.faker.txt_file(nb_chars=None) self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_txt_file_basename_string_template(self): """txt_file accepts StringTemplate as basename.""" sv = self.faker.txt_file( basename=StringTemplate("custom_txt_{word}"), ) filename = Path(sv.data["storage"].abspath(sv.data["filename"])) basename = filename.stem self.assertIn("custom_txt_", basename) self.assertNotIn("{word}", basename)
[docs] def test_txt_file_prefix_lazy_string_template(self): """txt_file accepts LazyStringTemplate as prefix.""" template = LazyStringTemplate("lazy_txt_{word}") sv = self.faker.txt_file(prefix=template) filename = Path(sv.data["storage"].abspath(sv.data["filename"])) basename = filename.stem self.assertTrue(basename.startswith("lazy_txt_")) self.assertNotIn("{word}", basename)
[docs] def test_generic_file(self) -> None: with self.subTest("Without text content"): file = self.faker.generic_file( content=self.faker.text(), extension="txt", ) self.assertTrue(os.path.exists(file.data["filename"])) with self.subTest("With bytes content"): file = self.faker.generic_file( content=self.faker.text().encode(), extension="txt", ) self.assertTrue(os.path.exists(file.data["filename"]))
[docs] def test_generic_file_accepts_string_template(self): """generic_file renders a StringTemplate before writing.""" template = "Hello {word}." sv = self.faker.generic_file( content=StringTemplate(template), extension="txt", ) stored = Path(sv.data["storage"].abspath(sv.data["filename"])) text = stored.read_text() assert "{word}" not in text assert len(text) > 0
[docs] def test_generic_file_accepts_lazy_string_template(self): """generic_file renders a LazyStringTemplate fresh on each call.""" template = LazyStringTemplate("Hello {word}.") sv1 = self.faker.generic_file(content=template, extension="txt") sv2 = self.faker.generic_file(content=template, extension="txt") text1 = Path( sv1.data["storage"].abspath(sv1.data["filename"]) ).read_text() text2 = Path( sv2.data["storage"].abspath(sv2.data["filename"]) ).read_text() assert "{word}" not in text1 assert "{word}" not in text2 assert text1.startswith("Hello ") assert text2.startswith("Hello ")
[docs] def test_generic_file_basename_string_template(self): """generic_file accepts StringTemplate as basename.""" sv = self.faker.generic_file( content="test content", extension="txt", basename=StringTemplate("custom_basename_{word}"), ) filename = Path(sv.data["storage"].abspath(sv.data["filename"])) basename = filename.stem self.assertIn("custom_basename_", basename) self.assertNotIn("{word}", basename)
[docs] def test_generic_file_basename_lazy_string_template(self): """generic_file accepts LazyStringTemplate as basename.""" template = LazyStringTemplate("lazy_basename_{word}") sv1 = self.faker.generic_file( content="test content", extension="txt", basename=template, ) sv2 = self.faker.generic_file( content="test content", extension="txt", basename=template, ) bn1 = Path(sv1.data["storage"].abspath(sv1.data["filename"])).stem bn2 = Path(sv2.data["storage"].abspath(sv2.data["filename"])).stem self.assertIn("lazy_basename_", bn1) self.assertIn("lazy_basename_", bn2) self.assertNotIn("{word}", bn1) self.assertNotIn("{word}", bn2)
[docs] def test_generic_file_prefix_string_template(self): """generic_file accepts StringTemplate as prefix.""" sv = self.faker.generic_file( content="test content", extension="txt", prefix=StringTemplate("custom_prefix_{word}"), ) filename = Path(sv.data["storage"].abspath(sv.data["filename"])) basename = filename.stem self.assertTrue(basename.startswith("custom_prefix_")) self.assertNotIn("{word}", basename)
[docs] def test_generic_file_prefix_lazy_string_template(self): """generic_file accepts LazyStringTemplate as prefix.""" template = LazyStringTemplate("lazy_prefix_{word}") sv1 = self.faker.generic_file( content="test content", extension="txt", prefix=template, ) sv2 = self.faker.generic_file( content="test content", extension="txt", prefix=template, ) bn1 = Path(sv1.data["storage"].abspath(sv1.data["filename"])).stem bn2 = Path(sv2.data["storage"].abspath(sv2.data["filename"])).stem self.assertTrue(bn1.startswith("lazy_prefix_")) self.assertTrue(bn2.startswith("lazy_prefix_")) self.assertNotIn("{word}", bn1) self.assertNotIn("{word}", bn2)
[docs] def test_generic_file_basename_absolute_path(self): """generic_file rejects absolute path in basename.""" with self.assertRaises(ValueError) as cm: self.faker.generic_file( content="test content", extension="txt", basename="/absolute/path", ) self.assertIn("must be a relative filename", str(cm.exception)) self.assertIn("not an absolute path", str(cm.exception))
[docs] def test_generic_file_prefix_absolute_path(self): """generic_file rejects absolute path in prefix.""" with self.assertRaises(ValueError) as cm: self.faker.generic_file( content="test content", extension="txt", prefix="/absolute/prefix", ) self.assertIn("must be a relative filename", str(cm.exception)) self.assertIn("not an absolute path", str(cm.exception))
[docs] def test_generic_file_basename_path_traversal(self): """generic_file rejects path traversal in basename.""" with self.assertRaises(ValueError) as cm: self.faker.generic_file( content="test content", extension="txt", basename="../etc/passwd", ) self.assertIn("path traversal", str(cm.exception))
[docs] def test_generic_file_prefix_path_traversal(self): """generic_file rejects path traversal in prefix.""" with self.assertRaises(ValueError) as cm: self.faker.generic_file( content="test content", extension="txt", prefix="..\\windows\\system32", ) self.assertIn("path traversal", str(cm.exception))
[docs] def test_generic_file_basename_invalid_chars(self): """generic_file rejects invalid characters in basename.""" with self.assertRaises(ValueError) as cm: self.faker.generic_file( content="test content", extension="txt", basename="file<name>", ) self.assertIn("invalid characters", str(cm.exception))
[docs] def test_generic_file_prefix_invalid_chars(self): """generic_file rejects invalid characters in prefix.""" with self.assertRaises(ValueError) as cm: self.faker.generic_file( content="test content", extension="txt", prefix="pre|fix?", ) self.assertIn("invalid characters", str(cm.exception))
[docs] def test_pdf_file_basename_validation(self): """pdf_file validates basename.""" with self.assertRaises(ValueError): self.faker.pdf_file(basename="/etc/passwd")
[docs] def test_txt_file_prefix_validation(self): """txt_file validates prefix.""" with self.assertRaises(ValueError): self.faker.txt_file(prefix="../traversal")
[docs] def test_create_inner_pdf_file(self): value = create_inner_pdf_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_text_pdf_file(self): value = create_inner_text_pdf_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_png_file(self): value = create_inner_png_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_svg_file(self): value = create_inner_svg_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_bmp_file(self): value = create_inner_bmp_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_gif_file(self): value = create_inner_gif_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_tif_file(self): value = create_inner_tif_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_ppm_file(self): value = create_inner_ppm_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_jpg_file(self): value = create_inner_jpg_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_wav_file(self): value = create_inner_wav_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_docx_file(self): value = create_inner_docx_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_rtf_file(self): value = create_inner_rtf_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_epub_file(self): value = create_inner_epub_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_odt_file(self): value = create_inner_odt_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_zip_file(self): value = create_inner_zip_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_zip_file_with_options(self): value = create_inner_zip_file( prefix="zzz_archive_", options={ "count": 5, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": { "prefix": "zzz_file_", }, "directory": "zzz", }, ) self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_zip_file_with_options_list_create(self): value = create_inner_zip_file( basename="alice-looking-through-the-glass", options={ "create_inner_file_func": list_create_inner_file, "create_inner_file_args": { "func_list": [ (create_inner_txt_file, {}), (create_inner_txt_file, {}), (create_inner_docx_file, {}), ] }, }, ) self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_tar_file(self): value = create_inner_tar_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_tar_file_with_options(self): value = create_inner_tar_file( prefix="ttt_archive_", options={ "count": 5, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": { "prefix": "ttt_file_", }, "directory": "ttt", }, ) self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_tar_file_with_options_list_create(self): value = create_inner_tar_file( basename="alice-looking-through-the-glass", options={ "create_inner_file_func": list_create_inner_file, "create_inner_file_args": { "func_list": [ (create_inner_txt_file, {}), (create_inner_txt_file, {}), (create_inner_docx_file, {}), ] }, }, ) self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_eml_file(self): value = create_inner_eml_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_eml_file_with_options(self): value = create_inner_eml_file( prefix="eee_email_", options={ "count": 5, "create_inner_file_func": create_inner_docx_file, "create_inner_file_args": { "prefix": "eee_file_", }, }, ) self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_create_inner_eml_file_with_options_list_create(self): value = create_inner_eml_file( basename="alice-looking-through-the-glass", options={ "create_inner_file_func": list_create_inner_file, "create_inner_file_args": { "func_list": [ (create_inner_txt_file, {}), (create_inner_txt_file, {}), (create_inner_docx_file, {}), ] }, }, ) self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_zip_dir_path_literal(self): """dir_path as a literal string places the file at that subpath.""" raw = self.faker.zip( options={ "count": 1, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": { "dir_path": "level1/level2/level3", }, "directory": "", } ) with zipfile.ZipFile(BytesIO(bytes(raw)), "r") as zf: names = zf.namelist() assert len(names) == 1 parts = Path(names[0]).parts assert parts[0] == "level1" assert parts[1] == "level2" assert parts[2] == "level3" assert not names[0].endswith("/")
[docs] def test_zip_dir_path_sanitization(self): """dir_path with unsafe paths like .., absolute paths, drive letters.""" with self.subTest("Path traversal with .."): raw = self.faker.zip( options={ "count": 1, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": { "dir_path": "../../../etc/passwd", }, "directory": "", } ) with zipfile.ZipFile(BytesIO(bytes(raw)), "r") as zf: names = zf.namelist() assert len(names) == 1 parts = Path(names[0]).parts assert ".." not in parts assert not names[0].startswith("/") with self.subTest("Absolute path"): raw = self.faker.zip( options={ "count": 1, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": { "dir_path": "/etc/passwd", }, "directory": "", } ) with zipfile.ZipFile(BytesIO(bytes(raw)), "r") as zf: names = zf.namelist() assert len(names) == 1 assert not names[0].startswith("/") with self.subTest("Backslash path traversal"): raw = self.faker.zip( options={ "count": 1, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": { "dir_path": "level1\\..\\..\\level2", }, "directory": "", } ) with zipfile.ZipFile(BytesIO(bytes(raw)), "r") as zf: names = zf.namelist() assert len(names) == 1 assert ".." not in names[0] with self.subTest("Directory argument with path traversal"): raw = self.faker.zip( options={ "count": 1, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": {}, "directory": "../../../etc/passwd", } ) with zipfile.ZipFile(BytesIO(bytes(raw)), "r") as zf: names = zf.namelist() assert len(names) == 1 assert ".." not in names[0] assert not names[0].startswith("/")
[docs] def test_zip_dir_path_template(self): """dir_path as a string_template expression is resolved before use.""" raw = self.faker.zip( options={ "count": 1, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": { "dir_path": "{dir_path(depth=2)}", }, "directory": "", } ) with zipfile.ZipFile(BytesIO(bytes(raw)), "r") as zf: names = zf.namelist() assert len(names) == 1 assert len(Path(names[0]).parts) == 3 assert not names[0].startswith("/")
[docs] def test_zip_without_dir_path_unaffected(self): """Existing callers that omit dir_path behave exactly as before.""" raw = self.faker.zip( options={ "count": 1, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": {}, "directory": "mydir", } ) with zipfile.ZipFile(BytesIO(bytes(raw)), "r") as zf: names = zf.namelist() assert len(names) == 1 assert names[0].startswith("mydir/") assert names[0].count("/") == 1
[docs] def test_tar_dir_path_literal(self): """Same guarantee holds for tar().""" raw = self.faker.tar( options={ "count": 1, "create_inner_file_func": create_inner_txt_file, "create_inner_file_args": { "dir_path": "a/b/c", }, "directory": "", } ) with tarfile.open(fileobj=BytesIO(bytes(raw))) as tf: names = tf.getnames() assert len(names) == 1 parts = Path(names[0]).parts assert parts[:3] == ("a", "b", "c")
[docs] def test_create_inner_generic_file_with_lazy_string_template_and_dir_path( self, ): """create_inner_generic_file with LazyStringTemplate content and dir_path produces independently rendered files at the correct in-archive path.""" template = LazyStringTemplate("<item>{word}</item>") raw = self.faker.zip( options={ "count": 3, "create_inner_file_func": create_inner_generic_file, "create_inner_file_args": { "content": template, "extension": "xml", "dir_path": "data/{word}", }, "directory": "", } ) with zipfile.ZipFile(BytesIO(bytes(raw)), "r") as zf: names = zf.namelist() contents = [zf.read(n).decode() for n in names] assert len(names) == 3 for name in names: parts = Path(name).parts assert parts[0] == "data" assert len(parts) == 3 for content in contents: assert "{word}" not in content assert content.startswith("<item>")
[docs] def test_create_inner_txt_file(self): value = create_inner_txt_file() self.assertTrue(value) self.assertIsInstance(value, StringValue)
[docs] def test_random_choice(self) -> None: _categories = ["art", "technology", "literature"] _choice = self.faker.random_choice(_categories) self.assertIn(_choice, _categories)
[docs] def test_random_sample(self) -> None: _categories = ["art", "technology", "literature"] _sample = self.faker.random_sample(_categories, 2) self.assertEqual(len(_sample), 2) for _element in _sample: self.assertIn(_element, _categories)
[docs] def test_city(self): city = self.faker.city() self.assertIn(city, self.faker._cities)
[docs] def test_country(self): country = self.faker.country() self.assertIn(country, self.faker._countries)
[docs] def test_geo_location(self): geo_location = self.faker.geo_location() self.assertIn(geo_location, self.faker._geo_locations)
[docs] def test_country_code(self): country_code = self.faker.country_code() self.assertIn(country_code, self.faker._country_codes) self.assertEqual(len(country_code), 2) self.assertTrue(country_code.isupper())
[docs] def test_locale(self): _locale = self.faker.locale() self.assertIn(_locale, self.faker._locales) self.assertIn("_", _locale) parts = _locale.split("_") self.assertTrue(len(parts), 2)
[docs] def test_latitude(self): """Test that the latitude function returns a valid latitude.""" for _ in range(50): # Run multiple times to test randomness lat = self.faker.latitude() self.assertTrue(-90 <= lat <= 90)
[docs] def test_longitude(self): """Test that the longitude function returns a valid longitude.""" for _ in range(50): # Run multiple times to test randomness lon = self.faker.longitude() self.assertTrue(-180 <= lon <= 180)
[docs] def test_latitude_longitude(self): """Test that the latlng returns a valid (latitude, longitude) pair.""" for _ in range(50): # Run multiple times to test randomness lat, lon = self.faker.latitude_longitude() self.assertTrue(-90 <= lat <= 90) self.assertTrue(-180 <= lon <= 180)
[docs] def test_iban(self): iban = self.faker.iban() self.assertEqual(len(iban), 22) self.assertTrue(iban[:2].isalpha()) self.assertTrue(iban[2:4].isdigit()) self.assertTrue(iban[4:].isdigit())
[docs] def test_isbn10(self): isbn10 = self.faker.isbn10() self.assertTrue(isbn10.count("-"), 3) parts = isbn10.split("-") self.assertEqual(len(parts), 4) self.assertTrue(all(part.isdigit() for part in parts[:-1])) self.assertTrue(parts[-1].isdigit() or parts[-1] == "X")
[docs] def test_isbn13(self): isbn13 = self.faker.isbn13() self.assertTrue(isbn13.count("-"), 4) parts = isbn13.split("-") self.assertEqual(len(parts), 5) self.assertIn(parts[0], ["978", "979"]) self.assertTrue(all(part.isdigit() for part in parts[1:]))
[docs] def test_isbn13_checksum(self): # Generate an ISBN-13 excluding the checksum prefix = random.choice(["978", "979"]) digits = [str(random.randint(0, 9)) for _ in range(9)] full_digits = list(prefix) + digits # Get the checksum using the _isbn13_checksum method calculated_checksum = self.faker._isbn13_checksum(full_digits) # Append the checksum and form the full ISBN-13 isbn = "".join(full_digits) + calculated_checksum # Re-calculate to verify correctness self.assertEqual( calculated_checksum, self.faker._isbn13_checksum(list(isbn[:-1])), )
[docs] def test_randomise_string(self): # Test pattern with both letters and digits placeholders pattern = "??##-####-??" expected_length = len(pattern) result = self.faker.randomise_string(pattern) self.assertEqual(len(result), expected_length) self.assertTrue( all(char in string.ascii_uppercase for char in result[:2]) ) self.assertTrue(all(char in string.digits for char in result[2:4])) self.assertEqual( result[4], "-", ) self.assertTrue(all(char in string.digits for char in result[5:9])) self.assertEqual(result[9], "-") self.assertTrue( all(char in string.ascii_uppercase for char in result[10:]) ) # Test pattern with only letters placeholders pattern = "???" result = self.faker.randomise_string(pattern) self.assertEqual(len(result), len(pattern)) self.assertTrue(all(char in string.ascii_uppercase for char in result)) # Test pattern with only digits placeholders pattern = "###" result = self.faker.randomise_string(pattern) self.assertEqual(len(result), len(pattern)) self.assertTrue(all(char in string.digits for char in result)) # Test pattern with no placeholders pattern = "ABC-123" result = self.faker.randomise_string(pattern) self.assertEqual(result, pattern)
[docs] def test_string_template(self) -> None: # Create a mock faker object with some methods mock_faker = MagicMock() mock_faker.name.return_value = "John Doe" mock_faker.sentence.return_value = "This is a test sentence." mock_faker.date.return_value = "2023-01-01" mock_faker.custom_method.return_value = "Custom value" mock_faker.method123.return_value = "Numeric method" mock_faker.boolean_method.return_value = "Boolean result" mock_faker.string_template = deepcopy(self.faker.string_template) template = ( "Name: {name()}\n" "Sentence: {sentence(nb_words=10)}\n" "Date: {date(start_date='-7d')}\n" "Custom: {custom_method(param='value')}" ) string_template = self.faker.string_template( template, faker=mock_faker, ) expected = ( "Name: John Doe\n" "Sentence: This is a test sentence.\n" "Date: 2023-01-01\n" "Custom: Custom value" ) self.assertEqual(string_template, expected) mock_faker.name.assert_called_once() mock_faker.sentence.assert_called_once_with(nb_words=10) mock_faker.date.assert_called_once_with(start_date="-7d") mock_faker.custom_method.assert_called_once_with(param="value")
[docs] def test_lazy_string_template(self) -> None: # Create a mock faker object with some methods mock_faker = MagicMock() mock_faker.name.return_value = "John Doe" mock_faker.sentence.return_value = "This is a test sentence." mock_faker.date.return_value = "2023-01-01" mock_faker.custom_method.return_value = "Custom value" mock_faker.method123.return_value = "Numeric method" mock_faker.boolean_method.return_value = "Boolean result" mock_faker.lazy_string_template = deepcopy( self.faker.lazy_string_template ) template = ( "Name: {name()}\n" "Sentence: {sentence(nb_words=10)}\n" "Date: {date(start_date='-7d')}\n" "Custom: {custom_method(param='value')}" ) string_template = self.faker.lazy_string_template( template, faker=mock_faker, ) expected = ( "Name: John Doe\n" "Sentence: This is a test sentence.\n" "Date: 2023-01-01\n" "Custom: Custom value" ) result = str(string_template) self.assertEqual(result, expected) mock_faker.name.assert_called_once() mock_faker.sentence.assert_called_once_with(nb_words=10) mock_faker.date.assert_called_once_with(start_date="-7d") mock_faker.custom_method.assert_called_once_with(param="value")
[docs] def test_storage(self) -> None: storage = FileSystemStorage() with self.assertRaises(ValueError): storage.generate_filename(extension=None) # type: ignore
[docs] def test_storage_integration(self) -> None: file = self.faker.txt_file() file_2 = self.faker.txt_file(basename="file_2") file_3 = self.faker.txt_file(basename="file_3") storage: FileSystemStorage = file.data["storage"] with self.subTest("Test os.path.exists"): self.assertTrue(os.path.exists(file.data["filename"])) with self.subTest("Test storage.exists on StringValue"): self.assertTrue(storage.exists(file)) with self.subTest("Test storage.exists on rel path"): self.assertTrue(storage.exists(str(file))) with self.subTest("Test storage.exists on abs path"): self.assertTrue(storage.exists(file.data["filename"])) with self.subTest("Test storage.abspath using relative path"): self.assertEqual( os.path.realpath(storage.abspath(str(file))), os.path.realpath(file.data["filename"]), ) with self.subTest("Test storage.abspath using absolute path"): self.assertEqual( os.path.realpath(storage.abspath(file.data["filename"])), os.path.realpath(file.data["filename"]), ) with self.subTest("Test storage.unlink on absolute path"): storage.unlink(file.data["filename"]) self.assertFalse(storage.exists(str(file))) self.assertFalse(storage.exists(file.data["filename"])) with self.subTest("Test storage.unlink on relative path"): storage.unlink(str(file_2)) self.assertFalse(storage.exists(file_2.data["filename"])) with self.subTest("Test storage.unlink on relative path"): storage.unlink(str(file_3)) self.assertFalse(storage.exists(file_3.data["filename"]))
[docs] def test_authorship_data(self): """Test `AuthorshipData`.""" authorship_data = AuthorshipData() with self.subTest("Testing UnicodeDecodeError case"): # Creating a text file with non-UTF-8 characters. # Using a character that is not compatible with UTF-8 but is with # Latin-1. For example, the byte sequence for a character not # representable in UTF-8. file = self.faker.generic_file( content=b"\xff\xff", extension="txt", basename="non_utf8_file", ) val = authorship_data._find_authorship_info(file.data["filename"]) self.assertFalse(val)
[docs] def test_metadata(self) -> None: """Test MetaData.""" with self.subTest("Test str"): metadata = MetaData() content = self.faker.word() metadata.add_content(content) self.assertEqual(metadata.content, content) with self.subTest("Test list"): metadata = MetaData() content = self.faker.words() metadata.add_content(content) self.assertEqual(metadata.content, "\n".join(content))
[docs] def test_faker_init(self) -> None: faker = Faker(alias="default") self.assertNotEqual(faker.alias, "default")
[docs] def test_get_by_uid(self) -> None: faker = Faker.get_by_uid(f"{__name__}.{Faker.__name__}") self.assertIs(faker, self.faker)
[docs] def test_get_by_alias(self) -> None: faker = Faker.get_by_alias("default") self.assertIs(faker, self.faker)
[docs] def test_factory_method(self) -> None: """Test FactoryMethod.""" with self.subTest("sentence"): sentence_factory_method = FactoryMethod("sentence") generated_sentence = sentence_factory_method() self.assertIsInstance(generated_sentence, str) with self.subTest("pyint"): pyint_factory_method = FactoryMethod("pyint") generated_int = pyint_factory_method() self.assertIsInstance(generated_int, int)
[docs] def test_factory_methods(self) -> None: # Assuming 'Faker' is the class with methods decorated by @provider faker = Faker() factory = Factory(faker) # Iterate through methods of Faker for attr_name in dir(faker): attr_value = getattr(faker, attr_name) if callable(attr_value) and getattr( attr_value, "is_provider", False ): # Check if Factory has the method self.assertTrue(hasattr(factory, attr_name))
[docs] def test_sub_factory(self) -> None: """Test FACTORY and SubFactory.""" # ************************* # ********* Models ******** # ************************* class MockPydanticField: """Mock field simulating a Pydantic model field.""" def __init__(self, type, default_factory): self.type = type self.default_factory = default_factory class MockPydanticModel: """Mock class simulating a Pydantic model.""" # Adjusting __fields__ to mimic Pydantic's structure __fields__ = { "id": MockPydanticField(int, lambda: 1), "name": MockPydanticField(str, lambda: "default"), "is_active": MockPydanticField(bool, lambda: True), "created_at": MockPydanticField(datetime, datetime.now), "optional_field": MockPydanticField( Optional[str], lambda: None ), } class Config: arbitrary_types_allowed = True id: int name: str is_active: bool created_at: datetime optional_field: Optional[str] = None def __init__(self, **kwargs): for name, value in kwargs.items(): setattr(self, name, value) class DjangoQuerySet(list): """Mimicking Django QuerySet class.""" def __init__(self, instance: Union["Article", "User"]) -> None: super().__init__() self.instance = instance def first(self) -> Union["Article", "User"]: return self.instance class DjangoManager: """Mimicking Django Manager class.""" def __init__(self, instance: Union["Article", "User"]) -> None: self.instance = instance def filter(self, *args, **kwargs) -> "DjangoQuerySet": return DjangoQuerySet(instance=self.instance) @dataclass(frozen=True) class Group: id: int name: str @dataclass class User: """User model.""" id: int username: str first_name: str last_name: str email: str date_joined: datetime = field(default_factory=datetime.utcnow) last_login: Optional[datetime] = None password: Optional[str] = None is_superuser: bool = False is_staff: bool = False is_active: bool = True groups: Set[Group] = field(default_factory=set) def save(self, *args, **kwargs): """Mimicking Django's Mode save method.""" self.save_called = True # noqa def set_password(self, password: str) -> None: self.password = xor_transform(password) @classproperty def objects(cls): # noqa: N805 """Mimicking Django's Manager behaviour.""" return DjangoManager( instance=fill_dataclass(cls), # type: ignore ) @dataclass class Article: id: int title: str slug: str content: str headline: str category: str pages: int auto_minutes_to_read: int author: User image: Optional[str] = ( None # Use str to represent the image path or URL ) pub_date: date = field(default_factory=date.today) safe_for_work: bool = False minutes_to_read: int = 5 def save(self, *args, **kwargs): """Mimicking Django's Mode save method.""" self.save_called = True # noqa @classproperty def objects(cls): # noqa: N805 """Mimicking Django's Manager behaviour.""" return DjangoManager( instance=fill_dataclass(cls), # type: ignore ) with ( self.subTest("fill_pydantic_model on dataclass"), self.assertRaises(ValueError), ): _article = fill_pydantic_model(Article) with self.subTest("fill_pydantic_model"): _obj = fill_pydantic_model(MockPydanticModel) with self.subTest("fill_dataclass"): _article = fill_dataclass(Article) # **************************** # *********** Other ********** # **************************** base_dir = Path(__file__).resolve().parent.parent media_root = base_dir / "media" storage = FileSystemStorage(root_path=media_root, rel_path="tmp") # **************************** # ******* ModelFactory ******* # **************************** def set_password(user: Any, password: str) -> None: user.set_password(password) def add_to_group(user: Any, name: str) -> None: group = GroupFactory(name=name) user.groups.add(group) categories = ( "art", "technology", "literature", ) class GroupFactory(ModelFactory): id = FACTORY.pyint() # type: ignore name = FACTORY.word() # type: ignore class Meta: model = Group get_or_create = ("name",) class UserFactory(ModelFactory): id = FACTORY.pyint() # type: ignore username = FACTORY.username() # type: ignore first_name = FACTORY.first_name() # type: ignore last_name = FACTORY.last_name() # type: ignore email = FACTORY.email() # type: ignore last_login = FACTORY.date_time() # type: ignore is_superuser = False is_staff = False is_active = FACTORY.pybool() # type: ignore date_joined = FACTORY.date_time() # type: ignore password = PreSave(set_password, password="test1234") group = PostSave(add_to_group, name="TestGroup1234") class Meta: model = User @trait def is_admin_user(self, instance: User) -> None: instance.is_superuser = True instance.is_staff = True instance.is_active = True @pre_save def _pre_save_method(self, instance): instance._pre_save_called = True @post_save def _post_save_method(self, instance): instance._post_save_called = True def set_auto_minutes_to_read(data): data["auto_minutes_to_read"] = data["pages"] class ArticleFactory(ModelFactory): id = FACTORY.pyint() # type: ignore title = FACTORY.sentence() # type: ignore slug = FACTORY.slug() # type: ignore content = FACTORY.text() # type: ignore headline = LazyAttribute(lambda o: o.content[:25]) category = LazyFunction(partial(random.choice, categories)) pages = FACTORY.pyint(min_value=1, max_value=100) # type: ignore auto_minutes_to_read = PreInit(set_auto_minutes_to_read) image = FACTORY.png_file(storage=storage) # type: ignore pub_date = FACTORY.date() # type: ignore safe_for_work = FACTORY.pybool() # type: ignore minutes_to_read = FACTORY.pyint( # type: ignore min_value=1, max_value=10 ) author = SubFactory(UserFactory) class Meta: model = Article with self.subTest("ModelFactory"): _article = ArticleFactory() # Testing SubFactory self.assertIsInstance(_article.author, User) self.assertIsInstance(_article.author.id, int) self.assertIsInstance( _article.author.is_staff, bool, ) self.assertIsInstance( _article.author.date_joined, datetime, ) # Testing LazyFunction self.assertIn(_article.category, categories) # Testing LazyAttribute self.assertIn(_article.headline, _article.content) # Testing PreInit self.assertEqual(_article.pages, _article.auto_minutes_to_read) # Testing Factory self.assertIsInstance(_article.id, int) self.assertIsInstance(_article.slug, str) # Testing hooks _user = _article.author self.assertTrue( hasattr(_user, "_pre_save_called") and _user._pre_save_called ) self.assertTrue( hasattr(_user, "_post_save_called") and _user._post_save_called ) # Testing get_or_create for Article model _article = ArticleFactory(id=1) self.assertIsInstance(_article, Article) self.assertEqual(_article.id, 1) # Testing traits _admin_user = UserFactory(is_admin_user=True) self.assertTrue( _admin_user.is_staff and _admin_user.is_superuser and _admin_user.is_active ) # Testing PreSave self.assertEqual(xor_transform(str(_user.password)), "test1234") _user = UserFactory( password=PreSave(set_password, password="1234test") ) self.assertEqual(xor_transform(str(_user.password)), "1234test") # Testing PostSave self.assertEqual(list(_user.groups)[0].name, "TestGroup1234") _user = UserFactory( group=PostSave(add_to_group, name="1234TestGroup") ) self.assertEqual(list(_user.groups)[0].name, "1234TestGroup") # ********************************** # ******* DjangoModelFactory ******* # ********************************** class DjangoUserFactory(DjangoModelFactory): id = FACTORY.pyint() # type: ignore username = FACTORY.username() # type: ignore first_name = FACTORY.first_name() # type: ignore last_name = FACTORY.last_name() # type: ignore email = FACTORY.email() # type: ignore last_login = FACTORY.date_time() # type: ignore is_superuser = False is_staff = False is_active = FACTORY.pybool() # type: ignore date_joined = FACTORY.date_time() # type: ignore password = PreSave(set_password, password="jest1234") group = PostSave(add_to_group, name="JestGroup1234") class Meta: model = User get_or_create = ("username",) @trait def is_admin_user(self, instance: User) -> None: instance.is_superuser = True instance.is_staff = True instance.is_active = True @pre_save def _pre_save_method(self, instance): instance._pre_save_called = True @post_save def _post_save_method(self, instance): instance._post_save_called = True class DjangoArticleFactory(DjangoModelFactory): id = FACTORY.pyint() # type: ignore title = FACTORY.sentence() # type: ignore slug = FACTORY.slug() # type: ignore content = FACTORY.text() # type: ignore headline = LazyAttribute(lambda o: o.content[:25]) category = LazyFunction(partial(random.choice, categories)) pages = FACTORY.pyint(min_value=1, max_value=100) # type: ignore auto_minutes_to_read = PreInit(set_auto_minutes_to_read) image = FACTORY.png_file(storage=storage) # type: ignore pub_date = FACTORY.date() # type: ignore safe_for_work = FACTORY.pybool() # type: ignore minutes_to_read = FACTORY.pyint( # type: ignore min_value=1, max_value=10, ) author = SubFactory(DjangoUserFactory) class Meta: model = Article @pre_save def _pre_save_method(self, instance): instance._pre_save_called = True @post_save def _post_save_method(self, instance): instance._post_save_called = True with self.subTest("DjangoModelFactory"): _django_article = DjangoArticleFactory(author__username="admin") # Testing SubFactory self.assertIsInstance(_django_article.author, User) self.assertIsInstance( _django_article.author.id, # type: ignore int, ) self.assertIsInstance( _django_article.author.is_staff, # type: ignore bool, ) self.assertIsInstance( _django_article.author.date_joined, # type: ignore datetime, ) # Since we're mimicking Django's behaviour, the following line would # fail on test, however would pass when testing against real Django # model (as done in the examples). # self.assertEqual(django_article.author.username, "admin") # Testing Factory self.assertIsInstance(_django_article.id, int) self.assertIsInstance(_django_article.slug, str) # Testing PreInit self.assertEqual( _django_article.pages, _django_article.auto_minutes_to_read, ) # Testing hooks self.assertTrue( hasattr(_django_article, "_pre_save_called") and _django_article._pre_save_called ) self.assertTrue( hasattr(_django_article, "_post_save_called") and _django_article._post_save_called ) # Testing batch creation _django_articles = DjangoArticleFactory.create_batch(5) self.assertEqual(len(_django_articles), 5) self.assertIsInstance(_django_articles[0], Article) # Testing get_or_create for Article model _django_article = DjangoArticleFactory(id=1) self.assertIsInstance(_django_article, Article) # Testing traits _django_admin_user = DjangoUserFactory(is_admin_user=True) self.assertTrue( _django_admin_user.is_staff and _django_admin_user.is_superuser and _django_admin_user.is_active ) # Testing PreSave _django_user = DjangoUserFactory() self.assertEqual( xor_transform(str(_django_user.password)), "jest1234", ) _django_user = DjangoUserFactory( password=PreSave(set_password, password="1234jest") ) self.assertEqual( xor_transform(str(_django_user.password)), "1234jest", ) # Testing PostSave self.assertEqual( list(_django_user.groups)[0].name, # type: ignore "JestGroup1234", ) _django_user = DjangoUserFactory( group=PostSave(add_to_group, name="1234JestGroup") ) self.assertEqual( list(_django_user.groups)[0].name, # type: ignore "1234JestGroup", ) # ********************************** # ****** TortoiseModelFactory ****** # ********************************** class TortoiseQuerySet(list): """Mimicking Tortoise QuerySet class.""" return_instance_on_query_first: bool = False def __init__( self, instance: Union["TortoiseArticle", "TortoiseUser"], ) -> None: super().__init__() self.instance = instance async def first( self, ) -> Optional[Union["TortoiseArticle", "TortoiseUser"]]: if not self.return_instance_on_query_first: return None return self.instance @dataclass(frozen=True) class TortoiseGroup: id: int name: str @classmethod def filter(cls, *args, **kwargs) -> "TortoiseQuerySet": return TortoiseQuerySet(instance=fill_dataclass(cls)) async def save(self, *args, **kwargs): """Mimicking Django's Mode save method.""" @dataclass class TortoiseUser: """User model.""" id: int username: str first_name: str last_name: str email: str date_joined: datetime = field(default_factory=datetime.utcnow) last_login: Optional[datetime] = None password: Optional[str] = None is_superuser: bool = False is_staff: bool = False is_active: bool = True groups: Set[TortoiseGroup] = field(default_factory=set) def set_password(self, password: str) -> None: self.password = xor_transform(password) @classmethod def filter(cls, *args, **kwargs) -> "TortoiseQuerySet": return TortoiseQuerySet(instance=fill_dataclass(cls)) async def save(self, *args, **kwargs): """Mimicking Django's Mode save method.""" self.save_called = True # noqa @dataclass class TortoiseArticle: id: int title: str slug: str content: str headline: str category: str pages: int auto_minutes_to_read: int author: TortoiseUser image: Optional[str] = ( None # Use str to represent the image path or URL ) pub_date: date = field(default_factory=date.today) safe_for_work: bool = False minutes_to_read: int = 5 @classmethod def filter(cls, *args, **kwargs) -> "TortoiseQuerySet": return TortoiseQuerySet(instance=fill_dataclass(cls)) async def save(self, *args, **kwargs): """Mimicking Django's Mode save method.""" self.save_called = True # noqa def add_to_tortoise_group(user: Any, name: str) -> None: group = TortoiseGroupFactory(name=name) user.groups.add(group) class TortoiseGroupFactory(TortoiseModelFactory): id = FACTORY.pyint() # type: ignore name = FACTORY.word() # type: ignore class Meta: model = TortoiseGroup get_or_create = ("name",) class TortoiseUserFactory(TortoiseModelFactory): id = FACTORY.pyint() # type: ignore username = FACTORY.username() # type: ignore first_name = FACTORY.first_name() # type: ignore last_name = FACTORY.last_name() # type: ignore email = FACTORY.email() # type: ignore last_login = FACTORY.date_time() # type: ignore is_superuser = False is_staff = False is_active = FACTORY.pybool() # type: ignore date_joined = FACTORY.date_time() # type: ignore password = PreSave(set_password, password="tost1234") group = PostSave(add_to_tortoise_group, name="TostGroup1234") class Meta: model = TortoiseUser get_or_create = ("username",) @trait def is_admin_user(self, instance: TortoiseUser) -> None: instance.is_superuser = True instance.is_staff = True instance.is_active = True @pre_save def _pre_save_method(self, instance): instance._pre_save_called = True @post_save def _post_save_method(self, instance): instance._post_save_called = True class TortoiseArticleFactory(TortoiseModelFactory): id = FACTORY.pyint() # type: ignore title = FACTORY.sentence() # type: ignore slug = FACTORY.slug() # type: ignore content = FACTORY.text() # type: ignore headline = LazyAttribute(lambda o: o.content[:25]) category = LazyFunction(partial(random.choice, categories)) pages = FACTORY.pyint(min_value=1, max_value=100) # type: ignore auto_minutes_to_read = PreInit(set_auto_minutes_to_read) image = FACTORY.png_file(storage=storage) # type: ignore pub_date = FACTORY.date() # type: ignore safe_for_work = FACTORY.pybool() # type: ignore minutes_to_read = FACTORY.pyint( # type: ignore min_value=1, max_value=10, ) author = SubFactory(TortoiseUserFactory) class Meta: model = TortoiseArticle @pre_save def _pre_save_method(self, instance): instance._pre_save_called = True @post_save def _post_save_method(self, instance): instance._post_save_called = True with self.subTest("TortoiseModelFactory"): _tortoise_article = TortoiseArticleFactory(author__username="admin") # Testing SubFactory self.assertIsInstance(_tortoise_article.author, TortoiseUser) self.assertIsInstance( _tortoise_article.author.id, # type: ignore int, ) self.assertIsInstance( _tortoise_article.author.is_staff, # type: ignore bool, ) self.assertIsInstance( _tortoise_article.author.date_joined, # type: ignore datetime, ) # Since we're mimicking Tortoise's behaviour, the following line # would fail on test, however would pass when testing against # real Tortoise model (as done in the examples). # self.assertEqual(tortoise_article.author.username, "admin") # Testing Factory self.assertIsInstance(_tortoise_article.id, int) self.assertIsInstance(_tortoise_article.slug, str) # Testing PreInit self.assertEqual( _tortoise_article.pages, _tortoise_article.auto_minutes_to_read, ) # Testing hooks self.assertTrue( hasattr(_tortoise_article, "_pre_save_called") and _tortoise_article._pre_save_called ) self.assertTrue( hasattr(_tortoise_article, "_post_save_called") and _tortoise_article._post_save_called ) # Testing batch creation _tortoise_articles = TortoiseArticleFactory.create_batch(5) self.assertEqual(len(_tortoise_articles), 5) self.assertIsInstance(_tortoise_articles[0], TortoiseArticle) # Testing get_or_create for Article model _tortoise_article = TortoiseArticleFactory(id=1) self.assertIsInstance(_tortoise_article, TortoiseArticle) # Testing traits _tortoise_admin_user = TortoiseUserFactory(is_admin_user=True) self.assertTrue( _tortoise_admin_user.is_staff and _tortoise_admin_user.is_superuser and _tortoise_admin_user.is_active ) # Testing PreSave _tortoise_user = TortoiseUserFactory() self.assertEqual( xor_transform(str(_tortoise_user.password)), "tost1234", ) _tortoise_user = TortoiseUserFactory( password=PreSave(set_password, password="1234tost") ) self.assertEqual( xor_transform(str(_tortoise_user.password)), "1234tost", ) # Testing PostSave self.assertEqual( list(_tortoise_user.groups)[0].name, # type: ignore "TostGroup1234", ) _tortoise_user = TortoiseUserFactory( group=PostSave(add_to_tortoise_group, name="1234TostGroup") ) self.assertEqual( list(_tortoise_user.groups)[0].name, # type: ignore "1234TostGroup", ) # ********************************** # ** Repeat for another condition ** TortoiseQuerySet.return_instance_on_query_first = True _tortoise_article = TortoiseArticleFactory(author__username="admin") _tortoise_user = TortoiseUserFactory(username="admin") # Testing SubFactory self.assertIsInstance(_tortoise_article.author, TortoiseUser) self.assertIsInstance(_tortoise_article, TortoiseArticle) self.assertIsInstance(_tortoise_user, TortoiseUser) self.assertIsInstance( _tortoise_article.author.id, # type: ignore int, ) self.assertIsInstance( _tortoise_article.author.is_staff, # type: ignore bool, ) self.assertIsInstance( _tortoise_article.author.date_joined, # type: ignore datetime, ) # Since we're mimicking Tortoise's behaviour, the following line # would fail on test, however would pass when testing against # real Tortoise model (as done in the examples). # self.assertEqual(_tortoise_article.author.username, "admin") # Testing Factory self.assertIsInstance(_tortoise_article.id, int) self.assertIsInstance(_tortoise_article.slug, str) self.assertIsInstance(_tortoise_user.id, int) self.assertIsInstance(_tortoise_user.username, str) # Testing hooks # self.assertFalse( # hasattr(_tortoise_article, "_pre_save_called") # ) # self.assertFalse( # hasattr(_tortoise_article, "_post_save_called") # ) # Testing batch creation _tortoise_articles = TortoiseArticleFactory.create_batch(5) self.assertEqual(len(_tortoise_articles), 5) self.assertIsInstance(_tortoise_articles[0], TortoiseArticle) # Testing traits _tortoise_admin_user = TortoiseUserFactory(is_admin_user=True) self.assertTrue( _tortoise_admin_user.is_staff and _tortoise_admin_user.is_superuser and _tortoise_admin_user.is_active ) # ********************************** # ***** SQLAlchemyModelFactory ***** # ********************************** class SQLAlchemySession: return_instance_on_query_first: bool = False def __init__(self) -> None: self.model = None self.instance = None def query(self, model) -> "SQLAlchemySession": self.model = model return self def filter_by(self, **kwargs) -> "SQLAlchemySession": return self def add(self, instance) -> None: self.instance = instance def commit(self) -> None: pass def first(self): if not self.return_instance_on_query_first: return None return fill_dataclass(self.model) # type: ignore def get_sqlalchemy_session(): return SQLAlchemySession() @dataclass(frozen=True) class SQLAlchemyGroup: id: int name: str @dataclass class SQLAlchemyUser: """User model.""" id: int username: str first_name: str last_name: str email: str date_joined: datetime = field(default_factory=datetime.utcnow) last_login: Optional[datetime] = None password: Optional[str] = None is_superuser: bool = False is_staff: bool = False is_active: bool = True groups: Set[SQLAlchemyGroup] = field(default_factory=set) def set_password(self, password: str) -> None: self.password = xor_transform(password) @dataclass class SQLAlchemyArticle: id: int title: str slug: str content: str headline: str category: str pages: int auto_minutes_to_read: int author: SQLAlchemyUser image: Optional[str] = ( None # Use str to represent the image path or URL ) pub_date: date = field(default_factory=date.today) safe_for_work: bool = False minutes_to_read: int = 5 def add_to_sqlalchemy_group(user: Any, name: str) -> None: group = SQLAlchemyGroupFactory(name=name) user.groups.add(group) class SQLAlchemyGroupFactory(SQLAlchemyModelFactory): id = FACTORY.pyint() # type: ignore name = FACTORY.word() # type: ignore class Meta: model = SQLAlchemyGroup get_or_create = ("name",) class MetaSQLAlchemy: get_session = get_sqlalchemy_session class SQLAlchemyUserFactory(SQLAlchemyModelFactory): id = FACTORY.pyint() # type: ignore username = FACTORY.username() # type: ignore first_name = FACTORY.first_name() # type: ignore last_name = FACTORY.last_name() # type: ignore email = FACTORY.email() # type: ignore last_login = FACTORY.date_time() # type: ignore is_superuser = False is_staff = False is_active = FACTORY.pybool() # type: ignore date_joined = FACTORY.date_time() # type: ignore password = PreSave(set_password, password="sest1234") group = PostSave(add_to_sqlalchemy_group, name="SestGroup1234") class Meta: model = SQLAlchemyUser get_or_create = ("username",) class MetaSQLAlchemy: get_session = get_sqlalchemy_session @trait def is_admin_user(self, instance: SQLAlchemyUser) -> None: instance.is_superuser = True instance.is_staff = True instance.is_active = True @pre_save def _pre_save_method(self, instance): instance._pre_save_called = True @post_save def _post_save_method(self, instance): instance._post_save_called = True class SQLAlchemyArticleFactory(SQLAlchemyModelFactory): id = FACTORY.pyint() # type: ignore title = FACTORY.sentence() # type: ignore slug = FACTORY.slug() # type: ignore content = FACTORY.text() # type: ignore headline = LazyAttribute(lambda o: o.content[:25]) category = LazyFunction(partial(random.choice, categories)) pages = FACTORY.pyint(min_value=1, max_value=100) # type: ignore auto_minutes_to_read = PreInit(set_auto_minutes_to_read) image = FACTORY.png_file(storage=storage) # type: ignore pub_date = FACTORY.date() # type: ignore safe_for_work = FACTORY.pybool() # type: ignore minutes_to_read = FACTORY.pyint( # type: ignore min_value=1, max_value=10, ) author = SubFactory(SQLAlchemyUserFactory) class Meta: model = SQLAlchemyArticle class MetaSQLAlchemy: get_session = get_sqlalchemy_session @pre_save def _pre_save_method(self, instance): instance._pre_save_called = True @post_save def _post_save_method(self, instance): instance._post_save_called = True with self.subTest("SQLAlchemyModelFactory"): _sqlalchemy_article = SQLAlchemyArticleFactory( author__username="admin" ) # Testing SubFactory self.assertIsInstance(_sqlalchemy_article.author, SQLAlchemyUser) self.assertIsInstance( _sqlalchemy_article.author.id, # type: ignore int, ) self.assertIsInstance( _sqlalchemy_article.author.is_staff, # type: ignore bool, ) self.assertIsInstance( _sqlalchemy_article.author.date_joined, # type: ignore datetime, ) # Since we're mimicking SQLAlchemy's behaviour, the following line # would fail on test, however would pass when testing against real # SQLAlchemy model (as done in the examples). # self.assertEqual(sqlalchemy_article.author.username, "admin") # Testing Factory self.assertIsInstance(_sqlalchemy_article.id, int) self.assertIsInstance(_sqlalchemy_article.slug, str) # Testing PreInit self.assertEqual( _sqlalchemy_article.pages, _sqlalchemy_article.auto_minutes_to_read, ) # Testing hooks self.assertTrue( hasattr(_sqlalchemy_article, "_pre_save_called") and _sqlalchemy_article._pre_save_called ) self.assertTrue( hasattr(_sqlalchemy_article, "_post_save_called") and _sqlalchemy_article._post_save_called ) # Testing batch creation _sqlalchemy_articles = SQLAlchemyArticleFactory.create_batch(5) self.assertEqual(len(_sqlalchemy_articles), 5) self.assertIsInstance(_sqlalchemy_articles[0], SQLAlchemyArticle) # Testing traits _sqlalchemy_admin_user = SQLAlchemyUserFactory(is_admin_user=True) self.assertTrue( _sqlalchemy_admin_user.is_staff and _sqlalchemy_admin_user.is_superuser and _sqlalchemy_admin_user.is_active ) # Testing PreSave _sqlalchemy_user = SQLAlchemyUserFactory() self.assertEqual( xor_transform(str(_sqlalchemy_user.password)), "sest1234" ) _sqlalchemy_user = SQLAlchemyUserFactory( password=PreSave(set_password, password="1234sest") ) self.assertEqual( xor_transform(str(_sqlalchemy_user.password)), "1234sest" ) # Testing PostSave self.assertEqual( list(_sqlalchemy_user.groups)[0].name, # type: ignore "SestGroup1234", ) _sqlalchemy_user = SQLAlchemyUserFactory( group=PostSave(add_to_sqlalchemy_group, name="1234SestGroup") ) self.assertEqual( list(_sqlalchemy_user.groups)[0].name, # type: ignore "1234SestGroup", ) # Repeat SQLAlchemy tests for another condition SQLAlchemySession.return_instance_on_query_first = True _sqlalchemy_article = SQLAlchemyArticleFactory( author__username="admin" ) _sqlalchemy_user = SQLAlchemyUserFactory(username="admin") # Testing SubFactory self.assertIsInstance(_sqlalchemy_article.author, SQLAlchemyUser) self.assertIsInstance(_sqlalchemy_article, SQLAlchemyArticle) self.assertIsInstance(_sqlalchemy_user, SQLAlchemyUser) self.assertIsInstance( _sqlalchemy_article.author.id, # type: ignore int, ) self.assertIsInstance( _sqlalchemy_article.author.is_staff, # type: ignore bool, ) self.assertIsInstance( _sqlalchemy_article.author.date_joined, # type: ignore datetime, ) # Since we're mimicking SQLAlchemy's behaviour, the following line # would fail on test, however would pass when testing against real # SQLAlchemy model (as done in the examples). # self.assertEqual(sqlalchemy_article.author.username, "admin") # Testing Factory self.assertIsInstance(_sqlalchemy_article.id, int) self.assertIsInstance(_sqlalchemy_article.slug, str) self.assertIsInstance(_sqlalchemy_user.id, int) self.assertIsInstance(_sqlalchemy_user.username, str) # Testing hooks self.assertFalse(hasattr(_sqlalchemy_article, "_pre_save_called")) self.assertFalse(hasattr(_sqlalchemy_article, "_post_save_called")) # Testing batch creation _sqlalchemy_articles = SQLAlchemyArticleFactory.create_batch(5) self.assertEqual(len(_sqlalchemy_articles), 5) self.assertIsInstance(_sqlalchemy_articles[0], SQLAlchemyArticle)
[docs] def test_registry_integration(self) -> None: """Test `add`.""" # Create a TXT file. txt_file_1 = FAKER.txt_file() with self.subTest("Check if `add` works"): # Check if `add` works (the file is in the registry) self.assertIn(txt_file_1, FILE_REGISTRY._registry) with self.subTest("Check if `search` works"): # Check if `search` works res = FILE_REGISTRY.search(str(txt_file_1)) self.assertIsNotNone(res) self.assertEqual(res, txt_file_1) with self.subTest("Check if `remove` by `StringValue` works"): # Check if `remove` by `StringValue`. FILE_REGISTRY.remove(txt_file_1) self.assertNotIn(txt_file_1, FILE_REGISTRY._registry) with self.subTest("Check if `remove` by `str` works"): # Create another TXT file and check if `remove` by `str` works. txt_file_2 = FAKER.txt_file() self.assertIn(txt_file_2, FILE_REGISTRY._registry) FILE_REGISTRY.remove(str(txt_file_2)) self.assertNotIn(txt_file_2, FILE_REGISTRY._registry) with self.subTest("Check if `clean_up` works"): # Check if `clean_up` works txt_file_3 = FAKER.txt_file() txt_file_4 = FAKER.txt_file() txt_file_5 = FAKER.txt_file() self.assertIn(txt_file_3, FILE_REGISTRY._registry) self.assertIn(txt_file_4, FILE_REGISTRY._registry) self.assertIn(txt_file_5, FILE_REGISTRY._registry) FILE_REGISTRY.clean_up() self.assertNotIn(txt_file_3, FILE_REGISTRY._registry) self.assertNotIn(txt_file_4, FILE_REGISTRY._registry) self.assertNotIn(txt_file_5, FILE_REGISTRY._registry)
[docs] def test_remove_by_string_not_found(self): res = FILE_REGISTRY.remove("i_do_not_exist.ext") self.assertFalse(res)
[docs] def test_remove_exceptions(self): txt_file = FAKER.txt_file() txt_file.data["storage"].unlink(txt_file) res = FILE_REGISTRY.remove(txt_file) self.assertFalse(res)
[docs] def test_clean_up_exceptions(self): # Redirect logger output to a string stream log_stream = io.StringIO() handler = logging.StreamHandler(log_stream) LOGGER.addHandler(handler) txt_file = FAKER.txt_file() txt_file.data["storage"].unlink(txt_file) # Clean up registry FILE_REGISTRY.clean_up() # Check the content of the logging output log_output = log_stream.getvalue() self.assertIn( f"Failed to unlink file {txt_file.data['filename']}", log_output, ) # Clean up by removing the handler LOGGER.removeHandler(handler)
[docs] class TestSlugify(unittest.TestCase):
[docs] def test_slugify_simple(self): """Test slugify with a simple alphanumeric string.""" self.assertEqual(slugify("HelloWorld"), "helloworld")
[docs] def test_slugify_spaces(self): """Test slugify with spaces.""" self.assertEqual(slugify("Hello World"), "helloworld")
[docs] def test_slugify_special_characters(self): """Test slugify with special characters.""" self.assertEqual( slugify("Hello!@#$%^&*()_+World"), "helloworld", )
[docs] def test_slugify_numbers(self): """Test slugify with numbers in the string.""" self.assertEqual(slugify("H3llo W0rld123"), "h3llow0rld123")
[docs] def test_slugify_empty_string(self): """Test slugify with an empty string.""" self.assertEqual(slugify(""), "")
[docs] def test_slugify_non_english_characters(self): """Test slugify with non-English characters.""" self.assertEqual(slugify("Ողջույն, աշխարհ։"), "")
[docs] def test_slugify_mixed_characters(self): """Test slugify with a mix of alphanumeric and special characters.""" self.assertEqual(slugify("1234!@#$abcXYZ"), "1234abcxyz")
[docs] class TestLazyStringTemplate(unittest.TestCase):
[docs] def setUp(self): # Create a mock faker object with some methods self.mock_faker = MagicMock() self.faker = FAKER self.mock_faker.name.return_value = "John Doe" self.mock_faker.sentence.return_value = "This is a test sentence." self.mock_faker.date.return_value = "2023-01-01" self.mock_faker.custom_method.return_value = "Custom value"
[docs] def test_simple_placeholder_replacement(self): template = "Hello, {name()}!" string_template = LazyStringTemplate(template, faker=self.mock_faker) expected = "Hello, John Doe!" result = string_template.render() self.assertEqual(result, expected) self.mock_faker.name.assert_called_once()
[docs] def test_placeholder_with_arguments(self): template = "Sentence: {sentence(nb_words=5)}" string_template = LazyStringTemplate(template, faker=self.mock_faker) expected = "Sentence: This is a test sentence." result = string_template.render() self.assertEqual(result, expected) self.mock_faker.sentence.assert_called_once_with(nb_words=5)
[docs] def test_multiple_placeholders(self): template = ( "Name: {name()}\n" "Sentence: {sentence(nb_words=10)}\n" "Date: {date(start_date='-7d')}\n" "Custom: {custom_method(param='value')}" ) string_template = LazyStringTemplate(template, faker=self.mock_faker) expected = ( "Name: John Doe\n" "Sentence: This is a test sentence.\n" "Date: 2023-01-01\n" "Custom: Custom value" ) result = string_template.render() self.assertEqual(result, expected) self.mock_faker.name.assert_called_once() self.mock_faker.sentence.assert_called_once_with(nb_words=10) self.mock_faker.date.assert_called_once_with(start_date="-7d") self.mock_faker.custom_method.assert_called_once_with(param="value")
[docs] def test_wrapping_functionality(self): template = "This is a long sentence that needs to be wrapped." string_template = LazyStringTemplate( template, wrap_chars_after=10, faker=self.mock_faker ) expected = "This is a\nlong\nsentence\nthat needs\nto be\nwrapped." result = string_template.render() self.assertEqual(result, expected)
[docs] def test_no_wrapping_when_not_specified(self): template = "This is a long sentence that does not need to be wrapped." string_template = LazyStringTemplate(template, faker=self.mock_faker) expected = "This is a long sentence that does not need to be wrapped." result = string_template.render() self.assertEqual(result, expected)
[docs] def test_missing_method_raises_attribute_error(self): template = "Hello, {nonexistent_method()}!" string_template = LazyStringTemplate(template, faker=self.faker) with self.assertRaises(AttributeError) as context: string_template.render() self.assertIn( "Method 'nonexistent_method' not found", str(context.exception) )
[docs] def test_argument_parsing_error_raises_value_error(self): template = "Date: {date(start_date='-7d' missing_comma)}" string_template = LazyStringTemplate(template, faker=self.mock_faker) with self.assertRaises(ValueError) as context: string_template.render() self.assertIn( "Error parsing arguments for 'date'", str(context.exception) ) self.mock_faker.date.assert_not_called()
[docs] def test_method_call_error_raises_value_error(self): # Configure the mock to raise an exception when called self.mock_faker.name.side_effect = Exception("Method error") template = "Hello, {name()}!" string_template = LazyStringTemplate(template, faker=self.mock_faker) with self.assertRaises(ValueError) as context: string_template.render() self.assertIn("Error calling method 'name'", str(context.exception)) self.mock_faker.name.assert_called_once()
[docs] def test_no_placeholders(self): template = "This string has no placeholders." string_template = LazyStringTemplate(template, faker=self.mock_faker) expected = "This string has no placeholders." result = string_template.render() self.assertEqual(result, expected) # No faker methods should be called self.mock_faker.assert_not_called()
[docs] def test_str_method(self): template = "Hello, {name()}!" string_template = LazyStringTemplate(template, faker=self.mock_faker) expected = "Hello, John Doe!" result = str(string_template) self.assertEqual(result, expected)
[docs] def test_repr_method(self): template = "Hello, {name()}!" string_template = LazyStringTemplate(template, faker=self.mock_faker) expected = "Hello, John Doe!" result = repr(string_template) self.assertEqual(result, expected)
[docs] def test_complex_argument_parsing(self): # Test with multiple arguments and different types template = "Custom: {custom_method(param1='value1', param2=123)}" string_template = LazyStringTemplate(template, faker=self.mock_faker) expected = "Custom: Custom value" result = string_template.render() self.assertEqual(result, expected) self.mock_faker.custom_method.assert_called_once_with( param1="value1", param2=123 )
[docs] def test_nested_placeholders(self): # Although nested placeholders are not supported, ensure they are # handled gracefully. template = "Nested: {{name()}}" string_template = LazyStringTemplate(template, faker=self.mock_faker) # The regex should match {name()}, so {{name()}} becomes {John Doe} expected = "Nested: {John Doe}" result = string_template.render() self.assertEqual(result, expected) self.mock_faker.name.assert_called_once()
[docs] def test_escape_braces(self): # Test that escaped braces are handled correctly template = "Escaped braces: \\{name()\\}" string_template = LazyStringTemplate(template, faker=self.faker) expected = "Escaped braces: \\{name()\\}" result = string_template.render() self.assertEqual(result, expected) self.mock_faker.name.assert_not_called()
[docs] def test_placeholder_with_no_parentheses(self): # Test that placeholders without parentheses are handled (assuming # they require parentheses). template = "Hello, {name}!" string_template = LazyStringTemplate(template, faker=self.mock_faker) # The regex expects parentheses, so {name} should remain unchanged expected = "Hello, John Doe!" result = string_template.render() self.assertEqual(result, expected) self.mock_faker.name.assert_called()
[docs] def test_placeholder_with_empty_arguments(self): # Test placeholders with empty parentheses template = "Hello, {name()} and {sentence()}!" string_template = LazyStringTemplate(template, faker=self.mock_faker) expected = "Hello, John Doe and This is a test sentence.!" result = string_template.render() self.assertEqual(result, expected) self.mock_faker.name.assert_called_once() self.mock_faker.sentence.assert_called_once()
[docs] def test_placeholder_with_spaces_in_arguments(self): # Test placeholders where arguments contain spaces template = ( "Date: {date(start_date = '2020-01-01', end_date = '2020-12-31')}" ) string_template = LazyStringTemplate(template, faker=self.mock_faker) expected = "Date: 2023-01-01" result = string_template.render() self.assertEqual(result, expected) self.mock_faker.date.assert_called_once_with( start_date="2020-01-01", end_date="2020-12-31" )
[docs] def test_placeholder_with_numeric_method_name(self): # Assuming method names are purely alphabetic, but testing with # numeric characters. template = "Numeric method: {method123()}!" string_template = LazyStringTemplate(template, faker=self.mock_faker) self.mock_faker.method123.return_value = "Numeric method" expected = "Numeric method: Numeric method!" result = string_template.render() self.assertEqual(result, expected) self.mock_faker.method123.assert_called_once()
[docs] def test_placeholder_with_underscores_in_method_name(self): # Test method names with underscores template = "Underscore method: {custom_method()}!" string_template = LazyStringTemplate(template, faker=self.mock_faker) expected = "Underscore method: Custom value!" result = string_template.render() self.assertEqual(result, expected) self.mock_faker.custom_method.assert_called_once()
[docs] def test_placeholder_with_boolean_arguments(self): # Test placeholders with boolean arguments self.mock_faker.boolean_method.return_value = "Boolean result" template = "Boolean: {boolean_method(flag=True)}" string_template = LazyStringTemplate(template, faker=self.mock_faker) expected = "Boolean: Boolean result" result = string_template.render() self.assertEqual(result, expected) self.mock_faker.boolean_method.assert_called_once_with(flag=True)
[docs] class TestStringTemplate(unittest.TestCase):
[docs] def setUp(self): # Create a mock faker object with some methods self.mock_faker = MagicMock() self.faker = FAKER self.mock_faker.name.return_value = "John Doe" self.mock_faker.sentence.return_value = "This is a test sentence." self.mock_faker.date.return_value = "2023-01-01" self.mock_faker.custom_method.return_value = "Custom value" self.mock_faker.method123.return_value = "Numeric method" self.mock_faker.boolean_method.return_value = "Boolean result"
[docs] def test_instance_is_str(self): template = "Hello, {name()}!" string_template = StringTemplate(template, faker=self.mock_faker) self.assertIsInstance(string_template, str) self.assertIsInstance(string_template, StringTemplate) self.assertEqual(string_template, "Hello, John Doe!")
[docs] def test_simple_placeholder_replacement(self): template = "Hello, {name()}!" string_template = StringTemplate(template, faker=self.mock_faker) expected = "Hello, John Doe!" self.assertEqual(string_template, expected) self.mock_faker.name.assert_called_once()
[docs] def test_placeholder_with_arguments(self): template = "Sentence: {sentence(nb_words=5)}" string_template = StringTemplate(template, faker=self.mock_faker) expected = "Sentence: This is a test sentence." self.assertEqual(string_template, expected) self.mock_faker.sentence.assert_called_once_with(nb_words=5)
[docs] def test_multiple_placeholders(self): template = ( "Name: {name()}\n" "Sentence: {sentence(nb_words=10)}\n" "Date: {date(start_date='-7d')}\n" "Custom: {custom_method(param='value')}" ) string_template = StringTemplate(template, faker=self.mock_faker) expected = ( "Name: John Doe\n" "Sentence: This is a test sentence.\n" "Date: 2023-01-01\n" "Custom: Custom value" ) self.assertEqual(string_template, expected) self.mock_faker.name.assert_called_once() self.mock_faker.sentence.assert_called_once_with(nb_words=10) self.mock_faker.date.assert_called_once_with(start_date="-7d") self.mock_faker.custom_method.assert_called_once_with(param="value")
[docs] def test_wrapping_functionality(self): template = "This is a long sentence that needs to be wrapped." string_template = StringTemplate( template, wrap_chars_after=10, faker=self.mock_faker, ) expected = "This is a\nlong\nsentence\nthat needs\nto be\nwrapped." self.assertEqual(string_template, expected)
[docs] def test_no_wrapping_when_not_specified(self): template = "This is a long sentence that does not need to be wrapped." string_template = StringTemplate(template, faker=self.mock_faker) expected = "This is a long sentence that does not need to be wrapped." self.assertEqual(string_template, expected) # No faker methods should be called self.mock_faker.assert_not_called()
[docs] def test_missing_method_raises_attribute_error(self): template = "Hello, {nonexistent_method()}!" with self.assertRaises(AttributeError) as context: StringTemplate(template, faker=self.faker) self.assertIn( "Method 'nonexistent_method' not found", str(context.exception), )
[docs] def test_argument_parsing_error_raises_value_error(self): template = "Date: {date(start_date='-7d' missing_comma)}" with self.assertRaises(ValueError) as context: StringTemplate(template, faker=self.mock_faker) self.assertIn( "Error parsing arguments for 'date'", str(context.exception), ) self.mock_faker.date.assert_not_called()
[docs] def test_method_call_error_raises_value_error(self): # Configure the mock to raise an exception when called self.mock_faker.name.side_effect = Exception("Method error") template = "Hello, {name()}!" with self.assertRaises(ValueError) as context: StringTemplate(template, faker=self.mock_faker) self.assertIn("Error calling method 'name'", str(context.exception)) self.mock_faker.name.assert_called_once()
[docs] def test_no_placeholders(self): template = "This string has no placeholders." string_template = StringTemplate(template, faker=self.mock_faker) expected = "This string has no placeholders." self.assertEqual(string_template, expected) # No faker methods should be called self.mock_faker.assert_not_called()
[docs] def test_str_method(self): template = "Hello, {name()}!" string_template = StringTemplate(template, faker=self.mock_faker) expected = "Hello, John Doe!" result = str(string_template) self.assertEqual(result, expected)
[docs] def test_repr_method(self): template = "Hello, {name()}!" string_template = StringTemplate(template, faker=self.mock_faker) expected = "'Hello, John Doe!'" result = repr(string_template) self.assertEqual(result, expected)
[docs] def test_complex_argument_parsing(self): # Test with multiple arguments and different types template = "Custom: {custom_method(param1='value1', param2=123)}" string_template = StringTemplate(template, faker=self.mock_faker) expected = "Custom: Custom value" self.assertEqual(string_template, expected) self.mock_faker.custom_method.assert_called_once_with( param1="value1", param2=123 )
[docs] def test_nested_placeholders(self): # Although nested placeholders are not supported, ensure they are # handled gracefully. template = "Nested: {{name()}}" string_template = StringTemplate(template, faker=self.mock_faker) # The regex should match {name()}, so {{name()}} becomes {John Doe} expected = "Nested: {John Doe}" self.assertEqual(string_template, expected) self.mock_faker.name.assert_called_once()
[docs] def test_escape_braces(self): # Test that escaped braces are handled correctly template = "Escaped braces: \\{name()\\}" string_template = StringTemplate(template, faker=self.mock_faker) expected = "Escaped braces: \\{name()\\}" self.assertEqual(string_template, expected) self.mock_faker.name.assert_not_called()
[docs] def test_placeholder_with_no_parentheses(self): # Test that placeholders without parentheses are handled (assuming # they require parentheses). template = "Hello, {name}!" string_template = StringTemplate(template, faker=self.mock_faker) # The regex expects parentheses, so {name} should remain unchanged expected = "Hello, John Doe!" self.assertEqual(string_template, expected) self.mock_faker.assert_not_called()
[docs] def test_placeholder_with_empty_arguments(self): # Test placeholders with empty parentheses template = "Hello, {name()} and {sentence()}!" string_template = StringTemplate(template, faker=self.mock_faker) expected = "Hello, John Doe and This is a test sentence.!" self.assertEqual(string_template, expected) self.mock_faker.name.assert_called_once() self.mock_faker.sentence.assert_called_once()
[docs] def test_placeholder_with_spaces_in_arguments(self): # Test placeholders where arguments contain spaces template = ( "Date: {date(start_date = '2020-01-01', end_date = '2020-12-31')}" ) string_template = StringTemplate(template, faker=self.mock_faker) expected = "Date: 2023-01-01" self.assertEqual(string_template, expected) self.mock_faker.date.assert_called_once_with( start_date="2020-01-01", end_date="2020-12-31" )
[docs] def test_placeholder_with_numeric_method_name(self): # Assuming method names are purely alphabetic, but testing with # numeric characters. template = "Numeric method: {method123()}!" string_template = StringTemplate(template, faker=self.mock_faker) expected = "Numeric method: Numeric method!" self.assertEqual(string_template, expected) self.mock_faker.method123.assert_called_once()
[docs] def test_placeholder_with_underscores_in_method_name(self): # Test method names with underscores template = "Underscore method: {custom_method()}!" string_template = StringTemplate(template, faker=self.mock_faker) expected = "Underscore method: Custom value!" self.assertEqual(string_template, expected) self.mock_faker.custom_method.assert_called_once()
[docs] def test_placeholder_with_boolean_arguments(self): # Test placeholders with boolean arguments template = "Boolean: {boolean_method(flag=True)}" string_template = StringTemplate(template, faker=self.mock_faker) expected = "Boolean: Boolean result" self.assertEqual(string_template, expected) self.mock_faker.boolean_method.assert_called_once_with(flag=True)
[docs] def test_join_with_string_template(self): # Test that StringTemplate can be used directly with join templates = [ StringTemplate("Hello, {name()}!", faker=self.mock_faker), StringTemplate("Welcome, {name()}!", faker=self.mock_faker), StringTemplate("Goodbye, {name()}!", faker=self.mock_faker), ] expected = ( "Hello, John Doe!\n---\nWelcome, John Doe!\n---\nGoodbye, John Doe!" ) joined_string = "\n---\n".join(templates) self.assertEqual(joined_string, expected) self.mock_faker.name.assert_called_with() self.assertEqual(self.mock_faker.name.call_count, 3)
[docs] def test_large_number_of_placeholders(self): # Test performance and correctness with a large number of placeholders template = "User {name()} has email {email()}." * 1000 self.mock_faker.email.return_value = "john.doe@example.com" string_template = StringTemplate(template, faker=self.mock_faker) expected = "User John Doe has email john.doe@example.com." * 1000 self.assertEqual(string_template, expected) self.assertEqual(self.mock_faker.name.call_count, 1000) self.assertEqual(self.mock_faker.email.call_count, 1000)
[docs] def test_join_with_no_templates(self): # Test joining an empty list templates = [] expected = "" joined_string = "\n---\n".join(templates) self.assertEqual(joined_string, expected)
[docs] def test_join_with_single_template(self): # Test joining a single template template = "Hello, {name()}!" string_template = StringTemplate(template, faker=self.mock_faker) expected = "Hello, John Doe!" joined_string = "\n---\n".join([string_template]) self.assertEqual(joined_string, expected) self.mock_faker.name.assert_called_once()