por Mark Tse, Desarrollador de Software, Desarrollo de Plataformas D2L
publicado originalmente el 24 de marzo de 2018
En este artículo, veremos cómo mantener una base de datos actualizada con los extractos diarios de Brightspace Data Sets (BDS).
Introducción
El Liberación del BDS Permite a los administradores acceder a datos sin procesar que se pueden agregar y filtrar según sus necesidades. Estos conjuntos de datos se actualizan a diario, por lo que los administradores deben regresar diariamente a Brightspace para obtener los datos más recientes. Este artículo te ayudará a empezar a automatizar este proceso, para que puedas dedicar más tiempo a trabajar con los datos y generar los informes que desees, y menos tiempo a mantener una base de datos actualizada.
El código fuente de este artículo está disponible en GitHub (en inglés).
Descripción general del flujo de trabajo
En este ejemplo de código se usa PostgreSQL y Pitón 3. Sin embargo, es posible realizar este flujo de trabajo con cualquier base de datos y lenguaje de programación.
Prerrequisitos
- BDS habilitado en Brightspace y programado para ejecutarse diariamente
- Registro de una aplicación OAuth 2.0 que permite actualizar tokens
- Un Token de actualización inicial
- Python 3.6, con bibliotecas dependientes instaladas (
pip install -r requirements.txt) - PostgreSQL (ejemplo probado con la versión 9.6.2) y la configuración del esquema mediante la ejecución de los archivos de esquema en
Esquema/Tablas - Un
config.json en la misma carpeta que el script, con lo siguiente:
{ "bspace_url": "https:///",
"client_id": "",
"client_secret": "",
"refresh_token": "",
"dbhost": "",
"dbname": "",
"dbuser": "",
"dbpassword": ""}
Nota: los permisos de este archivo de configuración deben establecerse de manera que solo sea legible por el usuario que ejecuta el script, ya que contiene información confidencial.
Obtención de un token de acceso de OAuth 2.0
En primer lugar, usamos el ID de cliente y el secreto del registro de la aplicación OAuth 2.0, así como el token de actualización que obtuvimos en el paso anterior para obtener un token de acceso.
CONFIG_LOCATION = 'config.json'
AUTH_SERVICE = 'https://auth.brightspace.com/'
...
def get_config():
con open(CONFIG_LOCATION, 'r') como f:
return json.load(f)
def trade_in_refresh_token(config):
# https://tools.ietf.org/html/rfc6749#section-6
respuesta = requests.post(
'{}/core/connect/token'.format(AUTH_SERVICE),
# Tipo de contenido 'application/x-www-form-urlencoded'
datos={
«grant_type»: «refresh_token»,
'refresh_token': config['refresh_token'],
'scope': 'core:*:*'
},
auth=HTTPBasicAuth(config['client_id'], config['client_secret'])
)
Si response.status_code != 200:
logger.error('Código de estado: %s; contenido: %s', response.status_code, respuesta.texto)
response.raise_for_status()
return response.json()
def put_config(config):
con open(CONFIG_LOCATION, 'w') como f:
json.dump(config, f, sort_keys=Verdadero)
config = get_config()
token_response = trade_in_refresh_token(config)
# Almacene el nuevo token de actualización para obtener un nuevo token de acceso next runconfig['refresh_token'] = token_response['refresh_token']put_config(config)
Los tokens de actualización son de un solo uso. Sin embargo, obtenemos un nuevo token de actualización con la respuesta del token de acceso, por lo que almacenamos el nuevo token de actualización en config.json para la próxima ejecución.
Descarga de los conjuntos de datos
Los conjuntos de datos que nos interesan se definen en la parte superior del script:
DATA_SET_METADATA = [
DataSetMetadata(
plugin='07a9e561-e22f-4e82-8dd6-7bfb14c91776',
tabla='org_units'
),
DataSetMetadata(
plugin='793668a8-2c58-4e5e-b263-412d28d5703f',
tabla='grade_objects'
),
DataSetMetadata(
plugin='1d6d722e-b572-456f-97c1-d526570daa6b',
tabla='usuarios'
),
DataSetMetadata(
plugin='9d8a96b4-8145-416d-bd18-11402bc58f8d',
tabla='grade_results'
)]
Para cada conjunto de datos, llamamos a Brightspace para obtener el conjunto de datos comprimido:
API_VERSION = 1,15
...
endpoint = '{bspace_url}/d2l/api/lp/{lp_version}/dataExport/bds/download/{plugin_id}'.format(
bspace_url=config['bspace_url'],
lp_version=API_VERSION,
plugin_id=complemento
)
headers = {'Autorización': 'Portador {}'.format(token_response['access_token'])}
respuesta = requests.get(endpoint, headers=headers)
Extracción del archivo CSV
Para extraer el conjunto de datos, cargamos el archivo zip en la memoria. Las secuencias se utilizan para evitar cargar todo el CSV descomprimido en la memoria a la vez.
Solo debe haber un archivo en el archivo.
Con IO. BytesIO(response.content) como response_stream:
con archivo zip. ZipFile(response_stream) como zipped_data_set:
archivos = zipped_data_set.nombrelista()
assert len(ficheros) == 1
csv_name = archivos[0]
con zipped_data_set.open(csv_name) como csv_data:
update_db(db_conn_params, tabla csv_data)
Actualización de la base de datos
Para actualizar la base de datos, primero cargamos todo el conjunto de datos en una tabla temporal:
cur.execute(
'''
CREATE TEMP TABLE tmp_{table} COMO
ESCOGER*
DE {tabla}
LÍMITE 0;
'''
.formato(tabla=tabla)
)
cur.copy_expert(
'''
COPIAR tmp_{tabla}
DE STDIN
CON (FORMATO CSV, ENCABEZADO);
'''
.formato(tabla=tabla),
csv_data
)
Nota: la sintaxis aquí es para PostgreSQL, y puede diferir dependiendo del motor SQL que esté utilizando.
Realizamos un upsert (insertar o actualizar) a la tabla principal cargando la consulta dentro Esquema/Upserts y ejecutándolo. Esta consulta upsert leerá todos los datos en el archivo TEMP tabla y actualizarla o insertarla en la tabla principal en función de la CLAVE PRINCIPAL.
upsert_query_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'esquema',
'upserts',
'upsert_{tabla}.sql'.format(tabla=tabla)
)
con open(upsert_query_file) como upsert_query:
cur.execute(upsert_query.read())
A continuación, se muestra un ejemplo de consulta upsert para PostgreSQL:
INSERTAR EN usuarios
ESCOGER
user_id,
user_name,
org_defined_id,
first_name,
middle_name,
last_name,
is_active,
Organización
internal_email,
external_email,
signup_date
DESDE tmp_users
SOBRE EL CONFLICTO, SOBRE LA users_pkey DE RESTRICCIÓN
CONJUNTO DE ACTUALIZACIÓN
user_name = EXCLUDED.user_name,
org_defined_id = EXCLUDED.org_defined_id,
first_name = EXCLUDED.first_name,
middle_name = EXCLUDED.middle_name,
last_name = EXCLUDED.last_name,
is_active = EXCLUDED.is_active,
organización = EXCLUIDO.organización,
internal_email = EXCLUDED.internal_email,
external_email = EXCLUDED.external_email,
signup_date = EXCLUDED.signup_date
Otros motores SQL pueden diferir ligeramente al ofrecer FUSIONAR En lugar de SOBRE EL CONFLICTO.
Finalmente, limpiamos soltando el TEMP y confirmando la transacción:
cur.execute('DROP TABLE tmp_{table}'.format(table=table))
...
conn.commit()
Trabajo programado
Este script se puede volver a ejecutar y se puede activar a diario. Junto con la tarea programada de BDS, una ejecución programada de este script mantendrá la base de datos actualizada con los datos más recientes.
Conclusión
Hemos mostrado cómo actualizar mediante programación una base de datos con un conjunto determinado de extractos diarios de BDS. Estos pasos eliminan la necesidad de actualizar manualmente la base de datos a medida que se actualizan las extracciones, lo que proporciona más tiempo para que pueda centrarse en el análisis de los datos.