diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1ce156..abad6f6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,8 +20,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, macos-14] - python-version: ['3.10', '3.11', '3.12', '3.13'] + os: [ubuntu-latest, macos-15] + python-version: ['3.11', '3.12', '3.13', '3.14'] steps: - uses: actions/checkout@v4 diff --git a/benchmark/simulation.py b/benchmark/simulation.py index 804cd7f..1dd87a6 100644 --- a/benchmark/simulation.py +++ b/benchmark/simulation.py @@ -16,7 +16,7 @@ import psutil import logging import threading -from typing import List, Tuple, Dict, Any, Optional +from typing import List, Dict from dataclasses import dataclass # Default configuration @@ -123,7 +123,7 @@ def _get_process_memory(self) -> float: process = psutil.Process(os.getpid()) return process.memory_info().rss / 1024 / 1024 - def _find_cachesim_binary(self) -> Optional[str]: + def _find_cachesim_binary(self) -> str | None: """Find the cachesim binary in common locations.""" possible_paths = [ "./src/libCacheSim/build/bin/cachesim", diff --git a/docs/src/en/developer.md b/docs/src/en/developer.md index 2007f3b..502abca 100644 --- a/docs/src/en/developer.md +++ b/docs/src/en/developer.md @@ -194,7 +194,7 @@ that links keep working in local builds and under the locale fallback. | Workflow | Trigger | What it does | |---|---|---| -| `.github/workflows/build.yml` | changes under `src/`, `libcachesim/`, `tests/` | Builds and tests on Ubuntu and macOS (Intel and Apple Silicon) across Python 3.10–3.13, and separately builds the docs | +| `.github/workflows/build.yml` | changes under `src/`, `libcachesim/`, `tests/` | Builds and tests on Ubuntu and macOS (Apple Silicon) across Python 3.11–3.14, and separately builds the docs | | `.github/workflows/docs.yml` | changes under `docs/` | Builds with `--strict` and deploys to GitHub Pages on `main` | | `.github/workflows/pypi-release.yml` | published release, or manual dispatch | Builds wheels with cibuildwheel and publishes to PyPI | diff --git a/docs/src/en/getting_started/installation.md b/docs/src/en/getting_started/installation.md index 996eb51..abc13ca 100644 --- a/docs/src/en/getting_started/installation.md +++ b/docs/src/en/getting_started/installation.md @@ -5,7 +5,7 @@ | | | |---|---| | **OS** | Linux / macOS | -| **Python** | 3.10 -- 3.13 | +| **Python** | 3.11 -- 3.14 | | **Architecture** | x86_64 / aarch64 | Windows is not supported. diff --git a/docs/src/en/getting_started/quickstart.md b/docs/src/en/getting_started/quickstart.md index 02cd549..bd44510 100644 --- a/docs/src/en/getting_started/quickstart.md +++ b/docs/src/en/getting_started/quickstart.md @@ -5,7 +5,7 @@ This guide will help you get started with libCacheSim. ## Prerequisites - OS: Linux / macOS -- Python: 3.10 -- 3.13 +- Python: 3.11 -- 3.14 ## Installation diff --git a/docs/src/zh/developer.md b/docs/src/zh/developer.md index 1ab3a03..0db2bb7 100644 --- a/docs/src/zh/developer.md +++ b/docs/src/zh/developer.md @@ -168,7 +168,7 @@ cd docs && mkdocs build --clean --strict | 工作流 | 触发条件 | 作用 | |---|---|---| -| `.github/workflows/build.yml` | `src/`、`libcachesim/`、`tests/` 下的改动 | 在 Ubuntu 和 macOS(Intel 与 Apple Silicon)上针对 Python 3.10–3.13 构建并测试,同时单独构建文档 | +| `.github/workflows/build.yml` | `src/`、`libcachesim/`、`tests/` 下的改动 | 在 Ubuntu 和 macOS(Apple Silicon)上针对 Python 3.11–3.14 构建并测试,同时单独构建文档 | | `.github/workflows/docs.yml` | `docs/` 下的改动 | 以 `--strict` 构建,并在 `main` 分支上部署到 GitHub Pages | | `.github/workflows/pypi-release.yml` | 发布 release,或手动触发 | 用 cibuildwheel 构建 wheel 并发布到 PyPI | diff --git a/docs/src/zh/getting_started/installation.md b/docs/src/zh/getting_started/installation.md index a6d8c52..c334152 100644 --- a/docs/src/zh/getting_started/installation.md +++ b/docs/src/zh/getting_started/installation.md @@ -5,7 +5,7 @@ | | | |---|---| | **操作系统** | Linux / macOS | -| **Python** | 3.10 -- 3.13 | +| **Python** | 3.11 -- 3.14 | | **架构** | x86_64 / aarch64 | 不支持 Windows。 diff --git a/docs/src/zh/getting_started/quickstart.md b/docs/src/zh/getting_started/quickstart.md index e3c49c6..fd7c3e1 100644 --- a/docs/src/zh/getting_started/quickstart.md +++ b/docs/src/zh/getting_started/quickstart.md @@ -5,7 +5,7 @@ ## 前置条件 - 操作系统:Linux / macOS -- Python:3.10 -- 3.13 +- Python:3.11 -- 3.14 ## 安装 diff --git a/libcachesim/__init__.pyi b/libcachesim/__init__.pyi index 51719fe..4111f3b 100644 --- a/libcachesim/__init__.pyi +++ b/libcachesim/__init__.pyi @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Optional, Callable, Any +from typing import Callable, Any from collections.abc import Iterator from .libcachesim_python import Reader, ReqOp, TraceType, SamplerType @@ -72,7 +72,7 @@ class ReaderInitParam: has_header_set: bool = False, delimiter: str = ",", trace_start_offset: int = 0, - sampler: Optional[Any] = None, + sampler: Any | None = None, ): ... class AnalysisParam: @@ -140,7 +140,7 @@ class Cache: class CacheBase: """Base class for all cache implementations""" - def __init__(self, _cache: Cache, admissioner: Optional["AdmissionerBase"] = None): ... + def __init__(self, _cache: Cache, admissioner: "AdmissionerBase" | None = None): ... def get(self, req: Request) -> bool: ... def find(self, req: Request, update_cache: bool = True) -> CacheObject: ... def can_insert(self, req: Request) -> bool: ... @@ -162,158 +162,158 @@ class CacheBase: # Core cache algorithms class LHD(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class LRU(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class FIFO(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class LFU(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class ARC(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class Clock(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, init_freq: int = 0, n_bit_counter: int = 1, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, init_freq: int = 0, n_bit_counter: int = 1, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class Random(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class LRUK(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, k: int = 2, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, k: int = 2, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... # Advanced algorithms class S3FIFO(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, small_size_ratio: float = 0.1, ghost_size_ratio: float = 0.9, move_to_main_threshold: int = 2, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, small_size_ratio: float = 0.1, ghost_size_ratio: float = 0.9, move_to_main_threshold: int = 2, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class Sieve(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class LIRS(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class TwoQ(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, a_in_size_ratio: float = 0.25, a_out_size_ratio: float = 0.5, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, a_in_size_ratio: float = 0.25, a_out_size_ratio: float = 0.5, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class SLRU(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class MQ(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, n_queue: int = 8, lifetime: int = 10000, qout_size_ratio: float = 4.0, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, n_queue: int = 8, lifetime: int = 10000, qout_size_ratio: float = 4.0, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class WTinyLFU(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, main_cache: str = "SLRU", window_size: float = 0.01, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, main_cache: str = "SLRU", window_size: float = 0.01, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class LeCaR(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, update_weight: bool = True, lru_weight: float = 0.5, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, update_weight: bool = True, lru_weight: float = 0.5, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class LFUDA(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class ClockPro(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, init_ref: int = 0, init_ratio_cold: float = 0.5, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, init_ref: int = 0, init_ratio_cold: float = 0.5, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class Clock2QPlus(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, fifo_size_ratio: float = 0.1, ghost_size_ratio: float = 0.9, move_to_main_threshold: int = 1, corr_window_ratio: float = 0.5, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, fifo_size_ratio: float = 0.1, ghost_size_ratio: float = 0.9, move_to_main_threshold: int = 1, corr_window_ratio: float = 0.5, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class Cacheus(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... # Optimal algorithms class Belady(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class BeladySize(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, n_samples: int = 128, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, n_samples: int = 128, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... # Probabilistic algorithms class LRUProb(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, prob: float = 0.5, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, prob: float = 0.5, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class FlashProb(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, ram_size_ratio: float = 0.05, disk_admit_prob: float = 0.2, ram_cache: str = "LRU", disk_cache: str = "FIFO", admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, ram_size_ratio: float = 0.05, disk_admit_prob: float = 0.2, ram_cache: str = "LRU", disk_cache: str = "FIFO", admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... # Size-based algorithms class Size(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class GDSF(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... # Hyperbolic algorithms class Hyperbolic(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... # Extra deps class ThreeLCache(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, objective: str = "byte-miss-ratio", admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, objective: str = "byte-miss-ratio", admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class GLCache(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, segment_size: int = 100, n_merge: int = 2, type: str = "learned", rank_intvl: float = 0.02, merge_consecutive_segs: bool = True, train_source_y: str = "online", retrain_intvl: int = 86400, admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, segment_size: int = 100, n_merge: int = 2, type: str = "learned", rank_intvl: float = 0.02, merge_consecutive_segs: bool = True, train_source_y: str = "online", retrain_intvl: int = 86400, admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... class LRB(CacheBase): def __init__( - self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, objective: str = "byte-miss-ratio", admissioner: Optional["AdmissionerBase"] = None, reader: Optional[ReaderProtocol] = None + self, cache_size: int | float, default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, objective: str = "byte-miss-ratio", admissioner: "AdmissionerBase" | None = None, reader: ReaderProtocol | None = None ): ... # Plugin cache @@ -326,13 +326,13 @@ class PluginCache(CacheBase): cache_miss_hook: Callable, cache_eviction_hook: Callable, cache_remove_hook: Callable, - cache_free_hook: Optional[Callable] = None, + cache_free_hook: Callable | None = None, cache_name: str = "PythonHookCache", default_ttl: int = 25920000, hashpower: int = 24, consider_obj_metadata: bool = False, - admissioner: Optional["AdmissionerBase"] = None, - reader: Optional[ReaderProtocol] = None, + admissioner: "AdmissionerBase" | None = None, + reader: ReaderProtocol | None = None, ): ... # Readers @@ -342,7 +342,7 @@ class TraceReader(ReaderProtocol): self, trace: Reader | str, trace_type: TraceType = TraceType.UNKNOWN_TRACE, - reader_init_params: Optional[ReaderInitParam] = None, + reader_init_params: ReaderInitParam | None = None, ): ... class SyntheticReader(ReaderProtocol): @@ -384,8 +384,8 @@ class TraceAnalyzer: self, reader: ReaderProtocol, output_path: str, - analysis_param: Optional[AnalysisParam] = None, - analysis_option: Optional[AnalysisOption] = None, + analysis_param: AnalysisParam | None = None, + analysis_option: AnalysisOption | None = None, ): ... def run(self) -> None: ... def cleanup(self) -> None: ... @@ -415,16 +415,16 @@ class BloomFilterAdmissioner(AdmissionerBase): def __init__(self): ... class ProbAdmissioner(AdmissionerBase): - def __init__(self, prob: Optional[float] = None): ... + def __init__(self, prob: float | None = None): ... class SizeAdmissioner(AdmissionerBase): - def __init__(self, size_threshold: Optional[int] = None): ... + def __init__(self, size_threshold: int | None = None): ... class SizeProbabilisticAdmissioner(AdmissionerBase): - def __init__(self, exponent: Optional[float] = None): ... + def __init__(self, exponent: float | None = None): ... class AdaptSizeAdmissioner(AdmissionerBase): - def __init__(self, max_iteration: Optional[int] = None, reconf_interval: Optional[int] = None): ... + def __init__(self, max_iteration: int | None = None, reconf_interval: int | None = None): ... class PluginAdmissioner(AdmissionerBase): def __init__( diff --git a/libcachesim/_s3_cache.py b/libcachesim/_s3_cache.py index 36874ff..b73f206 100644 --- a/libcachesim/_s3_cache.py +++ b/libcachesim/_s3_cache.py @@ -7,7 +7,6 @@ import re import shutil from pathlib import Path -from typing import Optional, Union from urllib.parse import urlparse logger = logging.getLogger(__name__) @@ -48,7 +47,7 @@ class _DataLoader: } def __init__( - self, bucket_name: str = DEFAULT_BUCKET, cache_dir: Optional[Union[str, Path]] = None, use_auth: bool = False + self, bucket_name: str = DEFAULT_BUCKET, cache_dir: str | Path | None = None, use_auth: bool = False ): self.bucket_name = self._validate_bucket_name(bucket_name) self.cache_dir = Path(cache_dir) if cache_dir else self.DEFAULT_CACHE_DIR @@ -201,7 +200,7 @@ def _download(self, key: str, dest: Path) -> None: temp.unlink() raise RuntimeError(f"Download failed for s3://{self.bucket_name}/{key}: {e}") - def load(self, key: str, force: bool = False, mode: str = "rb") -> Union[bytes, str]: + def load(self, key: str, force: bool = False, mode: str = "rb") -> bytes | str: path = self._cache_path(key) if not path.exists() or force: self._download(key, path) @@ -221,7 +220,7 @@ def is_cached(self, key: str) -> bool: except ValueError: return False - def clear_cache(self, key: Optional[str] = None) -> None: + def clear_cache(self, key: str | None = None) -> None: if key: try: path = self._cache_path(key) @@ -270,7 +269,7 @@ def list_s3_objects(self, prefix: str = "", delimiter: str = "/") -> dict: _data_loader = _DataLoader() -def set_cache_dir(cache_dir: Union[str, Path]) -> None: +def set_cache_dir(cache_dir: str | Path) -> None: """ Set the global cache directory for S3 downloads. @@ -300,7 +299,7 @@ def get_cache_dir() -> Path: return _data_loader.cache_dir -def clear_cache(s3_path: Optional[str] = None) -> None: +def clear_cache(s3_path: str | None = None) -> None: """ Clear cached files. diff --git a/libcachesim/cache.py b/libcachesim/cache.py index a8e27c9..6130644 100644 --- a/libcachesim/cache.py +++ b/libcachesim/cache.py @@ -1,6 +1,6 @@ from abc import ABC import logging -from typing import Callable, Optional +from typing import Callable from .libcachesim_python import ( CommonCacheParams, Request, @@ -65,7 +65,7 @@ def __init__(self, _cache: Cache, admissioner: AdmissionerBase = None): def get(self, req: Request) -> bool: return self._cache.get(req) - def find(self, req: Request, update_cache: bool = True) -> Optional[CacheObject]: + def find(self, req: Request, update_cache: bool = True) -> CacheObject | None: return self._cache.find(req, update_cache) def can_insert(self, req: Request) -> bool: @@ -431,7 +431,7 @@ def __init__( admissioner=admissioner ) - def insert(self, req: Request) -> Optional[CacheObject]: + def insert(self, req: Request) -> CacheObject | None: return super().insert(req) @@ -974,7 +974,7 @@ def __init__( cache_miss_hook: Callable, cache_eviction_hook: Callable, cache_remove_hook: Callable, - cache_free_hook: Optional[Callable] = None, + cache_free_hook: Callable | None = None, cache_name: str = "PythonHookCache", default_ttl: int = 86400 * 300, hashpower: int = 24, diff --git a/libcachesim/synthetic_reader.py b/libcachesim/synthetic_reader.py index 8438da1..3aa7d00 100644 --- a/libcachesim/synthetic_reader.py +++ b/libcachesim/synthetic_reader.py @@ -6,7 +6,6 @@ import numpy as np import random -from typing import Optional, Union, Any from collections.abc import Iterator from .libcachesim_python import Request, ReqOp @@ -44,10 +43,10 @@ def __init__( obj_size: int = 4000, time_span: int = 86400 * 7, start_obj_id: int = 0, - seed: Optional[int] = None, + seed: int | None = None, alpha: float = 1.0, dist: str = "zipf", - num_objects: Optional[int] = None, + num_objects: int | None = None, ): """ Initialize synthetic reader. @@ -92,7 +91,7 @@ def __init__( random.seed(seed) # Lazy generation: generate object IDs only when needed - self._obj_ids: Optional[np.ndarray] = None + self._obj_ids: np.ndarray | None = None @property def obj_ids(self) -> np.ndarray: @@ -226,7 +225,7 @@ def __next__(self) -> Request: return self.read_one_req() - def __getitem__(self, key: Union[int, slice]) -> Union[Request, SyntheticReaderSliceIterator]: + def __getitem__(self, key: int | slice) -> Request | SyntheticReaderSliceIterator: """Support index and slice access""" if isinstance(key, slice): # Handle slice @@ -309,7 +308,7 @@ def __init__( obj_size: int = 4000, time_span: int = 86400 * 7, start_obj_id: int = 0, - seed: Optional[int] = None, + seed: int | None = None, ): """Initialize base request generator.""" if num_objects <= 0 or num_requests <= 0: @@ -362,7 +361,7 @@ def __init__( obj_size: int = 4000, time_span: int = 86400 * 7, start_obj_id: int = 0, - seed: Optional[int] = None, + seed: int | None = None, ): """Initialize Zipf request generator.""" if alpha < 0: @@ -390,7 +389,7 @@ def create_zipf_requests( obj_size: int = 4000, time_span: int = 86400 * 7, start_obj_id: int = 0, - seed: Optional[int] = None, + seed: int | None = None, ) -> _ZipfRequestGenerator: """Create a Zipf-distributed request generator. @@ -423,7 +422,7 @@ def create_uniform_requests( obj_size: int = 4000, time_span: int = 86400 * 7, start_obj_id: int = 0, - seed: Optional[int] = None, + seed: int | None = None, ) -> _UniformRequestGenerator: """Create a uniform-distributed request generator. diff --git a/libcachesim/trace_reader.py b/libcachesim/trace_reader.py index b6aa873..eaae59a 100644 --- a/libcachesim/trace_reader.py +++ b/libcachesim/trace_reader.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import overload, Union, Optional, Any +from typing import overload from collections.abc import Iterator from urllib.parse import urlparse @@ -105,9 +105,9 @@ def __init__(self, trace: Reader) -> None: ... def __init__( self, - trace: Union[Reader, str], + trace: Reader | str, trace_type: TraceType = TraceType.UNKNOWN_TRACE, - reader_init_params: Optional[ReaderInitParam] = None, + reader_init_params: ReaderInitParam | None = None, ): if isinstance(trace, Reader): self._reader = trace @@ -375,7 +375,7 @@ def __next__(self) -> Request: raise StopIteration return req - def __getitem__(self, key: Union[int, slice]) -> Union[Request, TraceReaderSliceIterator]: + def __getitem__(self, key: int | slice) -> Request | TraceReaderSliceIterator: if isinstance(key, slice): # Handle slice total_len = self._reader.get_num_of_req() diff --git a/pyproject.toml b/pyproject.toml index cdc433b..0a4e59c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,15 +12,15 @@ name = "libcachesim" version = "0.3.5" description="Python bindings for libCacheSim" readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.11" keywords = ["performance", "cache", "simulator"] classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] dependencies = [ "numpy>=1.20.0", @@ -77,7 +77,7 @@ strip = false manylinux-x86_64-image = "quay.io/pypa/manylinux_2_34_x86_64" manylinux-aarch64-image = "quay.io/pypa/manylinux_2_34_aarch64" -build = ["cp39-*", "cp310-*", "cp311-*", "cp312-*", "cp313-*"] +build = ["cp311-*", "cp312-*", "cp313-*", "cp314-*"] skip = ["*-win32", "*-manylinux_i686", "*-musllinux*", "pp*"] # Set the environment variable for the wheel build step. @@ -126,7 +126,4 @@ ignore = [ "B007", # f-string format "UP032", - # Can remove once 3.10+ is the minimum Python version - "UP007", - "UP045" ]