SQLAlchemy - создать таблицу из ямла или словаря?

1

Есть ли способ создать динамическую таблицу из словаря, указанного в файле yaml? Я определяю много конфигурации ETL в файле yaml, поэтому мне было любопытно, могу ли я также добавить к нему аспект создания таблицы, поэтому мне не нужно модифицировать отдельный файл.sql в отдельном каталоге.

database:
  table: 'schema.fact_stuff'
  create_columns: [
    {}
  ] #not sure how this section should be

Я нашел решение в stackoverflow, которое сместило некоторые списки вместе, что-то похожее, но я бы предпочел явно определить каждый столбец.

{'column_name': 'id', 'column_type': Integer, 'primary_key': False, 'nullable': True}

Я закончил работу с этим:

from sqlalchemy.types import (Integer, NUMERIC, TEXT, BOOLEAN, TIMESTAMP, DATE)

sql_types = {'integer': Integer,
        'numeric': NUMERIC,
        'text': TEXT,
        'date': DATE,
        'timestamp': TIMESTAMP(timezone=False),
        'timestamptz': TIMESTAMP(timezone=True)}

exclude_list = ['original_name']
table_dict = [{k: v for k, v in d.items() if k not in exclude_list} for d in c[variable]['load']['columns']]
for column in table_dict:
    for key, val in column.copy().items():
        if key == 'type_':
            column[key] = sql_types[val]
        elif key == 'default':
            column[key] = dt.datetime.utcnow

metadata = sa.MetaData(schema=c[variable]['load']['schema'])
metadata.reflect(bind=engine, autoload=True)
fact = sa.Table(c[variable]['load']['table'], metadata, extend_existing=True,
        *(sa.Column(**kwargs) for kwargs in table_dict))
fact.create_all(engine, checkfirst=True)

Но затем я перешел к тому, чтобы pandas определял dtypes вместо того, чтобы определять их в файле yaml. Это создает sql с шаблоном jinja2, и я запускаю все мои источники данных для создания DDL.

def pandas_to_postgres(df):
    dtype_dict = {
      'i': 'integer',
      'O': 'text',
      'f': 'real',
      'b': 'boolean',
      'datetime64[ns]': 'timestamp',
      'datetime64[ns, UTC]': 'timestampz',
    }
    column_list = []
    column_dict = {}
    for k, v in df.dtypes.items():
        column_dict['name'] = k
        column_dict['dtype'] = dtype_dict.get(v.kind, 'text')
        column_list.append(column_dict.copy())
    return column_list


def generate_create_table(df, schema, table, table_type, columns, constraint, unique_columns):
    """ Returns a dictionary of coefs from training """
    query = Template(
        template
    ).render(
        schema_name=schema,
        table_name=table,
        table_type=table_type,
        columns=columns,
        constraint=constraint,
        constraint_columns=unique_columns
    )
    print(query)
Теги:
sqlalchemy
yaml

1 ответ

1
Лучший ответ

Сегодня этот выпуск поддерживает SQLAthanor (v.0.3.0). Используя SQLAthanor, вы можете программно генерировать объект Table SQLAlchemy со следующим кодом (при условии, что metadata содержат объект SQLAlchemy MetaData):

from sqlathanor import Table

my_table = Table.from_yaml('yaml_file.yaml', 
                           'my_table_name', 
                           metadata, 
                           primary_key = 'id')

ETA: Обратите внимание, что вы также можете создавать объекты Table используя Table.from_json(), Table.from_dict() и Table.from_csv().

Здесь документация (как правило) о том, как она работает: https://sqlathanor.readthedocs.io/en/latest/using.html#generating-sqlalchemy-tables-from-serialized-data

И здесь ссылка на документацию конкретного Table.from_yaml(): https://sqlathanor.readthedocs.io/en/latest/api.html#sqlathanor.schema.Table.from_yaml

(Я рекомендую просмотреть документацию по методу - он переходит в некоторые из "готовых" программных построений объекта Table из сериализованных данных)


ETA:

В принципе, способ создания программных Table заключается в том, что SQLAthanor:

  1. Сначала преобразует сериализованную строку (или файл) в Python dict. Для YAML стандартным де-сериализатором является PyYAML. Для JSON, по умолчанию де-сериализатору является simplejson (оба по умолчанию deserializers может быть переопределен с помощью deserialize_function аргумента).

  2. После того, как Python dict сгенерирован, SQLAthanor читает каждый из ключей в этом dict, чтобы определить имена столбцов. Он считывает значения для каждого ключа и на основе типа данных значения пытается "угадать" в типе данных SQLAlchemy.

  3. Учитывая то, что он нашел на шаге 2, он создает объект Table с объектами Column где каждый объект Column соответствует ключу де-сериализованного dict.

Если вам нужен более точный контроль над каждым Column, вы можете:

  • переопределить его тип данных SQLAlchemy, используя type_mapping аргумента (type_mapping получает dict, где клавиши верхнего уровня соответствуют имени столбца, и каждое значение является типом данных, чтобы применить к Column)
  • передать дополнительные аргументы ключевых слов в Column конструктору с использованием column_kwargs аргумента (column_kwargs получает dict, где клавиши верхнего уровня соответствует имени столбца, и каждое значение является dict с аргументами ключевых слов, которые будут поставлены этим конструктора столбец.

По умолчанию Table.from_<format>() не поддерживает вложенные структуры данных. По умолчанию для skip_nested установлено значение True, что означает, что ключ в де-сериализованном dict который содержит вложенный объект (или итерируемый или dict), будет пропущен (т.е. не получит соответствующий Column). Если вашей Table необходимо хранить вложенные данные, вы можете установить skip_nested в False и активировать default_to_str в True. Это приведет к преобразованию вложенных данных (итераций или объектов dict) в строки и, таким образом, сохранит их в столбце " Text (если не переопределено type_mapping).


Table.from_dict()

Ниже приведен пример dict который может быть поставлен в Table.from_dict():

sample_dict = {
    'id': 123,
    'some_column_name': 'Some Column Value',
    'created_datetime': datetime.utcnow()
}

my_table = Table.from_dict(sample_dict, 
                           'my_table', 
                           metadata, 
                           primary_key = 'id')

Когда подается в Table.from_dict() это dict будет производить Table объект с именем таблицы базы данных my_table, который содержит три колонки:

  • id который будет иметь тип Integer который установлен как первичный ключ таблицы
  • some_column_name которое будет иметь тип Text
  • created_datetime которое будет иметь тип DateTime

Table.from_yaml()

Ниже приведен один и тот же пример, но вместо этого используется строка/документ YAML, которая может быть Table.from_yaml() в Table.from_yaml():

sample_yaml = """
    id: 123
    some_column_name: Test Value
    created_timestamp: 2018-01-01T01:23:45.67890
"""

my_table = Table.from_yaml(sample_yaml, 
                           'my_table', 
                           metadata, 
                           primary_key = 'id')

Когда он будет Table.from_yaml() в Table.from_yaml() он сначала де-сериализует sample_yaml в dict же, как в предыдущем примере, а затем создает объект Table с именем таблицы базы данных my_table который содержит три столбца:

  • id который будет иметь тип Integer который установлен как первичный ключ таблицы
  • some_column_name которое будет иметь тип Text
  • created_datetime которое будет иметь тип DateTime

Надеюсь это поможет!

  • 0
    Ничего себе, да, я просто отредактировал свой вопрос и включил некоторые пути, по которым я пошел. У вас есть пример файла yaml или dict, который будет использоваться в качестве входных данных? В настоящее время я использую панд с df.columns и df.types и передаю их в шаблон jinja, потому что это было утомительным обновлением файла yaml вручную.
  • 1
    Не волнуйтесь! Я обновил свой ответ более подробным объяснением, включая пример использования Table.from_dict() и Table.from_yaml() с Python dict и соответствующим документом YAML. Надеюсь, это поможет уточнить!
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню