Skip to content

Commit da78c98

Browse files
authored
feat: plugins (#594)
- Adds support for plugins under the `experimental` property in `SuperTokensConfig` - Introduces a `SupertokensPublicConfig` class to work with plugins - Refactors the AccountLinking recipe to be automatically initialized on SuperTokens init - Adds `is_recipe_initialized` method to check if a recipe has been initialized - Adds new base classes for `RecipeInterface`, `APIInterface`, `Config` - Refactors existing config classes to follow `<Recipe>Config` and `<Recipe>NormalizedConfig` - Older names left in as aliases - Standardizes `__init__` exports - Adds `__all__` to recipe `__init__` files to explicitly declare exports - Adds ruff rule to format `__all__` exports
1 parent 8b541d7 commit da78c98

File tree

141 files changed

+4014
-1866
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

141 files changed

+4014
-1866
lines changed

CHANGELOG.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [unreleased]
1010

11+
## [0.31.0] - 2025-07-18
12+
### Adds plugins support
13+
- Adds an `experimental` property (`SuperTokensExperimentalConfig`) to the `SuperTokensConfig`
14+
- Plugins can be configured under using the `plugins` property in the `experimental` config
15+
- Refactors the AccountLinking recipe to be automatically initialized on SuperTokens init
16+
- Adds `is_recipe_initialized` method to check if a recipe has been initialized
17+
18+
### Breaking Changes
19+
- `AccountLinkingRecipe.get_instance` will now raise an exception if not initialized
20+
- Various config classes renamed for consistency across the codebase, and classes added where they were missing
21+
- Old classes added to the recipe modules as aliases for backward compatibility, but will be removed in future versions. Prefer using the renamed classes.
22+
- `InputOverrideConfig` renamed to `<Recipe>OverrideConfig`
23+
- `OverrideConfig` renamed to `Normalised<Recipe>OverrideConfig`
24+
- Input config classes like `<Recipe>InputConfig` renamed to `<Recipe>Config`
25+
- Normalised config classes like `<Recipe>Config` renamed to `Normalised<Recipe>Config`
26+
- Changed classes:
27+
- AccountLinking `InputOverrideConfig` -> `AccountLinkingOverrideConfig`
28+
- Dashboard `InputOverrideConfig` -> `DashboardOverrideConfig`
29+
- EmailPassword
30+
- `InputOverrideConfig` -> `EmailPasswordOverrideConfig`
31+
- `exceptions` export removed from `__init__`, import the `exceptions` module directly
32+
- EmailVerification
33+
- `InputOverrideConfig` -> `EmailVerificationOverrideConfig`
34+
- `exception` export removed from `__init__`, import the `exceptions` module directly
35+
- JWT `OverrideConfig` -> `JWTOverrideConfig`
36+
- MultiFactorAuth `OverrideConfig` -> `MultiFactorAuthOverrideConfig`
37+
- Multitenancy
38+
- `InputOverrideConfig` -> `MultitenancyOverrideConfig`
39+
- `exceptions` export removed from `__init__`, import the `exceptions` module directly
40+
- OAuth2Provider
41+
- `InputOverrideConfig` -> `OAuth2ProviderOverrideConfig`
42+
- `exceptions` export removed from `__init__`, import the `exceptions` module directly
43+
- OpenId `InputOverrideConfig` -> `OpenIdOverrideConfig`
44+
- Passwordless `InputOverrideConfig` -> `PasswordlessOverrideConfig`
45+
- Session
46+
- `InputOverrideConfig` -> `SessionOverrideConfig`
47+
- `exceptions` export removed from `__init__`, import the `exceptions` module directly
48+
- ThirdParty
49+
- `InputOverrideConfig` -> `ThirdPartyOverrideConfig`
50+
- `exceptions` export removed from `__init__`, import the `exceptions` module directly
51+
- TOTP `OverrideConfig` -> `TOTPOverrideConfig`
52+
- UserMetadata `InputOverrideConfig` -> `UserMetadataOverrideConfig`
53+
- UserRoles `InputOverrideConfig` -> `UserRolesOverrideConfig`
54+
1155
## [0.30.1] - 2025-07-21
1256
- Adds missing register credential endpoint to the Webauthn recipe
1357

dev-requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ 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
1314
pylint==3.2.7
14-
pyright==1.1.393
15+
pyright==1.1.402
1516
python-dotenv==1.0.1
1617
pytest==8.3.3
1718
pytest-asyncio==0.24.0

pyproject.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ line-length = 88 # Match Black's formatting
33
src = ["supertokens_python"]
44

55
[tool.ruff.lint]
6-
extend-select = ["I"] # enable import sorting
6+
extend-select = [
7+
"I", # enable import sorting
8+
"RUF022", # Sort __all__ exports
9+
]
710

811
[tool.ruff.format]
912
quote-style = "double" # Default
@@ -18,3 +21,5 @@ include = ["supertokens_python/", "tests/", "examples/"]
1821
addopts = " -v -p no:warnings"
1922
python_paths = "."
2023
xfail_strict = true
24+
# Removes requirement to use `@mark.asyncio` on async tests
25+
asyncio_mode = "auto"

setup.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -61,28 +61,28 @@
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(
8484
name="supertokens_python",
85-
version="0.30.1",
85+
version="0.31.0",
8686
author="SuperTokens",
8787
license="Apache 2.0",
8888
author_email="[email protected]",
@@ -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/__init__.py

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,61 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414

15-
from typing import Any, Callable, Dict, List, Optional
15+
from typing import Any, Dict, List, Optional
1616

1717
from typing_extensions import Literal
1818

1919
from supertokens_python.framework.request import BaseRequest
20+
from supertokens_python.recipe_module import RecipeModule
2021
from supertokens_python.types import RecipeUserId
2122

22-
from . import supertokens
23-
from .recipe_module import RecipeModule
23+
from .plugins import LoadPluginsResponse
24+
from .supertokens import (
25+
AppInfo,
26+
InputAppInfo,
27+
RecipeInit,
28+
Supertokens,
29+
SupertokensConfig,
30+
SupertokensExperimentalConfig,
31+
SupertokensInputConfig,
32+
SupertokensPublicConfig,
33+
)
2434

25-
InputAppInfo = supertokens.InputAppInfo
26-
Supertokens = supertokens.Supertokens
27-
SupertokensConfig = supertokens.SupertokensConfig
28-
AppInfo = supertokens.AppInfo
35+
# Some Pydantic models need a rebuild to resolve ForwardRefs
36+
# Referencing imports here to prevent lint errors.
37+
# Caveat: These will be available for import from this module directly.
38+
RecipeModule # type: ignore
39+
40+
# LoadPluginsResponse -> SupertokensPublicConfig
41+
LoadPluginsResponse.model_rebuild()
42+
# SupertokensInputConfig -> RecipeModule
43+
SupertokensInputConfig.model_rebuild()
2944

3045

3146
def init(
3247
app_info: InputAppInfo,
3348
framework: Literal["fastapi", "flask", "django"],
3449
supertokens_config: SupertokensConfig,
35-
recipe_list: List[Callable[[supertokens.AppInfo], RecipeModule]],
50+
recipe_list: List[RecipeInit],
3651
mode: Optional[Literal["asgi", "wsgi"]] = None,
3752
telemetry: Optional[bool] = None,
3853
debug: Optional[bool] = None,
54+
experimental: Optional[SupertokensExperimentalConfig] = None,
3955
):
4056
return Supertokens.init(
41-
app_info, framework, supertokens_config, recipe_list, mode, telemetry, debug
57+
app_info,
58+
framework,
59+
supertokens_config,
60+
recipe_list,
61+
mode,
62+
telemetry,
63+
debug,
64+
experimental=experimental,
4265
)
4366

4467

4568
def get_all_cors_headers() -> List[str]:
46-
return supertokens.Supertokens.get_instance().get_all_cors_headers()
69+
return Supertokens.get_instance().get_all_cors_headers()
4770

4871

4972
def get_request_from_user_context(
@@ -54,3 +77,23 @@ def get_request_from_user_context(
5477

5578
def convert_to_recipe_user_id(user_id: str) -> RecipeUserId:
5679
return RecipeUserId(user_id)
80+
81+
82+
is_recipe_initialized = Supertokens.is_recipe_initialized
83+
84+
85+
__all__ = [
86+
"AppInfo",
87+
"InputAppInfo",
88+
"RecipeInit",
89+
"RecipeUserId",
90+
"Supertokens",
91+
"SupertokensConfig",
92+
"SupertokensExperimentalConfig",
93+
"SupertokensPublicConfig",
94+
"convert_to_recipe_user_id",
95+
"get_all_cors_headers",
96+
"get_request_from_user_context",
97+
"init",
98+
"is_recipe_initialized",
99+
]

supertokens_python/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from __future__ import annotations
1616

1717
SUPPORTED_CDI_VERSIONS = ["5.3"]
18-
VERSION = "0.30.1"
18+
VERSION = "0.31.0"
1919
TELEMETRY = "/telemetry"
2020
USER_COUNT = "/users/count"
2121
USER_DELETE = "/user/remove"

supertokens_python/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,7 @@ class GeneralError(SuperTokensError):
4040

4141
class BadInputError(SuperTokensError):
4242
pass
43+
44+
45+
class PluginError(SuperTokensError):
46+
pass

0 commit comments

Comments
 (0)