Compartir a través de


TripPin, parte 5: paginación

En este tutorial de varias partes se describe la creación de una nueva extensión de origen de datos para Power Query. El tutorial está diseñado para realizarse secuencialmente: cada lección se basa en el conector creado en las lecciones anteriores, agregando incrementalmente nuevas funcionalidades al conector.

En esta lección:

  • Añadir soporte de paginación al conector

Muchas API REST devuelven datos en "páginas", lo que requiere que los clientes realicen múltiples solicitudes con el fin de combinar los resultados. Aunque hay algunas convenciones comunes para la paginación (como RFC 5988), suele variar de API a API. Afortunadamente, TripPin es un servicio OData y el estándar OData define una manera de realizar la paginación mediante los valores de odata.nextLink devueltos en el cuerpo de la respuesta.

Para simplificar las iteraciones anteriores del conector, la TripPin.Feed función no es sensible a las páginas. Simplemente analiza el código JSON que se devolvió de la solicitud y lo ha formateado como una tabla. Si está familiarizado con el protocolo OData, es posible que observe que se realizaron muchas suposiciones incorrectas en el formato de la respuesta (por ejemplo, suponiendo que haya un value campo que contenga una matriz de registros).

En esta lección, mejorarás la lógica de control de respuestas adaptándola a la página. Los tutoriales futuros hacen que la lógica de control de páginas sea más sólida y capaz de controlar varios formatos de respuesta (incluidos los errores del servicio).

Nota:

No es necesario implementar su propia lógica de paginación con conectores basados en OData.Feed, ya que lo controla todo automáticamente.

Verificación de paginación

Al implementar la compatibilidad con la paginación, debe conocer lo siguiente sobre la API:

  • ¿Cómo solicita la siguiente página de datos?
  • ¿El mecanismo de paginación implica calcular valores o extraer la dirección URL de la página siguiente de la respuesta?
  • ¿Cómo sabes cuándo detener la paginación?
  • ¿Hay parámetros relacionados con la paginación que debe tener en cuenta (por ejemplo, "tamaño de página")?

La respuesta a estas preguntas influye en la forma en que implementa la lógica de paginación. Aunque hay cierta cantidad de reutilización de código en implementaciones de paginación (como el uso de Table.GenerateByPage), la mayoría de los conectores terminan necesitando lógica personalizada.

Nota:

Esta lección contiene lógica de paginación para un servicio OData, que sigue un formato específico. Consulte la documentación de la API para determinar los cambios que debe realizar en el conector para admitir el formato de paginación de la API.

Información general sobre la paginación de OData

La paginación de OData está controlada por anotaciones nextLink contenidas en la carga de respuesta. El valor nextLink contiene la dirección URL de la página siguiente de datos. Para determinar si hay otra página de datos, busque un campo odata.nextLink en el objeto más externo de la respuesta. Si no hay ningún odata.nextLink campo, se leen todos los datos.

{
  "odata.context": "...",
  "odata.count": 37,
  "value": [
    { },
    { },
    { }
  ],
  "odata.nextLink": "...?$skiptoken=342r89"
}

Algunos servicios de OData permiten a los clientes proporcionar una preferencia de tamaño máximo de página, pero es el servicio el que debe respetarlo o no. Power Query debe ser capaz de controlar las respuestas de cualquier tamaño, por lo que no es necesario preocuparse por especificar una preferencia de tamaño de página; puede admitir lo que el servicio produzca.

Puede obtener más información sobre paginación controlada por el servidor en la especificación de OData.

Probar TripPin

Antes de corregir la implementación de paginación, confirme el comportamiento actual de la extensión del tutorial anterior. La siguiente consulta de prueba recupera la tabla People y agrega una columna de índice para mostrar el recuento de filas actual.

let
    source = TripPin.Contents(),
    data = source{[Name="People"]}[Data],
    withRowCount = Table.AddIndexColumn(data, "Index")
in
    withRowCount

Active Fiddler y ejecute la consulta en el SDK de Power Query. La consulta devuelve una tabla con ocho filas (índice 0 a 7).

Captura de pantalla de la pestaña Salida de los resultados de PQTest que muestra la tabla con las filas de índice 0 a 7.

Si observa el cuerpo de la respuesta en fiddler, efectivamente contiene un campo @odata.nextLink, lo que indica que hay más páginas de datos disponibles.

{
  "@odata.context": "https://services.odata.org/V4/TripPinService/$metadata#People",
  "@odata.nextLink": "https://services.odata.org/v4/TripPinService/People?%24skiptoken=8",
  "value": [
    { },
    { },
    { }
  ]
}

Implementación de un sistema de paginación para TripPin

Ahora realice los siguientes cambios en la extensión:

  1. Importe la función común Table.GenerateByPage .
  2. Agregue una GetAllPagesByNextLink función que use Table.GenerateByPage para pegar todas las páginas juntas.
  3. Agregue una GetPage función que pueda leer una sola página de datos.
  4. Agregue una GetNextLink función para extraer la siguiente dirección URL de la respuesta.
  5. Actualice TripPin.Feed para usar las nuevas funciones de lector de páginas.

Nota:

Como se indicó anteriormente en este tutorial, la lógica de paginación varía entre los orígenes de datos. La implementación aquí intenta dividir la lógica en funciones que deben ser reutilizables para los orígenes que usan los vínculos siguientes devueltos en la respuesta.

Table.GenerateByPage

Para combinar (potencialmente) varias páginas devueltas por el origen en una sola tabla, use Table.GenerateByPage. Esta función toma como argumento una getNextPage función que debe hacer exactamente lo que su nombre sugiere: capturar la siguiente página de datos. Table.GenerateByPage llama repetidamente a la función getNextPage, pasándole cada vez los resultados producidos la última vez que se llamó, hasta que null indique que no hay más páginas disponibles.

Dado que esta función no forma parte de la biblioteca estándar de Power Query, debe copiar su código fuente en el archivo .pq.

El cuerpo de la función GetAllPagesByNextLink implementa el argumento de la función getNextPage para Table.GenerateByPage. Llama a la función GetPage y recupera la dirección URL de la página siguiente de datos del campo NextLink del registro meta de la llamada anterior.

// Read all pages of data.
// After every page, we check the "NextLink" record on the metadata of the previous request.
// Table.GenerateByPage will keep asking for more pages until we return null.
GetAllPagesByNextLink = (url as text) as table =>
    Table.GenerateByPage((previous) => 
        let
            // if previous is null, then this is our first page of data
            nextLink = if (previous = null) then url else Value.Metadata(previous)[NextLink]?,
            // if NextLink was set to null by the previous call, we know we have no more data
            page = if (nextLink <> null) then GetPage(nextLink) else null
        in
            page
    );

Implementación de GetPage

La GetPage función usa Web.Contents para recuperar una sola página de datos del servicio TripPin y convierte la respuesta en una tabla. Pasa la respuesta de Web.Contents a la GetNextLink función para extraer la dirección URL de la página siguiente y la establece en el meta registro de la tabla devuelta (página de datos).

Esta implementación es una versión ligeramente modificada de la TripPin.Feed llamada de los tutoriales anteriores.

GetPage = (url as text) as table =>
    let
        response = Web.Contents(url, [ Headers = DefaultRequestHeaders ]),        
        body = Json.Document(response),
        nextLink = GetNextLink(body),
        data = Table.FromRecords(body[value])
    in
        data meta [NextLink = nextLink];

La GetNextLink función simplemente comprueba el cuerpo de la respuesta de un @odata.nextLink campo y devuelve su valor.

// In this implementation, 'response' is the parsed body of the response after the call to Json.Document.
// Look for the '@odata.nextLink' field and simply return null if it doesn't exist.
GetNextLink = (response) as nullable text => Record.FieldOrDefault(response, "@odata.nextLink");

Integrándolo todo

El último paso para implementar la lógica de paginación es actualizar TripPin.Feed para usar las nuevas funciones. Por ahora, estás simplemente llamando a través de GetAllPagesByNextLink, pero en tutoriales posteriores, vas a agregar nuevas capacidades (como aplicar un esquema y la lógica de parámetros de consulta).

TripPin.Feed = (url as text) as table => GetAllPagesByNextLink(url);

Si vuelve a ejecutar la misma consulta de prueba anterior en el tutorial, ahora debería ver el lector de páginas en acción. También debería ver que tiene 24 filas en la respuesta en lugar de ocho.

Captura de pantalla de la pestaña Salida del resultado de PQTest que muestra 24 filas de datos.

Si observa las solicitudes en Fiddler, ahora debería ver solicitudes independientes para cada página de datos.

Captura de pantalla de la salida de Fiddler con solicitudes independientes para cada página de datos.

Nota:

Es posible que observe solicitudes duplicadas para la primera página de datos del servicio, lo que no es ideal. La solicitud adicional es el resultado del comportamiento de comprobación de esquemas del motor M. Omitir este problema por ahora y resolverlo en el siguiente tutorial, donde se aplica un esquema explícito.

Conclusión

En esta lección se muestra cómo implementar la compatibilidad con la paginación para una API rest. Aunque la lógica probablemente varía entre las API, el patrón establecido aquí debe ser reutilizable con modificaciones menores.

En la siguiente lección, verá cómo aplicar un esquema explícito a los datos, yendo más allá de los tipos de datos simples text y number que obtiene de Json.Document.

Pasos siguientes

TripPin, parte 6: esquema