Tracking breastfeeding data using Python x NiceGUI

Introduction

Through a project at work involving python code, I had discovered that streamlit was an amazing tool to quickly create web apps to visualize datasets and test out their transformation by python scripts.

Streamlit • A faster way to build and share data apps
Streamlit is an open-source Python framework for data scientists and AI/ML engineers to deliver interactive data apps – in only a few lines of code.

With streamlit, you can with a very few lines, present data from a csv file with UI elements that are simple to use and nice-looking by default:

import streamlit as st
import pandas as pd
 
st.write("""
# My first app
Hello *world!*
""")
 
df = pd.read_csv("my_data.csv")
st.line_chart(df)
Hello World with Streamlit

However, I also noticed that Streamlit had limitations, the most important one being that the scripts are rerun each time the state is changed, i.e. when the user changes an input parameter, or clicks on a button to trigger an action. That works for simple cases, but not for more complex apps where you need to:

  • deal with randomness
  • handle user interaction without re-running everything (despite Streamlit's caching system)
  • manage multiple event loops
  • or manage the state in a more advanced way, for instance to take into account multiple users or sessions.

In search of an alternative, I found NiceGUI, a Python UI framework that precisely tries to address these shortcomings of Streamlit.

NiceGUI
NiceGUI is an easy-to-use, Python-based UI framework, which shows up in your web browser. You can create buttons, dialogs, Markdown, 3D scenes, plots and much more.

In a coding style that it rather similar to its predecessor, NiceGUI introduces:

  • callbacks to handle user interactions and preserve state,
  • props to customize the Quasar-based ui elements,
  • and tailwind classes to style them.

Here is an example:

from nicegui import ui
from nicegui.events import ValueChangeEventArguments

def show(event: ValueChangeEventArguments):
    name = type(event.sender).__name__
    ui.notify(f'{name}: {event.value}')

ui.button('Button', on_click=lambda: ui.notify('Click'))
with ui.row():
    ui.checkbox('Checkbox', on_change=show)
    ui.switch('Switch', on_change=show)
ui.radio(['A', 'B', 'C'], value='A', on_change=show).props('inline')
with ui.row():
    ui.input('Text input', on_change=show)
    ui.select(['One', 'Two'], value='One', on_change=show)
ui.link('And many more...', '/documentation').classes('mt-8')

ui.run()
Screenshot of the output of the above code

It sounded promising, and I just needed a project to try it out 😃

The project

The idea came when I thought it'd be nice to track and visualize breastfeeding meal statistics. I had started in the most straight-forward way, by storing data in an excel spreadsheet. But manually updating the sheet and plots as days went by became quickly cumbersome: it was the perfect opportunity (and the perfect excuse) to better use my sleepless nights, while playing around with this framework.

Although the immediate thought was that I could test how enjoyable it would be to create and deploy a web app based on python-based NiceGUI, the project quickly turned into an opportunity to progress my python development skills and explore some best practices for software development, such as:

It was the perfect opportunity (and the perfect excuse) to better use my sleepless nights, while playing around with the NiceGUI framework.

The result: milk-tracker

With 500 lines of code, I obtained a Minimum Viable Product. A few features have been added since, and the code is now made of about 1800 lines and 550 testing lines covering 27% of the code, and I feel like I managed to create a decent app to track meals and log memories. Among the features:

  • Access via my tmlmt.com domain, encrypted in https and protected by password
  • Recording of new meals, locking start time, counting rounds
  • Keeping track of daily Vitamin intake for the baby and the mother
  • Graphs to visualize all meals from the last 3 days
  • Data table to see the details of the last 10 meals
  • Daily statistics of meal count, duration and intervals
  • Recording of daily "memories"
"Memories" page, where one can log daily observations

The code is open sourced on Github with an MIT license: feel free to consult the repository to check the details. The readme file contains instructions on how to host and run it on your own server 😊

Lessons learned

NiceGUI: strengths and limitations

Overall, I had a good time using the framework and am happy I used it for this project. The maintainers are friendly, and actively developing it while supporting the community.

Deep-diving into key strengths and limitations, I would mention the following:

  • NiceGUI is based on FastAPI / Starlette + Uvicorn for the backend, and Vue (props, etc.) + Quasar (components) + Tailwind (styling) for the frontend. Which in itself has its pros and cons. One can leverage the power and customization potential of those great packages, but despite a rather good NiceGUI documentation, it does require some time to get acquainted with them.
  • The callback system (onclick, onchange etc.) and built-in storage functionalities (with different scopes: tab / browser / server) really enable to take control of the app. But if you want to keep the readability of your code at an acceptable level as the app grows, you need to structure your code by yourself.
Structure of the milk-tracker package as I developed it until now. None of this is required by NiceGUI but better for readability, in my opinion.
  • There is a rich and well-thought library of UI elements, but some things could sometimes be better thought through end-to-end, like displaying and updating a DataFrame in a table (see the Pull Request I submitted).
Quasar's rich API for its Input element (left) and some application examples (right)
  • Binding of certain properties can be done to scalars or shallow dictionaries, but you can’t easily use Vue’s reactivity patterns (reactive variables, computed refs, etc) without using a third-party extension. That makes the handling of changes in certain input parameters quite verbose.
  • The framework enables to quickly create apps to work with data or embedded systems (which is one of the main activities of the maintainers behind NiceGUI), but it is not a full fledge solution to create complex apps for the public: things like SEO, Security, etc. is much better handled by a plugin/module-based web app framework such as Nuxt (see next section)

Python vs Javascript/Typescript

I thought my need was simple enough to be covered by NiceGUI while keeping the code elegant. And it was indeed very fast to reach a minimum viable product (i.e displaying a list of meals in a table and a few graphs); that is actually the best thing about it. But it turned out that either my overall need was complex after all (I wanted multiple specific functionalities), or that NiceGUI was not necessarily the best fit for the level of development experience I was looking for.

To build full fledge web applications, I find that Typescript-based frameworks, such as Nuxt — that I used to create the Bodega Map (separate article to come later) — offer a much better developer experience despite the little extra effort and start-up cost in styling the UI to a satisfactory level:

  • For typing code, Python + mypy is nowhere close to Typescript which is faster (check at every keystroke vs at file save with mypy) and richer.
Typescript in action with live error checking in VSCode
  • For testing, I find vitest much powerful than pytest. An example being the management of snapshots and fixtures.
  • Nuxt and its folder structure, auto-imports, rich ecosystem of modules (as well as the already rich javascript packages ecosystem) and basis on Vue (reactivity/binding) constitute all in all a much toolset and offers a better development experience.
Nuxt: The Intuitive Vue Framework
Nuxt is an open source framework that makes web development intuitive and powerful. Create performant and production-grade full-stack web apps and websites with confidence.

Micromamba is much better than conda

A nice extra in all of this was the discovery of micromamba as python environment manager. Although it still lacks a few desirable functionalities (exporting pip packages, ) and a wider adoption by Python package developers (some don’t publish to conda-forge), it is lightweight and infinitely faster than conda. The speed is also perceptible when using it as environment manager for my Github Deploy Action.

Micromamba User Guide — documentation

Documentation of Micromamba

Conclusion

All in all, it was a great learning experience to create my breastfeeding tracker based on NiceGUI. While being aware of the limitations I’ve been facing with it, I am also grateful of its strengths, that I could count on to actually obtain a … nice GUI for the need I've had.

It is really incredible to see the diversity of languages (Python, Typescript, Go, Rust, C++, etc.) and frameworks that exist out there and I hope to be able to continue playing around with them as they develop, and keep that secret coding skill of mine.