<reponame>antonbabkin/reseng<gh_stars>0
# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/nbs/resources.ipynb (unless otherwise specified).

__all__ = ['Paths', 'paths', 'Resource', 'Registry', 'pull', 'push', 'registry', 'add', 'get', 'remove']

# Cell
import functools
from pathlib import Path
import shutil
import tempfile
import configparser
import json
import pickle
import datetime
import logging
import sys

# Cell
class Paths():
    """Collection of project paths."""
    def __init__(self, root=None):
        if root is None:
            self.root = self.locate_project_root()
        else:
            self.root = Path(root).absolute()

        config = configparser.ConfigParser()
        config.read(self.root / 'settings.ini')

        self.cache = self.abs(config['resources']['cache_dir'])

        self.remote = Path(config['resources']['remote_dir'])
        assert self.remote.is_absolute()
        assert self.remote.exists() and self.remote.is_dir()

    @staticmethod
    def locate_project_root():
        """Return path to project root directory.
        Root is identified by presence of "settings.ini" file.
        """
        d = Path.cwd()
        while d != d.parent:
            if (d / 'settings.ini').exists():
                return d
            d = d.parent
        raise Exception(f'Can not find project root at or above "{Path.cwd()}"')

    def abs(self, path):
        """Return absolutized `path`."""
        path = Path(path)
        return path if path.is_absolute() else self.root / path

paths = Paths()

# Cell
class Resource:
    """Abstraction of a single file."""
    def __init__(self, key, path, description='', sync=True, public=False):
        """Declare new resource. `key` should be a short name.
        Underlying files do not need to exist.
        `sync` enabled resources must have relative `path`,
        they can be synced between local project directory and remote resource repository.
        """
        self.key = key
        self._raw_path = path
        self.description = description
        self.sync = sync
        self.public = public

        path = Path(path)
        if sync:
            assert not path.is_absolute()
            self.path = paths.abs(path)
            self.remote_path = paths.remote / path
        else:
            if not path.is_absolute():
                path = paths.abs(path)
            self.path = path
            self.remote_path = None

    def __repr__(self):
        return repr(self._to_dict())

    __str__ = __repr__

    def _to_dict(self):
        return dict(key=self.key, path=self._raw_path, description=self.description, sync=self.sync, public=self.public)

    @classmethod
    def _from_dict(cls, d):
        return cls(**d)

    def push(self):
        """Copy sync-enabled resource from local to remote."""
        if not self.sync:
            print(f'Can not push sync-disabled resource "{self.key}"')
            return
        self.remote_path.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(self.path, self.remote_path)
        print(f'Pushed "{self.key}": {self.path} -> {self.remote_path}')

    def pull(self):
        """Copy sync-enabled resource from remote to local."""
        if not self.sync:
            print(f'Can not pull sync-disabled resource "{self.key}"')
            return
        self.path.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(self.remote_path, self.path)
        print(f'Pulled "{self.key}": {self.remote_path} -> {self.path}')

    def clear(self):
        """Remove local copy of sync-enabled resource."""
        if not self.sync:
            print(f'Can not clear sync-disabled resource "{self.key}"')
            return
        self.path.unlink()
        print(f'Removed local "{self.key}": {self.path}')

    def clear_remote(self):
        """Remove remote copy of sync-enabled resource."""
        if not self.sync:
            print(f'Can not clear sync-disabled resource "{self.key}"')
            return
        self.remote_path.unlink()
        print(f'Removed remote "{self.key}": {self.path}')

    def publish(self):
        """Send resource to public-facing repository."""
        pass

# Cell
class Registry:
    """Registry of resources, synced with file on disk."""
    def __init__(self, registry_file='resources.json'):
        self.file = paths.abs(registry_file)

        self.resources = dict()
        if self.file.exists():
            res_dicts = json.load(self.file.open())
            for res_dict in res_dicts.values():
                res = Resource._from_dict(res_dict)
                self.resources[res.key] = res
        else:
            self._dump()

    def _dump(self):
        """Save registry to disk.
        Records are ordered by key."""
        # Dicts iterate in the order of key insertions.
        # Assuming that json.dump() maintains that ordering.
        res_dicts = dict()
        for key in sorted(self.resources.keys()):
            res_dicts[key] = self.resources[key]._to_dict()
        json.dump(res_dicts, self.file.open('w'), indent=2)

    def add(self, res, overwrite=False):
        """Add new resource to registry."""
        if not overwrite and res.key in self.resources:
            print(f'Resource "{res.key}" already in the registry, did not overwrite.')
            return
        if res.key in self.resources:
            old_res = self.resources[res.key]
            print(f'Overwriting existing resource: {old_res}')
        self.resources[res.key] = res
        self._dump()
        print(f'Resource "{res.key}" added to registry.')

    def remove(self, key):
        """Remove resource with exact `key`."""
        if key not in self.resources:
            print(f'Resource "{key}" not found.')
            return
        del self.resources[key]
        self._dump()
        print(f'Resource "{key}" removed from registry.')

    def get(self, key_pattern='*'):
        """Return list of resources with key matching glob-like `key_pattern`.
        If match is unique, return single resource object.
        List is sorted by key.
        """
        matches = [r for k, r in self.resources.items()
                   if Path(k).match(key_pattern)]
        matches.sort(key=lambda res: res.key)
        return matches[0] if len(matches) == 1 else matches


# Cell
registry = Registry()

add = registry.add
get = registry.get
remove = registry.remove

def pull(key_pattern='*'):
    """Get resources matching `key_pattern` from remote repository."""
    for r in registry.get(key_pattern):
        if r.sync:
            r.pull()

def push(key_pattern='*'):
    """Send resources matching `key_pattern` to remote repository."""
    for r in registry.get(key_pattern):
        if r.sync:
            r.push()
# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/nbs/resources.ipynb (unless otherwise specified).

__all__ = ['Paths', 'paths', 'Resource', 'Registry', 'pull', 'push', 'registry', 'add', 'get', 'remove']

# Cell
import functools
from pathlib import Path
import shutil
import tempfile
import configparser
import json
import pickle
import datetime
import logging
import sys

# Cell
class Paths():
    """Collection of project paths."""
    def __init__(self, root=None):
        if root is None:
            self.root = self.locate_project_root()
        else:
            self.root = Path(root).absolute()

        config = configparser.ConfigParser()
        config.read(self.root / 'settings.ini')

        self.cache = self.abs(config['resources']['cache_dir'])

        self.remote = Path(config['resources']['remote_dir'])
        assert self.remote.is_absolute()
        assert self.remote.exists() and self.remote.is_dir()

    @staticmethod
    def locate_project_root():
        """Return path to project root directory.
        Root is identified by presence of "settings.ini" file.
        """
        d = Path.cwd()
        while d != d.parent:
            if (d / 'settings.ini').exists():
                return d
            d = d.parent
        raise Exception(f'Can not find project root at or above "{Path.cwd()}"')

    def abs(self, path):
        """Return absolutized `path`."""
        path = Path(path)
        return path if path.is_absolute() else self.root / path

paths = Paths()

# Cell
class Resource:
    """Abstraction of a single file."""
    def __init__(self, key, path, description='', sync=True, public=False):
        """Declare new resource. `key` should be a short name.
        Underlying files do not need to exist.
        `sync` enabled resources must have relative `path`,
        they can be synced between local project directory and remote resource repository.
        """
        self.key = key
        self._raw_path = path
        self.description = description
        self.sync = sync
        self.public = public

        path = Path(path)
        if sync:
            assert not path.is_absolute()
            self.path = paths.abs(path)
            self.remote_path = paths.remote / path
        else:
            if not path.is_absolute():
                path = paths.abs(path)
            self.path = path
            self.remote_path = None

    def __repr__(self):
        return repr(self._to_dict())

    __str__ = __repr__

    def _to_dict(self):
        return dict(key=self.key, path=self._raw_path, description=self.description, sync=self.sync, public=self.public)

    @classmethod
    def _from_dict(cls, d):
        return cls(**d)

    def push(self):
        """Copy sync-enabled resource from local to remote."""
        if not self.sync:
            print(f'Can not push sync-disabled resource "{self.key}"')
            return
        self.remote_path.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(self.path, self.remote_path)
        print(f'Pushed "{self.key}": {self.path} -> {self.remote_path}')

    def pull(self):
        """Copy sync-enabled resource from remote to local."""
        if not self.sync:
            print(f'Can not pull sync-disabled resource "{self.key}"')
            return
        self.path.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(self.remote_path, self.path)
        print(f'Pulled "{self.key}": {self.remote_path} -> {self.path}')

    def clear(self):
        """Remove local copy of sync-enabled resource."""
        if not self.sync:
            print(f'Can not clear sync-disabled resource "{self.key}"')
            return
        self.path.unlink()
        print(f'Removed local "{self.key}": {self.path}')

    def clear_remote(self):
        """Remove remote copy of sync-enabled resource."""
        if not self.sync:
            print(f'Can not clear sync-disabled resource "{self.key}"')
            return
        self.remote_path.unlink()
        print(f'Removed remote "{self.key}": {self.path}')

    def publish(self):
        """Send resource to public-facing repository."""
        pass

# Cell
class Registry:
    """Registry of resources, synced with file on disk."""
    def __init__(self, registry_file='resources.json'):
        self.file = paths.abs(registry_file)

        self.resources = dict()
        if self.file.exists():
            res_dicts = json.load(self.file.open())
            for res_dict in res_dicts.values():
                res = Resource._from_dict(res_dict)
                self.resources[res.key] = res
        else:
            self._dump()

    def _dump(self):
        """Save registry to disk.
        Records are ordered by key."""
        # Dicts iterate in the order of key insertions.
        # Assuming that json.dump() maintains that ordering.
        res_dicts = dict()
        for key in sorted(self.resources.keys()):
            res_dicts[key] = self.resources[key]._to_dict()
        json.dump(res_dicts, self.file.open('w'), indent=2)

    def add(self, res, overwrite=False):
        """Add new resource to registry."""
        if not overwrite and res.key in self.resources:
            print(f'Resource "{res.key}" already in the registry, did not overwrite.')
            return
        if res.key in self.resources:
            old_res = self.resources[res.key]
            print(f'Overwriting existing resource: {old_res}')
        self.resources[res.key] = res
        self._dump()
        print(f'Resource "{res.key}" added to registry.')

    def remove(self, key):
        """Remove resource with exact `key`."""
        if key not in self.resources:
            print(f'Resource "{key}" not found.')
            return
        del self.resources[key]
        self._dump()
        print(f'Resource "{key}" removed from registry.')

    def get(self, key_pattern='*'):
        """Return list of resources with key matching glob-like `key_pattern`.
        If match is unique, return single resource object.
        List is sorted by key.
        """
        matches = [r for k, r in self.resources.items()
                   if Path(k).match(key_pattern)]
        matches.sort(key=lambda res: res.key)
        return matches[0] if len(matches) == 1 else matches


# Cell
registry = Registry()

add = registry.add
get = registry.get
remove = registry.remove

def pull(key_pattern='*'):
    """Get resources matching `key_pattern` from remote repository."""
    for r in registry.get(key_pattern):
        if r.sync:
            r.pull()

def push(key_pattern='*'):
    """Send resources matching `key_pattern` to remote repository."""
    for r in registry.get(key_pattern):
        if r.sync:
            r.push()
# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/nbs/resources.ipynb (unless otherwise specified). # Cell # Cell Collection of project paths. Return path to project root directory.         Root is identified by presence of "settings.ini" file. Return absolutized `path`. # Cell Abstraction of a single file. Declare new resource. `key` should be a short name.         Underlying files do not need to exist.         `sync` enabled resources must have relative `path`,         they can be synced between local project directory and remote resource repository. Copy sync-enabled resource from local to remote. Copy sync-enabled resource from remote to local. Remove local copy of sync-enabled resource. Remove remote copy of sync-enabled resource. Send resource to public-facing repository. # Cell Registry of resources, synced with file on disk. Save registry to disk.         Records are ordered by key. # Dicts iterate in the order of key insertions. # Assuming that json.dump() maintains that ordering. Add new resource to registry. Remove resource with exact `key`. Return list of resources with key matching glob-like `key_pattern`.         If match is unique, return single resource object.         List is sorted by key. # Cell Get resources matching `key_pattern` from remote repository. Send resources matching `key_pattern` to remote repository.