ufaas 0.1.22__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.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2016 The Python Packaging Authority (PyPA)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
ufaas-0.1.22/PKG-INFO ADDED
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.2
2
+ Name: ufaas
3
+ Version: 0.1.22
4
+ Summary: A client for UFaaS.
5
+ Author-email: Mahdi Kiani <mahdikiany@gmail.com>
6
+ Maintainer-email: Mahdi Kiani <mahdikiany@gmail.com>
7
+ License: Copyright (c) 2016 The Python Packaging Authority (PyPA)
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
10
+ this software and associated documentation files (the "Software"), to deal in
11
+ the Software without restriction, including without limitation the rights to
12
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
13
+ of the Software, and to permit persons to whom the Software is furnished to do
14
+ so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Project-URL: Homepage, https://github.com/ufilesorg/ufiles-python
28
+ Project-URL: Bug Reports, https://github.com/ufilesorg/ufiles-python/issues
29
+ Project-URL: Funding, https://github.com/ufilesorg/ufiles-python
30
+ Project-URL: Say Thanks!, https://saythanks.io/to/mahdikiani
31
+ Project-URL: Source, https://github.com/ufilesorg/ufiles-python
32
+ Keywords: ufaas,saas,usso,finance,pricing,pay as you go
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: Topic :: Software Development :: Build Tools
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Programming Language :: Python :: 3 :: Only
42
+ Requires-Python: >=3.9
43
+ Description-Content-Type: text/markdown
44
+ License-File: LICENSE.txt
45
+ Requires-Dist: pydantic>=2
46
+ Requires-Dist: pyjwt[crypto]
47
+ Requires-Dist: usso>=0.27.2
48
+ Requires-Dist: fastapi_mongo_base
49
+ Requires-Dist: singleton_package
50
+ Requires-Dist: json-advanced
51
+
52
+ # UFiles-Client
53
+
54
+ The Ufiles-Client is a SDK for ufiles servers.
55
+
56
+ ## Installation
57
+
58
+ Install the ufiles client using pip:
59
+
60
+ ```bash
61
+ pip install ufiles
62
+ ```
63
+
64
+ ## Quick Start
65
+ Follow the quick start guides in the documentation to integrate ufiles in your application.
66
+
67
+ ## Contributing
68
+ Contributions are welcome! See CONTRIBUTING.md for more details on how to get involved.
69
+
70
+ ## License
71
+ Distributed under the MIT License. See LICENSE for more information.
ufaas-0.1.22/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # UFiles-Client
2
+
3
+ The Ufiles-Client is a SDK for ufiles servers.
4
+
5
+ ## Installation
6
+
7
+ Install the ufiles client using pip:
8
+
9
+ ```bash
10
+ pip install ufiles
11
+ ```
12
+
13
+ ## Quick Start
14
+ Follow the quick start guides in the documentation to integrate ufiles in your application.
15
+
16
+ ## Contributing
17
+ Contributions are welcome! See CONTRIBUTING.md for more details on how to get involved.
18
+
19
+ ## License
20
+ Distributed under the MIT License. See LICENSE for more information.
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ufaas"
7
+ version = "0.1.22"
8
+ description = "A client for UFaaS."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = {file = "LICENSE.txt"}
12
+ keywords = ["ufaas", "saas", "usso", "finance", "pricing", "pay as you go"]
13
+ authors = [
14
+ {name = "Mahdi Kiani", email = "mahdikiany@gmail.com"}
15
+ ]
16
+ maintainers = [
17
+ {name = "Mahdi Kiani", email = "mahdikiany@gmail.com"}
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "Intended Audience :: Developers",
22
+ "Topic :: Software Development :: Build Tools",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3 :: Only",
29
+ ]
30
+ dependencies = [
31
+ "pydantic>=2",
32
+ "pyjwt[crypto]",
33
+ "usso>=0.27.2",
34
+ "fastapi_mongo_base",
35
+ "singleton_package",
36
+ "json-advanced"
37
+ ]
38
+
39
+ [project.urls]
40
+ "Homepage" = "https://github.com/ufilesorg/ufiles-python"
41
+ "Bug Reports" = "https://github.com/ufilesorg/ufiles-python/issues"
42
+ "Funding" = "https://github.com/ufilesorg/ufiles-python"
43
+ "Say Thanks!" = "https://saythanks.io/to/mahdikiani"
44
+ "Source" = "https://github.com/ufilesorg/ufiles-python"
45
+
46
+ [tool.setuptools]
47
+ package-data = {"sample" = ["*.dat"]}
ufaas-0.1.22/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .ufaas import AsyncUFaaS, UFaaS
2
+
3
+ __all__ = ["UFaaS", "AsyncUFaaS"]
File without changes
@@ -0,0 +1,223 @@
1
+ import os
2
+
3
+ import singleton
4
+ from fastapi_mongo_base.schemas import PaginatedResponse
5
+ from usso.session import AsyncUssoSession, UssoSession
6
+
7
+
8
+ class App(UssoSession):
9
+ def __init__(
10
+ self,
11
+ app_name: str = "saas",
12
+ *,
13
+ ufaas_base_url: str = os.getenv("UFAAS_URL"),
14
+ usso_base_url: str | None = os.getenv("USSO_URL"),
15
+ api_key: str | None = os.getenv("UFAAS_API_KEY"),
16
+ usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
17
+ refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
18
+ client: UssoSession | None = None,
19
+ ):
20
+ super().__init__(
21
+ usso_base_url=usso_base_url,
22
+ api_key=api_key,
23
+ usso_refresh_url=usso_refresh_url,
24
+ refresh_token=refresh_token,
25
+ client=client,
26
+ )
27
+ if not ufaas_base_url and client and hasattr(client, "ufaas_base_url"):
28
+ ufaas_base_url = client.ufaas_base_url
29
+ if ufaas_base_url.endswith("/"):
30
+ ufaas_base_url = ufaas_base_url[:-1]
31
+ if not ufaas_base_url.endswith("/api/v1/apps"):
32
+ ufaas_base_url = f"{ufaas_base_url}/api/v1/apps"
33
+ self.ufaas_base_url = ufaas_base_url
34
+ self.app_name = app_name
35
+ self.app_url = f"{ufaas_base_url}/{app_name}/"
36
+ self.initiate_resources()
37
+
38
+ def initiate_resources(self, **kwargs):
39
+ pass
40
+
41
+
42
+ class AsyncApp(AsyncUssoSession):
43
+
44
+ def __init__(
45
+ self,
46
+ app_name: str = "saas",
47
+ *,
48
+ ufaas_base_url: str = os.getenv("UFAAS_URL"),
49
+ usso_base_url: str | None = os.getenv("USSO_URL"),
50
+ api_key: str | None = os.getenv("UFAAS_API_KEY"),
51
+ usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
52
+ refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
53
+ client: AsyncUssoSession | None = None,
54
+ ):
55
+ super().__init__(
56
+ usso_base_url=usso_base_url,
57
+ api_key=api_key,
58
+ usso_refresh_url=usso_refresh_url,
59
+ refresh_token=refresh_token,
60
+ client=client,
61
+ )
62
+ if not ufaas_base_url and client and hasattr(client, "ufaas_base_url"):
63
+ ufaas_base_url = client.ufaas_base_url
64
+ if ufaas_base_url.endswith("/"):
65
+ ufaas_base_url = ufaas_base_url[:-1]
66
+ if not ufaas_base_url.endswith("/api/v1/apps"):
67
+ ufaas_base_url = f"{ufaas_base_url}/api/v1/apps"
68
+ self.ufaas_base_url = ufaas_base_url
69
+ self.app_name = app_name
70
+ self.app_url = f"{ufaas_base_url}/{app_name}/"
71
+ self.initiate_resources()
72
+
73
+ def initiate_resources(self, **kwargs):
74
+ pass
75
+
76
+
77
+ class Resource(UssoSession, metaclass=singleton.Singleton):
78
+ def __init__(
79
+ self,
80
+ app_name: str = "saas",
81
+ resource_name: str = "usages",
82
+ schema: type = dict,
83
+ *,
84
+ ufaas_base_url: str = os.getenv("UFAAS_URL"),
85
+ usso_base_url: str | None = os.getenv("USSO_URL"),
86
+ api_key: str | None = os.getenv("UFAAS_API_KEY"),
87
+ usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
88
+ refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
89
+ client: UssoSession | None = None,
90
+ ):
91
+ super().__init__(
92
+ usso_base_url=usso_base_url,
93
+ api_key=api_key,
94
+ usso_refresh_url=usso_refresh_url,
95
+ refresh_token=refresh_token,
96
+ client=client,
97
+ )
98
+ if not ufaas_base_url and client and hasattr(client, "ufaas_base_url"):
99
+ ufaas_base_url = client.ufaas_base_url
100
+ if ufaas_base_url.endswith("/"):
101
+ ufaas_base_url = ufaas_base_url[:-1]
102
+ if not ufaas_base_url.endswith("/api/v1/apps"):
103
+ ufaas_base_url = f"{ufaas_base_url}/api/v1/apps"
104
+ self.ufaas_base_url = ufaas_base_url
105
+ self.app_name = app_name
106
+ self.resource_name = resource_name
107
+ self.app_url = f"{ufaas_base_url}/{app_name}/"
108
+ self.resource_url = f"{self.app_url}{resource_name}/"
109
+ self._config_schemas(schema)
110
+
111
+ def _config_schemas(self, schema: type = dict, **kwargs):
112
+ self.schema = schema
113
+ if schema == dict:
114
+ self.list_response_schema = dict
115
+ else:
116
+ self.list_response_schema = PaginatedResponse[schema]
117
+ self.list_item_schema = schema
118
+ self.retrieve_response_schema = schema
119
+ self.create_response_schema = schema
120
+ self.update_response_schema = schema
121
+ self.delete_response_schema = schema
122
+
123
+ def list_items(self, offset: int = 0, limit: int = 10, **kwargs):
124
+ resp = self.get(
125
+ self.resource_url, params={"offset": offset, "limit": limit, **kwargs}
126
+ )
127
+ resp.raise_for_status()
128
+ return self.list_response_schema(**resp.json())
129
+
130
+ def retrieve_item(self, uid: str, **kwargs):
131
+ resp = self.get(f"{self.resource_url}/{uid}", params=kwargs)
132
+ resp.raise_for_status()
133
+ return self.retrieve_response_schema(**resp.json())
134
+
135
+ def create_item(self, obj: dict, **kwargs):
136
+ resp = self.post(self.resource_url, json=obj, params=kwargs)
137
+ resp.raise_for_status()
138
+ return self.create_response_schema(**resp.json())
139
+
140
+ def update_item(self, uid: str, obj: dict, **kwargs):
141
+ resp = self.patch(f"{self.resource_url}/{uid}", json=obj, params=kwargs)
142
+ resp.raise_for_status()
143
+ return self.update_response_schema(**resp.json())
144
+
145
+ def delete_item(self, uid: str, **kwargs):
146
+ resp = self.delete(f"{self.resource_url}/{uid}", params=kwargs)
147
+ resp.raise_for_status()
148
+ return self.delete_response_schema(**resp.json())
149
+
150
+
151
+ class AsyncResource(AsyncUssoSession, metaclass=singleton.Singleton):
152
+
153
+ def __init__(
154
+ self,
155
+ app_name: str = "saas",
156
+ resource_name: str = "usages",
157
+ schema: type = dict,
158
+ *,
159
+ ufaas_base_url: str = os.getenv("UFAAS_URL"),
160
+ usso_base_url: str | None = os.getenv("USSO_URL"),
161
+ api_key: str | None = os.getenv("UFAAS_API_KEY"),
162
+ usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
163
+ refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
164
+ client: AsyncUssoSession | None = None,
165
+ ):
166
+ super().__init__(
167
+ usso_base_url=usso_base_url,
168
+ api_key=api_key,
169
+ usso_refresh_url=usso_refresh_url,
170
+ refresh_token=refresh_token,
171
+ client=client,
172
+ )
173
+ if not ufaas_base_url and client and hasattr(client, "ufaas_base_url"):
174
+ ufaas_base_url = client.ufaas_base_url
175
+ if ufaas_base_url.endswith("/"):
176
+ ufaas_base_url = ufaas_base_url[:-1]
177
+ if not ufaas_base_url.endswith("/api/v1/apps"):
178
+ ufaas_base_url = f"{ufaas_base_url}/api/v1/apps"
179
+ self.ufaas_base_url = ufaas_base_url
180
+ self.app_name = app_name
181
+ self.resource_name = resource_name
182
+ self.app_url = f"{ufaas_base_url}/{app_name}/"
183
+ self.resource_url = f"{self.app_url}{resource_name}/"
184
+ self._config_schemas(schema)
185
+
186
+ def _config_schemas(self, schema: type = dict, **kwargs):
187
+ self.schema = schema
188
+ if schema == dict:
189
+ self.list_response_schema = dict
190
+ else:
191
+ self.list_response_schema = PaginatedResponse[schema]
192
+ self.list_item_schema = schema
193
+ self.retrieve_response_schema = schema
194
+ self.create_response_schema = schema
195
+ self.update_response_schema = schema
196
+ self.delete_response_schema = schema
197
+
198
+ async def list_items(self, offset: int = 0, limit: int = 10, **kwargs):
199
+ resp = await self.get(
200
+ self.resource_url, params={"offset": offset, "limit": limit, **kwargs}
201
+ )
202
+ resp.raise_for_status()
203
+ return self.list_response_schema(**resp.json())
204
+
205
+ async def retrieve_item(self, uid: str, **kwargs):
206
+ resp = await self.get(f"{self.resource_url}/{uid}", params=kwargs)
207
+ resp.raise_for_status()
208
+ return self.retrieve_response_schema(**resp.json())
209
+
210
+ async def create_item(self, obj: dict, **kwargs):
211
+ resp = await self.post(self.resource_url, json=obj, params=kwargs)
212
+ resp.raise_for_status()
213
+ return self.create_response_schema(**resp.json())
214
+
215
+ async def update_item(self, uid: str, obj: dict, **kwargs):
216
+ resp = await self.patch(f"{self.resource_url}/{uid}", json=obj, params=kwargs)
217
+ resp.raise_for_status()
218
+ return self.update_response_schema(**resp.json())
219
+
220
+ async def delete_item(self, uid: str, **kwargs):
221
+ resp = await self.delete(f"{self.resource_url}/{uid}", params=kwargs)
222
+ resp.raise_for_status()
223
+ return self.delete_response_schema(**resp.json())
@@ -0,0 +1,3 @@
1
+ from .saas import AsyncEnrollment, AsyncSaaS, AsyncUsage, Enrollment, SaaS, Usage
2
+
3
+ __all__ = ["AsyncUsage", "AsyncEnrollment", "Enrollment", "Usage", "SaaS", "AsyncSaaS"]
@@ -0,0 +1,196 @@
1
+ import os
2
+ import uuid
3
+
4
+ from usso.session import AsyncUssoSession, UssoSession
5
+
6
+ from ..base_app import App, AsyncApp, AsyncResource, Resource
7
+ from .schemas import EnrollmentSchema, QuotaSchema, UsageSchema
8
+
9
+
10
+ class SaaS(App):
11
+ def __init__(
12
+ self,
13
+ *,
14
+ ufaas_base_url: str = os.getenv("UFAAS_URL"),
15
+ usso_base_url: str | None = os.getenv("USSO_URL"),
16
+ api_key: str | None = os.getenv("UFAAS_API_KEY"),
17
+ usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
18
+ refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
19
+ client: UssoSession | None = None,
20
+ ):
21
+ super().__init__(
22
+ app_name="saas",
23
+ ufaas_base_url=ufaas_base_url,
24
+ usso_base_url=usso_base_url,
25
+ api_key=api_key,
26
+ usso_refresh_url=usso_refresh_url,
27
+ refresh_token=refresh_token,
28
+ client=client,
29
+ )
30
+
31
+ def initiate_resources(self, **kwargs):
32
+ self.usages = Usage(client=self)
33
+ self.enrollments = Enrollment(client=self)
34
+
35
+
36
+ class AsyncSaaS(AsyncApp):
37
+
38
+ def __init__(
39
+ self,
40
+ *,
41
+ ufaas_base_url: str = os.getenv("UFAAS_URL"),
42
+ usso_base_url: str | None = os.getenv("USSO_URL"),
43
+ api_key: str | None = os.getenv("UFAAS_API_KEY"),
44
+ usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
45
+ refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
46
+ client: AsyncUssoSession | None = None,
47
+ ):
48
+ super().__init__(
49
+ app_name="saas",
50
+ ufaas_base_url=ufaas_base_url,
51
+ usso_base_url=usso_base_url,
52
+ api_key=api_key,
53
+ usso_refresh_url=usso_refresh_url,
54
+ refresh_token=refresh_token,
55
+ client=client,
56
+ )
57
+
58
+ def initiate_resources(self, **kwargs):
59
+ self.usages = AsyncUsage(client=self)
60
+ self.enrollments = AsyncEnrollment(client=self)
61
+
62
+
63
+ class Usage(Resource):
64
+
65
+ def __init__(
66
+ self,
67
+ *,
68
+ ufaas_base_url: str = os.getenv("UFAAS_URL"),
69
+ usso_base_url: str | None = os.getenv("USSO_URL"),
70
+ api_key: str | None = os.getenv("UFAAS_API_KEY"),
71
+ usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
72
+ refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
73
+ client: UssoSession | None = None,
74
+ ):
75
+ super().__init__(
76
+ app_name="saas",
77
+ resource_name="usages",
78
+ schema=UsageSchema,
79
+ ufaas_base_url=ufaas_base_url,
80
+ usso_base_url=usso_base_url,
81
+ api_key=api_key,
82
+ usso_refresh_url=usso_refresh_url,
83
+ refresh_token=refresh_token,
84
+ client=client,
85
+ )
86
+
87
+ def cancel_item(self, uid: str, **kwargs):
88
+ resp = self.post(f"{self.resource_url}{uid}/cancel", params=kwargs)
89
+ resp.raise_for_status()
90
+ return self.retrieve_response_schema(**resp.json())
91
+
92
+
93
+ class AsyncUsage(AsyncResource):
94
+
95
+ def __init__(
96
+ self,
97
+ *,
98
+ ufaas_base_url: str = os.getenv("UFAAS_URL"),
99
+ usso_base_url: str | None = os.getenv("USSO_URL"),
100
+ api_key: str | None = os.getenv("UFAAS_API_KEY"),
101
+ usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
102
+ refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
103
+ client: AsyncUssoSession | None = None,
104
+ ):
105
+ super().__init__(
106
+ app_name="saas",
107
+ resource_name="usages",
108
+ schema=UsageSchema,
109
+ ufaas_base_url=ufaas_base_url,
110
+ usso_base_url=usso_base_url,
111
+ api_key=api_key,
112
+ usso_refresh_url=usso_refresh_url,
113
+ refresh_token=refresh_token,
114
+ client=client,
115
+ )
116
+
117
+ async def cancel_item(self, uid: str, **kwargs):
118
+ resp = await self.post(f"{self.resource_url}{uid}/cancel", params=kwargs)
119
+ resp.raise_for_status()
120
+ return self.retrieve_response_schema(**resp.json())
121
+
122
+
123
+ class Enrollment(Resource):
124
+ def __init__(
125
+ self,
126
+ *,
127
+ ufaas_base_url: str = os.getenv("UFAAS_URL"),
128
+ usso_base_url: str | None = os.getenv("USSO_URL"),
129
+ api_key: str | None = os.getenv("UFAAS_API_KEY"),
130
+ usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
131
+ refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
132
+ client: UssoSession | None = None,
133
+ ):
134
+ super().__init__(
135
+ app_name="saas",
136
+ resource_name="enrollments",
137
+ schema=EnrollmentSchema,
138
+ ufaas_base_url=ufaas_base_url,
139
+ usso_base_url=usso_base_url,
140
+ api_key=api_key,
141
+ usso_refresh_url=usso_refresh_url,
142
+ refresh_token=refresh_token,
143
+ client=client,
144
+ )
145
+
146
+ def get_quotas(
147
+ self,
148
+ asset: str,
149
+ user_id: uuid.UUID | None = None,
150
+ variant: str | None = None,
151
+ **kwargs,
152
+ ):
153
+ if isinstance(user_id, uuid.UUID):
154
+ user_id = str(user_id)
155
+ params = {"asset": asset, "user_id": user_id, "variant": variant, **kwargs}
156
+ resp = self.get(f"{self.resource_url}quotas", params=params)
157
+ resp.raise_for_status()
158
+ return QuotaSchema(**resp.json())
159
+
160
+
161
+ class AsyncEnrollment(AsyncResource):
162
+ def __init__(
163
+ self,
164
+ *,
165
+ ufaas_base_url: str = os.getenv("UFAAS_URL"),
166
+ usso_base_url: str | None = os.getenv("USSO_URL"),
167
+ api_key: str | None = os.getenv("UFAAS_API_KEY"),
168
+ usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
169
+ refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
170
+ client: AsyncUssoSession | None = None,
171
+ ):
172
+ super().__init__(
173
+ app_name="saas",
174
+ resource_name="enrollments",
175
+ schema=EnrollmentSchema,
176
+ ufaas_base_url=ufaas_base_url,
177
+ usso_base_url=usso_base_url,
178
+ api_key=api_key,
179
+ usso_refresh_url=usso_refresh_url,
180
+ refresh_token=refresh_token,
181
+ client=client,
182
+ )
183
+
184
+ async def get_quotas(
185
+ self,
186
+ asset: str,
187
+ user_id: uuid.UUID | None = None,
188
+ variant: str | None = None,
189
+ **kwargs,
190
+ ):
191
+ if isinstance(user_id, uuid.UUID):
192
+ user_id = str(user_id)
193
+ params = {"asset": asset, "user_id": user_id, "variant": variant, **kwargs}
194
+ resp = await self.get(f"{self.resource_url}quotas", params=params)
195
+ resp.raise_for_status()
196
+ return QuotaSchema(**resp.json())
@@ -0,0 +1,89 @@
1
+ import uuid
2
+ from datetime import datetime
3
+ from decimal import Decimal
4
+ from enum import Enum
5
+ from typing import Literal
6
+
7
+ from fastapi_mongo_base.schemas import BusinessOwnedEntitySchema
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+
11
+ class Bundle(BaseModel):
12
+ asset: str
13
+ quota: Decimal
14
+ unit: str | None = None
15
+
16
+ model_config = ConfigDict(allow_inf_nan=True)
17
+
18
+
19
+ class AcquisitionType(str, Enum):
20
+ trial = "trial"
21
+ # credit = "credit"
22
+ purchased = "purchased"
23
+ gifted = "gifted"
24
+ # deferred = "deferred"
25
+ promotion = "promotion"
26
+ # subscription = "subscription"
27
+ # on_demand = "on_demand"
28
+ borrowed = "borrowed"
29
+ # freemium = "freemium"
30
+ postpaid = "postpaid"
31
+
32
+
33
+ class EnrollmentCreateSchema(BaseModel):
34
+ user_id: uuid.UUID
35
+ bundles: list[Bundle]
36
+
37
+ price: Decimal = Decimal(0)
38
+ invoice_id: str | None = None
39
+ start_at: datetime = Field(default_factory=datetime.now)
40
+ expire_at: datetime | None = None
41
+ duration: int | None = Field(None, description="Duration in days")
42
+ status: Literal["active", "inactive"] = "active"
43
+ acquisition_type: AcquisitionType = AcquisitionType.purchased
44
+
45
+ variant: str | None = None
46
+ meta_data: dict | None = None
47
+
48
+ due_date: datetime | None = None
49
+
50
+
51
+ class EnrollmentSchema(EnrollmentCreateSchema, BusinessOwnedEntitySchema):
52
+ paid_at: datetime | None = None
53
+ leftover_bundles: list[Bundle] = []
54
+
55
+
56
+ class QuotaSchema(BaseModel):
57
+ asset: str
58
+ quota: Decimal
59
+ unit: str | None = None
60
+ variant: str | None = None
61
+ _quota: Decimal | None = None
62
+
63
+ model_config = ConfigDict(allow_inf_nan=True)
64
+
65
+
66
+ class UsageCreateSchema(BaseModel):
67
+ user_id: uuid.UUID
68
+ enrollment_id: uuid.UUID | None = None
69
+ asset: str
70
+ amount: Decimal = Decimal(1)
71
+ variant: str | None = None
72
+ meta_data: dict | None = None
73
+
74
+
75
+ class UsageConsumption(BaseModel):
76
+ enrollment_id: uuid.UUID
77
+ amount: Decimal
78
+ leftover_bundles: list[Bundle] = []
79
+
80
+
81
+ class UsageSchema(BusinessOwnedEntitySchema):
82
+ # enrollment_id: uuid.UUID
83
+ # asset: str
84
+ # amount: Decimal
85
+
86
+ consumptions: list[UsageConsumption]
87
+ asset: str
88
+ amount: Decimal
89
+ variant: str | None = None
@@ -0,0 +1,36 @@
1
+ error_messages = {}
2
+
3
+
4
+ class UFaaSException(Exception):
5
+ def __init__(self, status_code: int, error: str, message: str = None):
6
+ self.status_code = status_code
7
+ self.error = error
8
+ self.message = message
9
+ if message is None:
10
+ self.message = error_messages[error]
11
+ super().__init__(message)
12
+
13
+
14
+ class InsufficientFunds(UFaaSException):
15
+ def __init__(self, message: str = None):
16
+ super().__init__(status_code=402, error="insufficient_funds", message=message)
17
+
18
+
19
+ class InvalidRequest(UFaaSException):
20
+ def __init__(self, message: str = None):
21
+ super().__init__(status_code=400, error="invalid_request", message=message)
22
+
23
+
24
+ class Unauthorized(UFaaSException):
25
+ def __init__(self, message: str = None):
26
+ super().__init__(status_code=401, error="unauthorized", message=message)
27
+
28
+
29
+ class Forbidden(UFaaSException):
30
+ def __init__(self, message: str = None):
31
+ super().__init__(status_code=403, error="forbidden", message=message)
32
+
33
+
34
+ class NotFound(UFaaSException):
35
+ def __init__(self, message: str = None):
36
+ super().__init__(status_code=404, error="not_found", message=message)
File without changes
@@ -0,0 +1,14 @@
1
+ from fastapi import Request
2
+ from fastapi.responses import JSONResponse
3
+
4
+ from ..exceptions import UFaaSException
5
+
6
+
7
+ async def ufaas_exception_handler(request: Request, exc: UFaaSException):
8
+ return JSONResponse(
9
+ status_code=exc.status_code,
10
+ content={"message": exc.message, "error": exc.error},
11
+ )
12
+
13
+
14
+ EXCEPTION_HANDLERS = {UFaaSException: ufaas_exception_handler}
@@ -0,0 +1,148 @@
1
+ import uuid
2
+ from datetime import datetime
3
+ from decimal import Decimal
4
+ from enum import Enum
5
+ from typing import Literal
6
+
7
+ from fastapi_mongo_base.schemas import BusinessOwnedEntitySchema
8
+ from pydantic import BaseModel, ConfigDict, field_serializer, model_validator
9
+
10
+
11
+ class Currency(str, Enum):
12
+ none = "none"
13
+
14
+ IRR = "IRR"
15
+ IRT = "IRT"
16
+
17
+ USD = "USD"
18
+ EUR = "EUR"
19
+ GBP = "GBP"
20
+
21
+ USDT = "USDT"
22
+ BTC = "BTC"
23
+ ETH = "ETH"
24
+
25
+
26
+ class WalletType(str, Enum):
27
+ user = "user"
28
+ business = "business"
29
+ app = "app"
30
+ app_operational = "app_operational"
31
+ app_income = "app_income"
32
+
33
+
34
+ class WalletSchema(BusinessOwnedEntitySchema):
35
+ wallet_type: WalletType = WalletType.user
36
+ main_currency: Currency = Currency.none
37
+
38
+ is_default: bool = True
39
+
40
+
41
+ class WalletDetailSchema(BusinessOwnedEntitySchema):
42
+ balance: dict[str, Decimal] = {}
43
+
44
+ model_config = ConfigDict(allow_inf_nan=True)
45
+
46
+ @field_serializer("balance")
47
+ def serialize_balance(self, balance: dict[str, Decimal]) -> dict[str, Decimal]:
48
+ return {k: (v if v.is_finite() else Decimal(0)) for k, v in balance.items()}
49
+
50
+ @field_serializer("wallet_type")
51
+ def serialize_wallet_type(self, wallet_type: WalletType) -> str:
52
+ return wallet_type.value
53
+
54
+ @field_serializer("main_currency")
55
+ def serialize_main_currency(self, main_currency: Currency) -> str:
56
+ return main_currency.value
57
+
58
+
59
+ class WalletCreateSchema(BaseModel):
60
+ user_id: uuid.UUID
61
+ meta_data: dict | None = None
62
+
63
+ wallet_type: WalletType = WalletType.user
64
+ main_currency: Currency = Currency.none
65
+
66
+ @model_validator(mode="before")
67
+ def validate_wallet_type(cls, values):
68
+ if values.get("wallet_type") and not values.get("main_currency"):
69
+ raise ValueError("main_currency is required for income wallet")
70
+ return values
71
+
72
+
73
+ class WalletUpdateSchema(BaseModel):
74
+ meta_data: dict | None = None
75
+
76
+
77
+ class WalletHoldSchema(BusinessOwnedEntitySchema):
78
+ wallet_id: uuid.UUID
79
+ currency: str
80
+ amount: Decimal
81
+ expires_at: datetime
82
+ status: str
83
+ description: str | None = None
84
+
85
+
86
+ class WalletHoldCreateSchema(BaseModel):
87
+ amount: Decimal
88
+ expires_at: datetime
89
+ status: str = "active"
90
+ meta_data: dict | None = None
91
+ description: str | None = None
92
+
93
+
94
+ class WalletHoldUpdateSchema(BaseModel):
95
+ expires_at: datetime | None = None
96
+ status: str | None = None
97
+ meta_data: dict | None = None
98
+ description: str | None = None
99
+
100
+
101
+ class TransactionSchema(BusinessOwnedEntitySchema):
102
+ proposal_id: uuid.UUID
103
+ wallet_id: uuid.UUID
104
+ amount: Decimal
105
+ currency: str
106
+ balance: Decimal
107
+ description: str | None = None
108
+ note: str | None = None
109
+
110
+ model_config = ConfigDict(allow_inf_nan=True)
111
+
112
+
113
+ class TransactionNoteUpdateSchema(BaseModel):
114
+ note: str
115
+
116
+
117
+ class Participant(BaseModel):
118
+ wallet_id: uuid.UUID
119
+ amount: Decimal
120
+
121
+
122
+ class ProposalSchema(BusinessOwnedEntitySchema):
123
+ issuer_id: uuid.UUID
124
+ amount: Decimal
125
+ description: str | None = None
126
+ note: str | None = None
127
+ currency: str
128
+ # status: str
129
+ task_status: str
130
+ participants: list[Participant]
131
+
132
+
133
+ class ProposalCreateSchema(BaseModel):
134
+ amount: Decimal
135
+ description: str | None = None
136
+ note: str | None = None
137
+ currency: str
138
+ task_status: Literal["draft", "init"] = "draft"
139
+ participants: list[Participant]
140
+ meta_data: dict | None = None
141
+
142
+
143
+ class ProposalUpdateSchema(BaseModel):
144
+ # status: str | None
145
+ task_status: Literal["init"] | None = None
146
+ description: str | None = None
147
+ note: str | None = None
148
+ meta_data: dict | None = None
@@ -0,0 +1,11 @@
1
+ import hashlib
2
+ from io import BytesIO
3
+
4
+
5
+ def calculate_file_hash(file: BytesIO) -> str:
6
+ file_hash = hashlib.md5()
7
+ file.seek(0) # Ensure we start from the beginning of the file
8
+ while chunk := file.read(8192):
9
+ file_hash.update(chunk)
10
+ file.seek(0) # Reset file pointer to the beginning
11
+ return file_hash.hexdigest()
@@ -0,0 +1,103 @@
1
+ import os
2
+ from urllib.parse import urlparse
3
+
4
+ from usso.session import AsyncUssoSession, UssoSession
5
+
6
+ from .apps.saas import AsyncSaaS, SaaS
7
+
8
+
9
+ class UFaaS(UssoSession):
10
+
11
+ def __init__(
12
+ self,
13
+ *,
14
+ ufaas_base_url: str = os.getenv("UFAAS_URL"),
15
+ usso_base_url: str | None = os.getenv("USSO_URL"),
16
+ api_key: str | None = os.getenv("UFAAS_API_KEY"),
17
+ usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
18
+ refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
19
+ client: UssoSession | None = None,
20
+ ):
21
+ if usso_base_url is None:
22
+ # calculate sso_url using ufiles_url
23
+ # for example: media.pixiee.io/v1/f -> sso.pixiee.io
24
+ # for example: media.ufaas.io/v1/f -> sso.ufaas.io
25
+ # for example: media.pixy.ir/api/v1/f -> sso.pixy.ir
26
+ # for example: storage.pixy.ir/api/v1/f -> sso.pixy.ir
27
+ parsed_url = urlparse(ufaas_base_url)
28
+ netloc = parsed_url.netloc
29
+ netloc_parts = netloc.split(".")
30
+ if len(netloc_parts) > 2:
31
+ netloc_parts[0] = "sso"
32
+ else:
33
+ netloc_parts = ["sso", netloc]
34
+ netloc = ".".join(netloc_parts)
35
+ usso_base_url = f"https://{netloc}"
36
+
37
+ super().__init__(
38
+ usso_base_url=usso_base_url,
39
+ api_key=api_key,
40
+ usso_refresh_url=usso_refresh_url,
41
+ refresh_token=refresh_token,
42
+ client=client,
43
+ )
44
+ if not ufaas_base_url and client and hasattr(client, "ufaas_base_url"):
45
+ ufaas_base_url = client.ufaas_base_url
46
+ assert ufaas_base_url, "UFAAS_URL is required"
47
+ if ufaas_base_url.endswith("/"):
48
+ ufaas_base_url = ufaas_base_url[:-1]
49
+ self.ufaas_base_url = ufaas_base_url
50
+ self.headers.update({"accept-encoding": "identity"})
51
+
52
+ self.initiate_apps()
53
+
54
+ def initiate_apps(self):
55
+ self.saas = SaaS(client=self)
56
+
57
+
58
+ class AsyncUFaaS(AsyncUssoSession):
59
+
60
+ def __init__(
61
+ self,
62
+ *,
63
+ ufaas_base_url: str = os.getenv("UFAAS_URL"),
64
+ usso_base_url: str | None = os.getenv("USSO_URL"),
65
+ api_key: str | None = os.getenv("UFILES_API_KEY"),
66
+ usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
67
+ refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
68
+ client: AsyncUssoSession | None = None,
69
+ ):
70
+ if usso_base_url is None:
71
+ # calculate sso_url using ufiles_url
72
+ # for example: media.pixiee.io/v1/f -> sso.pixiee.io
73
+ # for example: media.ufaas.io/v1/f -> sso.ufaas.io
74
+ # for example: media.pixy.ir/api/v1/f -> sso.pixy.ir
75
+ # for example: storage.pixy.ir/api/v1/f -> sso.pixy.ir
76
+ parsed_url = urlparse(ufaas_base_url)
77
+ netloc = parsed_url.netloc
78
+ netloc_parts = netloc.split(".")
79
+ if len(netloc_parts) > 2:
80
+ netloc_parts[0] = "sso"
81
+ else:
82
+ netloc_parts = ["sso", netloc]
83
+ netloc = ".".join(netloc_parts)
84
+ usso_base_url = f"https://{netloc}"
85
+
86
+ super().__init__(
87
+ usso_base_url=usso_base_url,
88
+ api_key=api_key,
89
+ usso_refresh_url=usso_refresh_url,
90
+ refresh_token=refresh_token,
91
+ client=client,
92
+ )
93
+ if not ufaas_base_url and client and hasattr(client, "ufaas_base_url"):
94
+ ufaas_base_url = client.ufaas_base_url
95
+ assert ufaas_base_url, "UFAAS_URL is required"
96
+ if ufaas_base_url.endswith("/"):
97
+ ufaas_base_url = ufaas_base_url[:-1]
98
+ self.ufaas_base_url = ufaas_base_url
99
+ self.headers.update({"accept-encoding": "identity"})
100
+ self.initiate_apps()
101
+
102
+ def initiate_apps(self):
103
+ self.saas = AsyncSaaS(client=self)
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.2
2
+ Name: ufaas
3
+ Version: 0.1.22
4
+ Summary: A client for UFaaS.
5
+ Author-email: Mahdi Kiani <mahdikiany@gmail.com>
6
+ Maintainer-email: Mahdi Kiani <mahdikiany@gmail.com>
7
+ License: Copyright (c) 2016 The Python Packaging Authority (PyPA)
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
10
+ this software and associated documentation files (the "Software"), to deal in
11
+ the Software without restriction, including without limitation the rights to
12
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
13
+ of the Software, and to permit persons to whom the Software is furnished to do
14
+ so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Project-URL: Homepage, https://github.com/ufilesorg/ufiles-python
28
+ Project-URL: Bug Reports, https://github.com/ufilesorg/ufiles-python/issues
29
+ Project-URL: Funding, https://github.com/ufilesorg/ufiles-python
30
+ Project-URL: Say Thanks!, https://saythanks.io/to/mahdikiani
31
+ Project-URL: Source, https://github.com/ufilesorg/ufiles-python
32
+ Keywords: ufaas,saas,usso,finance,pricing,pay as you go
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: Topic :: Software Development :: Build Tools
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Programming Language :: Python :: 3 :: Only
42
+ Requires-Python: >=3.9
43
+ Description-Content-Type: text/markdown
44
+ License-File: LICENSE.txt
45
+ Requires-Dist: pydantic>=2
46
+ Requires-Dist: pyjwt[crypto]
47
+ Requires-Dist: usso>=0.27.2
48
+ Requires-Dist: fastapi_mongo_base
49
+ Requires-Dist: singleton_package
50
+ Requires-Dist: json-advanced
51
+
52
+ # UFiles-Client
53
+
54
+ The Ufiles-Client is a SDK for ufiles servers.
55
+
56
+ ## Installation
57
+
58
+ Install the ufiles client using pip:
59
+
60
+ ```bash
61
+ pip install ufiles
62
+ ```
63
+
64
+ ## Quick Start
65
+ Follow the quick start guides in the documentation to integrate ufiles in your application.
66
+
67
+ ## Contributing
68
+ Contributions are welcome! See CONTRIBUTING.md for more details on how to get involved.
69
+
70
+ ## License
71
+ Distributed under the MIT License. See LICENSE for more information.
@@ -0,0 +1,20 @@
1
+ LICENSE.txt
2
+ README.md
3
+ pyproject.toml
4
+ src/ufaas/__init__.py
5
+ src/ufaas/exceptions.py
6
+ src/ufaas/schemas.py
7
+ src/ufaas/services.py
8
+ src/ufaas/ufaas.py
9
+ src/ufaas.egg-info/PKG-INFO
10
+ src/ufaas.egg-info/SOURCES.txt
11
+ src/ufaas.egg-info/dependency_links.txt
12
+ src/ufaas.egg-info/requires.txt
13
+ src/ufaas.egg-info/top_level.txt
14
+ src/ufaas/apps/__init__.py
15
+ src/ufaas/apps/base_app.py
16
+ src/ufaas/apps/saas/__init__.py
17
+ src/ufaas/apps/saas/saas.py
18
+ src/ufaas/apps/saas/schemas.py
19
+ src/ufaas/fastapi/__init__.py
20
+ src/ufaas/fastapi/integration.py
@@ -0,0 +1,6 @@
1
+ pydantic>=2
2
+ pyjwt[crypto]
3
+ usso>=0.27.2
4
+ fastapi_mongo_base
5
+ singleton_package
6
+ json-advanced
@@ -0,0 +1 @@
1
+ ufaas