Última actualización: 13 de diciembre de 2023

5.1. Streamlit

Nota

Propósito: es una libraría permite la creación de Dashboard interactivos.

Es un framework de aplicación de código abierto (un paquete de Python) que nos brinda el poder de crear aplicaciones atractivas sin ningún conocimiento de desarrollo de front-end.

Esto nos libera de involucrarnos en cualquier marco de trabajo front-end o codificación en HTML, CSS y JavaScript.

Utiliza Python puro para desarrollar su front-end.

../_images/streamlit_logo.png

Figura 5.1, Figura 5.1, Logotipo de librería Streamlit

5.1.1. ¿Cuándo usar Streamlit?

En primer lugar, si está codificando secuencias de comandos de Python que se ejecutan regularmente en una máquina con un programador de trabajos como cron, Streamlit no es útil para usted.

Pero si está desarrollando una herramienta que desea compartir con los miembros de su equipo, por ejemplo, una aplicación de investigación de palabras clave, puede usar Streamlit.

Además, si necesita un método de autenticación de usuario, la comunidad Streamlit desarrolló un paquete que puede manejarlo por usted.

5.1.2. Instalación

Para instalar el paquete streamlit ejecute el siguiente comando, el cual a continuación se presentan el correspondiente comando de tu sistema operativo:

$ pip install streamlit

Puede probar si la instalación se realizo correctamente, ejecutando el siguiente comando correspondiente a tu sistema operativo:

$ python -c "import streamlit ; print(streamlit.__version__)"

Si muestra el numero de la versión instalada de streamlit, tiene correctamente instalada la paquete. Con esto, ya tiene todo listo para continuar.

5.1.3. Práctica - Hello

A continuación se presenta la ejecución de una demostración que incluye Streamlit, ejecutando el siguiente comando correspondiente a tu sistema operativo:

$ streamlit hello

El anterior comando al ejecutar debe mostrar el siguiente mensaje:

Welcome to Streamlit. Check out our demo in your browser.

Local URL: http://localhost:8501
Network URL: http://172.28.94.109:8501

Ready to create your own Python apps super quickly?
Head over to https://docs.streamlit.io

May you create awesome apps!
Local URL

Dirección local de tu PC donde ejecuta esta demostración, valor por defecto es http://localhost:8501

Network URL

Dirección de la red local de tu PC donde donde puede compartir la forma como accede a esta demostración.

Abra el navegador web en la dirección local definida en el valor Local URL:

../_images/streamlit_hello_index.png

Figura 5.2, Figura 5.2, Streamlit Hello - Welcome to Streamlit! 👋


../_images/streamlit_hello_animation_demo.png

Figura 5.3, Figura 5.3, Streamlit Hello - Animation Demo

Esta aplicación muestra cómo puede usar Streamlit para crear animaciones geniales. Muestra un fractal animado basado en el conjunto de Julia. Utilice el control deslizante para ajustar diferentes parámetros.


../_images/streamlit_hello_plotting_demo.png

Figura 5.4, Figura 5.4, Streamlit Hello - Plotting Demo

Esta demostración ilustra una combinación de Plotting y animación con Streamlit. En este ejemplo se genera un montón de números aleatorios en un bucle durante unos 5 segundos.


../_images/streamlit_hello_mapping_demo.png

Figura 5.5, Figura 5.5, Streamlit Hello - Mapping Demo

Esta demostración muestra cómo usar st.pydeck_chart para mostrar datos geoespaciales.


../_images/streamlit_hello_dataframe_demo.png

Figura 5.6, Figura 5.6, Streamlit Hello - DataFrame Demo

Esta demostración muestra cómo usar st.write para visualizar Pandas DataFrames. (Datos cortesía de UN Data Explorer).

5.1.4. Práctica - Hello World

A continuación se presenta una práctica del famoso Hello World usando con Streamlit, a continuación la estructura de proyecto llamado hello_word:

hello_word/
├── __init__.py
├── app.py
└── requirements.txt

A continuación se presenta y explica el uso de cada archivo para esta proyecto:

Archivo app.py

Módulo de principal del programa.

1
2
3
import streamlit as st

st.write("Hello, World!")

Archivo requirements.txt

Archivo de requirements.txt de la herramienta de gestión de paquetes pip.

1
streamlit==1.15.2

A continuación se presenta la ejecución de una demostración personalizada de Hello World, ejecutando los siguientes comandos correspondiente a tu sistema operativo:

$ pip install -r hello_word/requirements.txt
$ streamlit run hello_word/app.py

El anterior comando al ejecutar debe mostrar el siguiente mensaje:

You can now view your Streamlit app in your browser.

Local URL: http://localhost:8501
Network URL: http://172.28.94.109:8501
Local URL

Dirección local de tu PC donde ejecuta esta demostración, valor por defecto es http://localhost:8501

Network URL

Dirección de la red local de tu PC donde donde puede compartir la forma como accede a esta demostración.

Abra el navegador web en la dirección local definida en el valor Local URL:

../_images/streamlit_hello_world.png

Figura 5.7, Figura 5.7, Streamlit - Hello World

De esta forma tiene una práctica real de crear un mensaje Hello World y usado la librería Streamlit.

5.1.5. Práctica - Dashboard

A continuación se presenta una práctica de un Dashboard de análisis de ventas de tres (03) sucursales a nivel nacional de una cadena de supermercado usando con Streamlit, a continuación la estructura de proyecto llamado dashboard:

dashboard/
├── app.py
├── __init__.py
├── requirements.txt
├── .streamlit
│   └── config.toml
└── ventas_supermercado.xlsx

A continuación se presenta y explica el uso de cada archivo para esta proyecto:

Archivo app.py

Módulo de principal del programa.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# @Email:  leonardocaballero@gmail.com
# @Website:  https://entrenamiento-data-scientist-python.readthedocs.io/
# @Github:  https://github.com/macagua
# @Project:  Tablero de Ventas con Streamlit


import os
import pandas as pd
import plotly.express as px
import streamlit as st

FULL_PATH = (
    os.path.dirname(os.path.abspath(__file__)) + os.sep + "ventas_supermercado.xlsx"
)

# emojis: https://www.webfx.com/tools/emoji-cheat-sheet/
st.set_page_config(
    page_title="Tablero de Ventas", page_icon=":bar_chart:", layout="wide"
)

# ---- LEER ARCHIVO EXCEL ----
@st.cache
def get_data_from_excel():
    df = pd.read_excel(
        io=FULL_PATH,
        engine="openpyxl",
        sheet_name="Ventas",
        skiprows=3,
        usecols="B:R",
        nrows=1000,
    )
    # Agregar columna 'hour' al dataframe
    df["hour"] = pd.to_datetime(df["Time"], format="%H:%M:%S").dt.hour
    df.rename(columns={"hour": "Hora"}, inplace=True)
    return df


df = get_data_from_excel()

# ---- BARRA LATERAL ----
st.sidebar.header("Por favor, filtre aquí:")
ciudad = st.sidebar.multiselect(
    "Seleccione la Ciudad:",
    options=df["Ciudad"].unique(),
    default=df["Ciudad"].unique(),
)

tipo_cliente = st.sidebar.multiselect(
    "Seleccione el tipo de Cliente:",
    options=df["Tipo_Cliente"].unique(),
    default=df["Tipo_Cliente"].unique(),
)

genero = st.sidebar.multiselect(
    "Seleccione el Genero:",
    options=df["Genero"].unique(),
    default=df["Genero"].unique(),
)

df_seleccion = df.query(
    "Ciudad == @ciudad & Tipo_Cliente ==@tipo_cliente & Genero == @genero"
)
df_seleccion.rename(columns={"Linea producto": "Línea de producto"}, inplace=True)

# ---- PAGINA PRINCIPAL ----
st.title(":bar_chart: Tablero de Ventas")
st.markdown("##")

# KPI PRINCIPALES
total_ventas = int(df_seleccion["Total"].sum(numeric_only=True))
puntuacion_media = round(df_seleccion["Clasificacion"].mean(), 1)
puntuacion_star = ":star:" * int(round(puntuacion_media, 0))
ventas_promedio_por_transaccion = round(df_seleccion["Total"].mean(), 2)

columna_izquierda, columna_media, columna_derecha = st.columns(3)
with columna_izquierda:
    st.subheader("Total de Ventas:")
    st.subheader(f"Bs. {total_ventas:,}")
with columna_media:
    st.subheader("Puntuación media:")
    st.subheader(f"{puntuacion_media} {puntuacion_star}")
with columna_derecha:
    st.subheader("Ventas promedio por transacción:")
    st.subheader(f"Bs. {ventas_promedio_por_transaccion}")

st.markdown("""---""")

# VENTAS POR LÍNEA DE PRODUCTO [GRÁFICO DE BARRAS]
ventas_por_linea_producto = (
    df_seleccion.groupby(by=["Línea de producto"])
    .sum(numeric_only=True)[["Total"]]
    .sort_values(by="Total")
)
fig_ventas_producto = px.bar(
    ventas_por_linea_producto,
    x="Total",
    y=ventas_por_linea_producto.index,
    orientation="h",
    title="<b>Ventas por Línea de Producto</b>",
    color_discrete_sequence=["#0083B8"] * len(ventas_por_linea_producto),
    template="plotly_white",
)
fig_ventas_producto.update_layout(
    plot_bgcolor="rgba(0,0,0,0)", xaxis=(dict(showgrid=False))
)

# VENTAS POR HORA [GRÁFICO DE BARRAS]
ventas_por_horas = df_seleccion.groupby(by=["Hora"]).sum(numeric_only=True)[["Total"]]
fig_ventas_por_horas = px.bar(
    ventas_por_horas,
    x=ventas_por_horas.index,
    y="Total",
    title="<b>Ventas por hora</b>",
    color_discrete_sequence=["#0083B8"] * len(ventas_por_horas),
    template="plotly_white",
)
fig_ventas_por_horas.update_layout(
    xaxis=dict(tickmode="linear"),
    plot_bgcolor="rgba(0,0,0,0)",
    yaxis=(dict(showgrid=False)),
)


columna_izquierda, columna_derecha = st.columns(2)
columna_izquierda.plotly_chart(fig_ventas_por_horas, use_container_width=True)
columna_derecha.plotly_chart(fig_ventas_producto, use_container_width=True)


# ---- HIDE STREAMLIT STYLE ----
hide_st_style = """
            <style>
            #MainMenu {visibility: hidden;}
            footer {visibility: hidden;}
            header {visibility: hidden;}
            </style>
            """
st.markdown(hide_st_style, unsafe_allow_html=True)

Archivo .streamlit/config.toml

Archivo de configuración de proyecto Streamlit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[theme]
# Primary accent color for interactive elements.
primaryColor = "#E694FF"

# Background color for the main content area.
backgroundColor = "#00172B"

# Background color used for the sidebar and most interactive widgets.
secondaryBackgroundColor = "#0083B8"

# Color used for almost all text.
textColor = "#FFF"

# Font family for all text in the app, except code blocks. One of "sans serif", "serif", or "monospace".
# Default: "sans serif"
font = "sans serif"

Archivo requirements.txt

Archivo de requirements.txt de la herramienta de gestión de paquetes pip.

1
2
3
4
openpyxl==3.0.10
plotly==5.11.0
pandas==1.5.2
streamlit==1.15.2

Archivo ventas_supermercado.xlsx

Archivo de la hoja de calculo de Microsoft Excel llamado ventas_supermercado.xlsx la cual no se incluye ya que cada vez que se inicia el programa app.py, para cuidar la creación de los datos iniciales.

../_images/streamlit_ventas_supermercado_xlsx.png

Figura 5.8, Figura 5.8, Archivo de Excel ventas_supermercado.xlsx

A continuación se presenta la ejecución de una demostración personalizada de Tablero de Ventas, ejecutando los siguientes comandos correspondiente a tu sistema operativo:

$ pip install -r dashboard/requirements.txt
$ streamlit run dashboard/app.py

El anterior comando al ejecutar debe mostrar el siguiente mensaje:

You can now view your Streamlit app in your browser.

Local URL: http://localhost:8501
Network URL: http://172.28.94.109:8501
Local URL

Dirección local de tu PC donde ejecuta esta demostración, valor por defecto es http://localhost:8501

Network URL

Dirección de la red local de tu PC donde donde puede compartir la forma como accede a esta demostración.

Abra el navegador web en la dirección local definida en el valor Local URL:

../_images/streamlit_tablero_ventas.png

Figura 5.9, Figura 5.9, Streamlit - Tablero de Ventas

De esta forma tiene una práctica real de crear un Tablero de Ventas, usando fuente de datos desde una hoja de calculo de Excel y usado la librería Streamlit.

5.1.6. Práctica - SQLite CRUD

A continuación se presenta una práctica de un simple Blog que hace las operaciones de manipulación CRUD para generar analítica de las entradas del Blog con Streamlit, a continuación la estructura de proyecto llamado sqlite_crud:

sqlite_crud/
├── app.py
├── db_initial.py
├── db.py
├── .env.example
├── __init__.py
├── layouts.py
├── requirements.txt
├── settings.py
├── simple_blog.sqlite3
└── .streamlit
    └── config.toml

A continuación se presenta y explica el uso de cada archivo para esta proyecto:

Archivo app.py

Módulo de principal del programa.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
import datetime
import logging
import matplotlib as plt
import os
import pandas as pd
import sqlite3
import streamlit as st
from db import create_connection
from layouts import (
    BLOG_HEADER_HTML_TPL,
    ENTRY_DETAILS_HTML_TPL,
    ENTRY_CONTENT_MSG_HTML_TPL,
    HOME_ENTRIES_HTML_TPL,
    NOTFOUND_ENTRY_MSG_HTML_TPL,
    NOTFOUND_SEARCH_MSG_HTML_TPL,
)
from settings import (
    APP_TITLE,
    PRIMARY_COLOR,
    BACKGROUND_COLOR,
    SECONDARY_BACKGROUND_COLOR,
    TEXT_COLOR,
    DB,
    INSERT_SQL_SCRIPTS,
    SELECT_DISTINCT_SQL_SCRIPTS,
    SELECT_SQL_SCRIPTS,
)
from streamlit_quill import st_quill
from wordcloud import WordCloud

# Logging App
logging.basicConfig(level=logging.INFO)

# Streamlit
st.set_option('deprecation.showPyplotGlobalUse', False)
st.set_page_config(
    page_title=APP_TITLE, page_icon=":page_facing_up:", layout="wide"
)
st.markdown(
    '''<style type="text/css">
    #MainMenu{visibility: hidden;}
    footer{visibility: hidden;}
    /*#root>div:nth-child(1)>div>div>div>div>section>div{padding-top: 0rem;}*/
    </style>''',
    unsafe_allow_html=True
)

# Functions
def add_data(db_connection, sql_script, author, title, content, postdate):
    """Add a Blog entry

    Args:
        db_connection (Connection): SQLite database connection representation
        sql_script (str): SQL script to execute
        author (str): The Article author
        title (str): The Article title
        content (str): The Article body
        postdate (datetime): The Article post date
    """

    try:
        cursor = db_connection.cursor()
        cursor.execute(
            sql_script,
            (author, title, content, postdate),
        )
        db_connection.commit()
        logging.info(
            {} record(s) was(were) successfully added to the table!".format(
                cursor.rowcount
            )
        )
    except sqlite3.Error as error:
        logging.error(f"Query for record(s) in table failed! {error}")
    finally:
        if cursor:
            cursor.close()
            logging.info("The cursor for add a Blog entry was closed successfully!")


def view_all_articles(db_connection, sql_script):
    """View all Blog Articles

    Args:
        db_connection (Connection): SQLite database connection representation
        sql_script (str): SQL script to execute

    Returns:
        list: Records list
    """

    records = []
    try:
        cursor = db_connection.cursor()
        cursor.execute(sql_script)
        records = cursor.fetchall()
        logging.info("The query to the table was successful!")
    except sqlite3.Error as error:
        logging.error(f"Query for record(s) in table failed! {error}")
    finally:
        if cursor:
            cursor.close()
            logging.info("The cursor for view all Blog articles was closed successfully!")
    return records


def view_all_article_titles(db_connection, sql_script):
    """View all Titles Blog Articles

    Args:
        db_connection (Connection): SQLite database connection representation
        sql_script (str): SQL script to execute

    Returns:
        list: Records list
    """

    records = []
    try:
        cursor = db_connection.cursor()
        cursor.execute(sql_script)
        records = cursor.fetchall()
        logging.info("The query to the table was successful!")
    except sqlite3.Error as error:
        logging.error(f"Query for record(s) in table failed! {error}")
    finally:
        if cursor:
            cursor.close()
            logging.info("The cursor for view all titles Blog articles was closed successfully!")
    return records


def get_blog_by_title(db_connection, title):
    """Get the title of Blog article

    Args:
        db_connection (Connection): SQLite database connection representation
        title (str): The Article title

    Returns:
        list: Records list
    """

    records = []
    try:
        cursor = db_connection.cursor()
        cursor.execute(f'SELECT * FROM articles WHERE title="{title}"')
        records = cursor.fetchall()
        logging.info("The query to the table by title was successful!")
    except sqlite3.Error as error:
        logging.error(f"Query for record(s) in table failed! {error}")
    finally:
        if cursor:
            cursor.close()
            logging.info("The cursor for get the title of Blog article was closed successfully!")
    return records


def get_blog_by_author(db_connection, author):
    """Get all Blog articles by author

    Args:
        db_connection (Connection): SQLite database connection representation
        author (str): The Article author

    Returns:
        list: Records list
    """

    records = []
    try:
        cursor = db_connection.cursor()
        cursor.execute(f'SELECT * FROM articles WHERE author="{author}"')
        records = cursor.fetchall()
        logging.info("The query to the table by author was successful!")
    except sqlite3.Error as error:
        logging.error(f"Query for record(s) in table failed! {error}")
    finally:
        if cursor:
            cursor.close()
            logging.info("The cursor for get all Blog articles by author was closed successfully!")
    return records


# def update_data(db_connection, author, title, content, postdate):
#     """Update the Blog article

#     Args:
#         db_connection (Connection): SQLite database connection representation
#         author (str): The Article author
#         title (str): The Article title
#         content (str): The Article body
#         postdate (datetime): The Article post date
#     """
#     pass


def delete_data(db_connection, title):
    """Delete the Blog article

    Args:
        db_connection (Connection): SQLite database connection representation
        title (str): The Article title
    """

    try:
        cursor = db_connection.cursor()
        cursor.execute(f'DELETE FROM articles WHERE title="{title}"')
        db_connection.commit()
        logging.info("The selected record was successfully deleted!")
    except sqlite3.Error as error:
        logging.error(f"Delete record(s) in table failed! {error}")
    finally:
        if cursor:
            cursor.close()
            logging.info("The cursor for delete the blog article was closed successfully!")


def render_about(*_) -> None:
    """Show App info
    """

    st.write("""\
# Streamlit App Demo - {app_title}
Howdy :wave:!
Welcome to my Streamlit Full Stack App exploration.
This started as the {app_title} Fullstack App with just Streamlit + SQLite.
""".format(app_title=APP_TITLE))


def main(db_connection):
    """A Simple CRUD Blog

    Args:
        db_connection (Connection): SQLite database connection representation
    """

    st.markdown(
        BLOG_HEADER_HTML_TPL.format(
            div_bg_color=SECONDARY_BACKGROUND_COLOR,
            h1_color=TEXT_COLOR,
            app_title=APP_TITLE
        ),
        unsafe_allow_html=True
    )

    menu = [
        "Home",
        "View Posts",
        "Add Posts",
        # "Update Posts",
        "Search...",
        "Manage Blog",
        "About"
    ]

    choice = st.sidebar.selectbox(
        "Menu", menu,
        help="Select a choice from the select list!"
    )

    if choice == "Home":
        st.subheader("Home")
        result = view_all_articles(db_connection, SELECT_SQL_SCRIPTS)

        if len(result) == 0:
            st.markdown(
                NOTFOUND_ENTRY_MSG_HTML_TPL,
                unsafe_allow_html=True
            )
        else:
            for article in result:
                post_author = article[0]
                post_title = article[1]
                post_content = str(article[2])[0:90]
                post_date = article[3]
                st.markdown(
                    HOME_ENTRIES_HTML_TPL.format(
                        div_bg_color=PRIMARY_COLOR,
                        title=post_title,
                        author=post_author,
                        date=post_date,
                        content=post_content
                    ),
                    unsafe_allow_html=True,
                )

    elif choice == "View Posts":
        st.subheader("View Articles")
        all_titles = [
            article[0] for article in view_all_article_titles(
                db_connection, SELECT_DISTINCT_SQL_SCRIPTS
            )
        ]
        post_list = st.sidebar.selectbox(
            "View Posts", all_titles,
            help="Select a choice from the select list!"
        )
        post_result = get_blog_by_title(db_connection, post_list)

        if len(all_titles) == 0:
            st.markdown(
                NOTFOUND_ENTRY_MSG_HTML_TPL,
                unsafe_allow_html=True
            )
        else:
            for article in post_result:
                post_author = article[0]
                post_title = article[1]
                post_content = article[2]
                post_date = article[3]
                # st.text(f"Reading Time:{readingTime(post_content)}")
                st.markdown(
                    ENTRY_DETAILS_HTML_TPL.format(
                        div_bg_color=PRIMARY_COLOR,
                        title=post_title,
                        author=post_author,
                        date=post_date
                    ),
                    unsafe_allow_html=True,
                )
                st.markdown(
                    ENTRY_CONTENT_MSG_HTML_TPL.format(
                        content=post_content
                    ),
                    unsafe_allow_html=True
                )

    elif choice == "Add Posts":
        st.subheader("Add Articles")
        blog_author = st.text_input(
            label="Enter Author Name",
            help="Please, enter the Author Name",
            placeholder="Example, Leonardo Caballero",
            max_chars=50
        )
        blog_title = st.text_input(
            label="Enter Post Title",
            help="Please, enter the Post Title",
            placeholder="Example, Data science for Python",
        )
        blog_content = st_quill(
            #value='',
            placeholder='Example, Python is very great option for Data science today!',
            html=True,
        )
        blog_post_date = st.date_input(
            label="Pick publication date",
            help="Please, pick the publication date",
            value=datetime.date.today()
        )
        if st.button(
            label="Add entry post",
            help="Here you safe a new entry post!",
        ):
            add_data(db_connection, INSERT_SQL_SCRIPTS, blog_author, blog_title, blog_content, blog_post_date)
            st.success(f"Post: {blog_title} saved")

    # elif choice == "Update Posts":
    #     unique_titles = [article[0] for article in view_all_article_titles(db_connection, SELECT_DISTINCT_SQL_SCRIPTS)]
    #     st.subheader("Update Posts")
    #     st.markdown("Select a article to delete on the follow select list")
    #     update_blog_by_title = st.selectbox(
    #         "Unique Title", unique_titles,
    #         help="Select a choice from the select list!"
    #     )
    #     if st.button(
    #         label="Update (This Can't Be Undone!)",
    #         help="I hope you know what you're getting into!",
    #     ):
    #         update_data(db_connection, update_blog_by_title)
    #         st.warning(f"Updated: '{update_blog_by_title}'")

    elif choice == "Search...":
        st.subheader("Search Articles")
        search_term = st.text_input(
            label="Enter Search Term",
            help="Enter the Search Term!",
            placeholder="Example fot title, Data science for Python or Example fot author, Leonardo Caballero",
        )
        search_choice = st.radio(
            label="Field to Search By",
            options=("title", "author"),
            help="Check one criteria option to search!",
            horizontal=True,
        )

        if st.button(
            label="Search",
            help="I hope you will found what do you search!",
        ):

            if search_choice == "title":
                article_result = get_blog_by_title(db_connection, search_term)
            elif search_choice == "author":
                article_result = get_blog_by_author(db_connection, search_term)

            if len(article_result) == 0:
                st.markdown(
                    NOTFOUND_SEARCH_MSG_HTML_TPL.format(
                        div_bg_color=PRIMARY_COLOR,
                    ),
                    unsafe_allow_html=True
                )
            else:
                for article in article_result:
                    post_author = article[0]
                    post_title = article[1]
                    post_content = article[2]
                    post_date = article[3]
                    # st.text(f"Reading Time:{readingTime(post_content)}")
                    st.markdown(
                        ENTRY_DETAILS_HTML_TPL.format(
                            div_bg_color=PRIMARY_COLOR,
                            title=post_title,
                            author=post_author,
                            date=post_date
                        ),
                        unsafe_allow_html=True,
                    )
                    st.markdown(
                        ENTRY_CONTENT_MSG_HTML_TPL.format(
                            content=post_content
                        ),
                        unsafe_allow_html=True
                    )

    elif choice == "Manage Blog":
        st.subheader("Manage Articles")

        result = view_all_articles(db_connection, SELECT_SQL_SCRIPTS)

        if len(result) == 0:
            st.markdown(
                NOTFOUND_ENTRY_MSG_HTML_TPL.format(
                    div_bg_color=PRIMARY_COLOR,
                ),
                unsafe_allow_html=True
            )
        else:
            clean_db = pd.DataFrame(
                result, columns=["Author", "Title", "Articles", "Post Date"]
            )
            st.dataframe(clean_db)

            unique_titles = [
                article[0] for article in view_all_article_titles(
                    db_connection, SELECT_DISTINCT_SQL_SCRIPTS
                )
            ]

            st.subheader("Delete Article")
            st.markdown("Select a article to delete on the follow select list")
            delete_blog_by_title = st.selectbox(
                "Unique Title", unique_titles,
                help="Select a choice from the select list!"
            )
            new_df = clean_db
            if st.button(
                label="Delete (This Can't Be Undone!)",
                help="I hope you know what you're getting into!",
            ):
                delete_data(db_connection, delete_blog_by_title)
                st.warning(f"Deleted: '{delete_blog_by_title}'")

            st.subheader("Generate Graphics")
            st.markdown("Select the checkbox for generate graphics")
            if st.checkbox(
                label="Metrics",
                help="Check this option for generate the Metrics Stats!",
            ):

                new_df["Length"] = new_df["Articles"].str.len()
                st.dataframe(new_df)

                st.subheader("Author Stats")
                new_df["Author"].value_counts().plot(kind="bar")
                st.pyplot()

                st.subheader("Author Stats")
                new_df["Author"].value_counts().plot.pie(autopct="%1.1f%%")
                st.pyplot()

            if st.checkbox(
                label="Word Cloud",
                help="Check this option for generate a Term Word Cloud!",
            ):
                st.subheader("Generate Word Cloud")
                # text = new_df['Articles'].iloc[0]
                text = ",".join(new_df["Articles"])
                wordcloud = WordCloud().generate(text)
                plt.pyplot.imshow(wordcloud, interpolation="bilinear")
                plt.pyplot.axis("off")
                st.pyplot()

            if st.checkbox(
                label="Bar Horizontal Plot",
                help="Check this option for generate a Bar Horizontal Plot!",
            ):
                st.subheader("Length of Articles")
                new_df = clean_db
                new_df["Length"] = new_df["Articles"].str.len()
                barh_plot = new_df.plot.barh(x="Author", y="Length", figsize=(20, 10))
                st.pyplot()

    elif choice == "About":
        render_about()


if __name__ == "__main__":

    connection = create_connection(DB)
    if connection is not None:
        main(connection)
    else:
        logging.error(f"ERROR! cannot create the database connection to the {DB} file.")

Archivo db_initial.py

Módulo que agregar datos iniciales de la publicación de la entrada del blog.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import logging
import os
import sqlite3
from faker import Faker
from db import create_connection, create_table
from settings import (
    ARTICLES_RANGE,
    DB,
    INSERT_SQL_SCRIPTS,
    TABLE_SQL_SCRIPTS,
)

# Logging App
logging.basicConfig(level=logging.INFO)

# Faker
fake = Faker()

# Functions
def generate_data_fake(faker):
    """Generate data fake for articles table

    Args:
        faker (faker.proxy.Faker): Faker proxy object

    Returns:
        list: Data faker records list
    """
    article, articles = (), []
    for _ in range(ARTICLES_RANGE):
        article = (
            faker.name(),
            faker.sentence(),
            str(faker.texts()
            ).replace("['", ""
            ).replace("']", ""
            ).replace("\\n", "<br/>"
            ).replace(".', '", ". "),
            faker.past_date()
        )
        articles.append(article)
    return articles


def add_data_initial(db_connection, sql_script, sql_script_values):
    """Add data initial of Blog entry post

    Args:
        db_connection (Connection): SQLite database connection representation
        sql_script (str): SQL script to execute
        sql_script_values (list): Data faker records list

    Returns:
        list: Records list
    """

    try:
        cursor = db_connection.cursor()
        cursor.executemany(
            sql_script,
            sql_script_values
        )
        db_connection.commit()
        logging.info(
            {} record(s) was(were) successfully added to the table!".format(
                cursor.rowcount
            )
        )
    except sqlite3.Error as error:
        logging.error(f"The add of record(s) in the table failed! {error}")
    finally:
        if cursor:
            cursor.close()
            logging.info("The cursor for data initial of blog entry posts was closed successfully!")
        if db_connection:
            db_connection.close()
            logging.info("Disconnect to database 'simple_blog.sqlite3' was closed successfully!")


if __name__ == "__main__":

    connection = create_connection(DB)
    if connection is not None:
        create_table(connection, TABLE_SQL_SCRIPTS)
        add_data_initial(connection, INSERT_SQL_SCRIPTS, generate_data_fake(fake))
    else:
        logging.error(f"ERROR! cannot create the database connection to the {DB} file.")

Archivo db.py

Módulo de funciones de la base de datos SQLite.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
""" SQLite database functions """

import logging
import os
import sqlite3


def create_connection(path):
    """Create a database connection to a SQLite database

    Args:
        path (str): The full path used to read the database

    Returns:
        db_connection (Connection): SQLite database connection representation
    """

    db_connection = None
    if os.path.exists(path):
        try:
            db_connection = sqlite3.connect(path)
            logging.info(
                f"Connection to database '{os.path.basename(path)}' was successful!"
            )
        except sqlite3.ProgrammingError as e:
            print(f"ERROR: A programming error has occurred: '{e}'!")
        except sqlite3.OperationalError as e:
            print(f"ERROR: The following occurred: '{e}'")
        # finally:
        #     if db_connection:
        #         db_connection.close()
        #         logging.info("The database connection was closed successfully!")
    else:
        try:
            db_connection = sqlite3.connect(path)
            logging.info(
                f"Database creation '{os.path.basename(path)}' was successful!"
            )
            logging.info(
                f"Connection to database '{os.path.basename(path)}' was established successfully!"
            )
        except sqlite3.ProgrammingError as e:
            print(f"ERROR: A programming error has occurred: '{e}'!")
        except sqlite3.OperationalError as e:
            print(f"ERROR: The following occurred: '{e}'")
        # finally:
        #     if db_connection:
        #         db_connection.close()
        #         logging.info("The database connection was closed successfully!")

    return db_connection


def create_table(db_connection, sql_script):
    """Create Database table

    Args:
        db_connection (Connection): SQLite database connection representation
        sql_script (str): SQL script to execute
    """

    try:
        cursor = db_connection.cursor()
        cursor.execute(sql_script)
        db_connection.commit()
        logging.info("The table was created successfully!")
    except sqlite3.Error as error:
        logging.error(f"Table creation failed! {error}")
    finally:
        if cursor:
            cursor.close()
            logging.info("The cursor for create database table was closed successfully!")
    #     if db_connection:
    #         db_connection.close()
    #         logging.info("The database connection was closed successfully!")

Archivo .env.example

Archivo plantilla dotenv del paquete adicional python-dotenv.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
### SQLite ######################################
DB_FILE=simple_blog.sqlite3

### Simple Blog App #############################

# Application title name
APP_TITLE=Simple Blog

# Number of range of blog posts to create
ARTICLES_RANGE=3

# Primary accent color for interactive elements.
PRIMARY_COLOR=#A6BB8D

# Background color for the main content area.
BACKGROUND_COLOR=#3C6255

# Background color used for the sidebar and most interactive widgets.
SECONDARY_BACKGROUND_COLOR=#61876E

# Color used for almost all text.
TEXT_COLOR=#EAE7B1

Archivo layouts.py

Módulo de plantillas de diseño Streamlit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
""" Streamlit Layout Templates """

### "Header" Section #############################################################
BLOG_HEADER_HTML_TPL = """
<div style="background-color:{div_bg_color};padding:10px;border-radius:10px">
  <h1 style="color:{h1_color};text-align:center;">{app_title}</h1>
</div>
"""

### "Home" Item Menu ##############################################################
HOME_ENTRIES_HTML_TPL = """
<div style="background-color:{div_bg_color};padding:10px;border-radius:5px;margin:10px;">
  <h4 style="color:white;text-align:center;">{title}</h4>
  <img src="https://www.w3schools.com/howto/img_avatar.png" alt="Avatar" style="vertical-align:middle;width:50px;height:50px;border-radius:50%;"/>
  <h5 style="color:white;">Author: {author}</h5>
  <h6 style="color:white;">Post Date: {date}</h6>
  <p style="background-color:silver;color:black;text-align:justify">{content}...</p>
</div>
"""

### "View Posts / Search..." Item(s) Menu ########################################
ENTRY_DETAILS_HTML_TPL = """
<div style="background-color:{div_bg_color};padding:10px;border-radius:5px;margin:10px;">
  <h4 style="color:white;text-align:center;">{title}</h4>
  <img src="https://www.w3schools.com/howto/img_avatar.png" alt="Avatar" style="vertical-align:middle;float:left;width:50px;height:50px;border-radius:50%;"/>
  <h5 style="color:white;">Author: {author}</h5>
  <h6 style="color:white;">Post Date: {date}</h6>
</div>
"""

### "View Posts / Search..." Item(s) Menu ########################################
ENTRY_CONTENT_MSG_HTML_TPL = """
<div style="background-color:silver;overflow-x:auto;padding:10px;border-radius:5px;margin:10px;">
  <p style="text-align:justify;color:black;padding:10px">{content}</p>
</div>
"""

### "Home / View Posts / Manage Blog" Item(s) Menu ###############################
NOTFOUND_ENTRY_MSG_HTML_TPL = """
<div style="background-color:{div_bg_color};padding:10px;border-radius:5px;margin:10px;">
  <h4 style="color:white;text-align:center;">Not article items found!!!</h4>
  <p style="color:white;text-align:center;">Please go to 'Add Posts' form to add an blog article entry</p>
</div>
"""

### "Search..." Item(s) Menu #####################################################
NOTFOUND_SEARCH_MSG_HTML_TPL = """
<div style="background-color:{div_bg_color};padding:10px;border-radius:5px;margin:10px;">
  <h4 style="color:white;text-align:center;">Not article items found!!!</h4>
  <p style="color:white;text-align:center;">Try with other Search criteria</p>
</div>
"""

Archivo requirements.txt

Archivo de requirements.txt de la herramienta de gestión de paquetes pip.

1
2
3
4
5
6
7
Faker==15.3.3
streamlit==1.15.2
streamlit-quill==0.0.3
pandas==1.5.2
wordcloud==1.8.2.2
matplotlib==3.6.2
python-dotenv==0.21.0

Archivo settings.py

Módulo de configuración de «Simple Blog»

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
""" Simple Blog Settings """

import os
from dotenv import load_dotenv

### DotENV #################################################################
load_dotenv()

# Name of the database to which to connect.
DB_FILE = os.getenv("DB_FILE")

# Application title name
APP_TITLE = os.getenv("APP_TITLE")

# Number of range of blog posts to create
ARTICLES_RANGE = int(os.getenv("ARTICLES_RANGE"))

# Color Palette: #3C6255 #61876E #A6BB8D #EAE7B1
# Color Hunt https://colorhunt.co/palette/3c625561876ea6bb8deae7b1

# Primary accent color for interactive elements.
# using by Blog Entry Post Information Sheet
PRIMARY_COLOR = os.getenv("PRIMARY_COLOR")

# Background color for the main content area.
BACKGROUND_COLOR = os.getenv("BACKGROUND_COLOR")

# Background color used for the sidebar and most interactive widgets.
# using by Blog Header Main Title Color
SECONDARY_BACKGROUND_COLOR = os.getenv("SECONDARY_BACKGROUND_COLOR")

# Color used for almost all text.
# using by Blog Header Main Title Background Color
TEXT_COLOR = os.getenv("TEXT_COLOR")

### SQLite3 ################################################################

DB_PATH = os.path.dirname(os.path.abspath(__file__)) + os.sep

# DB File
if "DB_PATH" in globals() and "DB_FILE" in globals():
    DB = DB_PATH + DB_FILE

# Script CREATE TABLE SQL a usar al crear la tabla
TABLE_SQL_SCRIPTS = """
    CREATE TABLE IF NOT EXISTS articles (
        author TEXT NOT NULL,
        title TEXT NOT NULL UNIQUE,
        content TEXT NOT NULL,
        post_date DATE NOT NULL
    );
"""

# Script INSERT SQL a usar al ingresar datos
INSERT_SQL_SCRIPTS = """
    INSERT INTO articles
        (author, title, content, post_date)
    VALUES (?, ?, ?, ?);
"""

# SELECT SQL script to use when querying data
SELECT_SQL_SCRIPTS = """SELECT * FROM articles;"""

# SELECT DISTINCT SQL script to use when querying data by title
SELECT_DISTINCT_SQL_SCRIPTS = """SELECT DISTINCT title FROM articles;"""

Archivo simple_blog.sqlite3

Archivo de la hoja de calculo de SQLite llamado simple_blog.sqlite3 la cual no se incluye ya que cada vez que se inicia el programa app.py, para cuidar la creación de los datos iniciales.

Archivo .streamlit/config.toml

Archivo de configuración de proyecto Streamlit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
[theme]
# Primary accent color for interactive elements.
primaryColor = "#A6BB8D"

# Background color for the main content area.
backgroundColor = "#3C6255"

# Background color used for the sidebar and most interactive widgets.
secondaryBackgroundColor = "#61876E"

# Color used for almost all text.
textColor = "#EAE7B1"

# Font family for all text in the app, except code blocks. One of "sans serif", "serif", or "monospace".
# Default: "sans serif"
font = "sans serif"

[deprecation]
showPyplotGlobalUse = false

A continuación se presenta la ejecución de una demostración de Simple Blog, ejecutando los siguientes comandos correspondiente a tu sistema operativo:

Antes de ejecutar debes instalar sus dependencias, con el siguiente comando:

$ pip install -r requirements.txt

Ademas debe instalar y editar el archivo .env, con el siguiente comando:

$ cp .env.example .env
$ nano .env

Truco

El archivo .env se definen las configuraciones de conexión a la base de datos, puede modificarlo cambiar valores de la conexión.

Truco

Para ejecutar el código fuente de esta practica debe invocar primero al modulo db_initial.py, abra una consola de comando, acceda al directorio donde se encuentra la estructura previa y ejecute el siguiente comando:

$ python db_initial.py

El anterior código al ejecutar debe mostrar el siguiente mensaje:

INFO:root:Database creation 'simple_blog.sqlite3' was successful!
INFO:root:Connection to database 'simple_blog.sqlite3' was established successfully!
INFO:root:The table was created successfully!
INFO:root:The cursor for create database table was closed successfully!
INFO:root:¡3 record(s) was(were) successfully added to the table!
INFO:root:The cursor for data initial of blog entry posts was closed successfully!
INFO:root:Disconnect to database 'simple_blog.sqlite3' was closed successfully!

Truco

Para ejecutar el código fuente de esta practica debe invocar al modulo app.py, abra una consola de comando, acceda al directorio donde se encuentra la estructura previa y ejecute el siguiente comando:

$ streamlit run app.py

El anterior comando al ejecutar debe mostrar el siguiente mensaje:

You can now view your Streamlit app in your browser.

Local URL: http://localhost:8501
Network URL: http://172.28.94.109:8501

Nota

Local URL

Dirección local de tu PC donde ejecuta esta demostración, valor por defecto es http://localhost:8501

Network URL

Dirección de la red local de tu PC donde donde puede compartir la forma como accede a esta demostración.

Abra el navegador web en la dirección local definida en el valor Local URL:

../_images/streamlit_sqlite_crud_blog_index.png

Figura 5.10, Figura 5.10, Streamlit - SQLite CRUD - Home View


../_images/streamlit_sqlite_crud_blog_view.png

Figura 5.11, Figura 5.11, Streamlit - SQLite CRUD - View Posts


../_images/streamlit_sqlite_crud_blog_search.png

Figura 5.12, Figura 5.12, Streamlit - SQLite CRUD - Search by title or author


../_images/streamlit_sqlite_crud_blog_add.png

Figura 5.13, Figura 5.13, Streamlit - SQLite CRUD - Add Posts


../_images/streamlit_sqlite_crud_blog_manage.png

Figura 5.14, Figura 5.14, Streamlit - SQLite CRUD - Manage Blog


../_images/streamlit_sqlite_crud_blog_delete.png

Figura 5.15, Figura 5.15, Streamlit - SQLite CRUD - Delete Posts


../_images/streamlit_sqlite_crud_blog_graphic_metrics_author_bar_plot.png

Figura 5.16, Figura 5.16, Streamlit - SQLite CRUD - Generate Bar Plot Author Stats Metrics Graphic


../_images/streamlit_sqlite_crud_blog_graphic_metrics_author_pie_plot.png

Figura 5.17, Figura 5.17, Streamlit - SQLite CRUD - Generate Pie Plot Author Stats Metrics Graphic


../_images/streamlit_sqlite_crud_blog_graphic_wordcloud.png

Figura 5.18, Figura 5.18, Streamlit - SQLite CRUD - Generate Word Cloud Graphic


../_images/streamlit_sqlite_crud_blog_graphic_barhorizontalplot.png

Figura 5.19, Figura 5.19, Streamlit - SQLite CRUD - Generate Bar Horizontal Plot Graphic

De esta forma tiene una práctica real de crear un CRUD, usando fuente de datos una base de datos SQLite y usado la librería Streamlit.


Ver también

Consulte la sección de lecturas suplementarias del entrenamiento para ampliar su conocimiento en esta temática.


¿Cómo puedo ayudar?

¡Mi soporte está aquí para ayudar!

Mi horario de oficina es de lunes a sábado, de 9 AM a 5 PM. GMT-4 - Caracas, Venezuela.

La hora aquí es actualmente 7:35 PM GMT-4.

Mi objetivo es responder a todos los mensajes dentro de un día hábil.

Contrata mi increíble soporte profesional