Przeglądaj źródła

Merge branch 'feature/converters' of 1ffy/sp-1 into master

Всеволод Левитан 1 rok temu
rodzic
commit
e76ab1becc

+ 2 - 1
config/settings.json

@@ -5,5 +5,6 @@
     "bank_account_id": 12345678901,
     "corresp_account_id": 12345678901,
     "property_name": "Авангард",
-    "property_type": "ОООАО"
+    "property_type": "ОООАО",
+    "export_format": "csv"
 }

+ 67 - 0
main.py

@@ -0,0 +1,67 @@
+import os
+from flask import Flask
+from src.errors.argument_exception import argument_exception
+from src.export.exporter_factory import exporter_factory
+
+from src.settings.settings_manager import settings_manager
+from src.logic.start_factory import start_factory
+from src.storage.storage import storage
+
+
+setman = settings_manager()
+start = None
+strg = None
+
+app = Flask(__name__)
+
+
+@app.route("/api/export/<storage_key>/<format>", methods=["GET"])
+def get_export_format(storage_key: str, format: str):
+    global setman, start, strg
+
+    try:
+        exp = exporter_factory().create(format)
+    except argument_exception as e:
+        return "Wrong format: " + e.error.error_text, 400
+
+    if storage_key not in strg.data:
+        return "Key not found", 404
+
+    return app.response_class(
+        response=exp.export_by_key(storage_key),
+        status=200,
+        mimetype=exp.strategy.mimetype,
+    )
+
+
+@app.route("/api/export/<storage_key>", methods=["GET"])
+def get_export(storage_key: str):
+    global setman, start, strg
+    format = setman.settings.export_format
+    try:
+        exp = exporter_factory().create(format)
+    except argument_exception as e:
+        return "Wrong format: " + e.error.error_text, 400
+
+    if storage_key not in strg.data:
+        return "Key not found", 404
+
+    return app.response_class(
+        response=exp.export_by_key(storage_key),
+        status=200,
+        mimetype=exp.strategy.mimetype,
+    )
+
+
+# Инициализация приложения
+def run():
+    global setman, start, strg
+    setman.open(os.path.dirname(__file__) + "/config/settings.json")
+    strg = storage()
+    start = start_factory(setman.settings, strg)
+    start.create()
+    app.run()
+
+
+if __name__ == "__main__":
+    run()

+ 7 - 0
src/convert/basic_converter.py

@@ -0,0 +1,7 @@
+from src.convert.converter import converter
+
+
+class basic_converter(converter):
+    @staticmethod
+    def convert(obj):
+        return str(obj)

+ 13 - 0
src/convert/collection_converter.py

@@ -0,0 +1,13 @@
+from src.convert.converter import converter
+
+
+class collection_converter(converter):
+    @staticmethod
+    def convert(obj):
+        from src.convert.converter_factory import converter_factory
+
+        items = []
+        for e in obj:
+            items.append(converter_factory.convert(e))
+
+        return items

+ 8 - 0
src/convert/converter.py

@@ -0,0 +1,8 @@
+from abc import ABC, abstractmethod
+
+
+class converter(ABC):
+    @staticmethod
+    @abstractmethod
+    def convert(obj):
+        pass

+ 39 - 0
src/convert/converter_factory.py

@@ -0,0 +1,39 @@
+import datetime
+from functools import singledispatch
+from src.convert.collection_converter import collection_converter
+from src.convert.dict_converter import dict_converter
+from src.models.abstract_reference import abstract_reference
+from src.convert.basic_converter import basic_converter
+from src.convert.datetime_converter import datetime_converter
+from src.convert.model_converter import model_converter
+
+
+class converter_factory:
+    @staticmethod
+    def convert(obj):
+        return converter_factory.create(obj).convert(obj)
+
+    @singledispatch
+    @staticmethod
+    def create(obj):
+        return basic_converter()
+
+    @create.register
+    def create_set(obj: set):
+        return collection_converter()
+
+    @create.register
+    def create_list(obj: list):
+        return collection_converter()
+
+    @create.register
+    def create_dict(obj: dict):
+        return dict_converter()
+
+    @create.register
+    def create_model(obj: abstract_reference):
+        return model_converter()
+
+    @create.register
+    def create_datetime(obj: datetime.datetime):
+        return datetime_converter()

+ 14 - 0
src/convert/datetime_converter.py

@@ -0,0 +1,14 @@
+from src.convert.converter import converter
+
+
+class datetime_converter(converter):
+    @staticmethod
+    def convert(obj):
+        return {
+            "year": obj.year,
+            "month": obj.month,
+            "day": obj.day,
+            "hour": obj.hour,
+            "minute": obj.minute,
+            "second": obj.second,
+        }

+ 13 - 0
src/convert/dict_converter.py

@@ -0,0 +1,13 @@
+from src.convert.converter import converter
+
+
+class dict_converter(converter):
+    @staticmethod
+    def convert(obj):
+        from src.convert.converter_factory import converter_factory
+
+        res = {}
+        for e in obj.keys():
+            res[converter_factory.convert(e)] = converter_factory.convert(obj[e])
+
+        return res

+ 22 - 0
src/convert/model_converter.py

@@ -0,0 +1,22 @@
+from src.models.abstract_reference import abstract_reference
+from src.convert.basic_converter import basic_converter
+from src.convert.converter import converter
+
+
+class model_converter(converter):
+    @staticmethod
+    def convert(obj):
+        from src.convert.converter_factory import converter_factory
+
+        properties = {}
+        for name in dir(obj.__class__):
+            if name == "error":
+                continue
+            attr = getattr(obj.__class__, name)
+            if isinstance(attr, property):
+                v = attr.fget(obj)
+                if issubclass(v.__class__, abstract_reference):
+                    properties[name] = f"{v.id}"
+                else:
+                    properties[name] = converter_factory.convert(v)
+        return properties

+ 9 - 1
src/export/exporter.py

@@ -21,6 +21,8 @@ class exporter:
         for model in models:
             res += f"\n{self.__strat.export_model(model)}"
 
+        res = self.__strat.postprocess(res)
+
         return res
 
     def export_by_key(self, key):
@@ -30,6 +32,12 @@ class exporter:
         self.__vtor.check_type(key, str)
         self.__vtor.check_length_greater(key, 0)
 
-        models = storage().data[key]
+        models = list(storage().data[key])
 
         return self.export_models(models)
+
+    @property
+    def strategy(self):
+        """Стратегия экспорта"""
+
+        return self.__strat

+ 40 - 0
src/export/exporter_factory.py

@@ -0,0 +1,40 @@
+from src.errors.argument_exception import argument_exception
+from src.export.strategies.export import export
+from src.export.exporter import exporter
+from src.export.strategies.csv_export import csv_export
+from src.export.strategies.markdown_export import markdown_export
+from src.export.strategies.json_export import json_export
+
+
+class exporter_factory:
+    __formats = {"csv": csv_export, "markdown": markdown_export, "json": json_export}
+
+    def create(self, format: str):
+        """Создать экспортер заданного формата"""
+
+        format = format.lower().strip()
+
+        if format not in self.__formats:
+            raise argument_exception("Wrong format name")
+
+        return self.make(self.__formats[format])
+
+    def make(self, strat: export):
+        """Создать экспортер с переданной стратегией"""
+
+        return exporter(strat())
+
+    def make_csv(self):
+        """Создать экспортер формата CSV"""
+
+        return exporter(csv_export())
+
+    def make_markdown(self):
+        """Создать экспортер формата M↓"""
+
+        return exporter(markdown_export())
+
+    def make_json(self):
+        """Создать экспортер формата JSON"""
+
+        return exporter(json_export())

+ 9 - 0
src/export/strategies/csv_export.py

@@ -28,3 +28,12 @@ class csv_export(export):
             res += str(val).replace(";", ",") + ";"
 
         return res[:-1]
+
+    def postprocess(self, text):
+        """Пост-обработка текста перед экспортом (по надобности)"""
+
+        return text
+
+    @property
+    def mimetype(self):
+        return "text/csv"

+ 13 - 2
src/export/strategies/export.py

@@ -14,7 +14,7 @@ class export:
                 properties[name] = attr.fget(model)
         return properties
 
-    def export_header(model: abstract_reference):
+    def export_header(self, model: abstract_reference):
         """
             Создать заголовок экспорта
         Args:
@@ -23,7 +23,7 @@ class export:
 
         pass
 
-    def export_model(model: abstract_reference):
+    def export_model(self, model: abstract_reference):
         """
             Экспортировать модель
         Args:
@@ -31,3 +31,14 @@ class export:
         """
 
         pass
+
+    def postprocess(self, text):
+        """Пост-обработка текста перед экспортом (по надобности)"""
+
+        return text
+
+    @property
+    def mimetype(self):
+        """Mimetype"""
+
+        pass

+ 19 - 4
src/export/strategies/json_export.py

@@ -1,3 +1,5 @@
+import json
+from src.convert.converter_factory import converter_factory
 from src.export.strategies.export import export
 from src.models.abstract_reference import abstract_reference
 
@@ -5,18 +7,31 @@ from src.models.abstract_reference import abstract_reference
 class json_export(export):
     """Класс стратегии для экспорта в JSON"""
 
-    def export_header(model: abstract_reference):
+    def export_header(self, model: abstract_reference):
         """
             Создать заголовок экспорта
         Args:
             model (abstract_reference): модель, по которой нужно построить заголовок
         """
-        pass
 
-    def export_model(model: abstract_reference):
+        return ""
+
+    def export_model(self, model: abstract_reference):
         """
             Экспортировать модель
         Args:
             model (abstract_reference): модель, строку с которой нужно создать
         """
-        pass
+
+        data = converter_factory.convert(model)
+
+        return json.dumps(data, indent=4) + ","
+
+    def postprocess(self, text):
+        """Пост-обработка текста перед экспортом (по надобности)"""
+
+        return f'{{\n    "items": [{text[:-1]}\n    ]\n}}'
+
+    @property
+    def mimetype(self):
+        return "application/json"

+ 25 - 4
src/export/strategies/markdown_export.py

@@ -5,18 +5,39 @@ from src.models.abstract_reference import abstract_reference
 class markdown_export(export):
     """Класс стратегии для экспорта в Markdown"""
 
-    def export_header(model: abstract_reference):
+    def export_header(self, model: abstract_reference):
         """
             Создать заголовок экспорта
         Args:
             model (abstract_reference): модель, по которой нужно построить заголовок
         """
-        pass
+        res = "| "
+        keys = super().get_properties(model).keys()
+        res += " | ".join(keys)
+        res += " |\n| "
+        for key in range(len(keys)):
+            res += "- |"
+        return res
 
-    def export_model(model: abstract_reference):
+    def export_model(self, model: abstract_reference):
         """
             Экспортировать модель
         Args:
             model (abstract_reference): модель, строку с которой нужно создать
         """
-        pass
+        res = "|"
+        properties = super().get_properties(model)
+        for property in properties.keys():
+            val = properties[property]
+            res += str(val) + " | "
+        res = res[:-2] + "|"
+        return res
+
+    def postprocess(self, text):
+        """Пост-обработка текста перед экспортом (по надобности)"""
+
+        return text
+
+    @property
+    def mimetype(self):
+        return "text/markdown"

+ 11 - 2
src/export/strategies/xml_export.py

@@ -5,7 +5,7 @@ from src.models.abstract_reference import abstract_reference
 class xml_export(export):
     """Класс стратегии для экспорта в XML"""
 
-    def export_header(model: abstract_reference):
+    def export_header(self, model: abstract_reference):
         """
             Создать заголовок экспорта
         Args:
@@ -13,10 +13,19 @@ class xml_export(export):
         """
         pass
 
-    def export_model(model: abstract_reference):
+    def export_model(self, model: abstract_reference):
         """
             Экспортировать модель
         Args:
             model (abstract_reference): модель, строку с которой нужно создать
         """
         pass
+
+    def postprocess(self, text):
+        """Пост-обработка текста перед экспортом (по надобности)"""
+
+        return text
+
+    @property
+    def mimetype(self):
+        return "application/xml"

+ 0 - 1
src/validation/validator.py

@@ -1,4 +1,3 @@
-from ast import arg
 import re
 from numbers import Number
 from src.errors.argument_exception import argument_exception

+ 63 - 0
tests/test_converters.py

@@ -0,0 +1,63 @@
+from datetime import datetime
+import unittest
+from src.convert.dict_converter import dict_converter
+from src.convert.collection_converter import collection_converter
+from src.models.abstract_reference import abstract_reference
+from src.models.nomenclature_group_model import nomenclature_group_model
+from src.convert.basic_converter import basic_converter
+from src.convert.converter_factory import converter_factory
+from src.convert.datetime_converter import datetime_converter
+from src.convert.model_converter import model_converter
+
+
+class test_converters(unittest.TestCase):
+    def test_factory(self):
+        assert isinstance(converter_factory.create(10), basic_converter)
+        assert isinstance(
+            converter_factory.create(abstract_reference("test")), model_converter
+        )
+        assert isinstance(
+            converter_factory.create_datetime(datetime.now()), datetime_converter
+        )
+
+    def test_basic(self):
+        num = 10
+        st = "test"
+        contor = basic_converter()
+
+        assert contor.convert(num) == str(num)
+        assert contor.convert(st) == st
+
+    def test_model(self):
+        g = nomenclature_group_model.create_group()
+        contor = model_converter()
+        conv = contor.convert(g)
+
+        assert str(g.id) == conv["id"]
+        assert g.name == conv["name"]
+
+    def test_datetime(self):
+        now = datetime.now()
+        contor = datetime_converter()
+        conv = contor.convert(now)
+
+        assert now.year == conv["year"]
+        assert now.month == conv["month"]
+        assert now.day == conv["day"]
+        assert now.hour == conv["hour"]
+        assert now.minute == conv["minute"]
+        assert now.second == conv["second"]
+
+    def test_collection(self):
+        col = [1, 2, 3, 4]
+        contor = collection_converter()
+        conv = contor.convert(col)
+
+        assert conv == ["1", "2", "3", "4"]
+
+    def test_dict(self):
+        d = {1: "1", 2: "2", 3: "3"}
+        contor = dict_converter()
+        conv = contor.convert(d)
+
+        assert conv == {"1": "1", "2": "2", "3": "3"}

+ 26 - 0
tests/test_export.py

@@ -1,4 +1,10 @@
+import json
 import unittest
+from src.export.strategies.json_export import json_export
+from src.models.recipe_model import recipe_model
+from src.models.nomenclature_group_model import nomenclature_group_model
+from src.models.nomenclature_model import nomenclature_model
+from src.models.ingredient_model import ingredient_model
 from src.export.strategies.csv_export import csv_export
 from src.export.exporter import exporter
 from src.models.measurement_unit_model import measurement_unit_model
@@ -30,3 +36,23 @@ class test_export(unittest.TestCase):
         # Проверки
         assert isinstance(csv, str)
         assert csv == expected
+
+    def test_json_recipe(self):
+        strg = storage()
+
+        nom = nomenclature_model(
+            "A",
+            "AA",
+            measurement_unit_model("g"),
+            nomenclature_group_model.create_group(),
+        )
+        ing = ingredient_model("AAA", nom, 10, nom.measurement_unit)
+        rec = recipe_model.create("AAAA", [ing], ["1", "2", "3"])
+
+        conv = exporter(json_export()).export_models([rec])
+
+        conv = json.loads(conv)
+
+        assert len(conv["items"][0]["ingredients"]) == 1
+        assert (conv["items"][0]["id"]) == str(rec.id)
+        assert len(conv["items"][0]["steps"]) == len(rec.steps)