-
Notifications
You must be signed in to change notification settings - Fork 8
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.create
de fato cria umtable_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
.