pax_global_header00006660000000000000000000000064145115767000014520gustar00rootroot0000000000000052 comment=231837460e6eb5522884117dc38311a374b36012 rstr-3.2.2/000077500000000000000000000000001451157670000125165ustar00rootroot00000000000000rstr-3.2.2/.circleci/000077500000000000000000000000001451157670000143515ustar00rootroot00000000000000rstr-3.2.2/.circleci/config.yml000066400000000000000000000003711451157670000163420ustar00rootroot00000000000000version: 2.1 jobs: test: docker: - image: fpob/tox steps: - checkout - run: name: Run tests command: tox workflows: test: jobs: - test rstr-3.2.2/.gitignore000066400000000000000000000001031451157670000145000ustar00rootroot00000000000000syntax: glob *.pyc dist/* *.egg-info .tox/* .env/* .credentials/* rstr-3.2.2/AUTHORS000066400000000000000000000002301451157670000135610ustar00rootroot00000000000000Brendan McCollam Andy Hayden MJ Schultz Tatiana Krikun 의성 정 Goya Tomohiro Xiaoqin Zhu Stéphane Blondon Pascal Corpet Mark Mayo Aurélien Gâteau rstr-3.2.2/LICENSE.txt000066400000000000000000000031441451157670000143430ustar00rootroot00000000000000Copyright (c) 2011, Leapfrog Direct Response, LLC All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Leapfrog Direct Response, LLC, including its subsidiaries and affiliates nor the names of its contributors, may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL LEAPFROG DIRECT RESPONSE, LLC, INCLUDING ITS SUBSIDIARIES AND AFFILIATES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. rstr-3.2.2/MANIFEST.in000066400000000000000000000000521451157670000142510ustar00rootroot00000000000000include LICENSE.txt include rstr/py.typed rstr-3.2.2/README.rst000066400000000000000000000140301451157670000142030ustar00rootroot00000000000000=============================== rstr = Random Strings in Python =============================== .. image:: https://circleci.com/gh/leapfrogonline/rstr.svg?style=svg :target: https://circleci.com/gh/leapfrogonline/rstr rstr is a helper module for easily generating random strings of various types. It could be useful for fuzz testing, generating dummy data, or other applications. It has no dependencies outside the standard library. A Word of Caution ----------------- By default, rstr uses the Python ``random`` module to generate pseudorandom text. This module is based on the Mersenne Twister and is *not* cryptographically secure. **If you wish to use rstr for password-generation or other cryptographic applications, you must create an instance that uses** SystemRandom_. For example: :: >> from rstr import Rstr >> from random import SystemRandom >> rs = Rstr(SystemRandom()) Use --- The basic method of rstr is ``rstr()``. At a minimum, it requires one argument, an alphabet of characters from which to create a string. :: >>> import rstr >>> rstr.rstr('ABC') 'AACAACCB' By default, it will return a string between 1 and 10 characters in length. You may specify an exact length by including it as a second argument: :: >>> rstr.rstr('ABC', 4) 'ACBC' You can also generate a range of lengths by adding two arguments. In the following case, rstr will return a string with a randomly selected length between 5 and 10 characters. :: >>> rstr.rstr('ABC', 5, 10) 'CBCCCABAA' It's also possible to include particular characters in your string. This is useful when testing a validator to make sure that certain characters are rejected. Characters listed in the 'include' argument will *always* be present somewhere in the resulting string. :: >>> rstr.rstr('ABC', include='&') 'CA&A' Conversely, you can exclude particular characters from the generated string. This is helpful when starting with a pre-defined population of characters. :: >>> import string >>> rstr.rstr(string.digits, exclude='5') '8661442' Note that any of the arguments that accept strings can also accept lists or tuples of strings: :: >>> rstr.rstr(['A', 'B', 'C'], include = ['@'], exclude=('C',)) 'BAAABBA@BAA' Other methods ------------- The other methods provided by rstr, besides ``rstr()`` and ``xeger()``, are convenience methods that can be called without arguments, and provide a pre-defined alphabet. They accept the same arguments as ``rstr()`` for purposes of specifying lengths and including or excluding particular characters. letters() The characters provided by string.letters in the standard library. uppercase() The characters provided by string.uppercase in the standard library. lowercase() The characters provided by string.lowercase in the standard library. printable() The characters provided by string.printable in the standard library. punctuation() The characters provided by string.punctuation in the standard library. nonwhitespace() The characters provided by string.printable in the standard library, except for those representing whitespace: tab, space, etc. digits() The characters provided by string.digits in the standard library. nondigits() The characters provided by the concatenation of string.letters and string.punctuation in the standard library. nonletters() The characters provided by the concatenation of string.digits and string.punctuation in the standard library. normal() Characters commonly accepted in text input, equivalent to string.digits + string.letters + ' ' (the space character). unambiguous() The characters provided by the concatenation of string.digits and string.letters except characters which are similar: 1, l and I, etc. postalsafe() Characters that are safe for use in postal addresses in the United States: upper- and lower-case letters, digits, spaces, and the punctuation marks period, hash (#), hyphen, and forward-slash. urlsafe() Characters safe (unreserved) for use in URLs: letters, digits, hyphen, period, underscore, and tilde. domainsafe() Characters that are allowed for use in hostnames, and consequently, in internet domains: letters, digits, and the hyphen. Xeger ----- Inspired by the Java library of the same name, the ``xeger()`` method allows users to create a random string from a regular expression. For example to generate a postal code that fits the Canadian format: >>> import rstr >>> rstr.xeger(r'[A-Z]\d[A-Z] \d[A-Z]\d') u'R6M 1W5' xeger works fine with most simple regular expressions, but it doesn't support all Python regular expression features. Custom Alphabets ---------------- If you have custom alphabets of characters that you would like to use with a method shortcut, you can specify them by keyword when instantiating an Rstr object: >>> from rstr import Rstr >>> rs = Rstr(vowels='AEIOU') >>> rs.vowels() 'AEEUU' You can also add an alphabet to an existing instance with the add_alphabet() method: >>> rs.add_alphabet('odds', '13579') >>> rs.odds() '339599519' Examples -------- You can combine rstr with Python's built-in string formatting to produce strings that fit a variety of templates. An email address: :: '{0}@{1}.{2}'.format(rstr.nonwhitespace(exclude='@'), rstr.domainsafe(), rstr.letters(3)) A URL: :: 'http://{0}.{1}/{2}/?{3}'.format(rstr.domainsafe(), rstr.letters(3), rstr.urlsafe(), rstr.urlsafe()) A postal address: :: """{0} {1} {2} {3} {4}, {5} {6} """.format(rstr.letters(4, 8).title(), rstr.letters(4, 8).title(), rstr.digits(3, 5), rstr.letters(4, 10).title(), rstr.letters(4, 15).title(), rstr.uppercase(2), rstr.digits(5), ) .. _SystemRandom: https://docs.python.org/3/library/random.html#random.SystemRandom rstr-3.2.2/RELEASE_NOTES000066400000000000000000000012551451157670000144740ustar00rootroot00000000000000# Release Notes ## Unreleased ## 3.2.2 - Removes type stubs for `random` to fix type-checking failures under Python 3.11. ## 3.2.1 - Fixes import failure under Python 3.11 ## 3.2.0 - PEP 561 compatible typing - Fix a bug that made xeger() fail under pypy3.8 ## 3.1.0 - Fix a bug where setting end_range but not start_range would fail. - Statically type the library, using PEP 484 annotations. - Drops test coverage for Python 3.6 (EoL December 2021). - Adds test coverage for Python 3.10. ## 3.0.0 - Drops support for Python 2.7 and 3.5 - Fixes a bug where `include` could make the generated string too long. - Add new `rstr.unambiguous()` method that omits homographs. rstr-3.2.2/pyproject.toml000066400000000000000000000023301451157670000154300ustar00rootroot00000000000000[build-system] requires = ["setuptools>=61.2", "wheel", "setuptools_scm[toml]>=3.4.3"] build-backend = "setuptools.build_meta" [project] name = "rstr" authors = [{name = "Leapfrog Direct Response LLC", email = "oss@leapfrogdevelopment.com"}] maintainers = [{name = "Brendan McCollam", email = "rstr@mccoll.am"}] description = "Generate random strings in Python" readme = "README.rst" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Topic :: Software Development :: Testing", ] keywords = [ "random string", "reverse regex", "reverse regular expression", "testing", "fuzz testing", ] urls = {Homepage = "https://github.com/leapfrogonline/rstr"} requires-python = ">=3.7" dynamic = ["version"] [tool.setuptools] package-dir = {} include-package-data = true [tool.setuptools.packages.find] include = ["rstr", "rstr.*"] [tool.setuptools_scm] rstr-3.2.2/rstr/000077500000000000000000000000001451157670000135105ustar00rootroot00000000000000rstr-3.2.2/rstr/__init__.py000066400000000000000000000017421451157670000156250ustar00rootroot00000000000000from rstr.xeger import Xeger from rstr.rstr_base import SameCharacterError Rstr = Xeger _default_instance = Rstr() rstr = _default_instance.rstr xeger = _default_instance.xeger # This allows convenience methods from rstr to be accessed at the package # level, without requiring the user to instantiate an Rstr() object. printable = _default_instance.printable letters = _default_instance.letters uppercase = _default_instance.uppercase lowercase = _default_instance.lowercase digits = _default_instance.digits punctuation = _default_instance.punctuation nondigits = _default_instance.nondigits nonletters = _default_instance.nonletters whitespace = _default_instance.whitespace nonwhitespace = _default_instance.nonwhitespace normal = _default_instance.normal word = _default_instance.word nonword = _default_instance.nonword unambiguous = _default_instance.unambiguous postalsafe = _default_instance.postalsafe urlsafe = _default_instance.urlsafe domainsafe = _default_instance.domainsafe rstr-3.2.2/rstr/py.typed000066400000000000000000000000001451157670000151750ustar00rootroot00000000000000rstr-3.2.2/rstr/rstr_base.py000066400000000000000000000163651451157670000160610ustar00rootroot00000000000000# Copyright (c) 2011, Leapfrog Direct Response, LLC # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the Leapfrog Direct Response, LLC, including # its subsidiaries and affiliates nor the names of its # contributors, may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL LEAPFROG DIRECT # RESPONSE, LLC, INCLUDING ITS SUBSIDIARIES AND AFFILIATES, BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import itertools import string from functools import partial import typing from typing import Iterable, List, Mapping, Optional, Sequence, TypeVar _T = TypeVar('_T') if typing.TYPE_CHECKING: from random import Random from typing import Protocol class _PartialRstrFunc(Protocol): def __call__( self, start_range: Optional[int] = ..., end_range: Optional[int] = ..., include: str = ..., exclude: str = ..., ) -> str: ... ALPHABETS: Mapping[str, str] = { 'printable': string.printable, 'letters': string.ascii_letters, 'uppercase': string.ascii_uppercase, 'lowercase': string.ascii_lowercase, 'digits': string.digits, 'punctuation': string.punctuation, 'nondigits': string.ascii_letters + string.punctuation, 'nonletters': string.digits + string.punctuation, 'whitespace': string.whitespace, 'nonwhitespace': string.printable.strip(), 'normal': string.ascii_letters + string.digits + ' ', 'word': string.ascii_letters + string.digits + '_', 'nonword': ''.join( set(string.printable).difference(string.ascii_letters + string.digits + '_') ), 'unambiguous': ''.join(set(string.ascii_letters + string.digits).difference('0O1lI')), 'postalsafe': string.ascii_letters + string.digits + ' .-#/', 'urlsafe': string.ascii_letters + string.digits + '-._~', 'domainsafe': string.ascii_letters + string.digits + '-', } class RstrBase(): '''Create random strings from a variety of alphabets. The alphabets for printable(), uppercase(), lowercase(), digits(), and punctuation() are equivalent to the constants by those same names in the standard library string module. nondigits() uses an alphabet of string.letters + string.punctuation nonletters() uses an alphabet of string.digits + string.punctuation nonwhitespace() uses an alphabet of string.printable.strip() normal() uses an alphabet of string.letters + string.digits + ' ' (the space character) postalsafe() is based on USPS Publication 28 - Postal Addressing Standards: http://pe.usps.com/text/pub28/pub28c2.html The characters allowed in postal addresses are letters and digits, periods, slashes, the pound sign, and the hyphen. urlsafe() uses an alphabet of unreserved characters safe for use in URLs. From section 2.3 of RFC 3986: "Characters that are allowed in a URI but do not have a reserved purpose are called unreserved. These include uppercase and lowercase letters, decimal digits, hyphen, period, underscore, and tilde. domainsafe() uses an alphabet of characters allowed in hostnames, and consequently, in internet domains: letters, digits, and the hyphen. ''' def __init__(self, _random: 'Random', **custom_alphabets: str) -> None: super().__init__() self._random = _random self._alphabets = dict(ALPHABETS) for alpha_name, alphabet in custom_alphabets.items(): self.add_alphabet(alpha_name, alphabet) def add_alphabet(self, alpha_name: str, characters: str) -> None: '''Add an additional alphabet to an Rstr instance and make it available via method calls. ''' self._alphabets[alpha_name] = characters def __getattr__(self, attr: str) -> '_PartialRstrFunc': if attr in self._alphabets: return partial(self.rstr, self._alphabets[attr]) message = f'Rstr instance has no attribute: {attr}' raise AttributeError(message) def sample_wr(self, population: Sequence[str], k: int) -> List[str]: '''Samples k random elements (with replacement) from a population''' return [self._random.choice(population) for i in itertools.repeat(None, k)] def rstr( self, alphabet: Iterable[str], start_range: Optional[int] = None, end_range: Optional[int] = None, include: Sequence[str] = '', exclude: Sequence[str] = '', ) -> str: '''Generate a random string containing elements from 'alphabet' By default, rstr() will return a string between 1 and 10 characters. You can specify a second argument to get an exact length of string. If you want a string in a range of lengths, specify the start and end of that range as the second and third arguments. If you want to make certain that particular characters appear in the generated string, specify them as "include". If you want to *prevent* certain characters from appearing, pass them as 'exclude'. ''' same_characters = set(include).intersection(exclude) if same_characters: message = "include and exclude parameters contain same character{plural} ({characters})".format( plural="s" if len(same_characters) > 1 else "", characters=", ".join(same_characters) ) raise SameCharacterError(message) popul = [char for char in list(alphabet) if char not in list(exclude)] if end_range is None: if start_range is None: start_range, end_range = (1, 10) else: k = start_range elif start_range is None: start_range = 1 if end_range: k = self._random.randint(start_range, end_range) # Make sure we don't generate too long a string # when adding 'include' to it: k = k - len(include) result = self.sample_wr(popul, k) + list(include) self._random.shuffle(result) return ''.join(result) class SameCharacterError(ValueError): pass rstr-3.2.2/rstr/xeger.py000066400000000000000000000101721451157670000151750ustar00rootroot00000000000000import random import string from itertools import chain import typing from typing import Any, Callable, Dict, Mapping, Pattern, Sequence, Union from rstr.rstr_base import RstrBase if typing.TYPE_CHECKING: from random import Random try: import re._parser as sre_parse # type: ignore[import-not-found] except ImportError: # Python < 3.11 import sre_parse # The * and + characters in a regular expression # match up to any number of repeats in theory, # (and actually 65535 repeats in python) but you # probably don't want that many repeats in your # generated strings. This sets an upper-bound on # repeats generated from + and * characters. STAR_PLUS_LIMIT = 100 class Xeger(RstrBase): '''Inspired by the Java library Xeger: http://code.google.com/p/xeger/ This class adds functionality to Rstr allowing users to generate a semi-random string from a regular expression.''' def __init__( self, _random: 'Random' = typing.cast('Random', random), **custom_alphabets: str, ) -> None: super().__init__(_random, **custom_alphabets) self._cache: Dict[str, str] = {} self._categories: Mapping[str, Callable[[], str]] = { 'category_digit': lambda: self._alphabets['digits'], 'category_not_digit': lambda: self._alphabets['nondigits'], 'category_space': lambda: self._alphabets['whitespace'], 'category_not_space': lambda: self._alphabets['nonwhitespace'], 'category_word': lambda: self._alphabets['word'], 'category_not_word': lambda: self._alphabets['nonword'], } self._cases: Mapping[str, Callable[..., Any]] = { 'literal': lambda x: chr(x), 'not_literal': lambda x: self._random.choice(string.printable.replace(chr(x), '')), 'at': lambda x: '', 'in': lambda x: self._handle_in(x), 'any': lambda x: self.printable(1, exclude='\n'), 'range': lambda x: [chr(i) for i in range(x[0], x[1] + 1)], 'category': lambda x: self._categories[x](), 'branch': lambda x: ''.join(self._handle_state(i) for i in self._random.choice(x[1])), 'subpattern': lambda x: self._handle_group(x), 'assert': lambda x: ''.join(self._handle_state(i) for i in x[1]), 'assert_not': lambda x: '', 'groupref': lambda x: self._cache[x], 'min_repeat': lambda x: self._handle_repeat(*x), 'max_repeat': lambda x: self._handle_repeat(*x), 'negate': lambda x: [False], } def xeger(self, string_or_regex: Union[str, Pattern[str]]) -> str: try: pattern = typing.cast(Pattern[str], string_or_regex).pattern except AttributeError: pattern = typing.cast(str, string_or_regex) parsed = sre_parse.parse(pattern) result = self._build_string(parsed) self._cache.clear() return result def _build_string(self, parsed: Any) -> str: newstr = [] for state in parsed: newstr.append(self._handle_state(state)) return ''.join(newstr) def _handle_state(self, state: Any) -> Any: opcode, value = state opcode = opcode.name.lower() if opcode == 'category': value = value.name.lower() return self._cases[opcode](value) def _handle_group(self, value: Sequence[Any]) -> str: result = ''.join(self._handle_state(i) for i in value[-1]) if value[0]: self._cache[value[0]] = result return result def _handle_in(self, value: Any) -> Any: candidates = list(chain(*(self._handle_state(i) for i in value))) if candidates[0] is False: candidates = list(set(string.printable).difference(candidates[1:])) return self._random.choice(candidates) def _handle_repeat(self, start_range: int, end_range: int, value: str) -> str: result = [] end_range = min((end_range, STAR_PLUS_LIMIT)) times = self._random.randint(start_range, end_range) for i in range(times): result.append(''.join(self._handle_state(i) for i in value)) return ''.join(result) rstr-3.2.2/tests/000077500000000000000000000000001451157670000136605ustar00rootroot00000000000000rstr-3.2.2/tests/__init__.py000066400000000000000000000000001451157670000157570ustar00rootroot00000000000000rstr-3.2.2/tests/test_package_level_access.py000066400000000000000000000006211451157670000213730ustar00rootroot00000000000000import unittest import re import rstr class TestPackageLevelFunctions(unittest.TestCase): def test_rstr(self) -> None: assert re.match(r'^[ABC]+$', rstr.rstr('ABC')) def test_xeger(self) -> None: assert re.match(r'^foo[\d]{10}bar$', rstr.xeger(r'^foo[\d]{10}bar$')) def test_convenience_function(self) -> None: assert re.match(r'^[a-zA-Z]+$', rstr.letters()) rstr-3.2.2/tests/test_rstr.py000066400000000000000000000106321451157670000162650ustar00rootroot00000000000000import re import unittest import random from rstr import Rstr, SameCharacterError def assert_matches(pattern: str, value: str) -> None: errmsg = f'{value} does not match {pattern}' assert re.match(pattern, value), errmsg class TestRstr(unittest.TestCase): def setUp(self) -> None: self.rs = Rstr() def test_specific_length(self) -> None: assert_matches('^A{5}$', self.rs.rstr('A', 5)) def test_length_range(self) -> None: assert_matches('^A{11,20}$', self.rs.rstr('A', 11, 20)) def test_end_range_no_start_range(self) -> None: assert_matches('^A{1,20}$', self.rs.rstr('A', end_range=20)) def test_custom_alphabet(self) -> None: assert_matches('^A{1,10}$', self.rs.rstr('AA')) def test_alphabet_as_list(self) -> None: assert_matches('^A{1,10}$', self.rs.rstr(['A', 'A'])) def test_include(self) -> None: assert_matches('^[ABC]*@[ABC]*$', self.rs.rstr('ABC', include='@')) def test_include_specific_length(self) -> None: ''' Verify including characters doesn't make the string longer than intended. ''' assert_matches('^[ABC@]{5}$', self.rs.rstr('ABC', 5, include='@')) def test_exclude(self) -> None: for _ in range(0, 100): assert 'C' not in self.rs.rstr('ABC', exclude='C') def test_include_as_list(self) -> None: assert_matches('^[ABC]*@[ABC]*$', self.rs.rstr('ABC', include=['@'])) def test_exclude_as_list(self) -> None: for _ in range(0, 100): assert 'C' not in self.rs.rstr('ABC', exclude=['C']) def test_raise_exception_if_include_and_exclude_parameters_contain_same_character(self) -> None: with self.assertRaisesRegex(SameCharacterError, r"include and exclude parameters contain same character \(B\)"): self.rs.rstr('A', include='B', exclude='B') self.rs.rstr('A', include=['B'], exclude=['B']) with self.assertRaisesRegex(SameCharacterError, r"include and exclude parameters contain same characters \(., .\)"): self.rs.rstr('A', include='BC', exclude='BC') class TestSystemRandom(TestRstr): def setUp(self) -> None: self.rs = Rstr(random.SystemRandom()) class TestDigits(unittest.TestCase): def setUp(self) -> None: self.rs = Rstr() def test_all_digits(self) -> None: assert_matches(r'^\d{1,10}$', self.rs.digits()) def test_digits_include(self) -> None: assert_matches(r'^\d*@\d*$', self.rs.digits(include='@')) def test_digits_exclude(self) -> None: for _ in range(0, 100): assert '5' not in self.rs.digits(exclude='5') class TestNondigits(unittest.TestCase): def setUp(self) -> None: self.rs = Rstr() def test_nondigits(self) -> None: assert_matches(r'^\D{1,10}$', self.rs.nondigits()) def test_nondigits_include(self) -> None: assert_matches(r'^\D*@\D*$', self.rs.nondigits(include='@')) def test_nondigits_exclude(self) -> None: for _ in range(0, 100): assert 'A' not in self.rs.nondigits(exclude='A') class TestLetters(unittest.TestCase): def setUp(self) -> None: self.rs = Rstr() def test_letters(self) -> None: assert_matches(r'^[a-zA-Z]{1,10}$', self.rs.letters()) def test_letters_include(self) -> None: assert_matches(r'^[a-zA-Z]*@[a-zA-Z]*$', self.rs.letters(include='@')) def test_letters_exclude(self) -> None: for _ in range(0, 100): assert 'A' not in self.rs.letters(exclude='A') class TestUnambiguous(unittest.TestCase): def setUp(self) -> None: self.rs = Rstr() def test_unambiguous(self) -> None: assert_matches('^[a-km-zA-HJ-NP-Z2-9]{1,10}$', self.rs.unambiguous()) def test_unambiguous_include(self) -> None: assert_matches('^[a-km-zA-HJ-NP-Z2-9@]{1,10}$', self.rs.unambiguous(include='@')) def test_unambiguous_exclude(self) -> None: for _ in range(0, 100): assert 'A' not in self.rs.unambiguous(exclude='A') class TestCustomAlphabets(unittest.TestCase): def test_alphabet_at_instantiation(self) -> None: rs = Rstr(vowels='AEIOU') assert_matches('^[AEIOU]{1,10}$', rs.vowels()) def test_add_alphabet(self) -> None: rs = Rstr() rs.add_alphabet('evens', '02468') assert_matches('^[02468]{1,10}$', rs.evens()) def main() -> None: unittest.main() if __name__ == '__main__': main() rstr-3.2.2/tests/test_xeger.py000066400000000000000000000056241451157670000164120ustar00rootroot00000000000000import re import unittest from rstr import Rstr class TestXeger(unittest.TestCase): def setUp(self) -> None: self.rs = Rstr() def test_literals(self) -> None: pattern = r'foo' assert re.match(pattern, self.rs.xeger(pattern)) def test_dot(self) -> None: ''' Verify that the dot character doesn't produce newlines. See: https://bitbucket.org/leapfrogdevelopment/rstr/issue/1/ ''' pattern = r'.+' for _ in range(100): assert re.match(pattern, self.rs.xeger(pattern)) def test_digit(self) -> None: pattern = r'\d' assert re.match(pattern, self.rs.xeger(pattern)) def test_nondigits(self) -> None: pattern = r'\D' assert re.match(pattern, self.rs.xeger(pattern)) def test_literal_with_repeat(self) -> None: pattern = r'A{3}' assert re.match(pattern, self.rs.xeger(pattern)) def test_literal_with_range_repeat(self) -> None: pattern = r'A{2,5}' assert re.match(pattern, self.rs.xeger(pattern)) def test_word(self) -> None: pattern = r'\w' assert re.match(pattern, self.rs.xeger(pattern)) def test_nonword(self) -> None: pattern = r'\W' assert re.match(pattern, self.rs.xeger(pattern)) def test_or(self) -> None: pattern = r'foo|bar' assert re.match(pattern, self.rs.xeger(pattern)) def test_or_with_subpattern(self) -> None: pattern = r'(foo|bar)' assert re.match(pattern, self.rs.xeger(pattern)) def test_range(self) -> None: pattern = r'[A-F]' assert re.match(pattern, self.rs.xeger(pattern)) def test_character_group(self) -> None: pattern = r'[ABC]' assert re.match(pattern, self.rs.xeger(pattern)) def test_carot(self) -> None: pattern = r'^foo' assert re.match(pattern, self.rs.xeger(pattern)) def test_dollarsign(self) -> None: pattern = r'foo$' assert re.match(pattern, self.rs.xeger(pattern)) def test_not_literal(self) -> None: pattern = r'[^a]' assert re.match(pattern, self.rs.xeger(pattern)) def test_negation_group(self) -> None: pattern = r'[^AEIOU]' assert re.match(pattern, self.rs.xeger(pattern)) def test_lookahead(self) -> None: pattern = r'foo(?=bar)' assert re.match(pattern, self.rs.xeger(pattern)) def test_lookbehind(self) -> None: pattern = r'(?<=foo)bar' assert re.search(pattern, self.rs.xeger(pattern)) def test_backreference(self) -> None: pattern = r'(foo|bar)baz\1' assert re.match(pattern, self.rs.xeger(pattern)) def test_zero_or_more_greedy(self) -> None: pattern = r'a*' assert re.match(pattern, self.rs.xeger(pattern)) def test_zero_or_more_non_greedy(self) -> None: pattern = r'a*?' assert re.match(pattern, self.rs.xeger(pattern)) rstr-3.2.2/tox.ini000066400000000000000000000003301451157670000140250ustar00rootroot00000000000000[tox] envlist = pypy38,pypy39,py37,py38,py39,py310,py311,typing skipsdist = true [testenv] commands=python -m unittest {posargs} [testenv:typing] deps = mypy==1.6.0 commands = mypy --strict {posargs: rstr}