magic-admin 2.1.2__py3-none-any.whl

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,8 @@
1
+ from magic_admin.magic import Magic # noqa: F401
2
+
3
+
4
+ # Magic API secret key.
5
+ api_secret_key = None
6
+
7
+ # A grace period time in second applied to the nbf field for token validation.
8
+ did_token_nbf_grace_period_s = 300
magic_admin/config.py ADDED
@@ -0,0 +1,9 @@
1
+ base_url = "https://api.toaster.magic.link"
2
+
3
+ api_secret_api_key_missing_message = (
4
+ "API secret key is missing. Please specify "
5
+ "an API secret key when you instantiate the `Magic(api_secret_key=<KEY>)` "
6
+ "object or use the environment variable, `MAGIC_API_SECRET_KEY`. You can "
7
+ "get your API secret key from https://dashboard.magic.link. If you are having "
8
+ "trouble, please don't hesitate to reach out to us at support@magic.link"
9
+ )
magic_admin/error.py ADDED
@@ -0,0 +1,100 @@
1
+ class MagicError(Exception):
2
+ def __init__(self, message=None):
3
+ super().__init__(message)
4
+ self._message = message
5
+
6
+ def __str__(self):
7
+ return self._message or "<empty message>"
8
+
9
+ def __repr__(self):
10
+ return "{error_class}(message={message!r})".format(
11
+ error_class=self.__class__.__name__,
12
+ message=self._message,
13
+ )
14
+
15
+ def to_dict(self):
16
+ return {"message": str(self)}
17
+
18
+
19
+ class DIDTokenInvalid(MagicError):
20
+ pass
21
+
22
+
23
+ class DIDTokenMalformed(MagicError):
24
+ pass
25
+
26
+
27
+ class DIDTokenExpired(MagicError):
28
+ pass
29
+
30
+
31
+ class ExpectedBearerStringError(MagicError):
32
+ pass
33
+
34
+
35
+ class APIConnectionError(MagicError):
36
+ pass
37
+
38
+
39
+ class RequestError(MagicError):
40
+ def __init__(
41
+ self,
42
+ message=None,
43
+ http_status=None,
44
+ http_code=None,
45
+ http_resp_data=None,
46
+ http_message=None,
47
+ http_error_code=None,
48
+ http_request_params=None,
49
+ http_request_data=None,
50
+ http_method=None,
51
+ ):
52
+ super().__init__(message)
53
+ self.http_status = http_status
54
+ self.http_code = http_code
55
+ self.http_resp_data = http_resp_data
56
+ self.http_message = http_message
57
+ self.http_error_code = http_error_code
58
+ self.http_request_params = http_request_params
59
+ self.http_request_data = http_request_data
60
+ self.http_method = http_method
61
+
62
+ def __repr__(self):
63
+ return (
64
+ "{error_class}(message={message!r}, "
65
+ "http_error_code={http_error_code}, "
66
+ "http_code={http_code}).".format(
67
+ error_class=self.__class__.__name__,
68
+ message=self._message or None,
69
+ http_error_code=self.http_error_code or None,
70
+ http_code=self.http_code or None,
71
+ )
72
+ )
73
+
74
+ def to_dict(self):
75
+ _dict = super().to_dict()
76
+ for attr in self.__dict__:
77
+ if attr.startswith("http_"):
78
+ _dict[attr] = self.__dict__[attr]
79
+
80
+ return _dict
81
+
82
+
83
+ class RateLimitingError(RequestError):
84
+ pass
85
+
86
+
87
+ class BadRequestError(RequestError):
88
+ pass
89
+
90
+
91
+ class AuthenticationError(RequestError):
92
+ pass
93
+
94
+
95
+ class ForbiddenError(RequestError):
96
+ pass
97
+
98
+
99
+ class APIError(RequestError):
100
+ pass
@@ -0,0 +1,140 @@
1
+ import json
2
+ import platform
3
+
4
+ from requests import Session
5
+ from requests.adapters import HTTPAdapter
6
+ from requests.packages.urllib3.util.retry import Retry
7
+
8
+ import magic_admin
9
+ from magic_admin import version
10
+ from magic_admin.config import api_secret_api_key_missing_message
11
+ from magic_admin.config import base_url
12
+ from magic_admin.error import APIConnectionError
13
+ from magic_admin.error import APIError
14
+ from magic_admin.error import AuthenticationError
15
+ from magic_admin.error import BadRequestError
16
+ from magic_admin.error import ForbiddenError
17
+ from magic_admin.error import RateLimitingError
18
+ from magic_admin.response import MagicResponse
19
+
20
+
21
+ class RequestsClient:
22
+ def __init__(self, retries, timeout, backoff_factor):
23
+ self._retries = retries
24
+ self._timeout = timeout
25
+ self._backoff_factor = backoff_factor
26
+
27
+ self._setup_request_session()
28
+
29
+ @staticmethod
30
+ def _get_platform_info():
31
+ platform_info = {}
32
+
33
+ for attr, func in [
34
+ ["platform", platform.platform],
35
+ ["language_version", platform.python_version],
36
+ ["uname", platform.uname],
37
+ ]:
38
+ try:
39
+ val = str(func())
40
+ except Exception as e:
41
+ val = "<{}>".format(str(e))
42
+
43
+ platform_info[attr] = val
44
+
45
+ return platform_info
46
+
47
+ def _setup_request_session(self):
48
+ """Take advantage of the ``requests.Session``. If client is making several
49
+ requests to the same host, the underlying TCP connection will be reused,
50
+ which can result in a significant performance increase.
51
+ """
52
+ self.http = Session()
53
+ self.http.mount(
54
+ base_url,
55
+ HTTPAdapter(
56
+ max_retries=Retry(
57
+ total=self._retries,
58
+ backoff_factor=self._backoff_factor,
59
+ ),
60
+ ),
61
+ )
62
+
63
+ def _get_request_headers(self):
64
+ user_agent = {
65
+ "language": "python",
66
+ "sdk_version": version.VERSION,
67
+ "publisher": "magic",
68
+ "http_lib": self.__class__.__name__,
69
+ **self._get_platform_info(),
70
+ }
71
+
72
+ if magic_admin.api_secret_key is None:
73
+ raise AuthenticationError(api_secret_api_key_missing_message)
74
+
75
+ return {
76
+ "X-Magic-Secret-Key": magic_admin.api_secret_key,
77
+ "User-Agent": json.dumps(user_agent),
78
+ }
79
+
80
+ def request(self, method, url, params=None, data=None):
81
+ try:
82
+ api_resp = self.http.request(
83
+ method,
84
+ url,
85
+ params=params,
86
+ # Requests auto-converts this to JSON and add content-type
87
+ # `application/json`.
88
+ json=data,
89
+ headers=self._get_request_headers(),
90
+ timeout=self._timeout,
91
+ )
92
+ except Exception as e:
93
+ return self._handle_request_error(e)
94
+
95
+ return self._parse_and_convert_to_api_response(
96
+ api_resp,
97
+ params,
98
+ data,
99
+ )
100
+
101
+ def _parse_and_convert_to_api_response(self, resp, request_params, request_data):
102
+ status_code = resp.status_code
103
+
104
+ if 200 <= status_code < 300:
105
+ return MagicResponse(resp.content, resp.json(), status_code)
106
+
107
+ if status_code == 429:
108
+ error_class = RateLimitingError
109
+ elif status_code == 400:
110
+ error_class = BadRequestError
111
+ elif status_code == 401:
112
+ error_class = AuthenticationError
113
+ elif status_code == 403:
114
+ error_class = ForbiddenError
115
+ else:
116
+ error_class = APIError
117
+
118
+ resp_data = resp.json()
119
+ raise error_class(
120
+ http_status=resp_data.get("status"),
121
+ http_code=status_code,
122
+ http_resp_data=resp_data.get("data"),
123
+ http_message=resp_data.get("message"),
124
+ http_error_code=resp_data.get("error_code"),
125
+ http_request_params=request_params,
126
+ http_request_data=request_data,
127
+ http_method=resp.request.method,
128
+ )
129
+
130
+ def _handle_request_error(self, e):
131
+ message = (
132
+ "Unexpected error thrown while communicating to Magic. "
133
+ "Please reach out to support@magic.link if the problem continues. "
134
+ "Error message: {error_class} was raised - {error_message}".format(
135
+ error_class=e.__class__.__name__,
136
+ error_message=str(e) or "no error message.",
137
+ )
138
+ )
139
+
140
+ raise APIConnectionError(message)
magic_admin/magic.py ADDED
@@ -0,0 +1,53 @@
1
+ import os
2
+
3
+ import magic_admin
4
+ from magic_admin.config import api_secret_api_key_missing_message
5
+ from magic_admin.config import base_url
6
+ from magic_admin.error import AuthenticationError
7
+ from magic_admin.http_client import RequestsClient
8
+ from magic_admin.resources.base import ResourceComponent
9
+
10
+
11
+ RETRIES = 3
12
+ TIMEOUT = 10
13
+ BACKOFF_FACTOR = 0.02
14
+
15
+
16
+ class Magic:
17
+ v1_client_info = base_url + "/v1/admin/client"
18
+
19
+ def __getattr__(self, attribute_name):
20
+ try:
21
+ return getattr(self._resource, attribute_name)
22
+ except AttributeError:
23
+ pass
24
+
25
+ return super().__getattribute__(attribute_name)
26
+
27
+ def __init__(
28
+ self,
29
+ api_secret_key=None,
30
+ client_id=None,
31
+ retries=RETRIES,
32
+ timeout=TIMEOUT,
33
+ backoff_factor=BACKOFF_FACTOR,
34
+ ):
35
+ self._resource = ResourceComponent()
36
+
37
+ self._resource.setup_request_client(retries, timeout, backoff_factor)
38
+ self._set_api_secret_key(api_secret_key)
39
+ init_requests_client = RequestsClient(retries, timeout, backoff_factor)
40
+ magic_admin.client_id = (
41
+ client_id
42
+ or init_requests_client.request("get", self.v1_client_info).data[
43
+ "client_id"
44
+ ]
45
+ )
46
+
47
+ def _set_api_secret_key(self, api_secret_key):
48
+ magic_admin.api_secret_key = api_secret_key or os.environ.get(
49
+ "MAGIC_API_SECRET_KEY",
50
+ )
51
+
52
+ if magic_admin.api_secret_key is None:
53
+ raise AuthenticationError(api_secret_api_key_missing_message)
@@ -0,0 +1,5 @@
1
+ class MagicResponse:
2
+ def __init__(self, content, resp_data, status_code):
3
+ self.content = content
4
+ self.status_code = status_code
5
+ self.data = resp_data
magic_admin/version.py ADDED
@@ -0,0 +1 @@
1
+ VERSION = "2.1.2"
@@ -0,0 +1,172 @@
1
+ Metadata-Version: 2.4
2
+ Name: magic-admin
3
+ Version: 2.1.2
4
+ Summary: Magic Python Library
5
+ Author-email: Magic <support@magic.link>
6
+ License: MIT
7
+ Project-URL: Website, https://magic.link
8
+ Keywords: magic,python,sdk
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: requests==2.32.5
17
+ Requires-Dist: web3==7.13.0
18
+ Requires-Dist: websockets==15.0.1
19
+
20
+ # Magic Admin Python SDK
21
+
22
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
23
+ [![PyPI version](https://badge.fury.io/py/magic-admin.svg)](https://badge.fury.io/py/magic-admin)
24
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE.txt)
25
+
26
+ The Magic Admin Python SDK provides a simple and powerful way to integrate Magic's authentication system into your Python applications. Handle [DID Tokens](https://docs.magic.link/embedded-wallets/authentication/features/decentralized-id#decentralized-id-did-tokens) and interact with Magic API endpoints with ease.
27
+
28
+ ## 📚 Documentation
29
+
30
+ 📖 **Full Documentation**: [Magic Python SDK Docs](https://docs.magic.link/embedded-wallets/sdk/server-side/python)
31
+
32
+ ## 🚀 Quick Start
33
+
34
+ ### Installation
35
+
36
+ ```bash
37
+ # Using pip
38
+ pip install magic-admin
39
+
40
+ # Using conda
41
+ conda install magic-admin
42
+ ```
43
+
44
+ ### Basic Usage
45
+
46
+ ```python
47
+ from magic_admin import Magic
48
+
49
+ # Initialize with your API secret key
50
+ magic = Magic(api_secret_key='your_api_secret_key_here')
51
+
52
+ # Validate a DID token
53
+ try:
54
+ magic.Token.validate('DID_TOKEN_FROM_CLIENT')
55
+ print("Token is valid!")
56
+ except Exception as e:
57
+ print(f"Token validation failed: {e}")
58
+ ```
59
+
60
+ ### Environment Variable Configuration
61
+
62
+ You can also load your API secret key from an environment variable:
63
+
64
+ ```bash
65
+ export MAGIC_API_SECRET_KEY="your_api_secret_key_here"
66
+ ```
67
+
68
+ ```python
69
+ from magic_admin import Magic
70
+
71
+ # Automatically uses MAGIC_API_SECRET_KEY environment variable
72
+ magic = Magic()
73
+ ```
74
+
75
+ > **Note**: The API secret key passed directly to `Magic()` takes precedence over the environment variable.
76
+
77
+ ### Network Configuration
78
+
79
+ Customize network behavior for your application:
80
+
81
+ ```python
82
+ magic = Magic(
83
+ api_secret_key='your_key',
84
+ retries=5, # Number of retry attempts
85
+ timeout=10, # Request timeout in seconds
86
+ backoff_factor=0.03 # Exponential backoff factor
87
+ )
88
+ ```
89
+
90
+ ## 🔧 Development
91
+
92
+ ### Prerequisites
93
+
94
+ - Python 3.11+
95
+ - Git
96
+
97
+ ### Setup Development Environment
98
+
99
+ ```bash
100
+ # Clone the repository
101
+ git clone https://github.com/magiclabs/magic-admin-python.git
102
+ cd magic-admin-python
103
+
104
+ # Create virtual environment and install dependencies
105
+ make development
106
+
107
+ # Activate the virtual environment
108
+ source virtualenv_run/bin/activate
109
+ ```
110
+
111
+ ### Running Tests
112
+
113
+ ```bash
114
+ # Run tests against all supported Python versions (3.11, 3.12, 3.13)
115
+ make test
116
+
117
+ # Run tests with coverage
118
+ make test
119
+ ```
120
+
121
+ ### Code Quality
122
+
123
+ This project uses [pre-commit](https://pre-commit.com/) to maintain code quality. Hooks run automatically on every commit.
124
+
125
+ ```bash
126
+ # Run pre-commit hooks manually
127
+ pre-commit run --all-files
128
+
129
+ # Install pre-commit hooks
130
+ pre-commit install
131
+ ```
132
+
133
+ ### Cleanup
134
+
135
+ ```bash
136
+ # Remove virtual environment, tox logs, and pytest cache
137
+ make clean
138
+ ```
139
+
140
+ ## 📋 Requirements
141
+
142
+ - **Python**: 3.11+
143
+ - **Dependencies**: See [requirements.txt](requirements.txt)
144
+ - **Development**: See [requirements-dev.txt](requirements-dev.txt)
145
+
146
+ ## 🤝 Contributing
147
+
148
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
149
+
150
+ ### Development Workflow
151
+
152
+ 1. Fork the repository
153
+ 2. Create a feature branch
154
+ 3. Make your changes
155
+ 4. Run tests: `make test`
156
+ 5. Run pre-commit: `pre-commit run --all-files`
157
+ 6. Submit a pull request
158
+
159
+ ## 📄 License
160
+
161
+ This project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for details.
162
+
163
+ ## 📝 Changelog
164
+
165
+ See [CHANGELOG.md](CHANGELOG.md) for a detailed history of changes.
166
+
167
+ ## 🔗 Links
168
+
169
+ - [Magic Documentation](https://docs.magic.link)
170
+ - [Magic Dashboard](https://dashboard.magic.link)
171
+ - [Magic Python SDK Docs](https://docs.magic.link/embedded-wallets/sdk/server-side/python)
172
+ - [DID Token Documentation](https://docs.magic.link/embedded-wallets/authentication/features/decentralized-id#decentralized-id-did-tokens)
@@ -0,0 +1,11 @@
1
+ magic_admin/__init__.py,sha256=DuRZ7GbiA_ZmOZ-9eOjTMvRDC5JuVE-4F7ZG4IoGfG4,213
2
+ magic_admin/config.py,sha256=n2HFDIxDTRRQTOYwLspb4lGUnObQTwK3gK5_kohWNDs,456
3
+ magic_admin/error.py,sha256=73yr-TWgxGY5kvMMrtPJKzP566HI41MZ_hVd6OcrFO0,2270
4
+ magic_admin/http_client.py,sha256=9Sg5rLhIZDfVo-xj9agkuqOOR_xQqHxLAAhg0fdlXJk,4570
5
+ magic_admin/magic.py,sha256=-ydXF1ALw4l53bWVZPuZRw-LIL-9LEIyaitggdd2AHw,1542
6
+ magic_admin/response.py,sha256=irjZDDLv79IzmIK_sohcZw-1UAbDZWsDjccKv0kujO8,178
7
+ magic_admin/version.py,sha256=g3cxz98sq2sH94EJfsXt9vgetLlENXp3IEKtEY6wMyg,18
8
+ magic_admin-2.1.2.dist-info/METADATA,sha256=e_IHIQzzeeQ6A-m1Vxg4fOdMcuU61Y03lx7CPo2V6m4,4433
9
+ magic_admin-2.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ magic_admin-2.1.2.dist-info/top_level.txt,sha256=apXKs0D-XDoIjenh62w68eONw8qHW7s5FgRoA5vc2aw,12
11
+ magic_admin-2.1.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ magic_admin