"""
.. _name_gen:
Name Generation and Selection
=============================
These functions generate names using the data in the
:ref:`names database <names_db>`. They handle gathering
the :ref:`name data <name_data>`, reading the
:ref:`configuration <config>`, generating the name,
and `simple modification <simple_mod>`.
.. autofunction:: mkname.create_compound_name
.. autofunction:: mkname.create_syllable_name
.. autofunction:: mkname.pick_name
Manually Configured Generation and Selection
--------------------------------------------
The following functions can also generate names, but they require
a little more work on your part to manage the configuration. Only
use these if, for some reason, you need to get between the process
of loading the configuration or names from the names database and
the generation of the name:
.. autofunction:: mkname.build_compound_name
.. autofunction:: mkname.build_from_syllables
.. autofunction:: mkname.select_name
Name Listing
============
The following function will list the names in the current
names database.
.. autofunction:: mkname.list_names
"""
from collections.abc import Sequence
from pathlib import Path
from warnings import warn
from mkname.constants import *
from mkname.db import get_names
from mkname.init import get_config, get_db
from mkname.mod import compound_names, mods
from mkname.model import Name, Section, SimpleMod
from mkname.utility import roll, split_into_syllables
# Names that will be imported when using *.
__all__ = [
'build_compound_name',
'build_compound_name_from_names',
'build_from_syllables',
'build_syllable_name_from_names',
'configure',
'create_compound_name',
'create_syllable_name',
'list_names',
'pick_name',
'select_name',
'select_name_from_names'
]
# Complete functions for making names.
[docs]
def create_compound_name(
num_names: int = 1,
mod: SimpleMod | None = None,
source: str | None = None,
culture: str | None = None,
date: int | None = None,
gender: str | None = None,
kind: str | None = None,
cfg_path: Path | str | None = None,
db_path: Path | str | None = None
) -> list[str]:
"""Generate a name by combining two random names.
:param num_names: (Optional.) The number of names
to create. Defaults to one.
:param mod: (Optional.) A simple modification
function for modifying the created names.
Defaults to not modifying the names.
:param source: (Optional.) Limit the names
used to the given :ref:`source<source>`
Defaults to all sources.
:param culture: (Optional.) Limit the names
used to the given :ref:`culture<culture>`
Defaults to all cultures.
:param date: (Optional.) Limit the names
used to the given :ref:`date<date>`
Defaults to all dates.
:param gender: (Optional.) Limit the names
used to the given :ref:`gender<gender>`
Defaults to all genders.
:param kind: (Optional.) Limit the names
used to the given :ref:`kind<kind>`
Defaults to all kinds.
:param cfg_path: (Optional.) The path to a
:ref:`configuration file<config>`.
Defaults to searching for config files.
:param db_path: (Optional.) The path to a
:ref:`names database<names_db>`.
Defaults to :ref:`searching<db_search>`
for the database.
:returns: A :class:`list` object.
:rtype: list
:usage:
To generate a compound name:
.. testsetup:: create_compound_name
from unittest.mock import patch
test_db = 'tests/data/big_names.db'
patch('mkname.init.get_default_db', return_value=test_db)
from mkname import create_compound_name
import yadr.operator as yop
yop.random.seed('spam123')
.. doctest:: create_compound_name
>>> create_compound_name()
['Sethel']
To generate three compound names:
.. doctest:: create_compound_name
>>> create_compound_name(3)
['Herika', 'Betty', 'Warthur']
To force :func:`mkname.create_compound_name` to use
a custom names database you built. It will also use
this database if it's the first found during a search,
but this will override that search:
.. doctest:: create_compound_name
>>> create_compound_name(db_path='tests/data/names.db')
['Tam']
To force :func:`mkname.create_compound_name` to use
a custom configuration you built. It will also use
this configuration if it's the last found during
a search, but this will override that search. This
can be used to change how :func:`mkname.create_compound_name`
combines the names:
.. testsetup:: create_compound_name_cfg
from mkname import create_compound_name
import yadr.operator as yop
yop.random.seed('spam123')
.. doctest:: create_compound_name_cfg
>>> create_compound_name(cfg_path='tests/data/test_config_full.toml')
['Haffles']
To generate a name from only male given names:
.. doctest:: create_compound_name
>>> create_compound_name(gender='male', kind='given')
['Llike']
"""
config, db_path = configure(cfg_path, db_path)
names = get_names(db_path, source, culture, date, gender, kind)
results = [build_compound_name_from_names(
names,
config['consonants'],
config['vowels']
) for _ in range(num_names)]
results = modify(results, mod)
return results
[docs]
def create_syllable_name(
num_syllables: int,
num_names: int = 1,
mod: SimpleMod | None = None,
source: str | None = None,
culture: str | None = None,
date: int | None = None,
gender: str | None = None,
kind: str | None = None,
cfg_path: Path | str | None = None,
db_path: Path | str | None = None
) -> list[str]:
"""Generate a name by combining syllables from random names.
:param num_syllables: The number of syllables
in the creeated names.
:param num_names: (Optional.) The number of names
to create. Defaults to one.
:param mod: (Optional.) A simple modification
function for modifying the created names.
Defaults to no modifying the names.
:param source: (Optional.) Limit the names
used to the given :ref:`source<source>`
Defaults to all sources.
:param culture: (Optional.) Limit the names
used to the given :ref:`culture<culture>`
Defaults to all cultures.
:param date: (Optional.) Limit the names
used to the given :ref:`date<date>`
Defaults to all dates.
:param gender: (Optional.) Limit the names
used to the given :ref:`gender<gender>`
Defaults to all genders.
:param kind: (Optional.) Limit the names
used to the given :ref:`kind<kind>`
Defaults to all kinds.
:param cfg_path: (Optional.) The path to a
:ref:`configuration file<config>`.
Defaults to searching for config files.
:param db_path: (Optional.) The path to a
:ref:`names database<names_db>`.
Defaults to :ref:`searching<db_search>`
for the database.
:returns: A :class:`list` object.
:rtype: list
:usage:
To generate a three syllable name:
.. testsetup:: create_syllable_name
from unittest.mock import patch
test_db = 'tests/data/big_names.db'
patch('mkname.init.get_default_db', return_value=test_db)
from mkname import create_syllable_name
import yadr.operator as yop
yop.random.seed('spam123')
.. doctest:: create_syllable_name
>>> create_syllable_name(3)
['Yerethar']
To generate three compound names:
.. doctest:: create_syllable_name
>>> create_syllable_name(3, num_names=3)
['Wilhurgar', 'Bassjuane', 'Bertollan']
To force :func:`mkname.create_syllable_name` to use
a custom names database you built. It will also use
this database if it's the first found during a search,
but this will override that search:
.. doctest:: create_syllable_name
>>> create_syllable_name(3, db_path='tests/data/names.db')
['Spamlesham']
To force :func:`mkname.create_syllable_name` to use
a custom configuration you built. It will also use
this configuration if it's the last found during
a search, but this will override that search. This
can be used to change how :func:`mkname.create_compound_name`
combines the names:
.. doctest:: create_syllable_name
>>> path = 'tests/data/test_config_full.toml'
>>> create_syllable_name(3, cfg_path=path)
['Spamhamspam']
To generate a four syllable name from only male given names:
.. doctest:: create_syllable_name
>>> create_syllable_name(4, gender='male', kind='given')
['Hontinryal']
"""
config, db_path = configure(cfg_path, db_path)
names = get_names(db_path, source, culture, date, gender, kind)
results = [build_syllable_name_from_names(
num_syllables,
names,
config['consonants'],
config['vowels']
) for _ in range(num_names)]
results = modify(results, mod)
return results
[docs]
def list_names(
source: str | None = None,
culture: str | None = None,
date: int | None = None,
gender: str | None = None,
kind: str | None = None,
cfg_path: Path | str | None = None,
db_path: Path | str | None = None
) -> list[str]:
"""List names in the :ref:`names database<names_db>`.
:param num_names: (Optional.) The number of names
:param source: (Optional.) Limit the names
used to the given :ref:`source<source>`
Defaults to all sources.
:param culture: (Optional.) Limit the names
used to the given :ref:`culture<culture>`
Defaults to all cultures.
:param date: (Optional.) Limit the names
used to the given :ref:`date<date>`
Defaults to all dates.
:param gender: (Optional.) Limit the names
used to the given :ref:`gender<gender>`
Defaults to all genders.
:param kind: (Optional.) Limit the names
used to the given :ref:`kind<kind>`
Defaults to all kinds.
:param cfg_path: (Optional.) The path to a
:ref:`configuration file<config>`.
Defaults to searching for config files.
:param db_path: (Optional.) The path to a
:ref:`names database<names_db>`.
Defaults to :ref:`searching<db_search>`
for the database.
:returns: A :class:`list` object.
:rtype: list
:usage:
To list all the names in the default names database:
.. doctest:: api
>>> list_names()
['Noah', 'Liam', 'Jacob', 'Will...
To list all the names in a custom names database. You can also
list the names in a custom database if it is the first found
during the :ref:`database search<db_search>`, but this will
override that search:
.. doctest:: api
>>> list_names(db_path='tests/data/names.db')
['spam', 'ham', 'tomato'...
To force :func:`mkname.list_names` to use
a custom configuration you built. It will also use
this configuration if it's the last found during
a search, but this will override that search.
.. doctest:: api
>>> list_names(cfg_path='tests/data/test_config_full.toml')
['spam', 'ham', 'tomato'...
To list the male given names:
.. doctest:: api
>>> list_names(gender='male', kind='given')
['Noah', 'Liam', 'Jacob'...
"""
config, db_path = configure(cfg_path, db_path)
names = get_names(db_path, source, culture, date, gender, kind)
results = [name.name for name in names]
return results
[docs]
def pick_name(
num_names: int = 1,
mod: SimpleMod | None = None,
source: str | None = None,
culture: str | None = None,
date: int | None = None,
gender: str | None = None,
kind: str | None = None,
cfg_path: Path | str | None = None,
db_path: Path | str | None = None
) -> list[str]:
"""Pick random names.
:param num_names: (Optional.) The number of names
to create. Defaults to one.
:param mod: (Optional.) A simple modification
function for modifying the created names.
Defaults to no modifying the names.
:param source: (Optional.) Limit the names
used to the given :ref:`source<source>`
Defaults to all sources.
:param culture: (Optional.) Limit the names
used to the given :ref:`culture<culture>`
Defaults to all cultures.
:param date: (Optional.) Limit the names
used to the given :ref:`date<date>`
Defaults to all dates.
:param gender: (Optional.) Limit the names
used to the given :ref:`gender<gender>`
Defaults to all genders.
:param kind: (Optional.) Limit the names
used to the given :ref:`kind<kind>`
Defaults to all kinds.
:param cfg_path: (Optional.) The path to a
:ref:`configuration file<config>`.
Defaults to searching for config files.
:param db_path: (Optional.) The path to a
:ref:`names database<names_db>`.
Defaults to :ref:`searching<db_search>`
for the database.
:returns: A :class:`list` object.
:rtype: list
:usage:
To select a name:
.. testsetup:: pick_name
from unittest.mock import patch
test_db = 'tests/data/big_names.db'
patch('mkname.init.get_default_db', return_value=test_db)
from mkname import pick_name
import yadr.operator as yop
yop.random.seed('spam123')
.. doctest:: pick_name
>>> pick_name()
['Sawyer']
To pick three names:
.. doctest:: pick_name
>>> pick_name(3)
['Ethel', 'Harper', 'Erika']
To force :func:`mkname.pick_name` to use
a custom names database you built. It will also use
this database if it's the first found during a search,
but this will override that search:
.. doctest:: pick_name
>>> pick_name(db_path='tests/data/names.db')
['ham']
To force :func:`mkname.pick_name` to use
a custom configuration you built. It will also use
this configuration if it's the last found during
a search, but this will override that search:
.. doctest:: pick_name
>>> path = 'tests/data/test_config_full.toml'
>>> pick_name(cfg_path=path)
['spam']
To pick a name from only male given names:
.. doctest:: pick_name
>>> pick_name(gender='male', kind='given')
['Clarence']
"""
config, db_path = configure(cfg_path, db_path)
names = get_names(db_path, source, culture, date, gender, kind)
results = [select_name_from_names(names) for _ in range(num_names)]
results = modify(results, mod)
return results
# Functions for making names from names.
def build_compound_name_from_names(
names: Sequence[Name],
consonants: Sequence[str] = CONSONANTS,
vowels: Sequence[str] = VOWELS
) -> str:
"""Construct a new game from two randomly selected names.
:param names: A list of Name objects to use for constructing
the new name.
:param consonants: (Optional.) The characters to consider as
consonants.
:param vowels: (Optional.) The characters to consider as vowels.
:return: A :class:str object.
:rtype: str
:usage:
.. testsetup:: build_compound_name
from unittest.mock import patch
test_db = 'tests/data/big_names.db'
patch('mkname.init.get_default_db', return_value=test_db)
from mkname import build_compound_name
from mkname.model import Name
import yadr.operator as yop
yop.random.seed('spam123')
.. doctest:: build_compound_name
>>> # The list of names needs to be Name objects.
>>> names = []
>>> names.append(Name(1, 'eggs', 'url', '', 1970, '', 'given'))
>>> names.append(Name(2, 'spam', 'url', '', 1970, '', 'given'))
>>> names.append(Name(3, 'tomato', 'url', '', 1970, '', 'given'))
>>>
>>> # Generate the name.
>>> build_compound_name(names)
'Teggs'
The function takes into account whether the starting letter of
each name is a vowel or a consonant when determining how to
create the name. You can affect this by changing which letters
it treats as consonants or vowels:
.. doctest:: build_compound_name
>>> # Seed the RNG to make this test predictable for this
>>> # example. Don't do this if you want random names.
>>> import yadr.operator as yop
>>> yop.random.seed('spam1')
>>>
>>> # The list of names needs to be Name objects.
>>> names = []
>>> names.append(Name(1, 'eggs', 'url', '', 1970, '', 'given'))
>>> names.append(Name(2, 'spam', 'url', '', 1970, '', 'given'))
>>> names.append(Name(3, 'tomato', 'url', '', 1970, '', 'given'))
>>>
>>> # Treat 't' as a vowel rather than a consonant.
>>> consonants = 'bcdfghjklmnpqrsvwxz'
>>> vowels = 'aeiout'
>>>
>>> # Generate the name.
>>> build_compound_name(names, consonants, vowels)
'Sptomato'
"""
root_name = select_name_from_names(names)
mod_name = select_name_from_names(names)
return compound_names(root_name, mod_name, consonants, vowels)
def build_syllable_name_from_names(
num_syllables: int,
names: Sequence[Name],
consonants: Sequence[str] = CONSONANTS,
vowels: Sequence[str] = VOWELS
) -> str:
"""Build a name from the syllables of the given names.
:param num_syllables: The number of syllables in the constructed
name.
:param names: A list of Name objects to use for constructing
the new name.
:param consonants: (Optional.) The characters to consider as
consonants.
:param vowels: (Optional.) The characters to consider as vowels.
:return: A :class:str object.
:rtype: str
:usage:
.. testsetup:: build_from_syllables
from unittest.mock import patch
test_db = 'tests/data/big_names.db'
patch('mkname.init.get_default_db', return_value=test_db)
from mkname import build_from_syllables
from mkname.model import Name
import yadr.operator as yop
yop.random.seed('spam123')
.. doctest:: build_from_syllables
>>> # The list of names needs to be Name objects.
>>> names = []
>>> names.append(Name(1, 'spameggs', 'url', '', 1970, '', 'given'))
>>> names.append(Name(2, 'eggsham', 'url', '', 1970, '', 'given'))
>>> names.append(Name(3, 'tomato', 'url', '', 1970, '', 'given'))
>>>
>>> # The number of syllables in the generated name.
>>> num_syllables = 3
>>>
>>> # Generate the name.
>>> build_from_syllables(num_syllables, names)
'Atspamegg'
The function takes into account whether each letter of each
name is a vowel or a consonant when determining how to split
the names into syllables. You can affect this by changing which
letters it treats as consonants or vowels:
.. doctest:: build_from_syllables
>>> # The list of names needs to be Name objects.
>>> names = []
>>> names.append(Name(1, 'spam', 'url', '', 1970, '', 'given'))
>>> names.append(Name(2, 'eggs', 'url', '', 1970, '', 'given'))
>>> names.append(Name(3, 'tomato', 'url', '', 1970, '', 'given'))
>>>
>>> # Treat 't' as a vowel rather than a consonant.
>>> consonants = 'bcdfghjklmnpqrtvwxz'
>>> vowels = 'aeious'
>>>
>>> # Generate the name.
>>> build_from_syllables(num_syllables, names, consonants, vowels)
'Amtomgs'
"""
base_names = [select_name_from_names(names) for _ in range(num_syllables)]
result = ''
for name in base_names:
syllables = split_into_syllables(name, consonants, vowels)
index = roll(f'1d{len(syllables)}') - 1
syllable = syllables[index]
result = f'{result}{syllable}'
return result.title()
def select_name_from_names(names: Sequence[Name]) -> str:
"""Select a name from the given list.
:param names: A list of Name objects to use for constructing
the new name.
:return: A :class:str object.
:rtype: str
:usage:
.. testsetup:: select_name
from unittest.mock import patch
test_db = 'tests/data/big_names.db'
patch('mkname.init.get_default_db', return_value=test_db)
from mkname import select_name
from mkname.model import Name
import yadr.operator as yop
yop.random.seed('spam123')
.. doctest:: select_name
>>> # The list of names needs to be Name objects.
>>> names = []
>>> names.append(Name(1, 'spam', 'url', '', 1970, '', 'given'))
>>> names.append(Name(2, 'eggs', 'url', '', 1970, '', 'given'))
>>> names.append(Name(3, 'tomato', 'url', '', 1970, '', 'given'))
>>>
>>> # Generate the name.
>>> select_name(names)
'tomato'
"""
index = roll(f'1d{len(names)}') - 1
return names[index].name
# Manual name-making functions, old names.
[docs]
def build_compound_name(
names: Sequence[Name],
consonants: Sequence[str] = CONSONANTS,
vowels: Sequence[str] = VOWELS
) -> str:
msg = (
'mkname.build_compound_name is deprecated. Use '
'mkname.build_compound_name_from_names instead.'
)
warn(msg, DeprecationWarning)
return build_compound_name_from_names(names, consonants, vowels)
[docs]
def build_from_syllables(
num_syllables: int,
names: Sequence[Name],
consonants: Sequence[str] = CONSONANTS,
vowels: Sequence[str] = VOWELS
) -> str:
msg = (
'mkname.build_from_syllables is deprecated. Use '
'mkname.build_syllable_name_from_names instead.'
)
warn(msg, DeprecationWarning)
return build_syllable_name_from_names(
num_syllables,
names,
consonants,
vowels
)
[docs]
def select_name(names: Sequence[Name]) -> str:
msg = (
'mkname.select_name is deprecated. Use '
'mkname.select_name_from_names instead.'
)
warn(msg, DeprecationWarning)
return select_name_from_names(names)
# Common utility functions.
def configure(
cfg_path: Path | str | None = None,
db_path: Path | str | None = None
) -> tuple[Section, Path]:
"""Configure based on the invocation arguments.
:param cfg_path: (Optional.) The path to a
:ref:`configuration file<config>`.
Defaults to searching for config files.
:param db_path: (Optional.) The path to a
:ref:`names database<names_db>`.
Defaults to :ref:`searching<db_search>`
for a names database.
:returns: A :class:`list` object.
:rtype: list
"""
config = get_config(cfg_path)['mkname']
db_path = get_db(db_path, conf_path=cfg_path)
return config, db_path
def modify(
names: Sequence[str],
mod: SimpleMod | None
) -> list[str]:
"""Use the given simple mod on the names.
:param names: The names to modify.
:param mod_key: A simple mod function.
:returns: A :class:`list` object.
:rtype: list
"""
if mod:
names = [mod(name) for name in names]
return list(names)