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

build: drop python 3.8 #205

Merged
merged 2 commits into from
Nov 21, 2024
Merged
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
4 changes: 1 addition & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.9", "3.11", "3.12", "3.13"]
include:
- os: ubuntu-latest
python-version: "3.8"
- os: macos-latest
- os: windows-latest
python-version: "3.10"

test-minreqs:
Expand Down
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ name = "useq-schema"
description = "Schema for multi-dimensional microscopy experiments"
readme = "README.md"
keywords = ["microscopy", "schema"]
requires-python = ">=3.8"
requires-python = ">=3.9"
license = { text = "BSD 3-Clause License" }
authors = [
{ email = "talley.lambert@gmail.com", name = "Talley Lambert" },
Expand All @@ -23,7 +23,6 @@ classifiers = [
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand Down Expand Up @@ -73,7 +72,7 @@ packages = ["src/useq"]
# https://beta.ruff.rs/docs/rules/
[tool.ruff]
line-length = 88
target-version = "py38"
target-version = "py39"
src = ["src", "tests"]

[tool.ruff.lint]
Expand Down
6 changes: 3 additions & 3 deletions src/useq/_base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
TYPE_CHECKING,
Any,
ClassVar,
Iterable,
Optional,
Type,
TypeVar,
Union,
)
Expand All @@ -16,6 +14,8 @@
from pydantic import BaseModel, ConfigDict

if TYPE_CHECKING:
from collections.abc import Iterable

from typing_extensions import Self

ReprArgs = Iterable[tuple[str | None, Any]]
Expand Down Expand Up @@ -84,7 +84,7 @@ class MutableModel(_ReplaceableModel):

class UseqModel(FrozenModel):
@classmethod
def from_file(cls: Type[_Y], path: Union[str, Path]) -> _Y:
def from_file(cls: type[_Y], path: Union[str, Path]) -> _Y:
"""Return an instance of this class from a file. Supports JSON and YAML."""
path = Path(path)
if path.suffix in {".yaml", ".yml"}:
Expand Down
18 changes: 8 additions & 10 deletions src/useq/_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,21 @@
import contextlib
import math
import warnings
from collections.abc import Iterable, Iterator, Sequence
from enum import Enum
from typing import (
TYPE_CHECKING,
Annotated,
Any,
Callable,
Iterable,
Iterator,
Optional,
Sequence,
Tuple,
Union,
)

import numpy as np
from annotated_types import Ge, Gt
from pydantic import Field, field_validator, model_validator
from typing_extensions import Annotated, Self, TypeAlias
from typing_extensions import Self, TypeAlias

from useq._point_visiting import OrderMode, TraversalOrder
from useq._position import (
Expand Down Expand Up @@ -76,11 +74,11 @@ class _GridPlan(_MultiPointPlan[PositionT]):
Engines MAY override this even if provided.
"""

overlap: Tuple[float, float] = Field((0.0, 0.0), frozen=True)
overlap: tuple[float, float] = Field((0.0, 0.0), frozen=True)
mode: OrderMode = Field(OrderMode.row_wise_snake, frozen=True)

@field_validator("overlap", mode="before")
def _validate_overlap(cls, v: Any) -> Tuple[float, float]:
def _validate_overlap(cls, v: Any) -> tuple[float, float]:
with contextlib.suppress(TypeError, ValueError):
v = float(v)
if isinstance(v, float):
Expand Down Expand Up @@ -156,7 +154,7 @@ def iter_grid_positions(
def __iter__(self) -> Iterator[PositionT]: # type: ignore [override]
yield from self.iter_grid_positions()

def _step_size(self, fov_width: float, fov_height: float) -> Tuple[float, float]:
def _step_size(self, fov_width: float, fov_height: float) -> tuple[float, float]:
dx = fov_width - (fov_width * self.overlap[0]) / 100
dy = fov_height - (fov_height * self.overlap[1]) / 100
return dx, dy
Expand Down Expand Up @@ -409,7 +407,7 @@ def __iter__(self) -> Iterator[RelativePosition]: # type: ignore [override]
seed = np.random.RandomState(self.random_seed)
func = _POINTS_GENERATORS[self.shape]

points: list[Tuple[float, float]] = []
points: list[tuple[float, float]] = []
needed_points = self.num_points
start_at = self.start_at
if isinstance(start_at, RelativePosition):
Expand Down Expand Up @@ -454,7 +452,7 @@ def num_positions(self) -> int:


def _is_a_valid_point(
points: list[Tuple[float, float]],
points: list[tuple[float, float]],
x: float,
y: float,
min_dist_x: float,
Expand Down
6 changes: 3 additions & 3 deletions src/useq/_hardware_autofocus.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, Optional, Tuple
from typing import Any, Optional

from pydantic import PrivateAttr

Expand Down Expand Up @@ -42,7 +42,7 @@ def event(self, event: MDAEvent) -> Optional[MDAEvent]:
if not self.should_autofocus(event):
return None

updates: Dict[str, Any] = {"action": self.as_action()}
updates: dict[str, Any] = {"action": self.as_action()}
if event.z_pos is not None and event.sequence is not None:
zplan = event.sequence.z_plan
if zplan and zplan.is_relative and "z" in event.index:
Expand Down Expand Up @@ -71,7 +71,7 @@ class AxesBasedAF(AutoFocusPlan):
is change, (in other words: every time the position is changed.).
"""

axes: Tuple[str, ...]
axes: tuple[str, ...]
_previous: dict = PrivateAttr(default_factory=dict)

def should_autofocus(self, event: MDAEvent) -> bool:
Expand Down
12 changes: 7 additions & 5 deletions src/useq/_iter_sequence.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations

from functools import lru_cache
from functools import cache
from itertools import product
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Iterator, cast
from typing import TYPE_CHECKING, Any, cast

from typing_extensions import TypedDict

Expand All @@ -14,6 +14,8 @@
from useq._z import AnyZPlan # noqa: TCH001 # noqa: TCH001

if TYPE_CHECKING:
from collections.abc import Iterator

from useq._mda_sequence import MDASequence
from useq._position import Position, PositionBase, RelativePosition

Expand All @@ -39,17 +41,17 @@ class PositionDict(TypedDict, total=False):
z_pos: float


@lru_cache(maxsize=None)
@cache
def _iter_axis(seq: MDASequence, ax: str) -> tuple[Channel | float | PositionBase, ...]:
return tuple(seq.iter_axis(ax))


@lru_cache(maxsize=None)
@cache
def _sizes(seq: MDASequence) -> dict[str, int]:
return {k: len(list(_iter_axis(seq, k))) for k in seq.axis_order}


@lru_cache(maxsize=None)
@cache
def _used_axes(seq: MDASequence) -> str:
return "".join(k for k in seq.axis_order if _sizes(seq)[k])

Expand Down
13 changes: 4 additions & 9 deletions src/useq/_mda_event.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
# don't add __future__.annotations here
# pydantic2 isn't rebuilding the model correctly

from collections.abc import Mapping, Sequence
from types import MappingProxyType
from typing import (
TYPE_CHECKING,
Any,
Dict,
List,
Mapping,
NamedTuple,
Optional,
Sequence,
Tuple,
)

import numpy as np
Expand All @@ -29,7 +25,7 @@
if TYPE_CHECKING:
from useq._mda_sequence import MDASequence

ReprArgs = Sequence[Tuple[Optional[str], Any]]
ReprArgs = Sequence[tuple[Optional[str], Any]]


class Channel(UseqModel):
Expand Down Expand Up @@ -182,7 +178,6 @@ class MDAEvent(UseqModel):
`False`.
"""

# MappingProxyType is not subscriptable on Python 3.8
index: Mapping[str, int] = Field(default_factory=lambda: MappingProxyType({}))
channel: Optional[Channel] = None
exposure: Optional[float] = Field(default=None, gt=0.0)
Expand All @@ -193,8 +188,8 @@ class MDAEvent(UseqModel):
z_pos: Optional[float] = None
slm_image: Optional[SLMImage] = None
sequence: Optional["MDASequence"] = Field(default=None, repr=False)
properties: Optional[List[PropertyTuple]] = None
metadata: Dict[str, Any] = Field(default_factory=dict)
properties: Optional[list[PropertyTuple]] = None
metadata: dict[str, Any] = Field(default_factory=dict)
action: AnyAction = Field(default_factory=AcquireImage)
keep_shutter_open: bool = False
reset_event_timer: bool = False
Expand Down
25 changes: 10 additions & 15 deletions src/useq/_mda_sequence.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
from __future__ import annotations

from collections.abc import Iterable, Iterator, Mapping, Sequence
from contextlib import suppress
from typing import (
TYPE_CHECKING,
Any,
Dict,
Iterable,
Iterator,
Mapping,
Optional,
Sequence,
Tuple,
Union,
)
from uuid import UUID, uuid4
Expand Down Expand Up @@ -182,24 +177,24 @@ class MDASequence(UseqModel):
```
"""

metadata: Dict[str, Any] = Field(default_factory=dict)
axis_order: Tuple[str, ...] = AXES
metadata: dict[str, Any] = Field(default_factory=dict)
axis_order: tuple[str, ...] = AXES
# note that these are BOTH just `Sequence[Position]` but we retain the distinction
# here so that WellPlatePlans are preserved in the model instance.
stage_positions: Union[WellPlatePlan, Tuple[Position, ...]] = Field(
stage_positions: Union[WellPlatePlan, tuple[Position, ...]] = Field(
default_factory=tuple, union_mode="left_to_right"
)
grid_plan: Optional[MultiPointPlan] = Field(
default=None, union_mode="left_to_right"
)
channels: Tuple[Channel, ...] = Field(default_factory=tuple)
channels: tuple[Channel, ...] = Field(default_factory=tuple)
time_plan: Optional[AnyTimePlan] = None
z_plan: Optional[AnyZPlan] = None
autofocus_plan: Optional[AnyAutofocusPlan] = None
keep_shutter_open_across: Tuple[str, ...] = Field(default_factory=tuple)
keep_shutter_open_across: tuple[str, ...] = Field(default_factory=tuple)

_uid: UUID = PrivateAttr(default_factory=uuid4)
_sizes: Optional[Dict[str, int]] = PrivateAttr(default=None)
_sizes: Optional[dict[str, int]] = PrivateAttr(default=None)

@property
def uid(self) -> UUID:
Expand All @@ -225,7 +220,7 @@ def _validate_keep_shutter_open_across(cls, v: tuple[str, ...]) -> tuple[str, ..
return v

@field_validator("channels", mode="before")
def _validate_channels(cls, value: Any) -> Tuple[Channel, ...]:
def _validate_channels(cls, value: Any) -> tuple[Channel, ...]:
if isinstance(value, str) or not isinstance(
value, Sequence
): # pragma: no cover
Expand All @@ -245,7 +240,7 @@ def _validate_channels(cls, value: Any) -> Tuple[Channel, ...]:
@field_validator("stage_positions", mode="before")
def _validate_stage_positions(
cls, value: Any
) -> Union[WellPlatePlan, Tuple[Position, ...]]:
) -> Union[WellPlatePlan, tuple[Position, ...]]:
if isinstance(value, np.ndarray):
if value.ndim == 1:
value = [value]
Expand Down Expand Up @@ -402,7 +397,7 @@ def _check_order(
raise ValueError(err) # pragma: no cover

@property
def shape(self) -> Tuple[int, ...]:
def shape(self) -> tuple[int, ...]:
"""Return the shape of this sequence.

!!! note
Expand Down
Loading