flake8-llm-sunset 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flake8_llm_sunset-0.0.1/PKG-INFO +37 -0
- flake8_llm_sunset-0.0.1/README.md +14 -0
- flake8_llm_sunset-0.0.1/pyproject.toml +37 -0
- flake8_llm_sunset-0.0.1/setup.cfg +4 -0
- flake8_llm_sunset-0.0.1/setup.py +3 -0
- flake8_llm_sunset-0.0.1/src/flake8_llm_sunset/__init__.py +3 -0
- flake8_llm_sunset-0.0.1/src/flake8_llm_sunset/checker.py +104 -0
- flake8_llm_sunset-0.0.1/src/flake8_llm_sunset.egg-info/PKG-INFO +37 -0
- flake8_llm_sunset-0.0.1/src/flake8_llm_sunset.egg-info/SOURCES.txt +12 -0
- flake8_llm_sunset-0.0.1/src/flake8_llm_sunset.egg-info/dependency_links.txt +1 -0
- flake8_llm_sunset-0.0.1/src/flake8_llm_sunset.egg-info/entry_points.txt +2 -0
- flake8_llm_sunset-0.0.1/src/flake8_llm_sunset.egg-info/requires.txt +4 -0
- flake8_llm_sunset-0.0.1/src/flake8_llm_sunset.egg-info/top_level.txt +1 -0
- flake8_llm_sunset-0.0.1/tests/test_checker.py +56 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flake8-llm-sunset
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A flake8 plugin that warns about expiring LLM model IDs
|
|
5
|
+
Author-email: nikkie <takuyafjp+develop@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
Project-URL: Repository, https://github.com/ftnext/llm-sunset-lint
|
|
8
|
+
Keywords: flake8
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: flake8>=7
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pytest>=9; extra == "test"
|
|
23
|
+
|
|
24
|
+
# flake8-llm-sunset
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```console
|
|
29
|
+
$ uv tool install flake8 --with flake8-llm-sunset
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```console
|
|
35
|
+
% flake8 --select LLS examples/adk_example_fields_output_schema_agent.py
|
|
36
|
+
examples/adk_example_fields_output_schema_agent.py:14:11: LLS001 Gemini model 'gemini-2.0-flash' was shut down on 2026-06-01
|
|
37
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# flake8-llm-sunset
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```console
|
|
6
|
+
$ uv tool install flake8 --with flake8-llm-sunset
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```console
|
|
12
|
+
% flake8 --select LLS examples/adk_example_fields_output_schema_agent.py
|
|
13
|
+
examples/adk_example_fields_output_schema_agent.py:14:11: LLS001 Gemini model 'gemini-2.0-flash' was shut down on 2026-06-01
|
|
14
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "flake8-llm-sunset"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "A flake8 plugin that warns about expiring LLM model IDs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT License" }
|
|
12
|
+
authors = [{ name = "nikkie", email = "takuyafjp+develop@gmail.com" }]
|
|
13
|
+
keywords = ["flake8"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
24
|
+
]
|
|
25
|
+
dependencies = ["flake8>=7"]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Repository = "https://github.com/ftnext/llm-sunset-lint"
|
|
29
|
+
|
|
30
|
+
[project.entry-points."flake8.extension"]
|
|
31
|
+
LLS = "flake8_llm_sunset:ModelExpiryChecker"
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
test = ["pytest>=9"]
|
|
35
|
+
|
|
36
|
+
[tool.pytest]
|
|
37
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""flake8 checker for model IDs with announced shutdown dates."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import calendar
|
|
7
|
+
import re
|
|
8
|
+
from datetime import date
|
|
9
|
+
from typing import Iterator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Source: https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/model-versions
|
|
13
|
+
# Models whose retirement date has not been announced are omitted.
|
|
14
|
+
GEMINI_SHUTDOWNS = {
|
|
15
|
+
"gemini-3.5-flash": date(2027, 5, 19),
|
|
16
|
+
"gemini-3.5-flash-lite": date(2027, 7, 21),
|
|
17
|
+
"gemini-3.1-flash-lite": date(2027, 5, 7),
|
|
18
|
+
"gemini-live-2.5-flash-native-audio": date(2026, 12, 13),
|
|
19
|
+
"gemini-2.5-pro": date(2026, 10, 16),
|
|
20
|
+
"gemini-2.5-flash": date(2026, 10, 16),
|
|
21
|
+
"gemini-2.5-flash-lite": date(2026, 10, 16),
|
|
22
|
+
"gemini-3.1-flash-image": date(2027, 5, 28),
|
|
23
|
+
"gemini-3-pro-image": date(2027, 5, 28),
|
|
24
|
+
"gemini-2.5-flash-image": date(2026, 10, 2),
|
|
25
|
+
"gemini-embedding-001": date(2028, 5, 20),
|
|
26
|
+
"gemini-2.0-flash": date(2026, 6, 1),
|
|
27
|
+
"gemini-2.0-flash-lite": date(2026, 6, 1),
|
|
28
|
+
"gemini-1.5-pro-001": date(2025, 5, 24),
|
|
29
|
+
"gemini-1.5-pro-002": date(2025, 9, 24),
|
|
30
|
+
"gemini-1.5-flash-001": date(2025, 5, 24),
|
|
31
|
+
"gemini-1.5-flash-002": date(2025, 9, 24),
|
|
32
|
+
"gemini-1.0-pro-001": date(2025, 4, 21),
|
|
33
|
+
"gemini-1.0-pro-002": date(2025, 4, 21),
|
|
34
|
+
"gemini-1.0-pro-vision-001": date(2025, 4, 21),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Google Cloud publishes these as the earliest possible retirement dates ("on
|
|
38
|
+
# or after"), rather than as fixed shutdown dates.
|
|
39
|
+
GEMINI_EARLIEST_SHUTDOWNS = {
|
|
40
|
+
"gemini-3.5-flash",
|
|
41
|
+
"gemini-3.5-flash-lite",
|
|
42
|
+
"gemini-3.1-flash-lite",
|
|
43
|
+
"gemini-3.1-flash-image",
|
|
44
|
+
"gemini-3-pro-image",
|
|
45
|
+
"gemini-embedding-001",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_MODEL_IDS = sorted(GEMINI_SHUTDOWNS, key=len, reverse=True)
|
|
49
|
+
_MODEL_ALTERNATIVES = "|".join(re.escape(model) for model in _MODEL_IDS)
|
|
50
|
+
_MODEL_PATTERN = re.compile(
|
|
51
|
+
rf"(?<![A-Za-z0-9_.-])({_MODEL_ALTERNATIVES})(?![A-Za-z0-9_.-])"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def one_month_before(value: date) -> date:
|
|
56
|
+
"""Subtract one calendar month, clamping to the target month's last day."""
|
|
57
|
+
year, month = value.year, value.month - 1
|
|
58
|
+
if month == 0:
|
|
59
|
+
year, month = year - 1, 12
|
|
60
|
+
day = min(value.day, calendar.monthrange(year, month)[1])
|
|
61
|
+
return date(year, month, day)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ModelExpiryChecker:
|
|
65
|
+
"""Find expiring Gemini model IDs in Python string literals."""
|
|
66
|
+
|
|
67
|
+
name = "flake8-llm-sunset"
|
|
68
|
+
version = "0.0.1"
|
|
69
|
+
today = staticmethod(date.today)
|
|
70
|
+
|
|
71
|
+
def __init__(self, tree: ast.AST) -> None:
|
|
72
|
+
self.tree = tree
|
|
73
|
+
|
|
74
|
+
def run(
|
|
75
|
+
self,
|
|
76
|
+
) -> Iterator[tuple[int, int, str, type["ModelExpiryChecker"]]]:
|
|
77
|
+
current_date = self.today()
|
|
78
|
+
for node in ast.walk(self.tree):
|
|
79
|
+
if not isinstance(node, ast.Constant) or not isinstance(node.value, str):
|
|
80
|
+
continue
|
|
81
|
+
for match in _MODEL_PATTERN.finditer(node.value):
|
|
82
|
+
model = match.group(1)
|
|
83
|
+
shutdown = GEMINI_SHUTDOWNS[model]
|
|
84
|
+
if current_date < one_month_before(shutdown):
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
if model in GEMINI_EARLIEST_SHUTDOWNS:
|
|
88
|
+
status = f"may shut down on or after {shutdown.isoformat()}"
|
|
89
|
+
elif current_date >= shutdown:
|
|
90
|
+
status = f"was shut down on {shutdown.isoformat()}"
|
|
91
|
+
else:
|
|
92
|
+
days = (shutdown - current_date).days
|
|
93
|
+
status = (
|
|
94
|
+
f"shuts down on {shutdown.isoformat()} ({days} days remaining)"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# col_offset points to the opening quote; the exact offset inside a
|
|
98
|
+
# string is deliberately not reconstructed from source spelling.
|
|
99
|
+
yield (
|
|
100
|
+
node.lineno,
|
|
101
|
+
node.col_offset,
|
|
102
|
+
f"LLS001 Gemini model '{model}' {status}",
|
|
103
|
+
type(self),
|
|
104
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flake8-llm-sunset
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A flake8 plugin that warns about expiring LLM model IDs
|
|
5
|
+
Author-email: nikkie <takuyafjp+develop@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
Project-URL: Repository, https://github.com/ftnext/llm-sunset-lint
|
|
8
|
+
Keywords: flake8
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: flake8>=7
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pytest>=9; extra == "test"
|
|
23
|
+
|
|
24
|
+
# flake8-llm-sunset
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```console
|
|
29
|
+
$ uv tool install flake8 --with flake8-llm-sunset
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```console
|
|
35
|
+
% flake8 --select LLS examples/adk_example_fields_output_schema_agent.py
|
|
36
|
+
examples/adk_example_fields_output_schema_agent.py:14:11: LLS001 Gemini model 'gemini-2.0-flash' was shut down on 2026-06-01
|
|
37
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
src/flake8_llm_sunset/__init__.py
|
|
5
|
+
src/flake8_llm_sunset/checker.py
|
|
6
|
+
src/flake8_llm_sunset.egg-info/PKG-INFO
|
|
7
|
+
src/flake8_llm_sunset.egg-info/SOURCES.txt
|
|
8
|
+
src/flake8_llm_sunset.egg-info/dependency_links.txt
|
|
9
|
+
src/flake8_llm_sunset.egg-info/entry_points.txt
|
|
10
|
+
src/flake8_llm_sunset.egg-info/requires.txt
|
|
11
|
+
src/flake8_llm_sunset.egg-info/top_level.txt
|
|
12
|
+
tests/test_checker.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flake8_llm_sunset
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
from datetime import date
|
|
3
|
+
|
|
4
|
+
from flake8_llm_sunset import ModelExpiryChecker
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def check(source: str, today: date):
|
|
8
|
+
checker = ModelExpiryChecker(ast.parse(source))
|
|
9
|
+
checker.today = lambda: today
|
|
10
|
+
return list(checker.run())
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_warns_one_calendar_month_before_shutdown():
|
|
14
|
+
errors = check('MODEL = "gemini-2.5-flash"', date(2026, 9, 16))
|
|
15
|
+
|
|
16
|
+
assert len(errors) == 1
|
|
17
|
+
assert errors[0][2] == (
|
|
18
|
+
"LLS001 Gemini model 'gemini-2.5-flash' shuts down on 2026-10-16 "
|
|
19
|
+
"(30 days remaining)"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_does_not_warn_before_warning_window():
|
|
24
|
+
assert check('MODEL = "gemini-2.5-flash"', date(2026, 9, 15)) == []
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_warns_for_already_shutdown_model():
|
|
28
|
+
errors = check(
|
|
29
|
+
'client.models.generate_content(model="gemini-2.0-flash", contents="hi")',
|
|
30
|
+
date(2026, 7, 22),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
assert "was shut down on 2026-06-01" in errors[0][2]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_finds_model_inside_larger_string():
|
|
37
|
+
errors = check(
|
|
38
|
+
'URL = "https://example.test/models/gemini-3.1-flash-image:predict"',
|
|
39
|
+
date(2027, 5, 1),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
assert len(errors) == 1
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_distinguishes_earliest_possible_shutdown_date():
|
|
46
|
+
errors = check('MODEL = "gemini-3.5-flash"', date(2027, 4, 19))
|
|
47
|
+
|
|
48
|
+
assert errors[0][2] == (
|
|
49
|
+
"LLS001 Gemini model 'gemini-3.5-flash' may shut down on or after "
|
|
50
|
+
"2027-05-19"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_ignores_unknown_or_no_shutdown_date_model():
|
|
55
|
+
source = 'MODELS = ["gemini-3.6-flash", "gemini-something-new"]'
|
|
56
|
+
assert check(source, date(2030, 1, 1)) == []
|