Python LiteralString: Trusted Strings

Published on: August 29, 2026
Reading time: 5 minutes
Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.

typing.LiteralString represents strings considered literal or derived only from other literal strings. It helps sensitive APIs distinguish developer-defined text from dynamic content received through users, files, networks, configuration, or databases. This distinction can reduce injection risks in SQL, shell commands, templates, and format strings, but it does not replace parameterization, escaping, validation, authorization, or least privilege.

This guide explains inference, string operations, SQL parameters, dynamic identifiers, shells, templates, builders, casts, taint-analysis limits, stubs, compatibility, testing, and layered security.

The dynamic-string problem

def execute_sql(query: str) -> None:
    ...

name = input("Name: ")
execute_sql(f"SELECT * FROM users WHERE name = '{name}'")

The signature accepts any string. A checker cannot distinguish a fixed query from one constructed with external input. The runtime example is vulnerable to injection.

Declaring LiteralString

from typing import LiteralString


def execute_sql(query: LiteralString) -> None:
    ...

execute_sql("SELECT * FROM users")

A string written directly in source is compatible. A general variable typed as str normally is not.

External input should be rejected

query: str = input("SQL: ")
execute_sql(query)  # expected static error

The checker reports that an arbitrary string does not satisfy the trusted contract. This creates a useful review and CI barrier.

LiteralString is a subtype of str

A LiteralString can be passed anywhere a normal str is accepted. The reverse is not true. Display, logging, and ordinary text processing continue to work.

Concatenating literals

prefix: LiteralString = "SELECT id, name "
suffix: LiteralString = "FROM users"
query = prefix + suffix
execute_sql(query)

Operations that combine only LiteralStrings can preserve trusted status. Exact inference depends on the checker and expression.

f-strings

table: LiteralString = "users"
query = f"SELECT * FROM {table}"

An f-string may remain LiteralString when every interpolated expression is trusted. If any expression is only str, trust should be lost.

Do not interpolate SQL data

Even with LiteralString, structure and values should remain separate:

def query(sql: LiteralString, parameters: tuple[object, ...]) -> None:
    ...

name = input("Name: ")
query(
    "SELECT * FROM users WHERE name = ?",
    (name,),
)

The query is literal and external data is sent separately. The database driver handles encoding and escaping. Parameterization is the primary defense.

Dynamic table names

Drivers usually parameterize values, not SQL identifiers. Select dynamic identifiers from a closed set:

from typing import Literal

Table = Literal["users", "orders"]


def table_name(table: Table) -> LiteralString:
    if table == "users":
        return "users"
    return "orders"

Do not cast arbitrary input to LiteralString. Validate against allowlists and return known literals.

Shell commands

def run_command(command: LiteralString) -> None:
    ...

The type may discourage commands built from external input, but the safer design is avoiding shell=True and passing an argument list:

subprocess.run(["git", "show", revision], check=True)

LiteralString does not make shell parsing safe.

HTML templates

def render(template: LiteralString, context: dict[str, object]) -> str:
    ...

A renderer can require a developer-controlled template and accept dynamic data separately. The engine must still apply context-aware escaping for HTML text, attributes, JavaScript, CSS, and URLs.

Logging format strings

def log(format_string: LiteralString, *args: object) -> None:
    ...

log("user %s signed in", name)

Keeping the format literal helps logging tools and prevents externally controlled placeholders. Values remain separate arguments.

Internationalization

Translated text loaded from catalogs is a normal str, even if the catalog belongs to the project. Do not force it into LiteralString merely to satisfy an API. Translation systems need a different trust model.

Configuration files

Text read from YAML, JSON, or environment variables is dynamic. It does not become literal because the file is committed to a repository. Deployment systems and operators may change configuration.

Trust-preserving functions

def add_limit(query: LiteralString) -> LiteralString:
    return query + " LIMIT 100"

A function can accept and return LiteralString when it adds only literal content. If it incorporates an arbitrary str, the return type should become str.

Safe builders

def order_by_name(query: LiteralString) -> LiteralString:
    return query + " ORDER BY name"

For dynamic choices, map enums or Literal values to fixed fragments. Avoid free-form fragments.

cast does not sanitize

from typing import cast

user_input = input("SQL: ")
query = cast(LiteralString, user_input)

The cast only silences the checker. It does not validate, escape, or change the string. Casting untrusted input destroys the guarantee.

Validation does not automatically create LiteralString

After a regex or allowlist check, a checker may still see str. Prefer functions that choose and return explicit known literals or a closed Literal union. Avoid a general “trust this string” helper.

Literal versus LiteralString

Literal["asc", "desc"] describes exact allowed values, and those values are compatible with LiteralString. Literal is ideal for closed options. LiteralString is useful when an API accepts any text built exclusively from trusted literals.

TypeGuard cannot prove origin

A TypeGuard cannot generally determine that an arbitrary runtime string originated from source literals. Origin is a static flow property, not observable content. Two equal strings may have different trust histories.

External libraries

A library return annotated as str loses trusted status even if the implementation currently returns constants. A library may promise LiteralString when it genuinely preserves literal origin across all supported behavior.

Stub files

Database, logging, and template libraries may use LiteralString in .pyi files. Test consumer experience with mypy and pyright. An annotation that is too restrictive can block legitimate use cases.

Not full taint tracking

LiteralString is a simple approximation. It does not track sources, sanitizers, contexts, encodings, database drivers, or cross-process flows like a complete taint-analysis system.

Layered security

Use LiteralString as an additional layer. SQL parameters, argument arrays, contextual escaping, allowlists, input validation, authorization, minimal privileges, and security tests remain necessary.

Compatibility

Use typing_extensions.LiteralString on older versions. Precision also depends on the checker. Keep tooling current and add typing cases to CI.

Common mistakes

  • Casting external input: no sanitization occurs.
  • Interpolating SQL values: use driver parameters.
  • Assuming shell commands are safe: avoid shell=True.
  • Treating configuration as literal: it is dynamic data.
  • Confusing it with full taint analysis: the model is limited.
  • Relying only on types: runtime defenses remain essential.

Complete example: SQL repository

from typing import Literal, LiteralString

Order = Literal["name", "created_at"]


def order_column(order: Order) -> LiteralString:
    if order == "name":
        return "name"
    return "created_at"


def list_users(connection, term: str, order: Order):
    column = order_column(order)
    query: LiteralString
    if column == "name":
        query = (
            "SELECT id, name FROM users "
            "WHERE name LIKE ? ORDER BY name"
        )
    else:
        query = (
            "SELECT id, name FROM users "
            "WHERE name LIKE ? ORDER BY created_at"
        )
    cursor = connection.execute(query, (f"%{term}%",))
    return cursor.fetchall()

Search values are parameterized. The ordering column is selected through a closed Literal union, and each final query is a source literal. External input never becomes SQL structure.

Static tests

from typing import assert_type

assert_type(order_column("name"), LiteralString)

text: str = input()
# execute_sql(text) should fail static analysis

Include accepted and rejected cases to protect the boundary when stubs and checkers change.

When to use it

Use LiteralString in APIs where textual structure should be developer-defined: queries, templates, formats, and expressions. Do not add it to ordinary text-processing functions that intentionally accept arbitrary user content.

Conclusion

LiteralString declares that an API expects text originating from trusted literals. It helps static tools reject dynamic strings in sensitive positions and encourages separation between structure and data.

The official Python LiteralString documentation explains inference. Use it as defense in depth, never as a replacement for parameterized queries, escaping, allowlists, validation, authorization, and least privilege.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Vivid close-up of a python resting among autumn leaves, showcasing its intricate patterns.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    get_origin and get_args: Inspect Generic Types

    Learn Python get_origin and get_args to inspect generics, unions, Annotated, Literal, aliases, and runtime type metadata safely.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    A detailed view of computer programming code on a screen, showcasing software development.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python dataclass_transform: Generated Classes

    Learn Python dataclass_transform to type decorators, base classes, and metaclasses that generate fields, __init__, and methods.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python TypeVarTuple: Variadic Generics

    Learn Python TypeVarTuple to preserve heterogeneous tuples, model dimensions, and build generics with variable type parameters.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    assert_type and reveal_type: Test Type Inference

    Learn Python assert_type and reveal_type to inspect inference, test typed APIs, and prevent static typing regressions.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python get_type_hints: Resolve Annotations

    Learn Python get_type_hints to resolve forward references, preserve Annotated metadata, and inspect functions and classes safely.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python runtime_checkable: Protocol at Runtime

    Learn Python runtime_checkable to test Protocols with isinstance, understand its limits, and design safer structural contracts.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026