@smbcloud / cli / commits / ddb750b

Add Python Auth SDK bindings using PyO3 and maturin

- Introduce `smbcloud-auth-py` crate for Python bindings to the Rust Auth SDK - Implement Python FFI in `src/lib.rs` using PyO3, exposing signup, login, logout, me, and remove APIs - Add Python package in `sdk/python` with `pyproject.toml`, `README.md`, and MIT license - Provide high-level `AuthClient` and `Environment` abstractions in `smbcloud_auth/__init__.py` - Update `Cargo.lock` with new dependencies for PyO3 and Python FFI - All workspace dependencies for the new crate are inherited from the root as required

Seto Elkahfi committed Mar 30, 2026 at 18:25 UTC ddb750b35d88e2e28681df088772b3bc75f30233
10 files changed +619
Cargo.lock
+117
@@ -1011,6 +1011,15 @@ dependencies = [
1011 "web-time",
1012 ]
1013
1014 +[[package]]
1015 +name = "indoc"
1016 +version = "2.0.7"
1017 +source = "registry+https://github.com/rust-lang/crates.io-index"
1018 +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
1019 +dependencies = [
1020 + "rustversion",
1021 +]
1022 +
1023 [[package]]
1024 name = "ipnet"
1025 version = "2.11.0"
@@ -1230,6 +1239,15 @@ version = "2.8.0"
1239 source = "registry+https://github.com/rust-lang/crates.io-index"
1240 checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
1241
1242 +[[package]]
1243 +name = "memoffset"
1244 +version = "0.9.1"
1245 +source = "registry+https://github.com/rust-lang/crates.io-index"
1246 +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
1247 +dependencies = [
1248 + "autocfg",
1249 +]
1250 +
1251 [[package]]
1252 name = "mime"
1253 version = "0.3.17"
@@ -1576,6 +1594,79 @@ dependencies = [
1594 "unicode-ident",
1595 ]
1596
1597 +[[package]]
1598 +name = "pyo3"
1599 +version = "0.22.6"
1600 +source = "registry+https://github.com/rust-lang/crates.io-index"
1601 +checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884"
1602 +dependencies = [
1603 + "cfg-if",
1604 + "indoc",
1605 + "libc",
1606 + "memoffset",
1607 + "once_cell",
1608 + "portable-atomic",
1609 + "pyo3-build-config",
1610 + "pyo3-ffi",
1611 + "pyo3-macros",
1612 + "unindent",
1613 +]
1614 +
1615 +[[package]]
1616 +name = "pyo3-build-config"
1617 +version = "0.22.6"
1618 +source = "registry+https://github.com/rust-lang/crates.io-index"
1619 +checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38"
1620 +dependencies = [
1621 + "once_cell",
1622 + "target-lexicon",
1623 +]
1624 +
1625 +[[package]]
1626 +name = "pyo3-ffi"
1627 +version = "0.22.6"
1628 +source = "registry+https://github.com/rust-lang/crates.io-index"
1629 +checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636"
1630 +dependencies = [
1631 + "libc",
1632 + "pyo3-build-config",
1633 +]
1634 +
1635 +[[package]]
1636 +name = "pyo3-macros"
1637 +version = "0.22.6"
1638 +source = "registry+https://github.com/rust-lang/crates.io-index"
1639 +checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453"
1640 +dependencies = [
1641 + "proc-macro2",
1642 + "pyo3-macros-backend",
1643 + "quote",
1644 + "syn 2.0.114",
1645 +]
1646 +
1647 +[[package]]
1648 +name = "pyo3-macros-backend"
1649 +version = "0.22.6"
1650 +source = "registry+https://github.com/rust-lang/crates.io-index"
1651 +checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe"
1652 +dependencies = [
1653 + "heck 0.5.0",
1654 + "proc-macro2",
1655 + "pyo3-build-config",
1656 + "quote",
1657 + "syn 2.0.114",
1658 +]
1659 +
1660 +[[package]]
1661 +name = "pythonize"
1662 +version = "0.22.0"
1663 +source = "registry+https://github.com/rust-lang/crates.io-index"
1664 +checksum = "90fcf491425978bd889015d5430f6473d91bdfa2097262f1e731aadcf6c2113e"
1665 +dependencies = [
1666 + "pyo3",
1667 + "serde",
1668 +]
1669 +
1670 [[package]]
1671 name = "quinn"
1672 version = "0.11.9"
@@ -2099,6 +2190,20 @@ dependencies = [
2190 "uuid",
2191 ]
2192
2193 +[[package]]
2194 +name = "smbcloud-auth-py"
2195 +version = "0.3.33"
2196 +dependencies = [
2197 + "pyo3",
2198 + "pythonize",
2199 + "serde",
2200 + "serde_json",
2201 + "smbcloud-auth",
2202 + "smbcloud-model",
2203 + "smbcloud-network",
2204 + "tokio",
2205 +]
2206 +
2207 [[package]]
2208 name = "smbcloud-auth-wasm"
2209 version = "0.3.33"
@@ -2437,6 +2542,12 @@ dependencies = [
2542 "syn 2.0.114",
2543 ]
2544
2545 +[[package]]
2546 +name = "target-lexicon"
2547 +version = "0.12.16"
2548 +source = "registry+https://github.com/rust-lang/crates.io-index"
2549 +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
2550 +
2551 [[package]]
2552 name = "tempfile"
2553 version = "3.24.0"
@@ -2916,6 +3027,12 @@ version = "0.2.2"
3027 source = "registry+https://github.com/rust-lang/crates.io-index"
3028 checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
3029
3030 +[[package]]
3031 +name = "unindent"
3032 +version = "0.2.4"
3033 +source = "registry+https://github.com/rust-lang/crates.io-index"
3034 +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
3035 +
3036 [[package]]
3037 name = "untrusted"
3038 version = "0.9.0"
crates/smbcloud-auth-py/Cargo.toml new
+26
@@ -0,0 +1,26 @@
1 +[package]
2 +name = "smbcloud-auth-py"
3 +version = "0.3.33"
4 +edition = "2024"
5 +authors = ["Seto Elkahfi <hej@setoelkahfi.se>"]
6 +description = "Python bindings for the smbCloud Auth SDK."
7 +license = "MIT"
8 +repository = "https://github.com/smbcloudXYZ/smbcloud-cli"
9 +documentation = "https://docs.smbcloud.xyz/cli"
10 +keywords = ["smbcloud", "auth", "sdk", "python", "pyo3"]
11 +categories = ["api-bindings", "authentication", "development-tools"]
12 +readme = "README.md"
13 +
14 +[lib]
15 +name = "smbcloud_auth_py"
16 +crate-type = ["cdylib"]
17 +
18 +[dependencies]
19 +pyo3 = { version = "0.22.6", features = ["abi3-py38", "extension-module"] }
20 +pythonize = "0.22.0"
21 +serde = { workspace = true, features = ["derive"] }
22 +serde_json = { workspace = true }
23 +smbcloud-auth = { workspace = true }
24 +smbcloud-model = { workspace = true }
25 +smbcloud-network = { workspace = true }
26 +tokio = { workspace = true, features = ["rt-multi-thread"] }
crates/smbcloud-auth-py/README.md new
+3
@@ -0,0 +1,3 @@
1 +# smbcloud-auth-py
2 +
3 +PyO3 bindings for the smbCloud Auth SDK.
crates/smbcloud-auth-py/src/lib.rs new
+221
@@ -0,0 +1,221 @@
1 +#![allow(unexpected_cfgs, unsafe_op_in_unsafe_fn)]
2 +
3 +use pyo3::{
4 + create_exception,
5 + exceptions::{PyException, PyValueError},
6 + prelude::*,
7 +};
8 +use pythonize::pythonize;
9 +use serde::Serialize;
10 +use smbcloud_auth::{
11 + client_credentials::ClientCredentials, login::login_with_client as rust_login_with_client,
12 + logout::logout_with_client as rust_logout_with_client,
13 + me::me_with_client as rust_me_with_client,
14 + remove::remove_with_client as rust_remove_with_client,
15 + signup::signup_with_client as rust_signup_with_client,
16 +};
17 +use smbcloud_model::{
18 + account::ErrorCode as AccountErrorCode, error_codes::ErrorResponse, login::AccountStatus,
19 +};
20 +use smbcloud_network::environment::Environment;
21 +use std::sync::OnceLock;
22 +use tokio::runtime::{Builder, Runtime};
23 +
24 +create_exception!(_native, NativeSdkError, PyException);
25 +
26 +#[derive(Serialize)]
27 +struct ErrorPayload {
28 + error_code: i32,
29 + error_name: String,
30 + message: String,
31 +}
32 +
33 +#[derive(Serialize)]
34 +#[serde(tag = "kind", rename_all = "snake_case")]
35 +enum LoginPayload {
36 + NotFound,
37 + Ready {
38 + access_token: String,
39 + },
40 + Incomplete {
41 + status: String,
42 + status_code: u32,
43 + message: String,
44 + },
45 +}
46 +
47 +fn runtime() -> &'static Runtime {
48 + static RUNTIME: OnceLock<Runtime> = OnceLock::new();
49 + RUNTIME.get_or_init(|| {
50 + Builder::new_multi_thread()
51 + .enable_all()
52 + .build()
53 + .expect("failed to initialize tokio runtime for smbcloud auth python sdk")
54 + })
55 +}
56 +
57 +fn parse_env(env: &str) -> PyResult<Environment> {
58 + env.parse()
59 + .map_err(|_| PyValueError::new_err("env must be 'dev' or 'production'"))
60 +}
61 +
62 +fn client_credentials<'a>(app_id: &'a str, app_secret: &'a str) -> ClientCredentials<'a> {
63 + ClientCredentials { app_id, app_secret }
64 +}
65 +
66 +fn serialize<'py, T>(py: Python<'py>, value: &T) -> PyResult<PyObject>
67 +where
68 + T: Serialize,
69 +{
70 + Ok(pythonize(py, value)?.unbind().into())
71 +}
72 +
73 +fn login_payload(status: AccountStatus) -> LoginPayload {
74 + match status {
75 + AccountStatus::NotFound => LoginPayload::NotFound,
76 + AccountStatus::Ready { access_token } => LoginPayload::Ready { access_token },
77 + AccountStatus::Incomplete { status } => LoginPayload::Incomplete {
78 + status: account_error_name(status).to_string(),
79 + status_code: status as u32,
80 + message: status.to_string(),
81 + },
82 + }
83 +}
84 +
85 +fn account_error_name(code: AccountErrorCode) -> &'static str {
86 + match code {
87 + AccountErrorCode::EmailNotFound => "email_not_found",
88 + AccountErrorCode::EmailUnverified => "email_unverified",
89 + AccountErrorCode::PasswordNotSet => "password_not_set",
90 + AccountErrorCode::GithubNotLinked => "github_not_linked",
91 + }
92 +}
93 +
94 +fn native_error(error: ErrorResponse) -> PyErr {
95 + let ErrorResponse::Error {
96 + error_code,
97 + message,
98 + } = error;
99 +
100 + let error_name = error_code.rb_constant_name();
101 + let payload = ErrorPayload {
102 + error_code: error_code as i32,
103 + error_name,
104 + message,
105 + };
106 +
107 + let message = serde_json::to_string(&payload).unwrap_or_else(|_| {
108 + "{\"error_code\":0,\"error_name\":\"Unknown\",\"message\":\"Unknown error.\"}".to_string()
109 + });
110 +
111 + NativeSdkError::new_err(message)
112 +}
113 +
114 +#[pyfunction]
115 +fn signup_with_client(
116 + py: Python<'_>,
117 + env: &str,
118 + app_id: &str,
119 + app_secret: &str,
120 + email: &str,
121 + password: &str,
122 +) -> PyResult<PyObject> {
123 + let env = parse_env(env)?;
124 + let client = client_credentials(app_id, app_secret);
125 + let result = runtime()
126 + .block_on(rust_signup_with_client(
127 + env,
128 + client,
129 + email.to_string(),
130 + password.to_string(),
131 + ))
132 + .map_err(native_error)?;
133 +
134 + serialize(py, &result)
135 +}
136 +
137 +#[pyfunction]
138 +fn login_with_client(
139 + py: Python<'_>,
140 + env: &str,
141 + app_id: &str,
142 + app_secret: &str,
143 + email: &str,
144 + password: &str,
145 +) -> PyResult<PyObject> {
146 + let env = parse_env(env)?;
147 + let client = client_credentials(app_id, app_secret);
148 + let result = runtime()
149 + .block_on(rust_login_with_client(
150 + env,
151 + client,
152 + email.to_string(),
153 + password.to_string(),
154 + ))
155 + .map(login_payload)
156 + .map_err(native_error)?;
157 +
158 + serialize(py, &result)
159 +}
160 +
161 +#[pyfunction]
162 +fn logout_with_client(
163 + env: &str,
164 + app_id: &str,
165 + app_secret: &str,
166 + access_token: &str,
167 +) -> PyResult<()> {
168 + let env = parse_env(env)?;
169 + let client = client_credentials(app_id, app_secret);
170 +
171 + runtime()
172 + .block_on(rust_logout_with_client(
173 + env,
174 + client,
175 + access_token.to_string(),
176 + ))
177 + .map_err(native_error)
178 +}
179 +
180 +#[pyfunction]
181 +fn me_with_client(
182 + py: Python<'_>,
183 + env: &str,
184 + app_id: &str,
185 + app_secret: &str,
186 + access_token: &str,
187 +) -> PyResult<PyObject> {
188 + let env = parse_env(env)?;
189 + let client = client_credentials(app_id, app_secret);
190 + let result = runtime()
191 + .block_on(rust_me_with_client(env, client, access_token))
192 + .map_err(native_error)?;
193 +
194 + serialize(py, &result)
195 +}
196 +
197 +#[pyfunction]
198 +fn remove_with_client(
199 + env: &str,
200 + app_id: &str,
201 + app_secret: &str,
202 + access_token: &str,
203 +) -> PyResult<()> {
204 + let env = parse_env(env)?;
205 + let client = client_credentials(app_id, app_secret);
206 +
207 + runtime()
208 + .block_on(rust_remove_with_client(env, client, access_token))
209 + .map_err(native_error)
210 +}
211 +
212 +#[pymodule(name = "_native")]
213 +fn python_module(py: Python<'_>, module: &Bound<'_, PyModule>) -> PyResult<()> {
214 + module.add("NativeSdkError", py.get_type_bound::<NativeSdkError>())?;
215 + module.add_function(wrap_pyfunction!(signup_with_client, module)?)?;
216 + module.add_function(wrap_pyfunction!(login_with_client, module)?)?;
217 + module.add_function(wrap_pyfunction!(logout_with_client, module)?)?;
218 + module.add_function(wrap_pyfunction!(me_with_client, module)?)?;
219 + module.add_function(wrap_pyfunction!(remove_with_client, module)?)?;
220 + Ok(())
221 +}
crates/smbcloud-s6n/src/lib.rs
+1
@@ -0,0 +1 @@
1 +
sdk/python/LICENSE new
+21
@@ -0,0 +1,21 @@
1 +MIT License
2 +
3 +Copyright (c) 2026 smbCloud
4 +
5 +Permission is hereby granted, free of charge, to any person obtaining a copy
6 +of this software and associated documentation files (the "Software"), to deal
7 +in the Software without restriction, including without limitation the rights
8 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 +copies of the Software, and to permit persons to whom the Software is
10 +furnished to do so, subject to the following conditions:
11 +
12 +The above copyright notice and this permission notice shall be included in all
13 +copies or substantial portions of the Software.
14 +
15 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 +SOFTWARE.
sdk/python/README.md new
+52
@@ -0,0 +1,52 @@
1 +# smbCloud SDK Auth
2 +
3 +Python authentication SDK for smbCloud, built from Rust.
4 +
5 +## About
6 +
7 +`smbcloud-sdk-auth` wraps the shared Rust Auth crate through PyO3 and maturin so
8 +Python apps can use the same tenant auth contract as the browser SDK.
9 +
10 +Current exported Python APIs include:
11 +
12 +- `signup_with_client`
13 +- `login_with_client`
14 +- `logout_with_client`
15 +- `me_with_client`
16 +- `remove_with_client`
17 +- `Environment`
18 +- `AuthClient`
19 +
20 +## Install
21 +
22 +```bash
23 +pip install smbcloud-sdk-auth
24 +```
25 +
26 +## Usage
27 +
28 +```python
29 +from smbcloud_auth import AuthClient, Environment
30 +
31 +client = AuthClient(
32 + env=Environment.PRODUCTION,
33 + app_id="app-id",
34 + app_secret="app-secret",
35 +)
36 +
37 +signup = client.signup("name@example.com", "password123")
38 +login = client.login("name@example.com", "password123")
39 +user = client.me(login["access_token"])
40 +```
41 +
42 +## Local packaging
43 +
44 +From `sdk/python`:
45 +
46 +```bash
47 +maturin build
48 +```
49 +
50 +## License
51 +
52 +MIT
sdk/python/pyproject.toml new
+37
@@ -0,0 +1,37 @@
1 +[build-system]
2 +requires = ["maturin>=1.7,<2.0"]
3 +build-backend = "maturin"
4 +
5 +[project]
6 +name = "smbcloud-sdk-auth"
7 +description = "Python Auth SDK for smbCloud, built from Rust."
8 +readme = "README.md"
9 +requires-python = ">=3.8"
10 +dynamic = ["version"]
11 +license = { text = "MIT" }
12 +authors = [{ name = "Seto Elkahfi", email = "hej@setoelkahfi.se" }]
13 +keywords = ["smbcloud", "auth", "sdk", "python", "rust"]
14 +classifiers = [
15 + "Development Status :: 4 - Beta",
16 + "Intended Audience :: Developers",
17 + "License :: OSI Approved :: MIT License",
18 + "Operating System :: MacOS",
19 + "Operating System :: Microsoft :: Windows",
20 + "Operating System :: POSIX :: Linux",
21 + "Programming Language :: Python :: 3",
22 + "Programming Language :: Rust",
23 + "Topic :: Internet",
24 + "Topic :: Software Development :: Libraries :: Python Modules",
25 +]
26 +
27 +[project.urls]
28 +Homepage = "https://github.com/smbcloudXYZ/smbcloud-cli/tree/main/sdk/python"
29 +Documentation = "https://docs.smbcloud.xyz/cli"
30 +Repository = "https://github.com/smbcloudXYZ/smbcloud-cli"
31 +Issues = "https://github.com/smbcloudXYZ/smbcloud-cli/issues"
32 +
33 +[tool.maturin]
34 +manifest-path = "../../crates/smbcloud-auth-py/Cargo.toml"
35 +bindings = "pyo3"
36 +strip = true
37 +module-name = "smbcloud_auth._native"
sdk/python/smbcloud_auth/__init__.py new
+141
@@ -0,0 +1,141 @@
1 +from __future__ import annotations
2 +
3 +import json
4 +from dataclasses import dataclass
5 +from enum import Enum
6 +from importlib.metadata import PackageNotFoundError, version
7 +from typing import Any, Dict
8 +
9 +from ._native import NativeSdkError
10 +from ._native import login_with_client as _login_with_client
11 +from ._native import logout_with_client as _logout_with_client
12 +from ._native import me_with_client as _me_with_client
13 +from ._native import remove_with_client as _remove_with_client
14 +from ._native import signup_with_client as _signup_with_client
15 +
16 +
17 +class Environment(str, Enum):
18 + DEV = "dev"
19 + PRODUCTION = "production"
20 +
21 +
22 +class SdkError(Exception):
23 + def __init__(self, error_code: int, error_name: str, message: str) -> None:
24 + super().__init__(message)
25 + self.error_code = error_code
26 + self.error_name = error_name
27 + self.message = message
28 +
29 +
30 +@dataclass(frozen=True)
31 +class AuthClient:
32 + env: Environment
33 + app_id: str
34 + app_secret: str
35 +
36 + def signup(self, email: str, password: str) -> Dict[str, Any]:
37 + return signup_with_client(self.env, self.app_id, self.app_secret, email, password)
38 +
39 + def login(self, email: str, password: str) -> Dict[str, Any]:
40 + return login_with_client(self.env, self.app_id, self.app_secret, email, password)
41 +
42 + def logout(self, access_token: str) -> None:
43 + logout_with_client(self.env, self.app_id, self.app_secret, access_token)
44 +
45 + def me(self, access_token: str) -> Dict[str, Any]:
46 + return me_with_client(self.env, self.app_id, self.app_secret, access_token)
47 +
48 + def remove(self, access_token: str) -> None:
49 + remove_with_client(self.env, self.app_id, self.app_secret, access_token)
50 +
51 +
52 +def signup_with_client(
53 + env: Environment | str,
54 + app_id: str,
55 + app_secret: str,
56 + email: str,
57 + password: str,
58 +) -> Dict[str, Any]:
59 + return _run(_signup_with_client, env, app_id, app_secret, email, password)
60 +
61 +
62 +def login_with_client(
63 + env: Environment | str,
64 + app_id: str,
65 + app_secret: str,
66 + email: str,
67 + password: str,
68 +) -> Dict[str, Any]:
69 + return _run(_login_with_client, env, app_id, app_secret, email, password)
70 +
71 +
72 +def logout_with_client(
73 + env: Environment | str,
74 + app_id: str,
75 + app_secret: str,
76 + access_token: str,
77 +) -> None:
78 + _run(_logout_with_client, env, app_id, app_secret, access_token)
79 +
80 +
81 +def me_with_client(
82 + env: Environment | str,
83 + app_id: str,
84 + app_secret: str,
85 + access_token: str,
86 +) -> Dict[str, Any]:
87 + return _run(_me_with_client, env, app_id, app_secret, access_token)
88 +
89 +
90 +def remove_with_client(
91 + env: Environment | str,
92 + app_id: str,
93 + app_secret: str,
94 + access_token: str,
95 +) -> None:
96 + _run(_remove_with_client, env, app_id, app_secret, access_token)
97 +
98 +
99 +def _run(func: Any, env: Environment | str, *args: str) -> Any:
100 + try:
101 + return func(_normalize_env(env), *args)
102 + except NativeSdkError as exc:
103 + raise _sdk_error_from_native(exc) from exc
104 +
105 +
106 +def _normalize_env(env: Environment | str) -> str:
107 + if isinstance(env, Environment):
108 + return env.value
109 + return Environment(env).value
110 +
111 +
112 +def _sdk_error_from_native(exc: NativeSdkError) -> SdkError:
113 + try:
114 + payload = json.loads(str(exc))
115 + except json.JSONDecodeError:
116 + return SdkError(0, "Unknown", str(exc))
117 +
118 + return SdkError(
119 + int(payload.get("error_code", 0)),
120 + str(payload.get("error_name", "Unknown")),
121 + str(payload.get("message", str(exc))),
122 + )
123 +
124 +
125 +try:
126 + __version__ = version("smbcloud-sdk-auth")
127 +except PackageNotFoundError:
128 + __version__ = "0.0.0"
129 +
130 +
131 +__all__ = [
132 + "AuthClient",
133 + "Environment",
134 + "SdkError",
135 + "__version__",
136 + "login_with_client",
137 + "logout_with_client",
138 + "me_with_client",
139 + "remove_with_client",
140 + "signup_with_client",
141 +]
sdk/python/smbcloud_auth/py.typed