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
+]