src/catedit/persistence.py
author Nicolas DURAND <nicolas.durand@iri.centrepompidou.fr>
Thu, 18 Dec 2014 12:07:03 +0100
changeset 13 ea5f985156b1
parent 12 8ca7be41e3ca
child 14 fc63b1a3d2ef
permissions -rw-r--r--
Fix to github error handling

"""
persistence.py:
contains what we need to make a (category) objects (see models.py) persist
beyond application scope and load them from outside sources

For now we can save, load and list
* to/from a file (using a turtle rdf graph serialization for categories)
* to/from a github repository (using a turtle rdf graph serialization
for categories)
"""
from abc import ABCMeta, abstractmethod
from catedit import app, github
from base64 import b64encode, b64decode
from flask.ext.github import GitHubError
import os
import json

LOGGER = app.logger


class Persistence:
    """
        Meta-class for all persistence classes
    """
    __metaclass__ = ABCMeta

    @abstractmethod
    def save(self, **kwargs):
        """
            Abstract - Saves object
        """
        return

    @abstractmethod
    def delete(self, **kwargs):
        """
            Abstract - Deletes object
        """
        return

    @abstractmethod
    def load(self, **kwargs):
        """
            Abstract - Loads object
        """
        return

    @abstractmethod
    def list(self, **kwargs):
        """
            Abstract - Lists objects
        """
        return


class PersistenceToFile(Persistence):
    """
        Persistence Class for saving to a local file (see config.py
        for tweaking)

        Expected kwargs for saving to/loading from a file are:
        * name : name of the file to write in/read from
        * content : desired content of the file when writing
    """
    def save(self, **kwargs):
        """
            Saves to a file
        """
        path_to_save = app.config["FILE_SAVE_DIRECTORY"]+kwargs["name"]
        file_to_save = open(path_to_save, 'wb')
        file_to_save.write(kwargs["content"])
        file_to_save.close()

    def load(self, **kwargs):
        """
            Loads from a file
        """
        path_to_load = app.config["FILE_SAVE_DIRECTORY"]+kwargs["name"]
        file_to_load = open(path_to_load, 'rb')
        file_content = file_to_load.read()
        file_to_load.close()
        return file_content

    def delete(self, **kwargs):
        """
            Deletes a file
        """
        path_to_delete = app.config["FILE_SAVE_DIRECTORY"]+kwargs["name"]
        os.remove(path_to_delete)

    # IDEA: return { file_name: file_content } type dict
    def list(self, **kwargs):
        """
            Lists all files in file directory (as set in config.py)
        """
        file_content_list = []
        for file_name in os.listdir(app.config["FILE_SAVE_DIRECTORY"]):
            if not file_name or file_name[0] == ".":
                continue
            path_to_load = open(app.config["FILE_SAVE_DIRECTORY"]+file_name)
            file_content = path_to_load.read()
            path_to_load.close()
            file_content_list.append(file_content)
        # LOGGER.debug(file_content_list)
        return file_content_list


class PersistenceToGithub(Persistence):
    """
        Persistence Class for saving to/loading from a Github repository (see
        config.py for tweaks)

        Expected kwargs for saving to/loading from a Github repository are:
        * name : name of the file to write in/read from
        * content : desired content of the file when writing
        * message : when saving or deleting, commit message to be saved
        to Github
    """
    def save(self, **kwargs):
        """
            Saves to a Github repository

            IMPORTANT: To save to a file Github, we use a PUT request, and
            if the file already exists we must put its sha in the request data.
            The first try-except block actually expects an error if creating a
            new file because we need to check if the file already exists
        """
        # LOGGER.debug(kwargs["content"])
        request_data = {"content": str(b64encode(kwargs["content"]), "ascii"),
                        "message": kwargs["message"]}
        try:
            filedict = github.get("repos/"
                                  + app.config["REPOSITORY_OWNER"]+"/"
                                  + app.config["REPOSITORY_NAME"]
                                  + "/contents/"
                                  + app.config["CATEGORIES_PATH"]
                                  + kwargs["name"])
            request_data["sha"] = filedict["sha"]
        except GitHubError as ghe:
            LOGGER.debug("Github sent an error, either: \
                         1- You're trying to create a new file named : "
                         + kwargs["name"] + " OR \
                         2- You're trying to edit a file named : "
                         + kwargs["name"] + ", \
                         in which case something went wrong \
                         trying to get its sha")
            LOGGER.debug(ghe.response.json())
        # LOGGER.debug(json.dumps(request_data))
        try:
            github.request('PUT',
                           "repos/"
                           + app.config["REPOSITORY_OWNER"]+"/"
                           + app.config["REPOSITORY_NAME"]
                           + "/contents/"
                           + app.config["CATEGORIES_PATH"]
                           + kwargs["name"],
                           data=json.dumps(request_data))
        except GitHubError as ghe:
            LOGGER.debug("Github Error trying to update file: "+kwargs["name"])
            LOGGER.debug("Github sent an error, if 404, either you may not \
                         have access to the repository or it doesn't exist ")
            LOGGER.debug(ghe.response.json())

    def load(self, **kwargs):
        """
            Loads from a Github repository
        """
        try:
            filedict = github.get("repos/"
                                  + app.config["REPOSITORY_OWNER"]+"/"
                                  + app.config["REPOSITORY_NAME"]
                                  + "/contents/"
                                  + app.config["CATEGORIES_PATH"]
                                  + kwargs["name"])
            file_content = str(b64decode(filedict["content"]), "utf-8")
        except GitHubError as ghe:
            LOGGER.debug("Github Error trying to get file: "+kwargs["name"])
            LOGGER.debug("Github sent an error, if 404, either you may not \
                         have access to the repository or it doesn't exist ")
            LOGGER.debug(ghe.response.text)
        return file_content

    def delete(self, **kwargs):
        """
            Deletes from a Github repository
        """
        request_data = {"message": kwargs["message"]}
        try:
            filedict = github.get("repos/"
                                  + app.config["REPOSITORY_OWNER"]+"/"
                                  + app.config["REPOSITORY_NAME"]
                                  + "/contents/"
                                  + app.config["CATEGORIES_PATH"]
                                  + kwargs["name"])
            request_data["sha"] = filedict["sha"]
        except GitHubError as ghe:
            LOGGER.debug("Github Error trying to get sha for \
                         file: "+kwargs["name"])
            LOGGER.debug(ghe.response.text)

        try:
            github.request('DELETE',
                           "repos/catedit-system/"
                           + app.config["REPOSITORY_NAME"]
                           + "/contents/categories/"
                           + kwargs["name"],
                           data=json.dumps(request_data))
        except GitHubError as ghe:
            LOGGER.debug("Github Error trying to delete file: "+kwargs["name"])
            LOGGER.debug(ghe.response.text)

    def list(self, **kwargs):
        """
            Lists all files in the github repository (as set in config.py)
        """
        filenames_list = []
        try:
            files_in_repo = github.get("repos/"
                                       + app.config["REPOSITORY_OWNER"]+"/"
                                       + app.config["REPOSITORY_NAME"]
                                       + "/contents/"
                                       + app.config["CATEGORIES_PATH"])
            filenames_list = [github_file["name"]
                              for github_file in files_in_repo]
            # LOGGER.debug(filenames_list)
        except GitHubError as ghe:
            LOGGER.debug("Github Error trying to get the file list in the \
                         category repository")
            LOGGER.debug("NOTE: Github sent an error, if 404 either you \
                         may not have access to the repository or it doesn't \
                         exist or there isn't any files in it")
            LOGGER.debug(ghe.response.text)

        file_content_list = []
        for filename in filenames_list:
            try:
                filedict = github.get("repos/"
                                      + app.config["REPOSITORY_OWNER"]+"/"
                                      + app.config["REPOSITORY_NAME"]
                                      + "/contents/"
                                      + app.config["CATEGORIES_PATH"]
                                      + filename)
                file_content_list.append(str(b64decode(filedict["content"]),
                                         "utf-8"))
            except GitHubError as ghe:
                LOGGER.debug("Github Error trying to get file: "+filename)
                LOGGER.debug(ghe.response.text)
        LOGGER.debug(file_content_list)
        return file_content_list