# Adding type hinting to our application

Type hinting can be really useful, particularly in larger applications. It makes it clear what data types arguments to functions should be, and what data types functions are returning. Also, IDEs can greatly benefit from type hinting in order to offer better suggestions and finding potential errors in your code.

If you're not familiar with type hinting in Python, we have an entire section dedicate to it in our Complete Python Course[1], and also a blog post dedicated to it[2].

# Type hinting primer

# Type hinting parameters

This is how we add type hinting to function parameters:

def my_function(name: str, age: int):
    print(name, age)

Just add : TYPE after each parameter, where TYPE can be one of:

  • A built-in data type, like str or int.
  • Any class.

Often we use the typing module[3] to let us add more information. For example, if a function accepts a list of strings, we would do this:

from typing import List

def my_function(names: List[str]):
    pass

Note the square brackets, List[str], are not a typo!

TIP

Starting with Python 3.9, we will no longer need the typing module for some of the most common builtin types, so we will be able to do list[str] without having to import anything[4].

# Type hinting return values

To add type hinting for the return value of a function, we use ->:

def sum(x: int, y: int) -> int:
    return x + y

# Adding type hinting to our code

Let's start with database.py. I'll go through and add type hinting to the functions where possible.

Note that psycopg2 does not support type hinting, so any connection parameters will be left un-typed.

Functions in database.py often return relatively complex values, such as rows from a table. These can be type-hinted as tuples.

For example, a row from the polls table has three values:

  • id, an integer
  • title, a string
  • owner_username, a string

Any function that returns rows from that table should be type-hinted like this:

from typing import List, Tuple

...

def get_polls(connection) -> List[Tuple[int, str, str]]:
    with connection:
        with connection.cursor() as cursor:
            cursor.execute(SELECT_ALL_POLLS)
            return cursor.fetchall()

That can get quite tricky to read, so I usually extract compound types to variables at the top of the file.

I'll create a custom type variable for each result set that our database is getting.

Poll = Tuple[int, str, str]
Vote = Tuple[str, int]
PollWithOption = Tuple[int, str, str, int, str, int]
PollResults = Tuple[int, str, int, float]

As a reminder, here's where they're used:

  • Poll is returned by the SELECT_ALL_POLLS query.
  • Vote is returned by the SELECT_RANDOM_VOTE query.
  • PollWithOption is returned by the SELECT_LATEST_POLL_WITH_OPTIONS and SELECT_POLL_WITH_OPTIONS queries.
  • PollResults is returned by the SELECT_POLL_VOTE_DETAILS query.

Then I'll go through the functions and add the return value type hints:

def get_polls(connection) -> List[Poll]:

...

def get_latest_poll(connection) -> List[PollWithOption]:

...

def get_poll_details(connection, poll_id: int) -> List[PollWithOption]:

...

def get_poll_and_vote_results(connection, poll_id: int) -> List[PollResults]:

...

def get_random_poll_vote(connection, option_id: int) -> Vote:

...

def create_poll(connection, title: str, owner: str, options: List[str]):

...

def add_poll_vote(connection, username: str, option_id: int):

...

Then I'll go over to app.py and add the type hint in the one function that might use it:

from typing import List

...

def _print_poll_options(poll_with_options: List[database.PollWithOption]):

...

  1. The Complete Python Course | Learn Python by Doing (Udemy) (opens new window) ↩︎

  2. Day 28: Type Hinting (The Teclado Blog) (opens new window) ↩︎

  3. typing -- Support for type hints (Python Official Documentation) (opens new window) ↩︎

  4. What's new in Python 3.9: PEP585 (opens new window) ↩︎