eaasy 0.1.0__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.
- eaasy-0.1.0/MANIFEST.in +7 -0
- eaasy-0.1.0/PKG-INFO +35 -0
- eaasy-0.1.0/README.md +1 -0
- eaasy-0.1.0/eaas/__init__.py +13 -0
- eaasy-0.1.0/eaas/api/__init__.py +5 -0
- eaasy-0.1.0/eaas/api/liveness.py +10 -0
- eaasy-0.1.0/eaas/eaas.py +68 -0
- eaasy-0.1.0/eaas/extensions/__init__.py +3 -0
- eaasy-0.1.0/eaas/extensions/builder.py +41 -0
- eaasy-0.1.0/eaasy.egg-info/PKG-INFO +35 -0
- eaasy-0.1.0/eaasy.egg-info/SOURCES.txt +15 -0
- eaasy-0.1.0/eaasy.egg-info/dependency_links.txt +1 -0
- eaasy-0.1.0/eaasy.egg-info/requires.txt +21 -0
- eaasy-0.1.0/eaasy.egg-info/top_level.txt +1 -0
- eaasy-0.1.0/setup.cfg +4 -0
- eaasy-0.1.0/setup.py +24 -0
- eaasy-0.1.0/tests/test_builder.py +115 -0
eaasy-0.1.0/MANIFEST.in
ADDED
eaasy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: eaasy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Build your e-commerce ea(a)sily
|
|
5
|
+
Home-page: https://github.com/ciulene/eaas
|
|
6
|
+
Author: Giuliano Errico
|
|
7
|
+
Author-email: errgioul2@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: aniso8601==9.0.1
|
|
14
|
+
Requires-Dist: attrs==24.2.0
|
|
15
|
+
Requires-Dist: blinker==1.9.0
|
|
16
|
+
Requires-Dist: click==8.1.7
|
|
17
|
+
Requires-Dist: Flask==3.1.0
|
|
18
|
+
Requires-Dist: Flask-Cors==5.0.0
|
|
19
|
+
Requires-Dist: flask-restx==1.3.0
|
|
20
|
+
Requires-Dist: gunicorn==23.0.0
|
|
21
|
+
Requires-Dist: importlib_resources==6.4.5
|
|
22
|
+
Requires-Dist: itsdangerous==2.2.0
|
|
23
|
+
Requires-Dist: Jinja2==3.1.4
|
|
24
|
+
Requires-Dist: jsonschema==4.23.0
|
|
25
|
+
Requires-Dist: jsonschema-specifications==2024.10.1
|
|
26
|
+
Requires-Dist: MarkupSafe==3.0.2
|
|
27
|
+
Requires-Dist: packaging==24.2
|
|
28
|
+
Requires-Dist: pytz==2024.2
|
|
29
|
+
Requires-Dist: referencing==0.35.1
|
|
30
|
+
Requires-Dist: rpds-py==0.22.3
|
|
31
|
+
Requires-Dist: setuptools==75.6.0
|
|
32
|
+
Requires-Dist: Werkzeug==3.1.3
|
|
33
|
+
Requires-Dist: wheel==0.45.1
|
|
34
|
+
|
|
35
|
+
# E-commerce As A Software
|
eaasy-0.1.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# E-commerce As A Software
|
eaasy-0.1.0/eaas/eaas.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from flask import Flask, request
|
|
2
|
+
from flask_restx import Api, Namespace
|
|
3
|
+
from flask_cors import CORS
|
|
4
|
+
from eaas.api import liveness_ns
|
|
5
|
+
from gunicorn.app.base import BaseApplication
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Eaas:
|
|
10
|
+
def __init__(self, logger:logging.Logger | bool | None = None, *args, **kwargs):
|
|
11
|
+
self.logger = self.__get_logger(logger)
|
|
12
|
+
name = kwargs.get('name', __name__)
|
|
13
|
+
version = kwargs.get('version', '1.0')
|
|
14
|
+
title = kwargs.get('title', 'API')
|
|
15
|
+
description = kwargs.get('description', 'A simple API')
|
|
16
|
+
doc=kwargs.get('doc', '/swagger')
|
|
17
|
+
self.app = Flask(name)
|
|
18
|
+
CORS(self.app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)
|
|
19
|
+
self.api = Api( self.app, version=version, title=title, description=description, doc=doc)
|
|
20
|
+
self.app.before_request(self.__log_request)
|
|
21
|
+
|
|
22
|
+
def __get_logger(self, logger:logging.Logger | bool | None) -> logging.Logger | None:
|
|
23
|
+
if logger is None:
|
|
24
|
+
return None
|
|
25
|
+
if isinstance(logger, bool):
|
|
26
|
+
if logger:
|
|
27
|
+
logging.basicConfig(
|
|
28
|
+
level=logging.INFO,
|
|
29
|
+
format='%(asctime)s %(levelname)s %(message)s'
|
|
30
|
+
)
|
|
31
|
+
return logging.getLogger(__name__)
|
|
32
|
+
return None
|
|
33
|
+
return logger
|
|
34
|
+
|
|
35
|
+
def __log_request(self):
|
|
36
|
+
if self.logger: self.logger.info(
|
|
37
|
+
f'{request.method} {request.endpoint} {request.remote_addr} {request.user_agent}'
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def run(self):
|
|
41
|
+
self.app.run()
|
|
42
|
+
|
|
43
|
+
def add_default_namespaces(self):
|
|
44
|
+
self.api.add_namespace(liveness_ns)
|
|
45
|
+
|
|
46
|
+
def add_namespace(self, namespace: Namespace):
|
|
47
|
+
self.api.add_namespace(namespace)
|
|
48
|
+
|
|
49
|
+
def create_app(self):
|
|
50
|
+
return self.app
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class GunEaas(BaseApplication):
|
|
54
|
+
def __init__(self, app: Flask, options: dict | None = None):
|
|
55
|
+
self.options = options or {}
|
|
56
|
+
self.application = app
|
|
57
|
+
super().__init__()
|
|
58
|
+
|
|
59
|
+
def load_config(self):
|
|
60
|
+
config = {
|
|
61
|
+
key: value for key, value in self.options.items()
|
|
62
|
+
if self.cfg is not None and key in self.cfg.settings and value is not None}
|
|
63
|
+
if self.cfg is not None:
|
|
64
|
+
for key, value in config.items():
|
|
65
|
+
self.cfg.set(key.lower(), value)
|
|
66
|
+
|
|
67
|
+
def load(self):
|
|
68
|
+
return self.application
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from eaas import BaseEntity
|
|
2
|
+
from flask_restx import Namespace, Model, OrderedModel, fields
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
def buil_model(entity: BaseEntity) -> tuple[Namespace, Model | OrderedModel]:
|
|
6
|
+
namespace = Namespace(entity.__name__, description=f"{entity.__name__} operations", path=f"/{entity.__name__.lower()}")
|
|
7
|
+
model_name = entity.__name__
|
|
8
|
+
model_properties = {}
|
|
9
|
+
column_list = entity.column_list()
|
|
10
|
+
for prop in column_list:
|
|
11
|
+
if not prop.name.startswith('_'):
|
|
12
|
+
if f'{prop.type}' == 'INTEGER':
|
|
13
|
+
model_properties[prop.name] = fields.Integer(
|
|
14
|
+
required=not prop.nullable,
|
|
15
|
+
description=prop.name,
|
|
16
|
+
default=0 if not prop.nullable else None,
|
|
17
|
+
example=0)
|
|
18
|
+
elif f'{prop.type}' == 'VARCHAR':
|
|
19
|
+
model_properties[prop.name] = fields.String(
|
|
20
|
+
required=not prop.nullable,
|
|
21
|
+
description=prop.name,
|
|
22
|
+
default='' if not prop.nullable else None,
|
|
23
|
+
example='')
|
|
24
|
+
elif f'{prop.type}' == 'DATETIME':
|
|
25
|
+
model_properties[prop.name] = fields.DateTime(
|
|
26
|
+
required=not prop.nullable,
|
|
27
|
+
description=prop.name,
|
|
28
|
+
default=datetime.now(timezone.utc) if not prop.nullable else None,
|
|
29
|
+
example=datetime.now(timezone.utc))
|
|
30
|
+
elif f'{prop.type}' == 'BOOLEAN':
|
|
31
|
+
model_properties[prop.name] = fields.Boolean(
|
|
32
|
+
required=not prop.nullable,
|
|
33
|
+
description=prop.name,
|
|
34
|
+
default=False,
|
|
35
|
+
example=False)
|
|
36
|
+
else:
|
|
37
|
+
print(f"\033[33mType {prop.type} not supported\033[0m")
|
|
38
|
+
|
|
39
|
+
model = namespace.model(model_name, model_properties)
|
|
40
|
+
|
|
41
|
+
return namespace, model
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: eaasy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Build your e-commerce ea(a)sily
|
|
5
|
+
Home-page: https://github.com/ciulene/eaas
|
|
6
|
+
Author: Giuliano Errico
|
|
7
|
+
Author-email: errgioul2@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: aniso8601==9.0.1
|
|
14
|
+
Requires-Dist: attrs==24.2.0
|
|
15
|
+
Requires-Dist: blinker==1.9.0
|
|
16
|
+
Requires-Dist: click==8.1.7
|
|
17
|
+
Requires-Dist: Flask==3.1.0
|
|
18
|
+
Requires-Dist: Flask-Cors==5.0.0
|
|
19
|
+
Requires-Dist: flask-restx==1.3.0
|
|
20
|
+
Requires-Dist: gunicorn==23.0.0
|
|
21
|
+
Requires-Dist: importlib_resources==6.4.5
|
|
22
|
+
Requires-Dist: itsdangerous==2.2.0
|
|
23
|
+
Requires-Dist: Jinja2==3.1.4
|
|
24
|
+
Requires-Dist: jsonschema==4.23.0
|
|
25
|
+
Requires-Dist: jsonschema-specifications==2024.10.1
|
|
26
|
+
Requires-Dist: MarkupSafe==3.0.2
|
|
27
|
+
Requires-Dist: packaging==24.2
|
|
28
|
+
Requires-Dist: pytz==2024.2
|
|
29
|
+
Requires-Dist: referencing==0.35.1
|
|
30
|
+
Requires-Dist: rpds-py==0.22.3
|
|
31
|
+
Requires-Dist: setuptools==75.6.0
|
|
32
|
+
Requires-Dist: Werkzeug==3.1.3
|
|
33
|
+
Requires-Dist: wheel==0.45.1
|
|
34
|
+
|
|
35
|
+
# E-commerce As A Software
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
eaas/__init__.py
|
|
5
|
+
eaas/eaas.py
|
|
6
|
+
eaas/api/__init__.py
|
|
7
|
+
eaas/api/liveness.py
|
|
8
|
+
eaas/extensions/__init__.py
|
|
9
|
+
eaas/extensions/builder.py
|
|
10
|
+
eaasy.egg-info/PKG-INFO
|
|
11
|
+
eaasy.egg-info/SOURCES.txt
|
|
12
|
+
eaasy.egg-info/dependency_links.txt
|
|
13
|
+
eaasy.egg-info/requires.txt
|
|
14
|
+
eaasy.egg-info/top_level.txt
|
|
15
|
+
tests/test_builder.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
aniso8601==9.0.1
|
|
2
|
+
attrs==24.2.0
|
|
3
|
+
blinker==1.9.0
|
|
4
|
+
click==8.1.7
|
|
5
|
+
Flask==3.1.0
|
|
6
|
+
Flask-Cors==5.0.0
|
|
7
|
+
flask-restx==1.3.0
|
|
8
|
+
gunicorn==23.0.0
|
|
9
|
+
importlib_resources==6.4.5
|
|
10
|
+
itsdangerous==2.2.0
|
|
11
|
+
Jinja2==3.1.4
|
|
12
|
+
jsonschema==4.23.0
|
|
13
|
+
jsonschema-specifications==2024.10.1
|
|
14
|
+
MarkupSafe==3.0.2
|
|
15
|
+
packaging==24.2
|
|
16
|
+
pytz==2024.2
|
|
17
|
+
referencing==0.35.1
|
|
18
|
+
rpds-py==0.22.3
|
|
19
|
+
setuptools==75.6.0
|
|
20
|
+
Werkzeug==3.1.3
|
|
21
|
+
wheel==0.45.1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
eaas
|
eaasy-0.1.0/setup.cfg
ADDED
eaasy-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
def parse_requirements(filename):
|
|
4
|
+
with open(filename, 'r') as file:
|
|
5
|
+
return [line.strip() for line in file if line.strip() and not line.startswith('#')]
|
|
6
|
+
|
|
7
|
+
setup(
|
|
8
|
+
name='eaasy',
|
|
9
|
+
version='0.1.0',
|
|
10
|
+
packages=find_packages(),
|
|
11
|
+
install_requires=parse_requirements('requirements.txt'),
|
|
12
|
+
author='Giuliano Errico',
|
|
13
|
+
author_email='errgioul2@gmail.com',
|
|
14
|
+
description='Build your e-commerce ea(a)sily',
|
|
15
|
+
long_description=open('README.md').read(),
|
|
16
|
+
long_description_content_type='text/markdown',
|
|
17
|
+
url='https://github.com/ciulene/eaas',
|
|
18
|
+
classifiers=[
|
|
19
|
+
'Programming Language :: Python :: 3',
|
|
20
|
+
'License :: OSI Approved :: MIT License',
|
|
21
|
+
'Operating System :: OS Independent',
|
|
22
|
+
],
|
|
23
|
+
python_requires='>=3.6',
|
|
24
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
from eaas.extensions import buil_model
|
|
4
|
+
from flask_restx import fields, Namespace, Model
|
|
5
|
+
|
|
6
|
+
class MockProperty:
|
|
7
|
+
def __init__(self, name, type, nullable):
|
|
8
|
+
self.name = name
|
|
9
|
+
self.type = type
|
|
10
|
+
self.nullable = nullable
|
|
11
|
+
|
|
12
|
+
@patch("builtins.print")
|
|
13
|
+
class TestBuilder(TestCase):
|
|
14
|
+
def setUp(self, *_):
|
|
15
|
+
# Create a mock BaseEntity
|
|
16
|
+
self.mock_entity = MagicMock()
|
|
17
|
+
self.mock_entity.__name__ = "TestEntity"
|
|
18
|
+
self.mock_entity.column_list.return_value = [
|
|
19
|
+
MockProperty("id", "INTEGER", False),
|
|
20
|
+
MockProperty("name", "VARCHAR", True),
|
|
21
|
+
MockProperty("email", "VARCHAR", False),
|
|
22
|
+
MockProperty("created_at", "DATETIME", False),
|
|
23
|
+
MockProperty("deleted_at", "DATETIME", True),
|
|
24
|
+
MockProperty("is_active", "BOOLEAN", True),
|
|
25
|
+
MockProperty("unsupported_field", "UNSUPPORTED", True)
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
def test_building_model_returns_expected_namespace(self, *_):
|
|
29
|
+
# Act
|
|
30
|
+
namespace, _ = buil_model(self.mock_entity)
|
|
31
|
+
|
|
32
|
+
# Assert
|
|
33
|
+
self.assertIsInstance(namespace, Namespace)
|
|
34
|
+
self.assertEqual(namespace.name, self.mock_entity.__name__)
|
|
35
|
+
self.assertEqual(namespace.description, "TestEntity operations")
|
|
36
|
+
self.assertEqual(namespace.path, "/testentity")
|
|
37
|
+
|
|
38
|
+
def test_building_model_returns_expected_properties(self, *_):
|
|
39
|
+
# Act
|
|
40
|
+
_, model = buil_model(self.mock_entity)
|
|
41
|
+
|
|
42
|
+
# Assert
|
|
43
|
+
self.assertIsInstance(model, Model)
|
|
44
|
+
self.assertIn("id", model)
|
|
45
|
+
self.assertIn("name", model)
|
|
46
|
+
self.assertIn("created_at", model)
|
|
47
|
+
self.assertIn("is_active", model)
|
|
48
|
+
|
|
49
|
+
def test_building_model_returns_expected_integer(self, *_):
|
|
50
|
+
# Act
|
|
51
|
+
_, model = buil_model(self.mock_entity)
|
|
52
|
+
|
|
53
|
+
# Assert
|
|
54
|
+
id_field = model["id"]
|
|
55
|
+
self.assertIsInstance(id_field, fields.Integer)
|
|
56
|
+
self.assertTrue(id_field.required)
|
|
57
|
+
self.assertEqual(id_field.default, 0)
|
|
58
|
+
self.assertEqual(id_field.example, 0)
|
|
59
|
+
|
|
60
|
+
def test_building_model_returns_expected_nullable_string(self, *_):
|
|
61
|
+
# Act
|
|
62
|
+
_, model = buil_model(self.mock_entity)
|
|
63
|
+
|
|
64
|
+
# Assert
|
|
65
|
+
name_field = model["name"]
|
|
66
|
+
self.assertIsInstance(name_field, fields.String)
|
|
67
|
+
self.assertFalse(name_field.required)
|
|
68
|
+
self.assertIsNone(name_field.default)
|
|
69
|
+
|
|
70
|
+
def test_building_model_returns_expected_required_string(self, *_):
|
|
71
|
+
# Act
|
|
72
|
+
_, model = buil_model(self.mock_entity)
|
|
73
|
+
|
|
74
|
+
# Assert
|
|
75
|
+
email_field = model["email"]
|
|
76
|
+
self.assertIsInstance(email_field, fields.String)
|
|
77
|
+
self.assertTrue(email_field.required)
|
|
78
|
+
self.assertEqual(email_field.default, "")
|
|
79
|
+
|
|
80
|
+
def test_building_model_returns_expected_nullable_datetime(self, *_):
|
|
81
|
+
# Act
|
|
82
|
+
_, model = buil_model(self.mock_entity)
|
|
83
|
+
|
|
84
|
+
# Assert
|
|
85
|
+
created_at_field = model["deleted_at"]
|
|
86
|
+
self.assertIsInstance(created_at_field, fields.DateTime)
|
|
87
|
+
self.assertFalse(created_at_field.required)
|
|
88
|
+
self.assertIsNone(created_at_field.default)
|
|
89
|
+
|
|
90
|
+
def test_building_model_returns_expected_required_datetime(self, *_):
|
|
91
|
+
# Act
|
|
92
|
+
_, model = buil_model(self.mock_entity)
|
|
93
|
+
|
|
94
|
+
# Assert
|
|
95
|
+
deleted_at_field = model["created_at"]
|
|
96
|
+
self.assertIsInstance(deleted_at_field, fields.DateTime)
|
|
97
|
+
self.assertTrue(deleted_at_field.required)
|
|
98
|
+
self.assertIsNotNone(deleted_at_field.default)
|
|
99
|
+
|
|
100
|
+
def test_building_model_returns_expected_boolean(self, *_):
|
|
101
|
+
# Act
|
|
102
|
+
_, model = buil_model(self.mock_entity)
|
|
103
|
+
|
|
104
|
+
# Assert
|
|
105
|
+
is_active_field = model["is_active"]
|
|
106
|
+
self.assertIsInstance(is_active_field, fields.Boolean)
|
|
107
|
+
self.assertFalse(is_active_field.required)
|
|
108
|
+
self.assertEqual(is_active_field.default, False)
|
|
109
|
+
|
|
110
|
+
def test_building_model_logs_unsupported_types(self, *_):
|
|
111
|
+
# Act
|
|
112
|
+
_, model = buil_model(self.mock_entity)
|
|
113
|
+
|
|
114
|
+
# Assert
|
|
115
|
+
self.assertNotIn("unsupported_field", model)
|