-
Notifications
You must be signed in to change notification settings - Fork 12
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:
- Edição dos metadados
- Migração
- Checagem
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.
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:
- Fazer o download dos pacotes atuais
- Fazer o update dos pacotes, adicionando o novo campo
- 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()
A checagem sempre consiste em dois passos:
- Verificar se o campo foi criado e populado.
- Verificar que o método
Metadata.createde fato cria umtable_config.yamlcotendo 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.