generateApiKey 0.4.7__tar.gz → 0.4.8__tar.gz

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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: generateApiKey
3
- Version: 0.4.7
3
+ Version: 0.4.8
4
4
  Summary: This package provides a convenient way to generate API keys using a secret, seed, and an optional include keyword. The generated keys are unique and secure, making them suitable for use in a variety of applications
5
5
  Home-page: https://github.com/nuhmanpk/generate-api-key
6
6
  Author: Nuhman Pk
@@ -10,13 +10,25 @@ Project-URL: Documentation, https://github.com/nuhmanpk/generate-api-key/blob/ma
10
10
  Project-URL: Funding, https://github.com/sponsors/nuhmanpk
11
11
  Project-URL: Source, https://github.com/nuhmanpk/generate-api-key/
12
12
  Project-URL: Tracker, https://github.com/nuhmanpk/generate-api-key/issues
13
- Platform: UNKNOWN
14
13
  Classifier: Programming Language :: Python :: 3.9
15
14
  Classifier: License :: OSI Approved :: MIT License
16
15
  Classifier: Operating System :: OS Independent
17
16
  Requires-Python: >=3.9
18
17
  Description-Content-Type: text/markdown
19
18
  License-File: LICENSE
19
+ Requires-Dist: cryptography
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: license
27
+ Dynamic: license-file
28
+ Dynamic: project-url
29
+ Dynamic: requires-dist
30
+ Dynamic: requires-python
31
+ Dynamic: summary
20
32
 
21
33
  [![Downloads](https://static.pepy.tech/personalized-badge/generateApiKey?period=total&units=abbreviation&left_color=grey&right_color=yellow&left_text=Total-Downloads)](https://pepy.tech/project/generateApiKey)
22
34
  [![Supported Versions](https://img.shields.io/pypi/pyversions/generateApiKey.svg)](https://pypi.org/project/YoutubeTags)
@@ -24,7 +36,7 @@ License-File: LICENSE
24
36
  ![PyPI](https://img.shields.io/pypi/v/generateApiKey)
25
37
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/generateApiKey)
26
38
  [![Downloads](https://static.pepy.tech/personalized-badge/generateApiKey?period=week&units=international_system&left_color=grey&right_color=brightgreen&left_text=Downloads/Week)](https://pepy.tech/project/generateApiKey)
27
- [![Upload to PIP](https://github.com/nuhmanpk/generate-api-key/actions/workflows/pypi-upload.yml/badge.svg?branch=main&event=workflow_dispatch)](https://github.com/nuhmanpk/generate-api-key/actions/workflows/Pypi-uploads.yml)
39
+ [![Upload to PIP](https://github.com/nuhmanpk/generate-api-key/actions/workflows/pypi-upload.yml/badge.svg)](https://github.com/nuhmanpk/generate-api-key/actions/workflows/pypi-upload.yml)
28
40
  ![PyPI - Format](https://img.shields.io/pypi/format/generateApiKey)
29
41
  # API Key Generation Package
30
42
 
@@ -59,93 +71,107 @@ To install your package, you can use the pip package manager by running the foll
59
71
  pip install generateApiKey
60
72
  ```
61
73
 
62
- # Importing the package
63
- Once the package is installed, you can import the package in your code by using the import statement:
64
- ```python
65
- from generateApiKey import generateApiKey
66
- ```
67
- # Generating an API key
68
- To generate an API key, you can use the generateApiKey() function and pass in the secret, seed, and an optional include keyword. For example:
74
+ # Quick start
69
75
 
70
- ### Without Dashes
76
+ The simplest way to get a secure key — no arguments, no `await`:
71
77
 
72
78
  ```python
73
- from generateApiKey import generateApiKey
79
+ from generateApiKey import generate_api_key
74
80
 
75
- seed='12'
76
- secret='Topsecrect'
77
- api_key= await generateApiKey(secret,seed) # KaEQfEOP0ZMXbkgElkuPRv6iUrMxQDytwrQPKhH0eO8
78
- print(api_key)
81
+ api_key = generate_api_key() # 'uYTvAuzrTHAeU202ScbN6jV8kxIMogDf'
79
82
  ```
80
83
 
84
+ That's it. Everything below is optional.
85
+
86
+ ## Common recipes
81
87
 
82
88
  ```python
83
- from generateApiKey import generateApiKey
84
- api_key = await generateApiKey(secret='Topsecret', seed='12',include='user1') # iFuOcas8lSCw8jKWVIg4ru9WXhfr8erl5YzWc8kQw
85
- print(api_key)
89
+ generate_api_key(length=48) # longer key
90
+ generate_api_key(prefix="sk") # 'sk-gRsNbb2lQ1cZxoT3...'
91
+ generate_api_key(dashes=True) # 'GdBA-6DhM-M3Fv-g99C...'
92
+ generate_api_key(charset="hex", length=32) # hex only
93
+ generate_api_key(charset="base58") # no ambiguous 0/O/I/l
94
+ generate_api_key(charset="ABC123", length=10) # your own alphabet
95
+ generate_api_key(case="upper", charset="alphabetic")
86
96
  ```
87
- ### With Dashes
97
+
98
+ ## Charsets
99
+
100
+ Pass a preset name or a literal string of characters to `charset`:
101
+
102
+ | Preset | Characters |
103
+ |--------|-----------|
104
+ | `alphanumeric` (default) | `A-Z a-z 0-9` |
105
+ | `base62` | `A-Z a-z 0-9` |
106
+ | `base58` | base62 minus `0 O I l` |
107
+ | `base64url` | `A-Z a-z 0-9 - _` |
108
+ | `alphabetic` | `A-Z a-z` |
109
+ | `numeric` | `0-9` |
110
+ | `hex` | `0-9 a-f` |
111
+ | `base16` | `0-9 A-F` |
112
+
113
+ ## Expiring keys
114
+
115
+ Set `expiry` (minutes). The returned key **is a string** but also carries expiry metadata:
88
116
 
89
117
  ```python
90
- from generateApiKey import generateApiKey
91
- api_key = await generateApiKey(secret='Topsecret', seed='12',
92
- include='user1',add_dashes=True) # 1ff0f93f-c14d-5768-b324-626f1099948b
93
- print(api_key)
118
+ key = generate_api_key(expiry=60)
119
+
120
+ headers = {"Authorization": key} # works — it's a str
121
+ key.expires_at # unix timestamp
122
+ key.is_expired # False (until 60 min pass)
94
123
  ```
95
- ### With prefix
96
124
 
97
- ```python
98
- seed='12'
99
- secret='Topsecrect'
100
- api_key=await generateApiKey(secret,seed,
101
- prefix='hf',add_dashes=True) # hf-db27e2d7-d0bb-5014-9f75-4c2fda33f727
102
- print(api_key)
125
+ ## Derived keys (secret + seed)
103
126
 
127
+ Provide both `secret` and `seed` to derive the key deterministically from them
128
+ (HMAC-SHA256). A random salt still makes each generated key unique:
129
+
130
+ ```python
131
+ generate_api_key("mysecret", "user-42", length=20, include="prod")
104
132
  ```
105
133
 
106
- ### With include
134
+ ## Async is optional
135
+
136
+ The library is synchronous by default. When you need an awaitable:
107
137
 
108
138
  ```python
109
- secret = 'mysecret'
110
- seed = 'randomseed'
111
- include = "TopSecretWord"
112
- api_key = await generateApiKey(secret, seed, include)
113
- print(api_key)
139
+ from generateApiKey import generate_api_key_async
140
+
141
+ api_key = await generate_api_key_async(length=48, prefix="sk")
114
142
  ```
115
- This will generate an API key based on the provided secret, seed, and include keyword.
143
+
144
+ > The original `await generateApiKey(secret, seed, ...)` still works unchanged
145
+ > for backwards compatibility, but `generate_api_key` is recommended.
116
146
 
117
147
  ## Error handling
118
- It is a good practice to handle errors, you can use the try and except block to handle any exception that may occur during the key generation process.
119
148
 
120
149
  ```python
121
150
  try:
122
- secret = 'mysecret'
123
- seed = 'randomseed'
124
- include = "Production"
125
- api_key = await generateApiKey(secret, seed, include)
126
- print(api_key)
127
- except Exception as e:
128
- print(e)
151
+ api_key = generate_api_key(length=32)
152
+ except ValueError as e:
153
+ print(e) # e.g. bad length, or only one of secret/seed given
129
154
  ```
130
155
 
131
- You can use the None return in the package function, to check if the key generation was successful.
156
+ # Parameters
132
157
 
133
- ```python
134
- api_key = await generateApiKey(secret, seed, include)
135
- if api_key is None:
136
- print("Failed to generate api key")
137
- else:
138
- print(api_key)
139
- ```
140
- # Parameters
158
+ All parameters are optional. Call `generate_api_key()` with nothing for a secure default.
159
+
160
+ | **Parameter** | **Type** | **Default** | **Description** |
161
+ |---------------|----------|-------------|-----------------|
162
+ | `secret` | `str` | `None` | Optional secret. Given with `seed`, the key is HMAC-derived instead of random. |
163
+ | `seed` | `str` | `None` | Optional per-key value, used together with `secret`. |
164
+ | `length` | `int` | `32` | Number of characters in the key body. |
165
+ | `charset` | `str` | `"alphanumeric"` | Preset name (see table) or a literal string of allowed characters. |
166
+ | `case` | `str` | `"mixed"` | `"mixed"`, `"lower"` or `"upper"`. Affects letters only. |
167
+ | `prefix` | `str` | `None` | Prefix joined with `separator`, e.g. `sk-...`. |
168
+ | `separator` | `str` | `"-"` | String between prefix and key. |
169
+ | `dashes` | `bool` | `False` | Group the key with dashes for readability. |
170
+ | `dash_group` | `int` | `4` | Group size when `dashes=True`. |
171
+ | `include` | `str` | `None` | Extra value mixed into a derived key (needs `secret` + `seed`). |
172
+ | `expiry` | `int` | `None` | Expiry in minutes; sets `.expires_at` / `.is_expired`. |
141
173
 
142
- | **Variable** | **Type** | **Required** | **Example Value** | **Default** | **Description** |
143
- |----------|---------|-----------|--------------------|-------------|-------------------|
144
- | secret | `String` | Yes | "Topsecret" | - | The secret key used to generate the API key. It should be kept private and not shared with unauthorized parties. |
145
- | seed | `String` | Yes | "12" | - | A value used to seed the generation of the API key. It can be any value, but it should be unique for each key. |
146
- | include | `String` | Optional | "user1" | - | An optional value that can be included in the generated API key. It can be used to identify the user or application associated with the key. |
147
- | add_dashes | `boolean` | Optional | True | False | A value used to determine wheather to add dashes or not in a key |
148
- | prefix | `String` | Optional | "hf" | None | A Value that is added infornt of the apikey generted to make your key unique for your platform |
174
+ Returns an `ApiKey` a `str` subclass, so it works anywhere a string does.
149
175
 
150
176
 
151
177
  By using this package, you are solely liable for any legal issues that may arise from its unauthorized use or misuse, as well as any security risks that may result from not properly securing the secret key used to generate the API keys.
@@ -158,6 +184,4 @@ We encourage you to use the package responsibly, and to report any security issu
158
184
 
159
185
 
160
186
 
161
- **Happy coding! 🚀**
162
-
163
-
187
+ Made with ❤️ , **Happy coding! 🚀**
@@ -4,7 +4,7 @@
4
4
  ![PyPI](https://img.shields.io/pypi/v/generateApiKey)
5
5
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/generateApiKey)
6
6
  [![Downloads](https://static.pepy.tech/personalized-badge/generateApiKey?period=week&units=international_system&left_color=grey&right_color=brightgreen&left_text=Downloads/Week)](https://pepy.tech/project/generateApiKey)
7
- [![Upload to PIP](https://github.com/nuhmanpk/generate-api-key/actions/workflows/pypi-upload.yml/badge.svg?branch=main&event=workflow_dispatch)](https://github.com/nuhmanpk/generate-api-key/actions/workflows/Pypi-uploads.yml)
7
+ [![Upload to PIP](https://github.com/nuhmanpk/generate-api-key/actions/workflows/pypi-upload.yml/badge.svg)](https://github.com/nuhmanpk/generate-api-key/actions/workflows/pypi-upload.yml)
8
8
  ![PyPI - Format](https://img.shields.io/pypi/format/generateApiKey)
9
9
  # API Key Generation Package
10
10
 
@@ -39,93 +39,107 @@ To install your package, you can use the pip package manager by running the foll
39
39
  pip install generateApiKey
40
40
  ```
41
41
 
42
- # Importing the package
43
- Once the package is installed, you can import the package in your code by using the import statement:
44
- ```python
45
- from generateApiKey import generateApiKey
46
- ```
47
- # Generating an API key
48
- To generate an API key, you can use the generateApiKey() function and pass in the secret, seed, and an optional include keyword. For example:
42
+ # Quick start
49
43
 
50
- ### Without Dashes
44
+ The simplest way to get a secure key — no arguments, no `await`:
51
45
 
52
46
  ```python
53
- from generateApiKey import generateApiKey
47
+ from generateApiKey import generate_api_key
54
48
 
55
- seed='12'
56
- secret='Topsecrect'
57
- api_key= await generateApiKey(secret,seed) # KaEQfEOP0ZMXbkgElkuPRv6iUrMxQDytwrQPKhH0eO8
58
- print(api_key)
49
+ api_key = generate_api_key() # 'uYTvAuzrTHAeU202ScbN6jV8kxIMogDf'
59
50
  ```
60
51
 
52
+ That's it. Everything below is optional.
53
+
54
+ ## Common recipes
61
55
 
62
56
  ```python
63
- from generateApiKey import generateApiKey
64
- api_key = await generateApiKey(secret='Topsecret', seed='12',include='user1') # iFuOcas8lSCw8jKWVIg4ru9WXhfr8erl5YzWc8kQw
65
- print(api_key)
57
+ generate_api_key(length=48) # longer key
58
+ generate_api_key(prefix="sk") # 'sk-gRsNbb2lQ1cZxoT3...'
59
+ generate_api_key(dashes=True) # 'GdBA-6DhM-M3Fv-g99C...'
60
+ generate_api_key(charset="hex", length=32) # hex only
61
+ generate_api_key(charset="base58") # no ambiguous 0/O/I/l
62
+ generate_api_key(charset="ABC123", length=10) # your own alphabet
63
+ generate_api_key(case="upper", charset="alphabetic")
66
64
  ```
67
- ### With Dashes
65
+
66
+ ## Charsets
67
+
68
+ Pass a preset name or a literal string of characters to `charset`:
69
+
70
+ | Preset | Characters |
71
+ |--------|-----------|
72
+ | `alphanumeric` (default) | `A-Z a-z 0-9` |
73
+ | `base62` | `A-Z a-z 0-9` |
74
+ | `base58` | base62 minus `0 O I l` |
75
+ | `base64url` | `A-Z a-z 0-9 - _` |
76
+ | `alphabetic` | `A-Z a-z` |
77
+ | `numeric` | `0-9` |
78
+ | `hex` | `0-9 a-f` |
79
+ | `base16` | `0-9 A-F` |
80
+
81
+ ## Expiring keys
82
+
83
+ Set `expiry` (minutes). The returned key **is a string** but also carries expiry metadata:
68
84
 
69
85
  ```python
70
- from generateApiKey import generateApiKey
71
- api_key = await generateApiKey(secret='Topsecret', seed='12',
72
- include='user1',add_dashes=True) # 1ff0f93f-c14d-5768-b324-626f1099948b
73
- print(api_key)
86
+ key = generate_api_key(expiry=60)
87
+
88
+ headers = {"Authorization": key} # works — it's a str
89
+ key.expires_at # unix timestamp
90
+ key.is_expired # False (until 60 min pass)
74
91
  ```
75
- ### With prefix
76
92
 
77
- ```python
78
- seed='12'
79
- secret='Topsecrect'
80
- api_key=await generateApiKey(secret,seed,
81
- prefix='hf',add_dashes=True) # hf-db27e2d7-d0bb-5014-9f75-4c2fda33f727
82
- print(api_key)
93
+ ## Derived keys (secret + seed)
94
+
95
+ Provide both `secret` and `seed` to derive the key deterministically from them
96
+ (HMAC-SHA256). A random salt still makes each generated key unique:
83
97
 
98
+ ```python
99
+ generate_api_key("mysecret", "user-42", length=20, include="prod")
84
100
  ```
85
101
 
86
- ### With include
102
+ ## Async is optional
103
+
104
+ The library is synchronous by default. When you need an awaitable:
87
105
 
88
106
  ```python
89
- secret = 'mysecret'
90
- seed = 'randomseed'
91
- include = "TopSecretWord"
92
- api_key = await generateApiKey(secret, seed, include)
93
- print(api_key)
107
+ from generateApiKey import generate_api_key_async
108
+
109
+ api_key = await generate_api_key_async(length=48, prefix="sk")
94
110
  ```
95
- This will generate an API key based on the provided secret, seed, and include keyword.
111
+
112
+ > The original `await generateApiKey(secret, seed, ...)` still works unchanged
113
+ > for backwards compatibility, but `generate_api_key` is recommended.
96
114
 
97
115
  ## Error handling
98
- It is a good practice to handle errors, you can use the try and except block to handle any exception that may occur during the key generation process.
99
116
 
100
117
  ```python
101
118
  try:
102
- secret = 'mysecret'
103
- seed = 'randomseed'
104
- include = "Production"
105
- api_key = await generateApiKey(secret, seed, include)
106
- print(api_key)
107
- except Exception as e:
108
- print(e)
119
+ api_key = generate_api_key(length=32)
120
+ except ValueError as e:
121
+ print(e) # e.g. bad length, or only one of secret/seed given
109
122
  ```
110
123
 
111
- You can use the None return in the package function, to check if the key generation was successful.
124
+ # Parameters
112
125
 
113
- ```python
114
- api_key = await generateApiKey(secret, seed, include)
115
- if api_key is None:
116
- print("Failed to generate api key")
117
- else:
118
- print(api_key)
119
- ```
120
- # Parameters
126
+ All parameters are optional. Call `generate_api_key()` with nothing for a secure default.
127
+
128
+ | **Parameter** | **Type** | **Default** | **Description** |
129
+ |---------------|----------|-------------|-----------------|
130
+ | `secret` | `str` | `None` | Optional secret. Given with `seed`, the key is HMAC-derived instead of random. |
131
+ | `seed` | `str` | `None` | Optional per-key value, used together with `secret`. |
132
+ | `length` | `int` | `32` | Number of characters in the key body. |
133
+ | `charset` | `str` | `"alphanumeric"` | Preset name (see table) or a literal string of allowed characters. |
134
+ | `case` | `str` | `"mixed"` | `"mixed"`, `"lower"` or `"upper"`. Affects letters only. |
135
+ | `prefix` | `str` | `None` | Prefix joined with `separator`, e.g. `sk-...`. |
136
+ | `separator` | `str` | `"-"` | String between prefix and key. |
137
+ | `dashes` | `bool` | `False` | Group the key with dashes for readability. |
138
+ | `dash_group` | `int` | `4` | Group size when `dashes=True`. |
139
+ | `include` | `str` | `None` | Extra value mixed into a derived key (needs `secret` + `seed`). |
140
+ | `expiry` | `int` | `None` | Expiry in minutes; sets `.expires_at` / `.is_expired`. |
121
141
 
122
- | **Variable** | **Type** | **Required** | **Example Value** | **Default** | **Description** |
123
- |----------|---------|-----------|--------------------|-------------|-------------------|
124
- | secret | `String` | Yes | "Topsecret" | - | The secret key used to generate the API key. It should be kept private and not shared with unauthorized parties. |
125
- | seed | `String` | Yes | "12" | - | A value used to seed the generation of the API key. It can be any value, but it should be unique for each key. |
126
- | include | `String` | Optional | "user1" | - | An optional value that can be included in the generated API key. It can be used to identify the user or application associated with the key. |
127
- | add_dashes | `boolean` | Optional | True | False | A value used to determine wheather to add dashes or not in a key |
128
- | prefix | `String` | Optional | "hf" | None | A Value that is added infornt of the apikey generted to make your key unique for your platform |
142
+ Returns an `ApiKey` a `str` subclass, so it works anywhere a string does.
129
143
 
130
144
 
131
145
  By using this package, you are solely liable for any legal issues that may arise from its unauthorized use or misuse, as well as any security risks that may result from not properly securing the secret key used to generate the API keys.
@@ -138,4 +152,4 @@ We encourage you to use the package responsibly, and to report any security issu
138
152
 
139
153
 
140
154
 
141
- **Happy coding! 🚀**
155
+ Made with ❤️ , **Happy coding! 🚀**
@@ -0,0 +1,173 @@
1
+ """generateApiKey — simple, secure API key generation.
2
+
3
+ Quick start::
4
+
5
+ from generateApiKey import generate_api_key
6
+
7
+ key = generate_api_key() # secure random, 32 chars
8
+ key = generate_api_key(length=48, prefix="sk") # sk-xxxxxxxx...
9
+ key = generate_api_key(expiry=60) # expires in 60 minutes
10
+
11
+ Everything is synchronous by default. Use :func:`generate_api_key_async`
12
+ when you need an awaitable, or the legacy :func:`generateApiKey`.
13
+ """
14
+
15
+ import time
16
+
17
+ from .utils import (
18
+ CHARSETS,
19
+ build_charset,
20
+ random_key,
21
+ derived_key,
22
+ add_dashes,
23
+ )
24
+
25
+ __all__ = [
26
+ "generate_api_key",
27
+ "generate_api_key_async",
28
+ "generateApiKey",
29
+ "ApiKey",
30
+ "is_expired",
31
+ "is_token_valid",
32
+ "CHARSETS",
33
+ ]
34
+
35
+
36
+ class ApiKey(str):
37
+ """An API key.
38
+
39
+ Behaves exactly like a ``str`` (usable in headers, f-strings, comparisons)
40
+ while carrying optional expiry metadata::
41
+
42
+ key = generate_api_key(expiry=60)
43
+ headers = {"Authorization": key} # works, it's a str
44
+ key.expires_at # unix timestamp or None
45
+ key.is_expired # bool
46
+ """
47
+
48
+ expires_at = None
49
+
50
+ def __new__(cls, value: str, expires_at: int = None) -> "ApiKey":
51
+ obj = super().__new__(cls, value)
52
+ obj.expires_at = expires_at
53
+ return obj
54
+
55
+ @property
56
+ def is_expired(self) -> bool:
57
+ """True if an expiry was set and it is now in the past."""
58
+ return self.expires_at is not None and time.time() >= self.expires_at
59
+
60
+
61
+ def generate_api_key(
62
+ secret: str = None,
63
+ seed: str = None,
64
+ *,
65
+ length: int = 32,
66
+ charset: str = "alphanumeric",
67
+ case: str = "mixed",
68
+ prefix: str = None,
69
+ separator: str = "-",
70
+ dashes: bool = False,
71
+ dash_group: int = 4,
72
+ include: str = None,
73
+ expiry: int = None,
74
+ ) -> ApiKey:
75
+ """Generate a secure API key.
76
+
77
+ With no arguments it returns a cryptographically random key. Pass both
78
+ ``secret`` and ``seed`` to derive the key from them via HMAC-SHA256 instead.
79
+
80
+ Args:
81
+ secret: Optional secret. If given with ``seed``, the key is HMAC-derived.
82
+ seed: Optional per-key value used together with ``secret``.
83
+ length: Number of characters in the key body (before prefix/dashes). Default 32.
84
+ charset: A preset name (see ``CHARSETS``) or a literal string of allowed
85
+ characters. Presets: alphanumeric, base62, base58, base64url,
86
+ alphabetic, numeric, hex, base16.
87
+ case: ``"mixed"`` (default), ``"lower"`` or ``"upper"``.
88
+ prefix: Optional prefix, joined with ``separator`` (e.g. ``sk-...``).
89
+ separator: String used between prefix and key. Default ``"-"``.
90
+ dashes: If True, group the key body with dashes for readability.
91
+ dash_group: Group size when ``dashes`` is True. Default 4.
92
+ include: Extra value mixed into a derived key (requires secret + seed).
93
+ expiry: Optional expiry in minutes. Sets ``ApiKey.expires_at``.
94
+
95
+ Returns:
96
+ An :class:`ApiKey` (a ``str`` subclass).
97
+
98
+ Raises:
99
+ ValueError: If ``length`` < 1, or a bad ``charset``/``case`` is given, or
100
+ only one of ``secret``/``seed`` is supplied.
101
+ """
102
+ if length < 1:
103
+ raise ValueError("length must be a positive integer.")
104
+ if (secret is None) != (seed is None):
105
+ raise ValueError("Provide both 'secret' and 'seed', or neither.")
106
+
107
+ chars = build_charset(charset, case)
108
+
109
+ if secret and seed:
110
+ body = derived_key(secret, seed, include, length, chars)
111
+ else:
112
+ body = random_key(length, chars)
113
+
114
+ if dashes:
115
+ body = add_dashes(body, dash_group)
116
+
117
+ key = f"{prefix}{separator}{body}" if prefix else body
118
+
119
+ expires_at = int(time.time()) + expiry * 60 if expiry else None
120
+ return ApiKey(key, expires_at)
121
+
122
+
123
+ async def generate_api_key_async(*args, **kwargs) -> ApiKey:
124
+ """Async wrapper around :func:`generate_api_key` with the same signature."""
125
+ return generate_api_key(*args, **kwargs)
126
+
127
+
128
+ def is_expired(key: "ApiKey") -> bool:
129
+ """Return True if ``key`` is an expired :class:`ApiKey`."""
130
+ return getattr(key, "is_expired", False)
131
+
132
+
133
+ # ---------------------------------------------------------------------------
134
+ # Backwards-compatible legacy API (async). Prefer generate_api_key above.
135
+ # ---------------------------------------------------------------------------
136
+
137
+ async def generateApiKey(
138
+ secret: str,
139
+ seed: str,
140
+ include: str = None,
141
+ add_dashes: bool = False,
142
+ prefix: str = None,
143
+ length: int = None,
144
+ expiry: int = None,
145
+ ) -> ApiKey:
146
+ """Legacy async entry point. Kept for backwards compatibility.
147
+
148
+ New code should use :func:`generate_api_key` (synchronous, more options).
149
+ """
150
+ if not secret or not seed:
151
+ raise ValueError("Secret and seed cannot be empty strings.")
152
+
153
+ return generate_api_key(
154
+ secret,
155
+ seed,
156
+ length=length or 32,
157
+ include=include,
158
+ prefix=prefix,
159
+ dashes=add_dashes,
160
+ dash_group=4,
161
+ expiry=expiry,
162
+ )
163
+
164
+
165
+ async def is_token_valid(token: str) -> bool:
166
+ """Legacy check for the ``_<timestamp>`` expiry token format."""
167
+ if "_" not in token:
168
+ return False
169
+ try:
170
+ expiration_time = int(token.split("_")[1])
171
+ return int(time.time()) < expiration_time
172
+ except (IndexError, ValueError):
173
+ return False
@@ -0,0 +1,82 @@
1
+ import hmac
2
+ import time
3
+ import hashlib
4
+ import string
5
+ from secrets import choice, token_bytes
6
+
7
+ # Named character sets available via the ``charset`` option.
8
+ CHARSETS = {
9
+ "alphanumeric": string.ascii_letters + string.digits,
10
+ "base62": string.ascii_letters + string.digits,
11
+ "alphabetic": string.ascii_letters,
12
+ "numeric": string.digits,
13
+ "hex": string.digits + "abcdef",
14
+ "base16": string.digits + "ABCDEF",
15
+ "base64url": string.ascii_letters + string.digits + "-_",
16
+ # base58 = base62 minus the ambiguous chars 0, O, I and l
17
+ "base58": "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
18
+ }
19
+
20
+
21
+ def build_charset(charset: str, case: str) -> str:
22
+ """Resolve a charset name (or a custom literal string) and apply a case rule.
23
+
24
+ Args:
25
+ charset: A named preset in ``CHARSETS`` or a literal string of characters.
26
+ case: One of ``"mixed"``, ``"lower"`` or ``"upper"``. Only affects letters.
27
+
28
+ Returns:
29
+ The final string of characters to draw from.
30
+ """
31
+ chars = CHARSETS.get(charset, charset)
32
+ if not chars:
33
+ raise ValueError(f"Unknown or empty charset: {charset!r}")
34
+
35
+ if case == "lower":
36
+ chars = "".join(dict.fromkeys(chars.lower()))
37
+ elif case == "upper":
38
+ chars = "".join(dict.fromkeys(chars.upper()))
39
+ elif case != "mixed":
40
+ raise ValueError("case must be one of 'mixed', 'lower', 'upper'.")
41
+ return chars
42
+
43
+
44
+ def random_key(length: int, chars: str) -> str:
45
+ """Return a cryptographically secure random string of ``length`` from ``chars``."""
46
+ return "".join(choice(chars) for _ in range(length))
47
+
48
+
49
+ def derived_key(secret: str, seed: str, include: str, length: int, chars: str) -> str:
50
+ """Derive a key from ``secret``/``seed`` using HMAC-SHA256 as a byte stream.
51
+
52
+ A fresh random salt keeps every generated key unique even for identical
53
+ inputs. The HMAC output is mapped onto ``chars`` to honour the charset.
54
+ """
55
+ salt = token_bytes(16)
56
+ message = f"{seed}{include or ''}{int(time.time())}".encode()
57
+ material = bytearray()
58
+ counter = 0
59
+ while len(material) < length:
60
+ block = hmac.new(
61
+ secret.encode(),
62
+ message + salt + counter.to_bytes(4, "big"),
63
+ hashlib.sha256,
64
+ ).digest()
65
+ material.extend(block)
66
+ counter += 1
67
+ return "".join(chars[b % len(chars)] for b in material[:length])
68
+
69
+
70
+ def add_dashes(key: str, group: int, sep: str = "-") -> str:
71
+ """Insert ``sep`` every ``group`` characters, e.g. ``ABCD-EFGH-IJKL``."""
72
+ if group < 1:
73
+ return key
74
+ return sep.join(key[i : i + group] for i in range(0, len(key), group))
75
+
76
+
77
+ def generate_expiration_token(minutes: int) -> str:
78
+ """Return a stateless expiry token of the form ``_<unix_timestamp>``.
79
+
80
+ Kept for backwards compatibility with :func:`generateApiKey`.
81
+ """
82
+ return f"_{int(time.time()) + minutes * 60}"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: generateApiKey
3
- Version: 0.4.7
3
+ Version: 0.4.8
4
4
  Summary: This package provides a convenient way to generate API keys using a secret, seed, and an optional include keyword. The generated keys are unique and secure, making them suitable for use in a variety of applications
5
5
  Home-page: https://github.com/nuhmanpk/generate-api-key
6
6
  Author: Nuhman Pk
@@ -10,13 +10,25 @@ Project-URL: Documentation, https://github.com/nuhmanpk/generate-api-key/blob/ma
10
10
  Project-URL: Funding, https://github.com/sponsors/nuhmanpk
11
11
  Project-URL: Source, https://github.com/nuhmanpk/generate-api-key/
12
12
  Project-URL: Tracker, https://github.com/nuhmanpk/generate-api-key/issues
13
- Platform: UNKNOWN
14
13
  Classifier: Programming Language :: Python :: 3.9
15
14
  Classifier: License :: OSI Approved :: MIT License
16
15
  Classifier: Operating System :: OS Independent
17
16
  Requires-Python: >=3.9
18
17
  Description-Content-Type: text/markdown
19
18
  License-File: LICENSE
19
+ Requires-Dist: cryptography
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: license
27
+ Dynamic: license-file
28
+ Dynamic: project-url
29
+ Dynamic: requires-dist
30
+ Dynamic: requires-python
31
+ Dynamic: summary
20
32
 
21
33
  [![Downloads](https://static.pepy.tech/personalized-badge/generateApiKey?period=total&units=abbreviation&left_color=grey&right_color=yellow&left_text=Total-Downloads)](https://pepy.tech/project/generateApiKey)
22
34
  [![Supported Versions](https://img.shields.io/pypi/pyversions/generateApiKey.svg)](https://pypi.org/project/YoutubeTags)
@@ -24,7 +36,7 @@ License-File: LICENSE
24
36
  ![PyPI](https://img.shields.io/pypi/v/generateApiKey)
25
37
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/generateApiKey)
26
38
  [![Downloads](https://static.pepy.tech/personalized-badge/generateApiKey?period=week&units=international_system&left_color=grey&right_color=brightgreen&left_text=Downloads/Week)](https://pepy.tech/project/generateApiKey)
27
- [![Upload to PIP](https://github.com/nuhmanpk/generate-api-key/actions/workflows/pypi-upload.yml/badge.svg?branch=main&event=workflow_dispatch)](https://github.com/nuhmanpk/generate-api-key/actions/workflows/Pypi-uploads.yml)
39
+ [![Upload to PIP](https://github.com/nuhmanpk/generate-api-key/actions/workflows/pypi-upload.yml/badge.svg)](https://github.com/nuhmanpk/generate-api-key/actions/workflows/pypi-upload.yml)
28
40
  ![PyPI - Format](https://img.shields.io/pypi/format/generateApiKey)
29
41
  # API Key Generation Package
30
42
 
@@ -59,93 +71,107 @@ To install your package, you can use the pip package manager by running the foll
59
71
  pip install generateApiKey
60
72
  ```
61
73
 
62
- # Importing the package
63
- Once the package is installed, you can import the package in your code by using the import statement:
64
- ```python
65
- from generateApiKey import generateApiKey
66
- ```
67
- # Generating an API key
68
- To generate an API key, you can use the generateApiKey() function and pass in the secret, seed, and an optional include keyword. For example:
74
+ # Quick start
69
75
 
70
- ### Without Dashes
76
+ The simplest way to get a secure key — no arguments, no `await`:
71
77
 
72
78
  ```python
73
- from generateApiKey import generateApiKey
79
+ from generateApiKey import generate_api_key
74
80
 
75
- seed='12'
76
- secret='Topsecrect'
77
- api_key= await generateApiKey(secret,seed) # KaEQfEOP0ZMXbkgElkuPRv6iUrMxQDytwrQPKhH0eO8
78
- print(api_key)
81
+ api_key = generate_api_key() # 'uYTvAuzrTHAeU202ScbN6jV8kxIMogDf'
79
82
  ```
80
83
 
84
+ That's it. Everything below is optional.
85
+
86
+ ## Common recipes
81
87
 
82
88
  ```python
83
- from generateApiKey import generateApiKey
84
- api_key = await generateApiKey(secret='Topsecret', seed='12',include='user1') # iFuOcas8lSCw8jKWVIg4ru9WXhfr8erl5YzWc8kQw
85
- print(api_key)
89
+ generate_api_key(length=48) # longer key
90
+ generate_api_key(prefix="sk") # 'sk-gRsNbb2lQ1cZxoT3...'
91
+ generate_api_key(dashes=True) # 'GdBA-6DhM-M3Fv-g99C...'
92
+ generate_api_key(charset="hex", length=32) # hex only
93
+ generate_api_key(charset="base58") # no ambiguous 0/O/I/l
94
+ generate_api_key(charset="ABC123", length=10) # your own alphabet
95
+ generate_api_key(case="upper", charset="alphabetic")
86
96
  ```
87
- ### With Dashes
97
+
98
+ ## Charsets
99
+
100
+ Pass a preset name or a literal string of characters to `charset`:
101
+
102
+ | Preset | Characters |
103
+ |--------|-----------|
104
+ | `alphanumeric` (default) | `A-Z a-z 0-9` |
105
+ | `base62` | `A-Z a-z 0-9` |
106
+ | `base58` | base62 minus `0 O I l` |
107
+ | `base64url` | `A-Z a-z 0-9 - _` |
108
+ | `alphabetic` | `A-Z a-z` |
109
+ | `numeric` | `0-9` |
110
+ | `hex` | `0-9 a-f` |
111
+ | `base16` | `0-9 A-F` |
112
+
113
+ ## Expiring keys
114
+
115
+ Set `expiry` (minutes). The returned key **is a string** but also carries expiry metadata:
88
116
 
89
117
  ```python
90
- from generateApiKey import generateApiKey
91
- api_key = await generateApiKey(secret='Topsecret', seed='12',
92
- include='user1',add_dashes=True) # 1ff0f93f-c14d-5768-b324-626f1099948b
93
- print(api_key)
118
+ key = generate_api_key(expiry=60)
119
+
120
+ headers = {"Authorization": key} # works — it's a str
121
+ key.expires_at # unix timestamp
122
+ key.is_expired # False (until 60 min pass)
94
123
  ```
95
- ### With prefix
96
124
 
97
- ```python
98
- seed='12'
99
- secret='Topsecrect'
100
- api_key=await generateApiKey(secret,seed,
101
- prefix='hf',add_dashes=True) # hf-db27e2d7-d0bb-5014-9f75-4c2fda33f727
102
- print(api_key)
125
+ ## Derived keys (secret + seed)
103
126
 
127
+ Provide both `secret` and `seed` to derive the key deterministically from them
128
+ (HMAC-SHA256). A random salt still makes each generated key unique:
129
+
130
+ ```python
131
+ generate_api_key("mysecret", "user-42", length=20, include="prod")
104
132
  ```
105
133
 
106
- ### With include
134
+ ## Async is optional
135
+
136
+ The library is synchronous by default. When you need an awaitable:
107
137
 
108
138
  ```python
109
- secret = 'mysecret'
110
- seed = 'randomseed'
111
- include = "TopSecretWord"
112
- api_key = await generateApiKey(secret, seed, include)
113
- print(api_key)
139
+ from generateApiKey import generate_api_key_async
140
+
141
+ api_key = await generate_api_key_async(length=48, prefix="sk")
114
142
  ```
115
- This will generate an API key based on the provided secret, seed, and include keyword.
143
+
144
+ > The original `await generateApiKey(secret, seed, ...)` still works unchanged
145
+ > for backwards compatibility, but `generate_api_key` is recommended.
116
146
 
117
147
  ## Error handling
118
- It is a good practice to handle errors, you can use the try and except block to handle any exception that may occur during the key generation process.
119
148
 
120
149
  ```python
121
150
  try:
122
- secret = 'mysecret'
123
- seed = 'randomseed'
124
- include = "Production"
125
- api_key = await generateApiKey(secret, seed, include)
126
- print(api_key)
127
- except Exception as e:
128
- print(e)
151
+ api_key = generate_api_key(length=32)
152
+ except ValueError as e:
153
+ print(e) # e.g. bad length, or only one of secret/seed given
129
154
  ```
130
155
 
131
- You can use the None return in the package function, to check if the key generation was successful.
156
+ # Parameters
132
157
 
133
- ```python
134
- api_key = await generateApiKey(secret, seed, include)
135
- if api_key is None:
136
- print("Failed to generate api key")
137
- else:
138
- print(api_key)
139
- ```
140
- # Parameters
158
+ All parameters are optional. Call `generate_api_key()` with nothing for a secure default.
159
+
160
+ | **Parameter** | **Type** | **Default** | **Description** |
161
+ |---------------|----------|-------------|-----------------|
162
+ | `secret` | `str` | `None` | Optional secret. Given with `seed`, the key is HMAC-derived instead of random. |
163
+ | `seed` | `str` | `None` | Optional per-key value, used together with `secret`. |
164
+ | `length` | `int` | `32` | Number of characters in the key body. |
165
+ | `charset` | `str` | `"alphanumeric"` | Preset name (see table) or a literal string of allowed characters. |
166
+ | `case` | `str` | `"mixed"` | `"mixed"`, `"lower"` or `"upper"`. Affects letters only. |
167
+ | `prefix` | `str` | `None` | Prefix joined with `separator`, e.g. `sk-...`. |
168
+ | `separator` | `str` | `"-"` | String between prefix and key. |
169
+ | `dashes` | `bool` | `False` | Group the key with dashes for readability. |
170
+ | `dash_group` | `int` | `4` | Group size when `dashes=True`. |
171
+ | `include` | `str` | `None` | Extra value mixed into a derived key (needs `secret` + `seed`). |
172
+ | `expiry` | `int` | `None` | Expiry in minutes; sets `.expires_at` / `.is_expired`. |
141
173
 
142
- | **Variable** | **Type** | **Required** | **Example Value** | **Default** | **Description** |
143
- |----------|---------|-----------|--------------------|-------------|-------------------|
144
- | secret | `String` | Yes | "Topsecret" | - | The secret key used to generate the API key. It should be kept private and not shared with unauthorized parties. |
145
- | seed | `String` | Yes | "12" | - | A value used to seed the generation of the API key. It can be any value, but it should be unique for each key. |
146
- | include | `String` | Optional | "user1" | - | An optional value that can be included in the generated API key. It can be used to identify the user or application associated with the key. |
147
- | add_dashes | `boolean` | Optional | True | False | A value used to determine wheather to add dashes or not in a key |
148
- | prefix | `String` | Optional | "hf" | None | A Value that is added infornt of the apikey generted to make your key unique for your platform |
174
+ Returns an `ApiKey` a `str` subclass, so it works anywhere a string does.
149
175
 
150
176
 
151
177
  By using this package, you are solely liable for any legal issues that may arise from its unauthorized use or misuse, as well as any security risks that may result from not properly securing the secret key used to generate the API keys.
@@ -158,6 +184,4 @@ We encourage you to use the package responsibly, and to report any security issu
158
184
 
159
185
 
160
186
 
161
- **Happy coding! 🚀**
162
-
163
-
187
+ Made with ❤️ , **Happy coding! 🚀**
@@ -3,8 +3,10 @@ MANIFEST.in
3
3
  README.md
4
4
  setup.py
5
5
  generateApiKey/__init__.py
6
+ generateApiKey/utils.py
6
7
  generateApiKey.egg-info/PKG-INFO
7
8
  generateApiKey.egg-info/SOURCES.txt
8
9
  generateApiKey.egg-info/dependency_links.txt
9
10
  generateApiKey.egg-info/requires.txt
10
- generateApiKey.egg-info/top_level.txt
11
+ generateApiKey.egg-info/top_level.txt
12
+ tests/test_generate.py
@@ -7,7 +7,7 @@ README = (file / "README.md").read_text()
7
7
 
8
8
  setuptools.setup(
9
9
  name="generateApiKey",
10
- version="0.4.7",
10
+ version="0.4.8",
11
11
  author="Nuhman Pk",
12
12
  author_email="nuhmanpk7@gmail.com",
13
13
  long_description = README,
@@ -0,0 +1,97 @@
1
+ import time
2
+ import asyncio
3
+
4
+ from generateApiKey import (
5
+ generate_api_key,
6
+ generate_api_key_async,
7
+ generateApiKey,
8
+ ApiKey,
9
+ is_expired,
10
+ CHARSETS,
11
+ )
12
+
13
+
14
+ def test_default_length_and_charset():
15
+ key = generate_api_key()
16
+ assert isinstance(key, ApiKey) and isinstance(key, str)
17
+ assert len(key) == 32
18
+ assert all(c in CHARSETS["alphanumeric"] for c in key)
19
+
20
+
21
+ def test_length():
22
+ assert len(generate_api_key(length=48)) == 48
23
+
24
+
25
+ def test_charset_hex():
26
+ key = generate_api_key(charset="hex", length=40)
27
+ assert all(c in CHARSETS["hex"] for c in key)
28
+
29
+
30
+ def test_case_upper():
31
+ key = generate_api_key(charset="alphabetic", case="upper", length=20)
32
+ assert key.isupper()
33
+
34
+
35
+ def test_custom_charset():
36
+ key = generate_api_key(charset="AB12", length=16)
37
+ assert set(key) <= set("AB12")
38
+
39
+
40
+ def test_prefix():
41
+ assert generate_api_key(prefix="sk", length=8).startswith("sk-")
42
+
43
+
44
+ def test_dashes():
45
+ key = generate_api_key(dashes=True, dash_group=4, length=16)
46
+ assert key.count("-") == 3
47
+
48
+
49
+ def test_expiry_metadata():
50
+ key = generate_api_key(expiry=60)
51
+ assert key.expires_at is not None
52
+ assert not key.is_expired
53
+ assert not is_expired(key)
54
+
55
+
56
+ def test_expired():
57
+ past = ApiKey("x", int(time.time()) - 1)
58
+ assert past.is_expired
59
+
60
+
61
+ def test_uniqueness():
62
+ assert len({generate_api_key() for _ in range(1000)}) == 1000
63
+
64
+
65
+ def test_secret_seed_pair_required():
66
+ for kwargs in ({"secret": "x"}, {"seed": "y"}):
67
+ try:
68
+ generate_api_key(**kwargs)
69
+ assert False, "expected ValueError"
70
+ except ValueError:
71
+ pass
72
+
73
+
74
+ def test_bad_length():
75
+ try:
76
+ generate_api_key(length=0)
77
+ assert False
78
+ except ValueError:
79
+ pass
80
+
81
+
82
+ def test_async_wrapper():
83
+ key = asyncio.run(generate_api_key_async(length=8))
84
+ assert len(key) == 8
85
+
86
+
87
+ def test_legacy_async():
88
+ key = asyncio.run(generateApiKey("s", "seed", prefix="hf", add_dashes=True))
89
+ assert key.startswith("hf-")
90
+
91
+
92
+ if __name__ == "__main__":
93
+ for name, fn in sorted(globals().items()):
94
+ if name.startswith("test_"):
95
+ fn()
96
+ print("ok", name)
97
+ print("all passed")
@@ -1,83 +0,0 @@
1
- import hashlib
2
- import hmac
3
- import uuid
4
- import random
5
- import base64
6
- import time
7
- import datetime
8
- import re
9
- from secrets import token_bytes
10
- from cryptography.hazmat.primitives import hashes
11
- from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
12
-
13
-
14
- async def generateApiKey(
15
- secret: str,
16
- seed: str,
17
- include: str = None,
18
- add_dashes: bool = False,
19
- prefix: str = None
20
- ) -> str:
21
- """
22
- Generate a secure API key based on a secret, seed, and optional parameters.
23
-
24
- Args:
25
- secret: The secret key used for hashing.
26
- seed: A unique identifier used to personalize the key.
27
- include: An optional string to be included in the key generation process.
28
- add_dashes: Whether to add dashes to the generated key for improved readability. Default False
29
- prefix: A string prefix for the API key. Followed by a '-'. Default None
30
-
31
- Returns:
32
- A unique and secure API key.
33
-
34
- Raises:
35
- ValueError: If the secret or seed is an empty string.
36
- """
37
-
38
- if not secret or not seed:
39
- raise ValueError("Secret and seed cannot be empty strings.")
40
-
41
- current_timestamp = int(time.time())
42
- now = datetime.datetime.now()
43
- pbkdf2_iterations = random.randint(1000, 10000)
44
- numeric_representation = int(
45
- f"{now.year}{now.month:02d}{now.day:02d}{now.hour:02d}{now.minute:02d}{now.second:02d}"
46
- )
47
-
48
- timestamp_hash = hashlib.sha256(str(current_timestamp).encode()).hexdigest()
49
- numeric_hash = hashlib.sha256(str(numeric_representation).encode()).hexdigest()
50
-
51
- if add_dashes:
52
- message = str(seed).encode()
53
- if include:
54
- pos = random.randint(0, 32)
55
- message = message[:pos] + include.encode() + message[pos:]
56
-
57
- combined_hash = f"{timestamp_hash}{numeric_hash}{message}"
58
- combined_hash = "".join(random.sample(combined_hash, len(combined_hash)))
59
- api_key = hmac.new(
60
- secret.encode(), combined_hash.encode(), hashlib.sha256
61
- ).hexdigest()
62
- api_key = str(uuid.uuid5(uuid.NAMESPACE_DNS, api_key))
63
- else:
64
- salt = token_bytes(16)
65
- kdf = PBKDF2HMAC(
66
- algorithm=hashes.SHA256(),
67
- salt=salt,
68
- iterations=pbkdf2_iterations,
69
- length=32,
70
- )
71
- seed = f"{seed}{current_timestamp}{numeric_hash}".encode()
72
- if include:
73
- pos = random.randint(0, len(seed))
74
- seed = seed[:pos] + include.encode() + seed[pos:]
75
-
76
- key_bytes = kdf.derive(seed)
77
- key_base64 = base64.urlsafe_b64encode(key_bytes).decode()
78
- api_key = re.sub(r"[^a-zA-Z0-9]", "", key_base64)
79
-
80
- if prefix:
81
- api_key = f"{prefix}-{api_key}"
82
-
83
- return api_key
File without changes
File without changes