<gh_stars>0
import io

from .backends import LocalBackend

__version__ = '0.1.0'

CHUNK_SIZE = 64 * 1024

default_backend = LocalBackend
default_middlewares = []

__all__ = ['Abstractify', 'default_backend', 'open', 'set_default_backend']


def set_default_backend(backend):
    global default_backend
    default_backend = backend

def set_default_middlewares(middlewares):
    global default_middlewares
    default_middlewares = middlewares


class Abstractify(io.IOBase):
    def __init__(self, path, mode='rb', backend=None, middlewares=None, read_callback=None, write_callback=None, **options):
        self.path = path
        self.mode = mode

        backend = backend or default_backend
        if backend.__class__ == type:
            self.backend = backend(**options)
        else:
            self.backend = backend

        # register middlewares
        self.middlewares = middlewares and [middleware(self, **options) for middleware in middlewares] or []
        self.middlewares.extend(middleware(self, **options) for middleware in default_middlewares)

        # register middleware methods
        for middleware in self.middlewares:
            methods = dir(middleware)
            for method_name in methods:
                method = getattr(middleware, method_name)
                if method_name.startswith('_') or method_name in ['read', 'write', 'flush'] or not callable(method):
                    continue

                setattr(self, method_name, method)

        if self.readable and not self.backend.readable:
            raise ValueError('You cannot read with this backend.')
        elif self.writeable and not self.backend.writeable:
            raise ValueError('You cannot write with this backend.')
        elif self.readable and any(not middleware.readable for middleware in self.middlewares):
            raise ValueError('You cannot read with this middleware selection.')
        elif self.writeable and any(not middleware.writeable for middleware in self.middlewares):
            raise ValueError('You cannot write with this middleware selection.')
        elif self.writeable and self.readable and any(not middleware.readwrite for middleware in self.middlewares):
            raise ValueError('You cannot both read and write with this middleware selection.')

        self.read_callback = read_callback
        self.write_callback = write_callback

        self.fp = self.backend.open(path, mode=mode)

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return self.close()

    def __iter__(self):
        return self.iter_chunks()

    def close(self):
        if self.readable:
            self.read()

        if self.writeable:
            if 'b' in self.mode:
                data = b''
            else:
                data = ''

            for middleware in self.middlewares:
                data += middleware.write(data) + middleware.flush()

            self.fp.write(data)

        self.fp.close()

    def iter_chunks(self, chunk_size=CHUNK_SIZE):
        while True:
            data = self.read(chunk_size)
            if not data:
                return

            yield data

    @classmethod
    def open(cls, path, mode='rb', backend=None, **options):
        return cls(path, mode=mode, backend=backend, **options)

    def read(self, size=-1):
        if not self.readable:
            raise ValueError('File not open in read mode.')
        elif any(not middleware.readable for middleware in self.middlewares):
            raise ValueError('You cannot read with this middleware selection.')

        data = self.fp.read(size)
        if not data:
            data = b'' if 'b' in self.mode else ''
            for middleware in self.middlewares:
                data = middleware.read(data) + middleware.flush()
            return data

        for middleware in self.middlewares:
            data = middleware.read(data)

        while size > 0 and len(data) < size:
            add_data = self.read(size - len(data))
            if not add_data:
                break
            data += add_data

        if size == -1:
            data += self.read()

        if self.read_callback:
            return self.read_callback(data)
        else:
            return data

    @property
    def readable(self):
        return any(l in self.mode for l in ['r', 'a', '+'])

    def seek(self, offset, whence=0):
        if not self.backend.seekable:
            raise ValueError('You cannot seek with this backend.')

        return self.fp.seek(offset, whence=whence)

    def tell(self):
        return self.fp.tell()

    @property
    def writeable(self):
        return any(l in self.mode for l in ['w', 'a', '+'])

    def write(self, data):
        if not self.writeable:
            raise ValueError('File not open in write mode.')
        elif any(not middleware.writeable for middleware in self.middlewares):
            raise ValueError('You cannot write with this middleware selection.')

        for middleware in self.middlewares:
            data = middleware.write(data)

        if self.write_callback:
            data = self.write_callback(data)

        return self.fp.write(data)


open = Abstractify.open
import io

from .backends import LocalBackend

__version__ = '0.1.0'

CHUNK_SIZE = 64 * 1024

default_backend = LocalBackend
default_middlewares = []

__all__ = ['Abstractify', 'default_backend', 'open', 'set_default_backend']


def set_default_backend(backend):
    global default_backend
    default_backend = backend

def set_default_middlewares(middlewares):
    global default_middlewares
    default_middlewares = middlewares


class Abstractify(io.IOBase):
    def __init__(self, path, mode='rb', backend=None, middlewares=None, read_callback=None, write_callback=None, **options):
        self.path = path
        self.mode = mode

        backend = backend or default_backend
        if backend.__class__ == type:
            self.backend = backend(**options)
        else:
            self.backend = backend

        # register middlewares
        self.middlewares = middlewares and [middleware(self, **options) for middleware in middlewares] or []
        self.middlewares.extend(middleware(self, **options) for middleware in default_middlewares)

        # register middleware methods
        for middleware in self.middlewares:
            methods = dir(middleware)
            for method_name in methods:
                method = getattr(middleware, method_name)
                if method_name.startswith('_') or method_name in ['read', 'write', 'flush'] or not callable(method):
                    continue

                setattr(self, method_name, method)

        if self.readable and not self.backend.readable:
            raise ValueError('You cannot read with this backend.')
        elif self.writeable and not self.backend.writeable:
            raise ValueError('You cannot write with this backend.')
        elif self.readable and any(not middleware.readable for middleware in self.middlewares):
            raise ValueError('You cannot read with this middleware selection.')
        elif self.writeable and any(not middleware.writeable for middleware in self.middlewares):
            raise ValueError('You cannot write with this middleware selection.')
        elif self.writeable and self.readable and any(not middleware.readwrite for middleware in self.middlewares):
            raise ValueError('You cannot both read and write with this middleware selection.')

        self.read_callback = read_callback
        self.write_callback = write_callback

        self.fp = self.backend.open(path, mode=mode)

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return self.close()

    def __iter__(self):
        return self.iter_chunks()

    def close(self):
        if self.readable:
            self.read()

        if self.writeable:
            if 'b' in self.mode:
                data = b''
            else:
                data = ''

            for middleware in self.middlewares:
                data += middleware.write(data) + middleware.flush()

            self.fp.write(data)

        self.fp.close()

    def iter_chunks(self, chunk_size=CHUNK_SIZE):
        while True:
            data = self.read(chunk_size)
            if not data:
                return

            yield data

    @classmethod
    def open(cls, path, mode='rb', backend=None, **options):
        return cls(path, mode=mode, backend=backend, **options)

    def read(self, size=-1):
        if not self.readable:
            raise ValueError('File not open in read mode.')
        elif any(not middleware.readable for middleware in self.middlewares):
            raise ValueError('You cannot read with this middleware selection.')

        data = self.fp.read(size)
        if not data:
            data = b'' if 'b' in self.mode else ''
            for middleware in self.middlewares:
                data = middleware.read(data) + middleware.flush()
            return data

        for middleware in self.middlewares:
            data = middleware.read(data)

        while size > 0 and len(data) < size:
            add_data = self.read(size - len(data))
            if not add_data:
                break
            data += add_data

        if size == -1:
            data += self.read()

        if self.read_callback:
            return self.read_callback(data)
        else:
            return data

    @property
    def readable(self):
        return any(l in self.mode for l in ['r', 'a', '+'])

    def seek(self, offset, whence=0):
        if not self.backend.seekable:
            raise ValueError('You cannot seek with this backend.')

        return self.fp.seek(offset, whence=whence)

    def tell(self):
        return self.fp.tell()

    @property
    def writeable(self):
        return any(l in self.mode for l in ['w', 'a', '+'])

    def write(self, data):
        if not self.writeable:
            raise ValueError('File not open in write mode.')
        elif any(not middleware.writeable for middleware in self.middlewares):
            raise ValueError('You cannot write with this middleware selection.')

        for middleware in self.middlewares:
            data = middleware.write(data)

        if self.write_callback:
            data = self.write_callback(data)

        return self.fp.write(data)


open = Abstractify.open
# register middlewares # register middleware methods