Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop support for Python < 3.9 #200

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.9, pypy3.10]
python-version: [3.9, "3.10", 3.11, 3.12, pypy3.9, pypy3.10]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand All @@ -43,19 +43,22 @@ jobs:
# Test compatibility with the oldest Python version we claim to support,
# and for fluent.runtime's compatibility with a range of fluent.syntax versions.
compatibility:
runs-on: ubuntu-20.04 # https://github.com/actions/setup-python/issues/544
runs-on: ubuntu-latest
strategy:
matrix:
fluent-syntax:
- ./fluent.syntax
- fluent.syntax==0.19.0
- fluent.syntax==0.18.1 six
- fluent.syntax==0.17.0 six
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.6
python-version: 3.9
cache: pip
cache-dependency-path: |
fluent.syntax/setup.py
fluent.runtime/setup.py
- run: python -m pip install ${{ matrix.fluent-syntax }}
- run: python -m pip install ./fluent.runtime
- run: python -m unittest discover -s fluent.runtime
4 changes: 2 additions & 2 deletions fluent.runtime/fluent/runtime/builtins.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from typing import Any, Callable, Dict
from typing import Any, Callable

from .types import FluentType, fluent_date, fluent_number

NUMBER = fluent_number
DATETIME = fluent_date


BUILTINS: Dict[str, Callable[[Any], FluentType]] = {
BUILTINS: dict[str, Callable[[Any], FluentType]] = {
"NUMBER": NUMBER,
"DATETIME": DATETIME,
}
19 changes: 9 additions & 10 deletions fluent.runtime/fluent/runtime/bundle.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Tuple, Union, cast
from typing import TYPE_CHECKING, Any, Callable, Literal, Union, cast

import babel
import babel.numbers
import babel.plural
from fluent.syntax import ast as FTL
from typing_extensions import Literal

from .builtins import BUILTINS
from .prepare import Compiler
Expand Down Expand Up @@ -34,16 +33,16 @@ class FluentBundle:

def __init__(
self,
locales: List[str],
functions: Union[Dict[str, Callable[[Any], "FluentType"]], None] = None,
locales: list[str],
functions: Union[dict[str, Callable[[Any], "FluentType"]], None] = None,
use_isolating: bool = True,
):
self.locales = locales
self._functions = {**BUILTINS, **(functions or {})}
self.use_isolating = use_isolating
self._messages: Dict[str, Union[FTL.Message, FTL.Term]] = {}
self._terms: Dict[str, Union[FTL.Message, FTL.Term]] = {}
self._compiled: Dict[str, Message] = {}
self._messages: dict[str, Union[FTL.Message, FTL.Term]] = {}
self._terms: dict[str, Union[FTL.Message, FTL.Term]] = {}
self._compiled: dict[str, Message] = {}
# The compiler is not typed, and this cast is only valid for the public API
self._compiler = cast(
Callable[[Union[FTL.Message, FTL.Term]], Message], Compiler()
Expand Down Expand Up @@ -90,8 +89,8 @@ def _lookup(self, entry_id: str, term: bool = False) -> Message:
return self._compiled[compiled_id]

def format_pattern(
self, pattern: Pattern, args: Union[Dict[str, Any], None] = None
) -> Tuple[Union[str, "FluentNone"], List[Exception]]:
self, pattern: Pattern, args: Union[dict[str, Any], None] = None
) -> tuple[Union[str, "FluentNone"], list[Exception]]:
if args is not None:
fluent_args = {
argname: native_to_fluent(argvalue)
Expand All @@ -100,7 +99,7 @@ def format_pattern(
else:
fluent_args = {}

errors: List[Exception] = []
errors: list[Exception] = []
env = ResolverEnvironment(
context=self, current=CurrentEnvironment(args=fluent_args), errors=errors
)
Expand Down
39 changes: 15 additions & 24 deletions fluent.runtime/fluent/runtime/fallback.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import codecs
import os
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Generator,
List,
Type,
Union,
cast,
)
from collections.abc import Generator
from typing import TYPE_CHECKING, Any, Callable, Union, cast

from fluent.syntax import FluentParser

Expand All @@ -32,24 +23,24 @@ class FluentLocalization:

def __init__(
self,
locales: List[str],
resource_ids: List[str],
locales: list[str],
resource_ids: list[str],
resource_loader: "AbstractResourceLoader",
use_isolating: bool = False,
bundle_class: Type[FluentBundle] = FluentBundle,
functions: Union[Dict[str, Callable[[Any], "FluentType"]], None] = None,
bundle_class: type[FluentBundle] = FluentBundle,
functions: Union[dict[str, Callable[[Any], "FluentType"]], None] = None,
):
self.locales = locales
self.resource_ids = resource_ids
self.resource_loader = resource_loader
self.use_isolating = use_isolating
self.bundle_class = bundle_class
self.functions = functions
self._bundle_cache: List[FluentBundle] = []
self._bundle_cache: list[FluentBundle] = []
self._bundle_it = self._iterate_bundles()

def format_value(
self, msg_id: str, args: Union[Dict[str, Any], None] = None
self, msg_id: str, args: Union[dict[str, Any], None] = None
) -> str:
for bundle in self._bundles():
if not bundle.has_message(msg_id):
Expand All @@ -63,7 +54,7 @@ def format_value(
) # Never FluentNone when format_pattern called externally
return msg_id

def _create_bundle(self, locales: List[str]) -> FluentBundle:
def _create_bundle(self, locales: list[str]) -> FluentBundle:
return self.bundle_class(
locales, functions=self.functions, use_isolating=self.use_isolating
)
Expand Down Expand Up @@ -95,8 +86,8 @@ class AbstractResourceLoader:
"""

def resources(
self, locale: str, resource_ids: List[str]
) -> Generator[List["Resource"], None, None]:
self, locale: str, resource_ids: list[str]
) -> Generator[list["Resource"], None, None]:
"""
Yield lists of FluentResource objects, corresponding to
each of the resource_ids.
Expand All @@ -118,18 +109,18 @@ class FluentResourceLoader(AbstractResourceLoader):
different roots.
"""

def __init__(self, roots: Union[str, List[str]]):
def __init__(self, roots: Union[str, list[str]]):
"""
Create a resource loader. The roots may be a string for a single
location on disk, or a list of strings.
"""
self.roots = [roots] if isinstance(roots, str) else roots

def resources(
self, locale: str, resource_ids: List[str]
) -> Generator[List["Resource"], None, None]:
self, locale: str, resource_ids: list[str]
) -> Generator[list["Resource"], None, None]:
for root in self.roots:
resources: List[Any] = []
resources: list[Any] = []
for resource_id in resource_ids:
path = self.localize_path(os.path.join(root, resource_id), locale)
if not os.path.isfile(path):
Expand Down
6 changes: 3 additions & 3 deletions fluent.runtime/fluent/runtime/prepare.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, List
from typing import Any

from fluent.syntax import ast as FTL

Expand All @@ -17,7 +17,7 @@ def compile(self, node: Any) -> Any:
nodename: str = type(node).__name__
if not hasattr(resolver, nodename):
return node
kwargs: Dict[str, Any] = vars(node).copy()
kwargs: dict[str, Any] = vars(node).copy()
for propname, propvalue in kwargs.items():
kwargs[propname] = self(propvalue)
handler = getattr(self, "compile_" + nodename, self.compile_generic)
Expand All @@ -31,7 +31,7 @@ def compile_Placeable(self, _: Any, expression: Any, **kwargs: Any) -> Any:
return expression
return resolver.Placeable(expression=expression, **kwargs)

def compile_Pattern(self, _: Any, elements: List[Any], **kwargs: Any) -> Any:
def compile_Pattern(self, _: Any, elements: list[Any], **kwargs: Any) -> Any:
if len(elements) == 1 and isinstance(elements[0], resolver.Placeable):
# Don't isolate isolated placeables
return resolver.NeverIsolatingPlaceable(elements[0].expression)
Expand Down
31 changes: 16 additions & 15 deletions fluent.runtime/fluent/runtime/resolver.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import contextlib
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Set, Union, cast
from collections.abc import Generator
from typing import TYPE_CHECKING, Any, Union, cast

import attr
from fluent.syntax import ast as FTL
Expand Down Expand Up @@ -42,7 +43,7 @@ class CurrentEnvironment:
# For Messages, VariableReference nodes are interpreted as external args,
# but for Terms they are the values explicitly passed using CallExpression
# syntax. So we have to be able to change 'args' for this purpose.
args: Dict[str, Any] = attr.ib(factory=dict)
args: dict[str, Any] = attr.ib(factory=dict)
# This controls whether we need to report an error if a VariableReference
# refers to an arg that is not present in the args dict.
error_for_missing_arg: bool = attr.ib(default=True)
Expand All @@ -51,9 +52,9 @@ class CurrentEnvironment:
@attr.s
class ResolverEnvironment:
context: "FluentBundle" = attr.ib()
errors: List[Exception] = attr.ib()
errors: list[Exception] = attr.ib()
part_count: int = attr.ib(default=0, init=False)
active_patterns: Set[FTL.Pattern] = attr.ib(factory=set, init=False)
active_patterns: set[FTL.Pattern] = attr.ib(factory=set, init=False)
current: CurrentEnvironment = attr.ib(factory=CurrentEnvironment)

@contextlib.contextmanager
Expand All @@ -72,7 +73,7 @@ def modified(
self.current = old_current

def modified_for_term_reference(
self, args: Union[Dict[str, Any], None] = None
self, args: Union[dict[str, Any], None] = None
) -> Any:
return self.modified(
args=args if args is not None else {}, error_for_missing_arg=False
Expand Down Expand Up @@ -100,13 +101,13 @@ class Literal(BaseResolver):
class Message(FTL.Entry, BaseResolver):
id: "Identifier"
value: Union["Pattern", None]
attributes: Dict[str, "Pattern"]
attributes: dict[str, "Pattern"]

def __init__(
self,
id: "Identifier",
value: Union["Pattern", None] = None,
attributes: Union[List["Attribute"], None] = None,
attributes: Union[list["Attribute"], None] = None,
comment: Any = None,
**kwargs: Any,
):
Expand All @@ -121,13 +122,13 @@ def __init__(
class Term(FTL.Entry, BaseResolver):
id: "Identifier"
value: "Pattern"
attributes: Dict[str, "Pattern"]
attributes: dict[str, "Pattern"]

def __init__(
self,
id: "Identifier",
value: "Pattern",
attributes: Union[List["Attribute"], None] = None,
attributes: Union[list["Attribute"], None] = None,
comment: Any = None,
**kwargs: Any,
):
Expand All @@ -143,7 +144,7 @@ class Pattern(FTL.Pattern, BaseResolver):
# Prevent messages with too many sub parts, for CPI DOS protection
MAX_PARTS = 1000

elements: List[Union["TextElement", "Placeable"]] # type: ignore
elements: list[Union["TextElement", "Placeable"]] # type: ignore

def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -294,7 +295,7 @@ def __call__(self, env: ResolverEnvironment) -> Any:
if isinstance(arg_val, (FluentType, str)):
return arg_val
env.errors.append(
TypeError("Unsupported external type: {}, {}".format(name, type(arg_val)))
TypeError(f"Unsupported external type: {name}, {type(arg_val)}")
)
return FluentNone(name)

Expand All @@ -306,7 +307,7 @@ class Attribute(FTL.Attribute, BaseResolver):

class SelectExpression(FTL.SelectExpression, BaseResolver):
selector: "InlineExpression"
variants: List["Variant"] # type: ignore
variants: list["Variant"] # type: ignore

def __call__(self, env: ResolverEnvironment) -> Union[str, FluentNone]:
key = self.selector(env)
Expand Down Expand Up @@ -368,8 +369,8 @@ def __call__(self, env: ResolverEnvironment) -> str:


class CallArguments(FTL.CallArguments, BaseResolver):
positional: List[Union["InlineExpression", Placeable]] # type: ignore
named: List["NamedArgument"] # type: ignore
positional: list[Union["InlineExpression", Placeable]] # type: ignore
named: list["NamedArgument"] # type: ignore


class FunctionReference(FTL.FunctionReference, BaseResolver):
Expand All @@ -384,7 +385,7 @@ def __call__(self, env: ResolverEnvironment) -> Any:
function = env.context._functions[function_name]
except LookupError:
env.errors.append(
FluentReferenceError("Unknown function: {}".format(function_name))
FluentReferenceError(f"Unknown function: {function_name}")
)
return FluentNone(function_name + "()")

Expand Down
13 changes: 5 additions & 8 deletions fluent.runtime/fluent/runtime/types.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import warnings
from datetime import date, datetime
from decimal import Decimal
from typing import Any, Dict, Type, TypeVar, Union, cast
from typing import Any, Literal, TypeVar, Union, cast

import attr
import pytz
from babel import Locale
from babel.dates import format_date, format_time, get_datetime_format, get_timezone
from babel.numbers import NumberPattern, parse_pattern
from typing_extensions import Literal

FORMAT_STYLE_DECIMAL = "decimal"
FORMAT_STYLE_CURRENCY = "currency"
Expand Down Expand Up @@ -106,7 +105,7 @@ def __new__(
return self._init(value, kwargs)

def _init(
self, value: Union[int, float, Decimal, "FluentNumber"], kwargs: Dict[str, Any]
self, value: Union[int, float, Decimal, "FluentNumber"], kwargs: dict[str, Any]
) -> "FluentNumber":
self.options = merge_options(
NumberFormatOptions,
Expand Down Expand Up @@ -211,7 +210,7 @@ def replacer(s: str) -> str:


def merge_options(
options_class: Type[Options], base: Union[Options, None], kwargs: Dict[str, Any]
options_class: type[Options], base: Union[Options, None], kwargs: dict[str, Any]
) -> Options:
"""
Given an 'options_class', an optional 'base' object to copy from,
Expand Down Expand Up @@ -346,7 +345,7 @@ class FluentDateType(FluentType):
# So we leave those alone, and implement another `_init_options`
# which is called from other constructors.
def _init_options(
self, dt_obj: Union[date, datetime], kwargs: Dict[str, Any]
self, dt_obj: Union[date, datetime], kwargs: dict[str, Any]
) -> None:
if "timeStyle" in kwargs and not isinstance(self, datetime):
raise TypeError(
Expand Down Expand Up @@ -437,6 +436,4 @@ def fluent_date(
elif isinstance(dt, FluentNone):
return dt
else:
raise TypeError(
"Can't use fluent_date with object {} of type {}".format(dt, type(dt))
)
raise TypeError(f"Can't use fluent_date with object {dt} of type {type(dt)}")
Loading
Loading