Skip to content

Metadados

lucascr91 edited this page Jul 29, 2022 · 2 revisions

Metadados

Criação de novo campo para metadados

Neste tutorial vamos mostrar como criar um novo campo de metadado no Ckan. Para o exemplo, criaremos um novo campo chamado n_rows onde teremos o número total de linhas da tabela. A criação de um novo campo de metadado consiste nos seguintes passos:

  1. Edição dos metadados
  2. Migração
  3. Checagem

Edição dos metadados

Antes de iniciar a edição dos metadados para incluir o campo desejado é preciso buildar o site localmente. Para instruções de como buildar o site localmente, veja o README do repositório principal.

Com o site rodando localmente, podemos iniciar a edição dos metadados. Como vamos adicionar um campo que se relaciona com as tabelas, precisamos editar o items do campo resources em bdm_table. Para isso, abra o arquivo ckanext-basedosdados/ckanext/basedosdados/validator/resources/bdm/table/__init__.py e adicione o campo desejado:

.
.
.
class BdmTable(_CkanDefaultResource):
    # fmt: off
    resource_type: Literal["bdm_table"]

    dataset_id                : Str                                                       = DATASET_ID_FIELD
    table_id                  : Str                                                       = TABLE_ID_FIELD
    .
    .
    .
    title                     : Optional[Str]                                             = TITLE_FIELD
    number_rows               : Optional[int]                                             = N_ROWS_FIELD

Note que explicitamos que o campo que estamos criando é do tipo int.

Após a edição do arquivo __init__.py, devemos agora editar o arquivo fields_definitions.py que encontra-se na mesma pasta. Neste arquivo precisamos i) criar um novo Field e ii) alterar os valores no argumento yaml_order no field anterior e no field posterior. Para nosso caso, temos:

COLUMNS_FIELD = Field(
    title="Colunas",
    description=to_line(
        [
            "Quais são as colunas? Certifique-se de escrever uma boa descrição, as pessoas vão gostar",
            "para saber sobre o que é a coluna.",
            "Adicionar todas as colunas manualmente pode ser bastante cansativo, por isso, quando",
            "inicializando este arquivo de configuração, você pode apontar a função para uma amostra de dados que",
            "preencherá automaticamente as colunas.",
            "Algumas colunas existirão apenas na tabela final, você as construirá em `publish.sql`.",
            "Para esses, defina is_in_staging como False.",
            "Além disso, você deve adicionar as colunas de partição aqui e definir is_partition como True.",
        ]
    ),
    yaml_order={
        "id_before": "partitions",
        "id_after": "number_rows",
    },
)

NUMBER_ROWS_FIELD = Field(
    title="Número de Linhas da Tabela",
    yaml_order={
        "id_before": "columns",
        "id_after": "metadata_modified",
    },
)

METADATA_MODIFIED_FIELD = Field(
    title="Data da Última Modificação dos Metadados",
    yaml_order={
        "id_before": "number_rows",
        "id_after": None,
    },
)

Note os valores do argumento yaml_order em cada um dos fields.

Migração

Após a edição dos metadados, precisamos realizar a migração do novo campo para que todas as tabelas tenha essa informação em seus metadados. A forma mais prática de implementar a migração usando python é usar como template um notebook usado em outra migração. Estes notebooks podem ser encontrados na pasta utils/migration.

A migração consiste nos seguintes passos:

  1. Fazer o download dos pacotes atuais
  2. Fazer o update dos pacotes, adicionando o novo campo
  3. Migração

Todos esses passos devem ser realizados nos 3 ambientes, ou seja, local, staginng e prod.

Para realizar os passos acima, precisamos definir uma classe chamada Migrator, usar a função download_package do módulo migration_functions.py e criar uma função função para popular o novo campo. A classe Migrator é definida da seguinte forma:

class Migrator:
    def __init__(self, ckan_remote: RemoteCKAN, package_dict):
        self.ckan_remote = ckan_remote
        self.package_dict = package_dict

    def create(self):
        try:
            self.ckan_remote.action.package_create(**self.package_dict)
        except NotFound as e:
            print(e)

    def update(self):
        try:
            self.ckan_remote.action.package_update(**self.package_dict)
        except NotFound as e:
            print(e)

    def purge(self):
        try:
            self.ckan_remote.action.dataset_purge(id=self.package_dict["name"])
        except NotFound as e:
            print(e)

    def delete(self):
        try:
            self.ckan_remote.action.package_delete(id=self.package_dict["name"])
        except NotFound as e:
            print(e)

    def validate(self):
        try:
            self.ckan_remote.action.bd_dataset_validate(**self.package_dict)
        except NotFound as e:
            print(e)

A função para popular o campo number_rows é:

def get_number_rows(package):
    for i, resource in enumerate(package["resources"]):
        if resource["resource_type"] == "bdm_table":
            if "number_rows" not in resource or resource["number_rows"] == "":
                dataset_id = resource['dataset_id']
                table_id = resource['table_id']
                try:
                    query = f"SELECT COUNT(*) AS n_rows FROM `basedosdados.{dataset_id}.{table_id}`"
                    n_rows = read_sql(query=query, billing_project_id='basedosdados-dev', from_file=True)['n_rows'].to_list()[0]
                    resource["number_rows"] = int(n_rows)
                except:
                    resource["number_rows"] = None             

    return package

Agora seguimos o passo a passo na ordem exposta acima. Para o Download, fazemos:

LOCAL_CKAN_URL = "http://localhost:5000"
DEV_CKAN_URL = "https://staging.basedosdados.org"
PROD_CKAN_URL = "https://basedosdados.org"

local_packages = download_packages(LOCAL_CKAN_URL, "dev")
dev_packages = download_packages(DEV_CKAN_URL, "dev")
prod_packages = download_packages(PROD_CKAN_URL, "prod")

Então adicionamos o novo campo no pacote local:

update_packages = []
for package in tqdm(local_packages):
        update_packages.append(get_number_rows(package))

Por fim, realizamos a migração:

ckan_remote = RemoteCKAN(LOCAL_CKAN_URL, apikey=api_key_dev)

for package in update_packages:
    migration = Migrator(ckan_remote, package)
    migration.validate()
    migration.update()

Checagem

A checagem sempre consiste em dois passos:

  1. Verificar se o campo foi criado e populado.
  2. Verificar que o método Metadata.create de fato cria um table_config.yaml cotendo o novo campo.

Para verificar se o campo foi criado, acesse o enndpoint http://localhost/api/3/action/bd_dataset_search?q=&resource_type=bdm_table&page=1&page_size=100.

A segunda checagem exige a edição do arquivo ~/.basedosdados/config.toml, alterando os campos [ckan]:url para http://localhost/5000 e [ckan]:api_key para a chave de API de dev. Após a edição do toml, abra uma sessão de python e rode o seguinte snipet:

import basedosdados as bd 

md = bd.Metadata(table_id='microdados_domicilio_1970', dataset_id='br_ibge_censo_demografico')
md.create(if_exists='replace')

Agora verifique o conteúdo do table_config.yaml em ~/bases/br_ibge_censo_demografico/microdados_domicilio_1970/table_config.yaml. Se o novo está presente neste arquivo, está tudo certo!

Tanto a migração quanto a checagem devem ser feitas para staging e prod. Os passos são análogos, a única diferença é que o segundo passo da checagem pode ser feito apenas depois do deployment.

Clone this wiki locally