SkillAgentSearch skills...

Flusso

Rust Inspired Type-Safe Errors and Missing Values for Python.

Install / Use

/learn @gum-tech/Flusso
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

What is flusso?

flusso is a library for Python that aims to safely handle exceptions and missing values, similar to how Rust handles them with its Option and Result types.

In short, Flusso empowers you to craft Python code that is:

  • Free from None values
  • Devoid of exceptions

In python, None represents intentionally missing values and exceptions are used for handling errors.

Python skips using missing values and exceptions can lead to issues and bugs like:

  • NoneType errors
  • runtime errors
  • unexpected behaviour
  • unhandled exceptions
  • sensitive data leakages through exceptions
  • race conditions
  • and so on.

Instead, Python provides two special generic Option and Result to deal with the above cases.

flusso implements the Option & Result types for python.

Why should you use flusso?

There are already several excellent libraries that implement functional patterns in python. Why flusso?

These libraries are usually general-purpose toolkits aiming to implement all the functional programming patterns and abstractions. flusso has a more focused goal. We wanted a library specifically to ~~dominate~~ safely handle exceptions and missing values (None). The same way as it’s implemented in Rust.

Other distinguishing features of flusso:

  • Zero dependencies: flusso has no external dependencies.
  • Practical: • Rather than bore you with all the Monad / Category theory talk, we focus on the practical applications of Monads in a way you can use today. Just as you don’t need to understand group theory to do basic arithmetic, you don’t need to understand monad theory to use flusso.
  • Leverages Python's pattern matching for concise and expressive code
  • Provides an intuitive way to handle optional values and error handling
  • Eliminates the need for writing code with None or exceptions
  • Compatible with the latest Python features and best practices
  • Fully typed with annotations, following PEP 484 guidelines

Convinced?

Great! Let’s get started.

Installation

> pip install flusso

If you find this package useful, please click the star button !

<div id="toc"></div>

Table of contents

Option<T>

Introduction

“Null has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.” - Tony Hoare, the inventor of null

None values can be difficult to detect and handle correctly. When a None value is encountered, it may not be immediately clear why it is there or how to handle it. This can lead to bugs that are hard to diagnose and fix.

Another problem with None values is that they can cause runtime errors if they are not properly handled. For example, if a program attempts to access a property of an object that is None, it will often raise a NullPointerException or similar error. These errors can be difficult to anticipate and debug, especially if they occur deep in the codebase or if there are many layers of abstraction involved.

To avoid these problems, we use Option as an alternative way of representing the absence of a value or the lack of an object reference.

A brief background on Option Monad

A monad is a design pattern that allows for the creation of sequenced computations, or "actions," that can be combined in a predictable way.

The option monad is a specific type of monad that represents computations that may or may not return a value.

Option monad types allow for the explicit representation of the possibility of a missing value, and they provide methods for handling these cases in a predictable and composable way.

The option monad is usually implemented as an algebraic data type with two cases: Some and Nothing. The Some case represents a computation that has a value, and it is parameterized by the type of the value. The Nothing case represents a computation that has a missing value.

Option monad helps us safely handle missing values in a predictable and composable way without being afraid of the null pointer exception, runtime errors, and unexpected behaviour in our code.

⬆️ Back to top

Basic usage

Example I

Let’s start with a common example you see in many codebases today.

  class User:
      def __init__(self, id: int, fullname: str, username: str):
          self.id = id
          self.fullname = fullname
          self.username = username

  users = [
      User(1, "Leonardo Da Vinci", "leo"),
      User(2, "Galileo Galilei", "gaga")
  ]

  def get_user(id: int) -> Union[User, None]:
      return next((user for user in users if user.id == id), None)

  def get_user_name(id: int) -> Union[str, None]:
      user = get_user(id)
      if user is None:
          return None
      return user.username

  username = get_user_name(1)

  if username is not None:
      print(username)
  else:
      print("User not found")

This code focuses on telling the computer how to perform a task, step by step. It involves specifying the sequence of actions that the computer should take and the specific operations it should perform at each step.

The code also uses None to define missing values. Even with a simple example like this, it’s not immediately clear where the None is coming from when we check if the username is None. In large codebases, this can be a nightmare to diagnose and fix.

However, since this code style is more familiar and follows a more traditional control flow, it can be easier to understand for most programmers.

Let's rewrite this with a declarative style using flusso

  from flusso.option import Option, Some, Nothing

  class User:
      def __init__(self, id: int, fullname: str, username: str):
          self.id = id
          self.fullname = fullname
          self.username = username

  users = [
      User(1, "Leonardo Da Vinci", "leo"),
      User(2, "Galileo Galilei", "gaga")
  ]

  def get_user(id: int) -> Option[User]:
      user = next((user for user in users if user.id == id), Nothing)
      return Some(user)

  def get_username(id: int) -> Option[str]:
      return get_user(id).fmap(lambda user: user.username)


  match get_username(1):
      # Matches any `Some` instance and binds its value to the `username` variable
      case Some(username):
          print('User found: {0}'.format(username))

      # Matches `Nothing` instance
      case Nothing:
          print('User not found!')

  # Alternatively
  # if username.is_some():
  #     print(username.unwrap())
  # else:
  #     print("User not found")

This code style focuses on describing the input (the user's ID) and the desired output (the username). The match function handles the case where the user is not found by providing a default value (in this case, a message saying "User not found").

With flusso, we have successfully handled missing values in a predictable and composable way.

Example II

Let’s look at another example of using option to handle optional values.

if the value of an object can be empty or optional like the middle_nameof User in the following example, we can set its data type as an Optiontype.

 from flusso.option import Option, Some, Nothing

  def get_full_name(first_name: str, middle_name: Option[str], last_name: str) -> str:
      match(middle_name):
          case Some(mname):
              print(f"{first_name} {mname} {last_name}")

          # Matches `Nothing` instance
          case Nothing:
              print(f"{first_name} {last_name}")

  get_full_name("Galileo", None, "Galilei"); # Galileo Galilei
  get_full_name("Leonardo", Some("Da"), "Vinci"); # Leonardo Da Vinci

Let’s look at another example by chaining calculations

  def sine(x: float) -> Option[float]:
          return math.sin(x)

  def cube(x: float) -> Option[float]:
      return x * x * x

  def inc(x: float) -> Option[float]:
      return x + 1

  def double(x: float) -> Option[float]:
      return x ** 2

  def divide(x: float, y: float) -> Option[float]:
      return x / y if y > 0 else Nothing

  def sineCubedIncDoubleDivideBy10(x: float):
      return (
          Some(x)
          .fmap(sine)
          .fmap(cube)
          .fmap(inc)
          .fmap(double)
          .fmap(lambda x: divide(x, 10))
      )

  match(sineCubedIncDoubleDivideBy10(30)):
      case Some(result):
          print(f"`Result is {result}")

      # Matches `Nothing` instance
      case Nothing:
          print("Please check your inputs")

⬆️ Back to top

Benefits

There are several reasons why you might want to use flusso.option in your code:

  1. To avoid NoneType errors: As mentioned earlier, the Option is a way of representing optional values in a type-safe way. This can help you avoid NoneType errors by allowing you to explicitly handle the absence of a value in your code.
  2. To make your code more readable: Using the Option can make your code more readable, because it clearly indicates when a value may be absent. This can make it easier for other developers to understand your code and can reduce the need for comments explainin
View on GitHub
GitHub Stars20
CategoryDevelopment
Updated5mo ago
Forks2

Languages

Python

Security Score

92/100

Audited on Oct 6, 2025

No findings