From e159657b4b6302fc1d8e48de05e84a92f7ac51c7 Mon Sep 17 00:00:00 2001
From: jrybicki-jsc <j.rybicki@fz-juelich.de>
Date: Fri, 28 May 2021 14:39:29 +0200
Subject: [PATCH] refactor storage for python naming convention

---
 .gitignore                                  |  3 +-
 apiserver/main.py                           |  8 +-
 apiserver/storage/JsonFileStorageAdapter.py | 87 ++++++++++++---------
 apiserver/storage/LocationStorage.py        | 58 ++++++++------
 tests/storage_tests/test_jsonbackend.py     |  2 +-
 5 files changed, 90 insertions(+), 68 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4433965..003d145 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 **__pycache__/
+.vscode/*
 
 # contains data for local tests
 app/
-site/
\ No newline at end of file
+site/
diff --git a/apiserver/main.py b/apiserver/main.py
index 105f956..5a9a122 100644
--- a/apiserver/main.py
+++ b/apiserver/main.py
@@ -74,7 +74,7 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
 @app.get("/{location_data_type}")
 async def list_datasets(location_data_type: LocationDataType):
     # list id and name of every registered dataset for the specified type
-    return adapter.getList(location_data_type)
+    return adapter.get_list(location_data_type)
 
 
 @app.put("/{location_data_type}")
@@ -82,14 +82,14 @@ async def add_dataset(location_data_type: LocationDataType, dataset: LocationDat
                       token: str = Depends(oauth2_scheme)):
     # register a new dataset, the response will contain the new dataset and its id
     usr = "testuser"
-    return adapter.addNew(location_data_type, dataset, usr)
+    return adapter.add_new(location_data_type, dataset, usr)
 
 
 @app.get("/{location_data_type}/{dataset_id}")
 async def get_specific_dataset(location_data_type: LocationDataType, dataset_id: str):
     # returns all information about a specific dataset, identified by id
     try:
-        return adapter.getDetails(location_data_type, dataset_id)
+        return adapter.get_details(location_data_type, dataset_id)
     except FileNotFoundError:
         raise HTTPException(
             status_code=404, detail='The provided id does not exist for this datatype.')
@@ -102,7 +102,7 @@ async def update_specific_dataset(location_data_type: LocationDataType,
     # update the information about a specific dataset, identified by id
     usr = "testuser"
     try:
-        return adapter.updateDetails(location_data_type, dataset_id, dataset, usr)
+        return adapter.update_details(location_data_type, dataset_id, dataset, usr)
     except FileNotFoundError:
         raise HTTPException(
             status_code=404, detail='The provided id does not exist for this datatype.')
diff --git a/apiserver/storage/JsonFileStorageAdapter.py b/apiserver/storage/JsonFileStorageAdapter.py
index 86a9972..a136aea 100644
--- a/apiserver/storage/JsonFileStorageAdapter.py
+++ b/apiserver/storage/JsonFileStorageAdapter.py
@@ -8,74 +8,85 @@ from apiserver.config import ApiserverSettings
 
 from typing import List
 
+
 class StoredData:
     actualData: LocationData
     users: List[str]
 
     def toDict(self):
-        return {'actualData' : self.actualData.__dict__, 'users' : self.users}
+        return {'actualData': self.actualData.__dict__, 'users': self.users}
 
 
-# This stores LocationData via the StoredData Object as json files
-# These Jsonfiles then contain the actualData, as well as the users with permissions for this LocationData
-# all users have full permission to to anything with this dataobject, uncluding removing their own access (this might trigger a confirmation via the frontend, but this is not enforced via the api)
-# IMPORTANT: The adapter does not check for authentication or authorization, it should only be invoked if the permissions have been checked
 class JsonFileStorageAdapter(AbstractLocationDataStorageAdapter):
-    data_dir: str
+    """ This stores LocationData via the StoredData Object as json files
+
+    These Jsonfiles then contain the actualData, as well as the users with permissions 
+    for this LocationData all users have full permission to to anything with 
+    this dataobject, uncluding removing their own access (this might trigger a 
+    confirmation via the frontend, but this is not enforced via the api)
+
+    IMPORTANT: The adapter does not check for authentication or authorization, 
+    it should only be invoked if the permissions have been checked
+    """
 
     def __init__(self, settings: ApiserverSettings):
         AbstractLocationDataStorageAdapter.__init__(self)
         self.data_dir = settings.json_storage_path
         if not (os.path.exists(self.data_dir) and os.path.isdir(self.data_dir)):
-            raise Exception('Data Directory \"' + self.data_dir + '\" does not exist.')
+            raise Exception(f"Data Directory {self.data_dir} does not exist.")
 
-    def getList(self, type: LocationDataType) -> List:
+    def get_list(self, type: LocationDataType) -> List:
         localpath = os.path.join(self.data_dir, type.value)
         if not (os.path.isdir(localpath)):
-            # This type has apparently not yet been used at all, create its directory and return an empty json file
+            # This type has apparently not yet been used at all, 
+            # create its directory and return an empty json file
             os.mkdir(localpath)
             return []
         else:
-            allFiles = [f for f in os.listdir(localpath) if os.path.isfile(os.path.join(localpath, f))]
-            # now each file has to be checked for its filename (= id) and the LocationData name (which is inside the json)
+            allFiles = [f for f in os.listdir(
+                localpath) if os.path.isfile(os.path.join(localpath, f))]
+            # now each file has to be checked for its filename (= oid) 
+            # and the LocationData name (which is inside the json)
             retList = []
             for f in allFiles:
                 with open(os.path.join(localpath, f)) as file:
                     data = json.load(file)
-                    retList.append({data['actualData']['name'] : f})
+                    retList.append({data['actualData']['name']: f})
             return retList
 
-    def addNew(self, type: LocationDataType, data: LocationData, usr: str):
+    def add_new(self, type: LocationDataType, data: LocationData, usr: str):
         localpath = os.path.join(self.data_dir, type.value)
         if not (os.path.isdir(localpath)):
-            # This type has apparently not yet been used at all, therefore we need to create its directory
+            # This type has apparently not yet been used at all, 
+            # therefore we need to create its directory
             os.mkdir(localpath)
-        # create a unique id, by randomly generating one, and re-choosing if it is already taken
-        id = str(uuid.uuid4())
-        while (os.path.exists(os.path.join(localpath, id))):
-            id = str(uuid.uuid4())
+        # create a unique oid, by randomly generating one, 
+        # and re-choosing if it is already taken
+        oid = str(uuid.uuid4())
+        while (os.path.exists(os.path.join(localpath, oid))):
+            oid = str(uuid.uuid4())
         toStore = StoredData()
         toStore.users = [usr]
         toStore.actualData = data
-        with open(os.path.join(localpath, id), 'w') as json_file:
+        with open(os.path.join(localpath, oid), 'w') as json_file:
             json.dump(toStore.toDict(), json_file)
-        return {id : data}
+        return {oid: data}
 
-    def getDetails(self, type: LocationDataType, id: str):
+    def get_details(self, type: LocationDataType, oid: str):
         localpath = os.path.join(self.data_dir, type.value)
-        fullpath = os.path.join(localpath, id)
+        fullpath = os.path.join(localpath, oid)
         if not os.path.isfile(fullpath):
-            raise FileNotFoundError('The requested Object does not exist.')
+            raise FileNotFoundError(f"The requested object ({oid}) does not exist.")
         with open(fullpath) as file:
             data = json.load(file)
         return data['actualData']
 
-    def updateDetails(self, type:LocationDataType, id:str, data: LocationData, usr: str):
+    def update_details(self, type: LocationDataType, oid: str, data: LocationData, usr: str):
         localpath = os.path.join(self.data_dir, type.value)
-        fullpath = os.path.join(localpath, id)
+        fullpath = os.path.join(localpath, oid)
         if not os.path.isfile(fullpath):
-            raise FileNotFoundError('The requested Object does not exist.')
-        
+            raise FileNotFoundError(f"The requested object ({oid}) does not exist.")
+
         toStore = StoredData()
         toStore.actualData = data
 
@@ -86,25 +97,25 @@ class JsonFileStorageAdapter(AbstractLocationDataStorageAdapter):
 
         with open(fullpath, 'w') as file:
             json.dump(toStore.toDict(), file)
-        return {id : data}
+        return {oid: data}
 
-    def delete(self, type:LocationDataType, id:str, usr: str):
+    def delete(self, type: LocationDataType, oid: str, usr: str):
         localpath = os.path.join(self.data_dir, type.value)
-        fullpath = os.path.join(localpath, id)
-        
+        fullpath = os.path.join(localpath, oid)
+
         if not os.path.isfile(fullpath):
-            raise FileNotFoundError('The requested Object does not exist.')
-        
+            raise FileNotFoundError(f"The requested object {oid} does not exist.")
+
         os.remove(fullpath)
 
-    def getOwner(self, type: LocationDataType, id: str):
+    def get_owner(self, type: LocationDataType, oid: str):
         raise NotImplementedError()
 
-    def checkPerm(self, type: LocationDataType, id: str, usr: str):
+    def check_perm(self, type: LocationDataType, oid: str, usr: str):
         raise NotImplementedError()
 
-    def addPerm(self, type: LocationDataType, id: str, authUsr: str, newUser: str):
+    def add_perm(self, type: LocationDataType, oid: str, authUsr: str, newUser: str):
         raise NotImplementedError()
 
-    def rmPerm(self, type: LocationDataType, id: str, usr: str, rmUser: str):
-        raise NotImplementedError()
\ No newline at end of file
+    def rm_perm(self, type: LocationDataType, oid: str, usr: str, rmUser: str):
+        raise NotImplementedError()
diff --git a/apiserver/storage/LocationStorage.py b/apiserver/storage/LocationStorage.py
index 28b4796..929a629 100644
--- a/apiserver/storage/LocationStorage.py
+++ b/apiserver/storage/LocationStorage.py
@@ -5,59 +5,69 @@ from typing import Optional
 from typing import Dict
 from enum import Enum
 
+
 class LocationDataType(Enum):
-    DATASET: str = 'dataset'
-    STORAGETARGET: str = 'storage_target'
+    DATASET = 'dataset'
+    STORAGETARGET = 'storage_target'
+
 
 class LocationData(BaseModel):
     name: str
     url: str
     metadata: Optional[Dict[str, str]]
 
-#
-'''
-This is an abstract storage adapter for storing information about datasets, storage targets and similar things.
-It can easily be expanded to also store other data (that has roughly similar metadata), just by expanding the LocationDataType Enum.
-
-In general, all data is public. This means, that the adapter does not do any permission checking, except when explicitly asked via the checkPerm function.
-The caller therefore has to manually decide when to check for permissions, and not call any function unless it is already authorized (or does not need any authorization).
 
-The usr: str (the user id) that is required for several functions, is a unique and immutable string, that identifies the user. This can be a verified email or a user name. 
-The management of authentication etc. is done by the caller, this adapter assumes that the user id fulfills the criteria.
-Permissions are stored as a list of user ids, and every id is authorized for full access.
-'''
 class AbstractLocationDataStorageAdapter:
+    '''
+    This is an abstract storage adapter for storing information about datasets, 
+    storage targets and similar things. It can easily be expanded to also store 
+    other data (that has roughly similar metadata), just by expanding 
+    the `LocationDataType` Enum.
+
+    In general, all data is public. This means, that the adapter does not 
+    do any permission checking, except when explicitly asked via the `checkPerm`
+    function. The caller therefore has to manually decide when to check for 
+    permissions, and not call any function unless it is already authorized 
+    (or does not need any authorization).
+
+    The usr: str (the user id) that is required for several functions, is 
+    a unique and immutable string, that identifies the user. This can be a 
+    verified email or a user name. The management of authentication etc. is 
+    done by the caller, this adapter assumes that the user id fulfills the criteria.
+    Permissions are stored as a list of user ids, and every id is authorized for full access.
+    '''
     # get a list of all LocationData Elements with the provided type, as pairs of {name : id}
-    def getList(self, type: LocationDataType):
+
+    def get_list(self, type: LocationDataType):
         raise NotImplementedError()
 
     # add a new element of the provided type, assign and return the id and the new data as {id : LocationData}
-    def addNew(self, type: LocationDataType, data: LocationData, usr: str):
+    def add_new(self, type: LocationDataType, data: LocationData, usr: str):
         raise NotImplementedError()
 
-    # return the LocationData of the requested object (identified by id and type)
-    def getDetails(self, type: LocationDataType, id: str):
+    # return the LocationData of the requested object (identified by oid and type)
+    def get_details(self, type: LocationDataType, oid: str):
         raise NotImplementedError()
 
-    # change the details of the requested object, return {id : newData}
-    def updateDetails(self, type:LocationDataType, id:str, data: LocationData, usr: str):
+    # change the details of the requested object, return {oid : newData}
+    def update_details(self, type: LocationDataType, oid: str, data: LocationData, usr: str):
         raise NotImplementedError()
 
-    def delete(self, type:LocationDataType, id:str, usr: str):
+    def delete(self, type: LocationDataType, oid: str, usr: str):
         raise NotImplementedError()
 
     # return the owner of the requested object; if multiple owners are set, return them is a list
-    def getOwner(self, type: LocationDataType, id: str):
+    def get_owner(self, type: LocationDataType, oid: str):
         raise NotImplementedError()
 
     # check if the given user has permission to change the given object
-    def checkPerm(self, type: LocationDataType, id: str, usr: str):
+    def check_perm(self, type: LocationDataType, oid: str, usr: str):
         raise NotImplementedError()
 
     # add user to file perm
-    def addPerm(self, type: LocationDataType, id: str, usr: str):
+    def add_perm(self, type: LocationDataType, oid: str, usr: str):
         raise NotImplementedError()
 
     # remove user from file perm
-    def rmPerm(self, type: LocationDataType, id: str, usr: str):
+    def rm_perm(self, type: LocationDataType, oid: str, usr: str):
         raise NotImplementedError()
diff --git a/tests/storage_tests/test_jsonbackend.py b/tests/storage_tests/test_jsonbackend.py
index 01369bf..ea2313b 100644
--- a/tests/storage_tests/test_jsonbackend.py
+++ b/tests/storage_tests/test_jsonbackend.py
@@ -24,6 +24,6 @@ class SomeTests(unittest.TestCase):
 
     def test_getList(self):
         test_type = LocationDataType.DATASET
-        lst = self.store.getList(type=test_type)
+        lst = self.store.get_list(type=test_type)
         self.assertEqual(lst, [],  'Id should not be none')
         
\ No newline at end of file
-- 
GitLab