Blank rows displayed in DataTable

I create a Dash DataTable that updates the rows of the table based on a period of time retrieved via a datetimepicker, but the resulting rows are returned as blank rows, as shown here:

What could be causing that the resulting rows are being displayed in blank?

Here is my code:
# Importa las librerías necesarias para la encripción de datos
from hmac import new
from hashlib import md5

Importa la librerías necesarias para realizar consultas web

from requests import get, post

Importa la librería necesaria para manipular objetos de tipo fecha-tiempo

from datetime import datetime, timedelta

Importa la librería necesaría para manipular datos

from pandas import read_json

Importa las librerías necesarias para crear una web app de tipo dashboard para visualización de datos

from dash import Dash
from dash.dependencies import Input, Output
from dash_html_components import Div, H2, Img
from dash_core_components import Dropdown, DatePickerRange
from dash_table import DataTable

crea las variables necesarias para conectarse al servicio web de future time

url = ‘https://someurl.com/someapiv2/system/LoginUser
usr = ‘user’
pwd = ‘password’
pwdh = pwd.encode()
ts = str(datetime.utcnow().strftime(’%Y-%m-%d %H:%M:%S’)) # ; del datetime
msg = (usr + pwd + ts).encode()

crea la firma hash HMAC MD5 utilizando la contraseña como llave y la guarda en la variable digest

digest = new(pwdh, msg, md5).hexdigest()

Crea los argumentos necesarios para acceder al webservice y los asigna a la variable argumentos

arg = {‘LoginName’: usr,
‘Signature’: digest,
‘TimeStamp’: ts}

Establece la conexión al web service, utiliza el recurso /System/LoginUser y autentica al usuario,

guarda en la variable r el resultado de la conexión.

r = post(url, arg)

Obtiene el UserID

UserId = str(r.json()[‘Result’][11:13])

Crea las variable necesarias para consumir el servicio de Working Day

url = ‘https://someurl.net/someapiv2/WorkingDay/Get
Keywords = ‘’
StartDate = str((datetime.today() - timedelta(1)).strftime(’%Y-%m-%d’)) + ’ 00:00:00’
EndDate = str((datetime.today() - timedelta(1)).strftime(’%Y-%m-%d’)) + ’ 23:59:59’
Filter = ‘todos’
OrderBy = ‘’

Construye el nuevo mensaje a ser firmado

msg = (Keywords + StartDate + EndDate + Filter + OrderBy + UserId).encode()

Crea la firma hash HMAC MD5

digest = new(pwdh, msg, md5).hexdigest() # ; del new, pwdh, msg, md5

Crea los argumentos necesarios para acceder al webservice y los asigna a la variable argumentos

arg = {‘Keywords’: ‘’,
‘StartDate’: StartDate,
‘EndDate’: EndDate,
‘Filter’: Filter,
‘OrderBy’: ‘’,
‘UserID’: UserId,
‘Signature’: digest} # ; del Keywords, StartDate, EndDate, Filter, OrderBy, UserId, digest

Invoca mediante get el recurso /WorkingDay/Get

r = get(url, arg) # ; del url, arg

Convierte la cadena json del webservice a dataframe de pandas

df = read_json(r.json()[‘Result’]) # ; del r; read_json

Filtra las columnas necesarias para el reporte de faltas injustificadas

df = df.filter(items=[‘ExportId’, ‘Name’, ‘OU5’, ‘Date’, ‘TimeGroup’, ‘Exceptions’])

Renombra las columnas del reporte

df.columns = [‘Código de empleado’, ‘Nombre del empleado’,
‘Departamento’, ‘Fecha’, ‘Horario’, ‘Excepción’]

Inicializa la aplicación web

app = Dash(‘BI-UYEDA’)

Define las hojas de estilos externas a utilizar

external_css = [
https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css”, # Normalize the CSS
https://fonts.googleapis.com/css?family=Open+Sans|Roboto”, # Fonts
https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css”,
https://cdn.rawgit.com/TahiriNadia/styles/faf8c1c3/stylesheet.css”,
https://cdn.rawgit.com/TahiriNadia/styles/b1026938/custum-styles_phyloapp.css
]

Añade las hojas de estilos a la aplicación

for css in external_css:
app.css.append_css({“external_url”: css})

server = app.server

Define la estructura de la aplicación

app.layout = Div([
Div([
H2(“Inteligencia de negocios UYEDA”),
Img(src=“http://someurl.mx/common/images/some_logo_img.png”)],
className=“banner”),
Div([
Div([
], style={
‘margin’: ‘5px’,
‘text-align’: ‘center’,
‘vertical-align’: ‘middle’,
‘width’: ‘48%’,
‘display’: ‘inline-block’
}),
Div([
DatePickerRange(
id=‘fechas’,
start_date=str((datetime.today() - timedelta(1)).strftime(’%Y-%m-%d’)),
end_date=str((datetime.today() - timedelta(1)).strftime(’%Y-%m-%d’)),
display_format=‘DD/MM/YYYY’,
max_date_allowed=str((datetime.today() - timedelta(1)).strftime(’%Y-%m-%d’)),
min_date_allowed=‘2018/01/01’
),
], style={
‘margin’: ‘5px’,
‘text-align’: ‘center’,
‘vertical-align’: ‘middle’,
‘width’: ‘48%’,
‘display’: ‘inline-block’
})

],
    className="container"
),
Div([
    DataTable(id='table',
              columns=[{'name': i, 'id': i} for i in df.columns],
              data=[{}], #df.to_dict('rows'),
              sorting=True,
              style_table={'overflowX': 'scroll'},
              style_cell={'minWidth': '0px',
                          'maxWidth': '180px',
                          'whiteSpace': 'normal'},
              css=[{
                  'selector': '.dash-cell div.dash-cell-value',
                  'rule': 'display: inline; white-space: inherit; overflow: inherit; text-overflow: inherit;'
              }],
              virtualization=True,
              pagination_mode=True,
              n_fixed_rows=1,
              merge_duplicate_headers=True
              )
],
    className="container")

])

Crea las llamadas a la aplicación para actualizar las filas

@app.callback(
Output(‘table’, ‘data’),
[Input(‘fechas’, ‘start_date’),
Input(‘fechas’, ‘end_date’)]
)

Crea la función que actualiza las filas de la tabla

def act_filas(start_date, end_date):
“”"
Esta función actualiza las filas de un datatable
:param start_date: Cadena de caracteres que indica la fecha inicial del reporte
:param end_date: Cadena de caracteres que indica la fehca final del reporte
:return: Retorna las filas que actualizaran las filas del datatable
“”"
# crea las variables necesarias para conectarse al servicio web de future time
url = ‘https://someurl.net/someapiv2/system/LoginUser
usr = ‘user’
pwd = ‘password’
pwdh = pwd.encode()
ts = str(datetime.utcnow().strftime(’%Y-%m-%d %H:%M:%S’)) # ; del datetime
msg = (usr + pwd + ts).encode()

# crea la firma hash HMAC MD5 utilizando la contraseña como llave y la guarda en la variable digest
digest = new(pwdh, msg, md5).hexdigest()

# Crea los argumentos necesarios para acceder al webservice y los asigna a la variable argumentos
arg = {'LoginName': usr,
       'Signature': digest,
       'TimeStamp': ts}

# Establece la conexión al web service, utiliza el recurso /System/LoginUser y autentica al usuario,
# guarda en la variable r el resultado de la conexión.
r = post(url, arg)

# Obtiene el UserID
UserId = str(r.json()['Result'][11:13])

# Crea las variable necesarias para consumir el servicio de Working Day
url = 'https://someurl.net/someapiv2/WorkingDay/Get'
Keywords = ''
StartDate = start_date + ' 00:00:00'
EndDate = end_date + ' 23:59:59'
Filter = 'todos'
OrderBy = ''

# Construye el nuevo mensaje a ser firmado
msg = (Keywords + StartDate + EndDate + Filter + OrderBy + UserId).encode()

# Crea la firma hash HMAC MD5
digest = new(pwdh, msg, md5).hexdigest()  # ; del new, pwdh, msg, md5

# Crea los argumentos necesarios para acceder al webservice y los asigna a la variable argumentos
arg = {'Keywords': '',
       'StartDate': StartDate,
       'EndDate': EndDate,
       'Filter': Filter,
       'OrderBy': '',
       'UserID': UserId,
       'Signature': digest}  # ; del Keywords, StartDate, EndDate, Filter, OrderBy, UserId, digest

# Invoca mediante get el recurso /WorkingDay/Get
r = get(url, arg)  # ; del url, arg

# Convierte la cadena json del webservice a dataframe de pandas
df = read_json(r.json()['Result'])

# Reemplaza los valores nulos por caracter vacio
df.fillna('', None, None, True)

# Descarta los registros correspondientes a practicantes, del cómputo de faltas
df.drop(df[df['TimeGroup'] == 'Practicantes'].index, inplace=True)

# Descarta los registros correspondientes a horarios libres, del cómputo de faltas
df.drop(df[df['TimeGroup'] == 'Libre'].index, inplace=True)

# Descarta los registros correspondientes a descansos, del cómputo de faltas
df.drop(df[df['ProjectedTimeToWork'].str.contains('00:00:00')].index, inplace=True)

# Descarta los registros correspondientes a trabajadores borrados, del cómputo de faltas
df.drop(df[df['Name'].str.contains('Borrado')].index, inplace=True)

# Descarta los registros correspondientes a visitantes, del cómputo de faltas
df.drop(df[df['Name'].str.contains('Visitante')].index, inplace=True)

# Descarta los registros correspondientes a personal directivo, del cómputo de faltas
df.drop(df[df['Name'].str.contains('Director')].index, inplace=True)

# Descarta los registros correspondientes a trabajadores borrados, del cómputo de faltas
df.drop(df[df['Name'].str.contains('New node')].index, inplace=True)

# Descarta los registros correspondientes a personal de soporte y pruebas, del cómputo de faltas
df.drop(df[df['Name'].str.contains('Soporte y Pruebas')].index, inplace=True)

# Descarta los registros correspondientes a Incapacidad IMSS Riesgo de Trabajo, del cómputo de faltas
df.drop(df[df['Exceptions'].str.contains('[057]')].index, inplace=True)

# Descarta los registros correspondientes a permiso sin goce de sueldo 069, del cómputo de faltas
df.drop(df[df['Exceptions'].str.contains('Permiso Sin Goce de Sueldo')].index, inplace=True)

# Descarta los registros correspondientes a Día de castigo 069, del cómputo de faltas
df.drop(df[df['Exceptions'].str.contains('Día de castigo')].index, inplace=True)

# Descarta los registros correspondientes a vacaciones, del cómputo de faltas
df.drop(df[df['Exceptions'].str.contains('[801]')].index, inplace=True)

# Descarta los registros correspondientes a falta con goce de sueldo 800, del cómputo de faltas
df.drop(df[df['Exceptions'].str.contains('falta con goce de sueldo')].index, inplace=True)

# Descarta los registros correspondientes a dias feriados, del cómputo de faltas
df.drop(df[df['Exceptions'].str.contains('Dia No Laborable General de Ley día feriado')].index, inplace=True)

# Descarta los registros correspondientes a Incapacidad IMSS Enfermedad General 060, del cómputo de faltas
df.drop(df[df['Exceptions'].str.contains('Incapacidad IMSS Enfermedad General')].index, inplace=True)

# Descarta los registros correspondientes a omisión de registro de entrada
df.drop(df[df['InHour'].str[11:16] > '00:00'].index, inplace=True)

# Filtra las columnas necesarias para el reporte de faltas injustificadas
df = df.filter(items=['ExportId', 'Name', 'OU5', 'Date', 'TimeGroup', 'Exceptions'])

# Renombra las columnas del reporte
df.columns = ['Código de empleado', 'Nombre del empleado',
              'Departamento', 'Fecha', 'Horario', 'Excepción']

# Envía los resultados del DataFrame a las filas del data table
data = [{'name': i, 'id': i} for i in df.to_dict('rows')]

# Retorna los registros para actualizar la tabla
return data

if name == ‘main’:
app.run_server(debug=True)