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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from setuptools import setup
2
+
3
+ setup()
@@ -0,0 +1,3 @@
1
+ from .checker import ModelExpiryChecker
2
+
3
+ __all__ = ["ModelExpiryChecker"]
@@ -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,2 @@
1
+ [flake8.extension]
2
+ LLS = flake8_llm_sunset:ModelExpiryChecker
@@ -0,0 +1,4 @@
1
+ flake8>=7
2
+
3
+ [test]
4
+ pytest>=9
@@ -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)) == []