Mastering Typing Extensions A Detailed Guide with Code Examples

Mastering Typing Extensions: A Detailed Guide

The `typing-extensions` library is a valuable tool for Python developers. It provides backports of new type hint features to older versions of Python, enabling developers to enrich code with powerful type annotations even on legacy systems. This article covers various features of `typing-extensions`, with code examples, and provides an app example utilizing these features.

Why Use Typing Extensions?

The `typing-extensions` module bridges the gap between newer and older Python versions by backporting important typing-related features. It’s particularly handy if you need to maintain compatibility across multiple Python versions.

Key APIs and Usage Examples

1. Literal

The Literal type is used to define specific allowed values for a variable.

  from typing_extensions import Literal

  def greet_user(status: Literal['active', 'inactive', 'banned']) -> str:
      return f"User status is: {status}"

  print(greet_user('active'))  # Output: User status is: active

2. TypedDict

The TypedDict helper allows you to define dictionary types with specific key-value types.

  from typing_extensions import TypedDict

  class User(TypedDict):
      id: int
      name: str

  def get_user_name(user: User) -> str:
      return user['name']

  user = {'id': 1, 'name': 'Alice'}
  print(get_user_name(user))  # Output: Alice

3. Protocol

Protocol enables structural subtyping, which verifies if objects conform to certain protocols.

  from typing_extensions import Protocol

  class Greeter(Protocol):
      def greet(self) -> str:
          ...

  class EnglishGreeter:
      def greet(self) -> str:
          return "Hello!"

  def say_hello(greeter: Greeter) -> None:
      print(greeter.greet())

  eng = EnglishGreeter()
  say_hello(eng)  # Output: Hello!

4. Final

The Final type is used to indicate that a variable or method should not be overwritten.

  from typing_extensions import Final

  APP_VERSION: Final = "1.0.0"

  print(APP_VERSION)  # Output: 1.0.0

5. @overload

The @overload decorator is used for function signatures that accept multiple type variations.

  from typing import overload
  from typing_extensions import overload

  @overload
  def process(value: int) -> str:
      ...

  @overload
  def process(value: str) -> int:
      ...

  def process(value):
      if isinstance(value, int):
          return str(value)
      elif isinstance(value, str):
          return len(value)

  print(process(42))  # Output: '42'
  print(process('test'))  # Output: 4

6. NotRequired (Python 3.11+)

With TypedDict, you can make keys optional using NotRequired.

  from typing_extensions import TypedDict, NotRequired

  class Config(TypedDict):
      host: str
      port: NotRequired[int]

  config: Config = {"host": "localhost"}
  print(config)  # Output: {'host': 'localhost'}

7. Self (Python 3.11+)

Self is used to annotate return types for methods that return an instance of their class.

  from typing_extensions import Self

  class Fluent:
      def set_value(self, value: int) -> Self:
          self.value = value
          return self

  fluent = Fluent().set_value(5)
  print(fluent.value)  # Output: 5

8. is_typeddict

The is_typeddict function checks if a type is a TypedDict.

  from typing_extensions import TypedDict, is_typeddict

  class Person(TypedDict):
      name: str
      age: int

  print(is_typeddict(Person))  # Output: True
  

Building a Mini-App with Typing Extensions

The following example demonstrates how to build a basic API service using typing extensions:

  from typing_extensions import TypedDict, Literal, NotRequired

  class UserDetails(TypedDict):
      id: int
      name: str
      status: Literal['active', 'inactive', 'banned']
      email: NotRequired[str]

  database: dict[int, UserDetails] = {}

  def create_user(id: int, name: str, status: Literal['active', 'inactive', 'banned'], email: str = None) -> None:
      user: UserDetails = {'id': id, 'name': name, 'status': status}
      if email:
          user['email'] = email
      database[id] = user

  def get_user(id: int) -> UserDetails:
      return database.get(id, None)

  create_user(1, 'Alice', 'active', 'alice@example.com')
  create_user(2, 'Bob', 'inactive')

  print(get_user(1))  
  # Output: {'id': 1, 'name': 'Alice', 'status': 'active', 'email': 'alice@example.com'}
  print(get_user(2))  
  # Output: {'id': 2, 'name': 'Bob', 'status': 'inactive'}

Conclusion

By using `typing-extensions`, you can ensure your codebase is robust, readable, and compliant with type hinting standards even on older versions of Python. The above examples showcase only a fraction of its comprehensive features. Start using it today, and take your Python typing game to the next level!

Leave a Reply

Your email address will not be published. Required fields are marked *