瀏覽代碼

Sync to sources.

Cixo Develop 1 月之前
父節點
當前提交
5dcc288e1f

+ 2 - 0
server_source/__init__.py

@@ -28,6 +28,8 @@ from .attachment import attachment_proxy
 
 from .item import item
 
+from .aleatory_file_name import aleatory_file_name
+
 from .config_exceptions import config_exception
 from .config_exceptions import key_not_implemented
 from .config_exceptions import bad_type_loaded

+ 105 - 0
server_source/aleatory_file_name.py

@@ -0,0 +1,105 @@
+import os
+import string
+
+class aleatory_file_name:
+    """
+    That create new random file name, with given size and given extension.
+    File name is returned as a string. Creating new instance of that class
+    does not return an instance, but only string result.
+    """
+
+    def __new__(cls, extension: str = "", size: int = 32) -> str:
+        """
+        That create new random file name from system random number 
+        generator, extension and size. Resulted file name have given size,
+        given extension but name is fully random. Name could contain small
+        letters, big letters and digits.
+
+        Parameters
+        ----------
+        extension : str (default: "")
+            Extension of the file.
+        
+        size : int (default: 32)
+            Lenght of the file name.
+
+        Returns
+        -------
+        str 
+            Random name of the file.
+        """
+
+        name_lenght = size
+       
+        if len(extension) > 0:
+            name_lenght -= 1 + len(extension)
+
+        name = cls.__get_random_name(name_lenght)
+        return cls.__get_file_name(name, extension)
+
+    @classmethod
+    def __get_chars(cls) -> str:
+        """
+        It return full aphabet with small and big letters. To letters it 
+        add also digits.
+
+        Returns
+        -------
+        str
+            Full alphabet with digits, big and small letters.
+        """
+        return str(string.ascii_letters + string.digits)
+
+    @classmethod
+    def __get_random_name(cls, size: int) -> str:
+        """
+        That generate random name (random string wich letters from alphabet
+        generated in __get_chars) with size passed by intiger parameter.
+
+        Parameters
+        ----------
+        size : int
+            Lenght of random name.
+
+        Returns
+        -------
+        str
+            Random name with given lenght.
+        """
+        
+        chars = cls.__get_chars()
+        result = str()
+
+        for count in range(size):
+            random = os.urandom(1)[0]
+            random = random % len(chars)
+
+            result = result + chars[random]
+
+        return result
+
+    @classmethod
+    def __get_file_name(cls, name: str, extension: str) -> str:
+        """
+        That create file name from name and extension. When extension is
+        blank, then it return file name without extension. When extension 
+        is passed then return file name as "name.extension".
+
+        Parameters
+        ----------
+        name : str
+            Name of the file.
+
+        extensions : str
+            Extension of the file.
+
+        Returns
+        -------
+        str
+            Full file name from name and extension.
+        """
+
+        if len(extension) > 0:
+            return name + "." + extension
+        
+        return name

+ 79 - 4
server_source/attachment.py

@@ -1,8 +1,12 @@
+import pathlib
+
 from .model import model
 from .proxy import proxy
 from .field_generator import field_generator
 from .constants import constants
 from .validators import validators
+from .attachment_file import attachment_file
+from .exceptions import resources_not_exists
 
 class attachment(model):
     """
@@ -21,6 +25,8 @@ class attachment(model):
 
     name = field_generator.name()
     description = field_generator.description()
+    resources = field_generator.path()
+    
 
     def _validators(self) -> dict:
         """
@@ -35,7 +41,8 @@ class attachment(model):
 
         return {
             "name": validators.name,
-            "description": validators.description
+            "description": validators.description,
+            "resources": validators.path
         }
 
 class attachment_proxy(proxy):   
@@ -48,18 +55,86 @@ class attachment_proxy(proxy):
     @classmethod create : proxy
         That create new attachment from that name.
 
-    
+    set_name : None
+        That change visible name of the attachment.
+
+    set_description : None
+        That change description of the attachment.
     """
 
     @classmethod
-    def create(cls, name: str) -> proxy:
+    def create(cls, name: str, resources: attachment_file) -> proxy:
+        """
+        That create new proxy with new object, which does not exists in 
+        database inside.
+
+        Parameters
+        ----------
+        name : str
+            Name of the new attachment.
+        resources : str
+            Path in the resources directory to the attachment file.
+
+        Raises
+        ------
+        resources_not_exists
+            When attachment file does not exists on disk.
+
+        Returns
+        -------
+        proxy
+            New proxy with new attachment inside it.
+        """
+
+        if not resources.exists():
+            raise resources_not_exists(resources.path)
+
         return cls(attachment(
             name = name,
-            description = constants.empty_text()
+            description = constants.empty_text(),
+            resources = resources.name
         ))
 
+    def set_resources(self, target: attachment_file) -> None:
+        """
+        That change path to the attachment file in the resources directory.
+
+        Parameters
+        ----------
+        target : pathlib.Path
+            Path to new attachment file in the resources directory.
+
+        Raises
+        ------
+        resources_not_exists
+            When attachment file does not exists on disk.
+        """
+        
+        if not target.exists():
+            raise resources_not_exists(target.path)
+
+        self._target.resources = target.name
+
     def set_name(self, target: str) -> None:
+        """
+        That change name of the attachment
+
+        Parameters
+        ----------
+        target : str
+            New name of the attachment to set.
+        """
+
         self._target.name = target
 
     def set_description(self, target: str) -> None:
+        """
+        That chanhe description of the attachment.
+
+        Parameters
+        ----------
+        target : str
+            New description of the attachment to set.
+        """
+
         self._target.description = target

+ 49 - 0
server_source/attachment_file.py

@@ -0,0 +1,49 @@
+import pathlib 
+import aiofiles
+
+from .aleatory_file_name import aleatory_file_name
+
+class attachment_file:
+    def __init__(
+        self, 
+        extension: str,
+        root: pathlib.Path
+    ) -> None:
+        self.__root = root
+        self.__name = self.__get_name(extension)
+
+    async def load(self) -> bytes:
+        async with aiofiles.open(self.path, "rb") as handler:
+            return await handler.read()
+
+    async def store(self, content: bytes) -> object:
+        async with aiofiles.open(self.path, "wb") as handler:
+            await handler.write(content)
+        
+        return self
+
+    def __get_name(self, extension: str) -> str:
+        while True:
+            new_name = aleatory_file_name(extension)
+            new_path = self.root / pathlib.Path(new_name)
+
+            if not new_path.is_file() and not new_path.exists():
+                return new_name 
+    
+    def exists(self) -> bool:
+        return self.path.exists() and self.path.is_file()
+     
+    @property
+    def name(self) -> str:
+        return self.__name
+
+    @property
+    def root(self) -> pathlib.Path: 
+        return self.__root
+
+    @property
+    def path(self) -> pathlib.Path:
+        return self.root / pathlib.Path(self.name)
+
+        
+        

+ 30 - 0
server_source/attachments_manager.py

@@ -0,0 +1,30 @@
+import pathlib
+import base64
+
+from .attachment_file import attachment_file
+from .exceptions import resources_directory_not_exists
+
+class attachments_manager:
+    def __init__(
+        self, 
+        resources: pathlib.Path, 
+        init_directory: bool = False
+    ) -> None:
+        if init_directory:
+            resources.mkdir()
+            
+        if not resources.is_dir() or not resources.exists():
+            raise resources_directory_not_exists(resources)
+
+        self.__resources = resouces
+
+    @property
+    def resources(self) -> pathlib.Path:
+        return self.__resources
+
+    def uploaded(self, content: str, extension: str) -> attachment_file:
+        content_bytes = content.encode("ascii")
+        decoded = base64.b64decode(content_bytes)
+
+        return attachment_file(decoded, extension, self.__resources)
+        

+ 82 - 1
server_source/constants.py

@@ -1,21 +1,102 @@
 class constants:
+    """
+    That class stores constant values, which is used in a lot of places
+    in the app code. Storing constants in one place makes it easier to 
+    change in the future.
+
+    Methods
+    -------
+    @staticmethid empty_text : str
+        Return simple empty string (""), but it is more descriptive than
+        str().
+
+    @staticmethod app_name : dtr
+        Return name of the app, which is required for example in 
+        relationship builder
+
+    @classmethod get_model_name : str
+        Return name of the model for given class, or name in string..
+    
+    @staticmethod get_related_name : str
+        It create name of the reverse relation field for other models.
+    """
+
     @staticmethod
     def empty_text() -> str:
+        """
+        That return simple empty text, to make app more readable.
+
+        Returns
+        -------
+        str
+            Empty string.
+        """
+
         return str()
 
     @staticmethod
     def app_name() -> str:
+        """
+        That retutn name of the app, useable for example when building 
+        relations in database.
+
+        Returns
+        -------
+        str
+            Name of the app.
+        """
+
         return str("app")
 
     @classmethod
     def get_model_name(cls, target: str | type) -> str:
+        """
+        That generate model name from given model class, or model name in 
+        string. It does not return constant, but generate name from given
+        parameters constant data in class.
+
+        Parameters
+        ----------
+        target : str | type
+            Target class or class name.
+
+        Returns
+        -------
+        str
+            Model name of given model class or given model class name.
+        """
+
         if type(target) is not str:
             target = target.__name__
 
         return str(cls.app_name() + "." + target)
 
     @staticmethod
-    def get_related_name(target: str | type, field: str | None = None) -> str:
+    def get_related_name(
+        target: str | type, 
+        field: str | None = None
+    ) -> str:
+        """
+        It return name of the field, which is related to other object by 
+        reverse relation. Target is name of model which own relation with, 
+        in other words, model which has foreign key. Field is neccssary only
+        when object who has relation uses same model in more than one 
+        foreign key. Then field must be name of the foreign key field.
+
+        For example, when item A has field to_b, which is foreign key of 
+        model B, when call to that would be "get_related_name(A)", or if 
+        b is used in other fields, then it would looks like 
+        "get_related_name(A, "to_b")".
+
+        Parameters
+        ----------
+        target : str | type
+            Name or class of the model, which use relation.
+
+        field : str | None (Default: None)
+            Name of the field, which use relation.
+        """
+
         if type(target) is not str:
             target = target.__name__
 

+ 14 - 0
server_source/exceptions.py

@@ -1,3 +1,5 @@
+import pathlib
+
 class nick_in_use(Exception):
     def __init__(self, nick: str) -> None:  
         super().__init__("Nick \"" + nick + "\" already in use.")
@@ -13,6 +15,13 @@ class invalid_nick_syntax(Exception):
 
         super().__init__(comment)
 
+class resources_directory_not_exists(Exception):
+    def __init__(self, resources: pathlib.Path) -> None:
+        comment = "Resources directory \"" + str(resources) + "\" does "
+        comment = comment + "not exists."
+
+        super().__init__(comment)
+
 class user_is_not_admin(Exception): 
     def __init__(self): 
         super().__init__("To do that action user must be admin.")
@@ -38,3 +47,8 @@ class nick_not_exists(Exception):
 class apikey_not_exists(Exception):
     def __init__(self, apikey: str) -> None:    
         super().__init__("Api Key \"" + apikey + "\" does not exists.")
+
+class resources_not_exists(Exception):
+    def __init__(self, target: pathlib.Path) -> None:
+        comment = "Attachment file \"" + str(target) + "\" does not exists."
+        super().__init__(comment)

+ 21 - 0
server_source/field_generator.py

@@ -3,6 +3,23 @@ import tortoise.fields
 from .constants import constants
 
 class field_generator:
+    """
+    That class is used to store methods, which generate fields by items 
+    which would be stored in that fields, like nick, password or 
+    description.
+
+    Methods
+    -------
+    id : tortoise.fields.IntField
+        Field for storing objects ID.
+
+    name : tortoise.fields.TextField
+        Field which store name.
+
+    password : tortosie.fields.TextField
+        Field which store password.
+    """
+
     @staticmethod
     def id() -> tortoise.fields.IntField:
         return tortoise.fields.IntField(primary_key = True)
@@ -59,6 +76,10 @@ class field_generator:
     def boolean() -> tortoise.fields.BooleanField:
         return tortoise.fields.BooleanField()
 
+    @staticmethod
+    def path() -> tortoise.fields.TextField:
+        return tortoise.fields.TextField(max_lenght = 4096)
+
     @staticmethod
     def __connected_names(
         target: type, 

+ 101 - 0
server_source/model.py

@@ -4,12 +4,82 @@ from .field_generator import field_generator
 from .constants import constants
 
 class model(tortoise.models.Model):
+    """
+    This is base class for all models in the app. It store base fields, like
+    ID, and have functions, which is necessary to validate object before 
+    save it in the database.
+
+    Fields
+    ------
+    id : field_generator.id
+        ID of the object.
+
+    Methods
+    -------
+    _validators : dict
+        Return dict of validators, which would be used to validate fields of
+        the object. For example, when object have field "name", and function 
+        "val_name(name: str) -> str" would be used to validate it, then that
+        dict must contain "{ 'name': val_name }".
+
+    __validate_item : None
+        That validate sinle item of the object. To pass validation, result
+        from validating function must be same as before validaion, and that
+        function must not raise and error.
+
+    __get : any
+        That return content of given field of object.
+
+    validate : object
+        That run all validators. When any validators found problem with 
+        content of object, then raise an ValueError. If all went nice, then
+        return self.
+
+    Classes
+    -------
+    Meta
+        Fields
+        ------
+        app : str
+            Name of the app.
+    """
+
     id = field_generator.id()
 
     def _validators(self) -> dict:
+        """
+        That return dict of fields, which must be validated, with validators
+        which must be used. Format of the dict is "{ 'name of the field': 
+        validation_function }". By default it is empty, and none of the field
+        is not validating.
+
+        Returns
+        -------
+        dict
+            Dictionary with validators for fields.
+        """
+
         return dict()
 
     def __validate_item(self, validator: callable, key: str) -> None:
+        """
+        It validate single field of the object. When validation is passed, 
+        it return without errors, in other way it raise ValueError.
+
+        Parameters
+        ----------
+        validator : callable
+            Validator which would be used to validate field.
+
+        key : str
+            Name of the field.
+
+        Raises
+        ------
+        ValueError
+            When field contain incorrect data.
+        """
+
         try:
             if validator(self.__get(key)) == self.__get(key):
                 return
@@ -28,9 +98,40 @@ class model(tortoise.models.Model):
             raise ValueError(info)
 
     def __get(self, key: str) -> any:
+        """
+        That return field with given name.
+
+        Parameters
+        ----------
+        key : str
+            Name of the field.
+
+        Returns
+        -------
+        any
+            Value of the key.
+        """
+
         return self.__getattribute__(key)
 
     def validate(self) -> object:
+        """
+        That function make full validation of the object. It validate all 
+        fields, which is in the __validators() dict. When validation had 
+        been passed, it return self. When validation couldn't being passed
+        it raises ValueError.
+
+        Returns
+        -------
+        model
+            Object instance itself.
+
+        Raises
+        ------
+        ValueError
+            When any field contain incorrect data.
+        """
+
         for key, validator in self._validators().items():
             self.__validate_item(validator, key)
         

+ 8 - 2
server_source/validators.py

@@ -23,6 +23,12 @@ class validators(validators_base):
 
         return content
 
+    @staticmethod
+    def path(content: str) -> str:
+        content = validators._validate_lenght(content, "path", 1, 4096)
+
+        return content
+
     @staticmethod
     def name(content: str) -> str:
         content = validators._validate_lenght(content, "name", 4, 30)
@@ -32,8 +38,8 @@ class validators(validators_base):
 
     @staticmethod
     def surname(content: str) -> str:
-        content = validators._validate_lenght(content, "surnick", 4, 30)
-        content = validators._validate_generic_name(content, "surnick")
+        content = validators._validate_lenght(content, "surname", 4, 30)
+        content = validators._validate_generic_name(content, "surname")
 
         return content
 

+ 14 - 0
server_tests/008-random_name.py

@@ -0,0 +1,14 @@
+import sys
+import pathlib
+
+test_file = pathlib.Path(__file__)
+project = test_file.parent.parent
+
+sys.path.append(str(project))
+
+import server_source as source
+from test import test
+
+print(source.aleatory_file_name("txt"))
+print(source.aleatory_file_name())
+print(source.aleatory_file_name("jpg"))