import argparse
import datetime
import logging
import os
import random
from os import path
import faker
import psycopg
from faker.providers import address, internet, misc, person
LOGGER = logging.getLogger(__name__)
LOGGING_FORMAT = '[%(asctime)-15s] %(levelname)-8s %(message)s'
LOCALES = [
'ar_AA',
'ar_EG',
'ar_JO',
'ar_PS',
'ar_SA',
'bg_BG',
'bs_BA',
'cs_CZ',
'de_AT',
'de_CH',
'de_DE',
'dk_DK',
'el_CY',
'el_GR',
'en_AU',
'en_CA',
'en_GB',
'en_IE',
'en_NZ',
'en_TH',
'en_US',
'es_ES',
'es_MX',
'et_EE',
'fa_IR',
'fi_FI',
'fr_CH',
'fr_FR',
'he_IL',
'hi_IN',
'hr_HR',
'hu_HU',
'hy_AM',
'id_ID',
'it_IT',
'ja_JP',
'ka_GE',
'ko_KR',
'lb_LU',
'lt_LT',
'lv_LV',
'mt_MT',
'ne_NP',
'nl_BE',
'nl_NL',
'no_NO',
'pl_PL',
'pt_BR',
'pt_PT',
'ro_RO',
'ru_RU',
'sk_SK',
'sl_SI',
'sv_SE',
'th_TH',
'tr_TR',
'tw_GH',
'uk_UA',
'zh_CN',
'zh_TW',
]
STATES = ['unverified', 'verified', 'suspended']
TYPES = ['billing', 'delivery']
ADDRESS_SQL = """\
INSERT INTO test.addresses
(created_at, last_modified_at, user_id, "type", address1, address2,
address3, locality, region, postal_code, country)
VALUES (%(created_at)s, %(last_modified_at)s, %(user_id)s, %(type)s,
%(address1)s, %(address2)s, %(address3)s, %(locality)s,
%(region)s, %(postal_code)s, %(country)s)
RETURNING id;"""
ICON_SQL = 'SELECT lo_from_bytea(0, %(data)s)'
USER_SQL = """\
INSERT INTO test.users
(created_at, last_modified_at, state, email, name, surname,
display_name, locale, password_salt, password, signup_ip, icon)
VALUES (%(created_at)s, %(last_modified_at)s, %(state)s, %(email)s,
%(name)s, %(surname)s, %(display_name)s, %(locale)s,
%(password_salt)s, %(password)s, %(signup_ip)s, %(icon)s)
RETURNING id, created_at;"""
def add_connection_options_to_parser(parser):
conn = parser.add_argument_group('Connection Options')
conn.add_argument(
'-d',
'--dbname',
action='store',
default=os.environ.get('PGDATABASE', 'postgres'),
help='database name to connect to',
)
conn.add_argument(
'-h',
'--host',
action='store',
default=os.environ.get('PGHOST', 'localhost'),
help='database server host or socket directory',
)
conn.add_argument(
'-p',
'--port',
action='store',
type=int,
default=int(os.environ.get('PGPORT', 5432)),
help='database server port number',
)
conn.add_argument(
'-U',
'--username',
action='store',
default=os.environ.get('PGUSER', 'postgres'),
help='The PostgreSQL username to operate as',
)
def add_logging_options_to_parser(parser):
group = parser.add_argument_group(title='Logging Options')
group.add_argument(
'-L',
'--log-file',
action='store',
help='Log to the specified filename. If not specified, '
'log output is sent to STDOUT',
)
group.add_argument(
'-v',
'--verbose',
action='store_true',
help='Increase output verbosity',
)
group.add_argument(
'--debug', action='store_true', help='Extra verbose debug logging'
)
def configure_logging(args):
level = logging.WARNING
if args.verbose:
level = logging.INFO
elif args.debug:
level = logging.DEBUG
filename = args.log_file if args.log_file else None
if filename:
filename = path.abspath(filename)
if not path.exists(path.dirname(filename)):
filename = None
logging.basicConfig(level=level, filename=filename, format=LOGGING_FORMAT)
def generate_address(fake, user_id, created_at):
last_modified_at = None
if fake.boolean(chance_of_getting_true=25):
last_modified_at = created_at + fake.time_delta(
end_datetime=datetime.datetime.now(tz=datetime.UTC)
)
address1 = fake.street_address()
address2 = None
if '\n' in address1:
parts = address1.split('\n')
address1 = parts[0]
address2 = parts[1]
try:
address3 = fake.secondary_address()
except AttributeError:
address3 = None
try:
region = fake.state()
except AttributeError:
region = None
addr = {
'created_at': created_at,
'last_modified_at': last_modified_at,
'user_id': user_id,
'type': fake.random_element(TYPES),
'address1': address1,
'address2': address2,
'address3': address3,
'locality': fake.city(),
'region': region,
'postal_code': fake.postcode(),
'country': fake.country(),
}
LOGGER.debug('Returning %r', addr)
return addr
def generate_user(fake, locale_fake, locale, icon_oid):
created_at = fake.date_time_this_year()
last_modified_at = None
if fake.boolean(chance_of_getting_true=45):
last_modified_at = created_at + fake.time_delta(
end_datetime=datetime.datetime.now(tz=datetime.UTC)
)
name = locale_fake.first_name()
surname = locale_fake.last_name()
display_name = None
if fake.boolean(chance_of_getting_true=50):
if fake.boolean(chance_of_getting_true=50):
display_name = name
else:
display_name = f'{name} {surname}'
user = {
'created_at': created_at,
'last_modified_at': last_modified_at,
'state': fake.random_element(STATES),
'email': locale_fake.safe_email(),
'name': name,
'surname': surname,
'display_name': display_name,
'locale': locale.replace('_', '-'),
'password_salt': fake.uuid4(),
'password': fake.password(12), 'signup_ip': fake.ipv4_public(),
'icon': icon_oid,
}
LOGGER.debug('Returning user: %r', user)
return user
def generate_users(args, cursor):
LOGGER.info('Creating %i users', args.user_count)
fake = faker.Faker()
fake.add_provider(address)
fake.add_provider(internet)
fake.add_provider(misc)
for _offset in range(0, args.user_count):
icon_oid = None
if fake.boolean(chance_of_getting_true=25):
cursor.execute(
ICON_SQL,
{'data': fake.binary(length=random.randint(10000, 200000))},
)
icon_oid = cursor.fetchone()[0]
locale = fake.random_element(LOCALES)
locale_fake = get_locale_faker(locale)
try:
cursor.execute(
USER_SQL, generate_user(fake, locale_fake, locale, icon_oid)
)
except psycopg.IntegrityError as err:
LOGGER.error('Error creating user: %s', err)
continue
user = cursor.fetchone()
LOGGER.info('Created user %s', user[0])
created_at = user[1]
for _offset in range(random.randint(0, 2)):
cursor.execute(
ADDRESS_SQL, generate_address(locale_fake, user[0], created_at)
)
addr = cursor.fetchone()
LOGGER.info('Created address %s for user %s', addr[0], user[0])
created_at = created_at + fake.time_delta(
end_datetime=datetime.datetime.now(tz=datetime.UTC)
)
def get_locale_faker(locale=None):
fake = faker.Faker(locale)
fake.add_provider(address)
fake.add_provider(internet)
fake.add_provider(person)
return fake
def main(args):
configure_logging(args)
conn = psycopg.connect(
host=args.host,
port=args.port,
dbname=args.dbname,
user=args.username,
autocommit=True,
)
cursor = conn.cursor()
generate_users(args, cursor)
conn.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Generates fixture data for tests',
conflict_handler='resolve',
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
add_connection_options_to_parser(parser)
add_logging_options_to_parser(parser)
parser.add_argument(
'--user-count',
action='store',
type=int,
default=250,
help='How many users to generate',
)
main(parser.parse_args())