Skip to content

Commit 11a28ca

Browse files
committed
feat: improves plugin version checks
- Adds dependency on `packaging` to compare versions - Versions expected to follow PEP 440 style specifiers - Sorts items in `setup.py` for clarity ref: supertokens/supertokens-node#1021
1 parent 5b1f37e commit 11a28ca

File tree

5 files changed

+84
-26
lines changed

5 files changed

+84
-26
lines changed

dev-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ fastapi==0.115.5
77
Flask==3.0.3
88
flask-cors==5.0.0
99
nest-asyncio==1.6.0
10+
packaging==25.0
1011
pdoc3==0.11.0
1112
pre-commit==3.5.0
1213
pyfakefs==5.7.4

setup.py

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,23 +61,23 @@
6161
}
6262

6363
exclude_list = [
64-
"tests",
65-
"examples",
66-
"hooks",
67-
".gitignore",
64+
".circleci",
6865
".git",
66+
".github",
67+
".gitignore",
68+
".pylintrc",
69+
"Makefile",
6970
"addDevTag",
7071
"addReleaseTag",
71-
"frontendDriverInterfaceSupported.json",
7272
"coreDriverInterfaceSupported.json",
73-
".github",
74-
".circleci",
75-
"html",
76-
"pyrightconfig.json",
77-
"Makefile",
78-
".pylintrc",
7973
"dev-requirements.txt",
8074
"docs-templates",
75+
"examples",
76+
"frontendDriverInterfaceSupported.json",
77+
"hooks",
78+
"html",
79+
"pyrightconfig.json",
80+
"tests",
8181
]
8282

8383
setup(
@@ -112,22 +112,23 @@
112112
],
113113
keywords="",
114114
install_requires=[
115+
"Deprecated<1.3.0",
115116
# [crypto] ensures that it installs the `cryptography` library as well
116117
# based on constraints specified in https://github.com/jpadilla/pyjwt/blob/master/setup.cfg#L50
117118
"PyJWT[crypto]>=2.5.0,<3.0.0",
118-
"httpx>=0.15.0,<1.0.0",
119-
"pycryptodome<3.21.0",
120-
"tldextract<6.0.0",
119+
"aiosmtplib>=1.1.6,<4.0.0",
121120
"asgiref>=3.4.1,<4",
122-
"typing_extensions>=4.1.1,<5.0.0",
123-
"Deprecated<1.3.0",
121+
"httpx>=0.15.0,<1.0.0",
122+
"packaging>=25.0,<26.0",
124123
"phonenumbers<9",
125-
"twilio<10",
126-
"aiosmtplib>=1.1.6,<4.0.0",
127124
"pkce<1.1.0",
125+
"pycryptodome<3.21.0",
126+
"pydantic>=2.10.6,<3.0.0",
128127
"pyotp<3",
129128
"python-dateutil<3",
130-
"pydantic>=2.10.6,<3.0.0",
129+
"tldextract<6.0.0",
130+
"twilio<10",
131+
"typing_extensions>=4.1.1,<5.0.0",
131132
],
132133
python_requires=">=3.8",
133134
include_package_data=True,

supertokens_python/plugins.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
runtime_checkable,
1515
)
1616

17+
from packaging.specifiers import SpecifierSet
18+
from packaging.version import Version
1719
from typing_extensions import Protocol
1820

1921
from supertokens_python.constants import VERSION
@@ -387,14 +389,14 @@ def load_plugins(
387389
continue
388390

389391
if isinstance(plugin.compatible_sdk_versions, list):
390-
version_constraints = plugin.compatible_sdk_versions
392+
version_constraints = ",".join(plugin.compatible_sdk_versions)
391393
else:
392-
version_constraints = [plugin.compatible_sdk_versions]
394+
version_constraints = plugin.compatible_sdk_versions
393395

394-
if VERSION not in version_constraints:
395-
# TODO: Better checks
396+
if not SpecifierSet(version_constraints).contains(Version(VERSION)):
396397
raise Exception(
397-
f"Plugin version mismatch. Version {VERSION} not in {version_constraints=} for plugin {plugin.id}"
398+
f"Incompatible SDK version for plugin {plugin.id}. "
399+
f"Version {VERSION} not found in compatible versions {version_constraints}"
398400
)
399401

400402
# TODO: Overkill, but could topologically sort the plugins based on dependencies

tests/plugins/plugins.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def plugin_factory(
8989
override_config: bool = False,
9090
deps: Optional[List[SuperTokensPlugin]] = None,
9191
add_init: bool = False,
92+
compatible_sdk_versions: Optional[Union[str, List[str]]] = None,
9293
):
9394
override_map_obj: OverrideMap = {PluginTestRecipe.recipe_id: RecipePluginOverride()}
9495

@@ -110,9 +111,14 @@ def plugin_factory(
110111
if add_init:
111112
init_fn = init_factory(identifier)
112113

114+
if compatible_sdk_versions is None:
115+
sdk_versions = f"=={VERSION}"
116+
else:
117+
sdk_versions = compatible_sdk_versions
118+
113119
class Plugin(SuperTokensPlugin):
114120
id: str = identifier
115-
compatible_sdk_versions: Union[str, List[str]] = [VERSION]
121+
compatible_sdk_versions: Union[str, List[str]] = sdk_versions
116122
override_map: Optional[OverrideMap] = override_map_obj
117123
init: Any = init_fn
118124
dependencies: Optional[SuperTokensPluginDependencies] = dependency_factory(deps)

tests/plugins/test_plugins.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from functools import partial
2-
from typing import Any, List
2+
from typing import Any, List, Union
3+
from unittest.mock import patch
34

45
from pytest import fixture, mark, param, raises
56
from supertokens_python import (
@@ -450,3 +451,50 @@ async def test_route_handlers_callable(handler_response: Any, expectation: Any):
450451
)
451452

452453
assert res == expected_output
454+
455+
456+
@mark.parametrize(
457+
("sdk_version", "compatible_versions", "expectation"),
458+
[
459+
param(
460+
"1.5.0",
461+
">=1.0.0,<2.0.0",
462+
outputs(None),
463+
id="[Valid][1.5.0][>=1.0.0,<2.0.0] as string",
464+
),
465+
param(
466+
"1.5.0",
467+
[">=1.0.0", "<2.0.0"],
468+
outputs(None),
469+
id="[Valid][1.5.0][>=1.0.0,<2.0.0] as list of strings",
470+
),
471+
param(
472+
"2.0.0",
473+
[">=1.0.0,<2.0.0"],
474+
raises(Exception, match="Incompatible SDK version for plugin plugin1."),
475+
id="[Invalid][2.0.0][>=1.0.0,<2.0.0]",
476+
),
477+
],
478+
)
479+
def test_versions(
480+
sdk_version: str,
481+
compatible_versions: Union[str, List[str]],
482+
expectation: Any,
483+
):
484+
plugin = plugin_factory(
485+
"plugin1",
486+
override_functions=False,
487+
override_apis=False,
488+
compatible_sdk_versions=compatible_versions,
489+
)
490+
491+
with patch("supertokens_python.plugins.VERSION", sdk_version):
492+
with expectation as _:
493+
partial_init(
494+
recipe_list=[
495+
recipe_factory(override_functions=False, override_apis=False),
496+
],
497+
experimental=SupertokensExperimentalConfig(
498+
plugins=[plugin],
499+
),
500+
)

0 commit comments

Comments
 (0)