Source code for fptokens

import os
import re
import attr

from copy import deepcopy
from itertools import product

from path import Path

__version__ = '0.1.1'
__all__ = []


[docs]class TokenError(ValueError): """ Exception indicating an error related to Tokens, inherits from :class:`ValueError`. """ def __init__(self, message): super(TokenError, self).__init__(message)
@attr.s(cmp=False, hash=False, repr=False)
[docs]class Token(object): """ Token object for use in :class:`~fptokens.Filename`. :param name: Token name :type name: str :param escape: Escape character, default: ``$`` :type escape: str """ _name = attr.ib() escape = attr.ib(default='$') def __attrs_post_init__(self): self._name = self.__convert_token(self._name) @property def name(self): """ Return the token name. :return: Token name :rtype: str """ return self._name @name.setter def name(self, value): """ Given a ``value``, strip the value of the escape character and set the token name. :param value: New token name :type value: str """ self._name = self.__convert_token(value) @property def token(self): """ Return the full token including the escape characters either side. :return: Token :rtype: str """ return '{0}{1}{0}'.format(self.escape, self.name) def __convert_token(self, token): pattern = r'\{esc}(\w+)\{esc}'.format(esc=self.escape) match = re.match(pattern, token) if not match: raise TokenError('Invalid token') return match.group(1) def __key(self): return (self.name, self.escape) def __eq__(self, other): if isinstance(other, self.__class__): return self.__key() == other.__key() return False def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash(self.__key()) def __repr__(self): return '<Token: {0}>'.format(self.token)
@attr.s(cmp=False, hash=False, repr=False)
[docs]class Filename(object): """ Filenames with support for tokens. :param root: Root location :type root: str :param folders: Folder names, attribute supports tokens :type folders: list of str :param base: Basename of the file, attribute supports tokens :type base: list of str :param separator: Separator for basename elements :type separator: str :param extension: Filename extension :type extension: str :param escape: Escape character for tokens, default: ``$`` :type escape: str """ _root = attr.ib() folders = attr.ib(default=[], validator=attr.validators.instance_of(list)) base = attr.ib(default=[], validator=attr.validators.instance_of(list)) separator = attr.ib(default='_', validator=attr.validators.instance_of(str)) extension = attr.ib(default='jpg', validator=attr.validators.instance_of(str)) escape = attr.ib(default='$') def __attrs_post_init__(self): self._root = Path(self._root) @property def root(self): """ Return the filename's root location. :return: Root location :rtype: :class:`~path.Path` """ return self._root @root.setter def root(self, value): """ Given a ``value``, convert the value to a :class:`~path.Path` and set the filename's root location. :param value: New root location :type value: str """ self._root = Path(value) @property def dirname(self): """ Return the filename's location. :return: Dirname :rtype: str """ return self.root / os.path.sep.join( [str(folder) for folder in self.folders]) @property def basename(self): """ Return the filename's basename. :return: Basename :rtype: str """ return '{0}.{1}'.format(self.separator.join( [str(base) for base in self.base]), self.extension) @property def tokens(self): """ Return a list of all tokens in ``folders`` and ``base``. :return: List of tokens :rtype: list """ folders = set([token for token in self.folders if isinstance(token, Token)]) basename = set([token for token in self.base if isinstance(token, Token)]) return [token for token in folders.union(basename)] @property def abspath(self): """ Return the filename's full absolute path. :return: Absolute path :rtype: :class:`~path.Path` """ return (self.dirname / self.basename).abspath()
[docs] def make(self): """Create the filename's location if it does not exist.""" if any([token for token in self.folders if isinstance(token, Token)]): raise TokenError('Replace tokens with string values ' 'to create folders') self.dirname.makedirs_p()
[docs] def parse(self): """ Parse the filename's ``folders`` and ``base`` attributes, detect components that match the token pattern and replace these with :class:`~fptokens.Token` objects. """ for base_components in (self.folders, self.base): for component_idx, component in enumerate(base_components): if isinstance(component, Token): continue pattern = r'(\{esc}\w+\{esc})'.format(esc=self.escape) tokens = re.findall(pattern, component) if tokens: if len(tokens) > 1: raise TokenError('Limit: one token per component.') token = tokens[0] base_components[component_idx] = Token(name=token, escape=self.escape)
def _replace_tokens(self, permutation, permutation_fp): """ Given a ``permutation`` (token_name, token_value) and a ``permutation_fp``, replace all matching tokens with permutation values and return the resulting :class:`~fptokens.Filename`. :param permutation: Permutation (token_name, token_value) :type permutation: tuple :permutation_fp: :class:`~fptokens.Filename`, tokenised :type permutation_fp: :class:`~fptokens.Filename` :return: :class:`~fptokens.Filename` with replaced tokens :rtype: :class:`~fptokens.Filename` """ for components in (permutation_fp.folders, permutation_fp.base): for comp_idx, comp in enumerate(components): if isinstance(comp, Token): for token_name, token_value in permutation: if comp.name == token_name: components[comp_idx] = token_value return permutation_fp
[docs] def resolve(self, **kwargs): """ Given a set of \**kwargs, yield all possible permutations for the data set provided. Raise :class:`~fptokens.TokenError` if :class:`~fptokens.Filename` does not haven tokens or the data provided does not match the tokens. :params \**kwargs: Permutation data """ if not self.tokens: raise TokenError('Tokens required for resolve()') tokens = self.tokens[:] while tokens: token = tokens.pop() if token.name not in kwargs: raise TokenError('Data missing for token {0}'.format(token)) perm_data = [] for token_name, token_data in kwargs.iteritems(): perm_data.append([(token_name, data) for data in token_data]) for permutation in product(*perm_data): yield self._replace_tokens(permutation, deepcopy(self))
def __key(self): return (self.root, self.folders, self.basename, self.separator, self.extension) def __eq__(self, other): if isinstance(other, self.__class__): return self.__key() == other.__key() return False def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash(self.__key()) def __repr__(self): return '<Filename: {0}>'.format(self.abspath)