Source code for tri.token

from collections import Hashable, OrderedDict
import csv
import sys
from io import BytesIO

from tri.declarative import creation_ordered, declarative, with_meta
from tri.struct import FrozenStruct, Struct, merged

if sys.version_info < (3, 0):  # pragma: no mutate
    from StringIO import StringIO
else:
    from io import StringIO  # pragma: no cover


__version__ = '1.1.0'  # pragma: no mutate


class PRESENT(object):
    def __init__(self, attribute_name):
        self.attribute_name = attribute_name


MISSING = object()


[docs]@creation_ordered class TokenAttribute(FrozenStruct): def __init__(self, **kwargs): kwargs.setdefault('description') kwargs.setdefault('default', MISSING) kwargs.setdefault('value') kwargs.setdefault('optional_value') super(TokenAttribute, self).__init__(**kwargs)
[docs]@creation_ordered @declarative(TokenAttribute, add_init_kwargs=False) class Token(FrozenStruct): name = TokenAttribute() @classmethod def attribute_names(cls): return tuple(cls.get_declared().keys()) def __init__(self, *args, **kwargs): for arg in args: if isinstance(arg, PRESENT): kwargs[arg.attribute_name] = PRESENT else: # pragma: no cover assert False, "Unexpected position argument: %s" % (arg, ) # pragma: no mutate token_attributes = self.get_declared() if type(self) is Token: # Make a fake definition if user did not bother to make a proper sub-class token_attributes_from_kwargs = [(name, TokenAttribute()) for name in kwargs] token_attributes = OrderedDict(list(token_attributes.items()) + token_attributes_from_kwargs) new_kwargs = Struct() for name, token_attribute in token_attributes.items(): default = token_attribute.default if token_attribute.default is MISSING: default = None new_kwargs[name] = kwargs.pop(name, default) object.__setattr__(self, '__override__', kwargs.pop('__override__', False)) assert len(kwargs) == 0, "Unexpected constructor arguments: %s" % (kwargs, ) # pragma: no mutate if new_kwargs.name is not None: for name, token_attribute in token_attributes.items(): if token_attribute.value is not None: existing_value = new_kwargs[name] if existing_value is None: new_kwargs[name] = token_attribute.value(**new_kwargs) if token_attribute.optional_value is not None: existing_value = new_kwargs[name] if existing_value is PRESENT or isinstance(existing_value, PRESENT): new_value = token_attribute.optional_value(**new_kwargs) # Only update if we got a value (otherwise retain the PRESENT marker) if new_value is not None: new_kwargs[name] = new_value for name, value in new_kwargs.items(): if not isinstance(value, Hashable): raise ValueError("Attribute {} has unhashable value: {}".format(name, value)) super(Token, self).__init__(**new_kwargs)
[docs] def __repr__(self): return "<%s: %s%s>" % (type(self).__name__, (self.prefix + '.') if getattr(self, 'prefix', None) else '', self.name if self.name else '(unnamed)')
def __unicode__(self): return u"%s%s" % ((self.prefix + '.') if getattr(self, 'prefix', None) else '', self.name if self.name else '(unnamed)') # pragma: no mutate
[docs] def __str__(self): return "%s%s" % ((self.prefix + '.') if getattr(self, 'prefix', None) else '', self.name if self.name else '(unnamed)')
def duplicate(self, **overrides): result = merged(self, overrides) # __setattr__ since FrozenStruct is read-only object.__setattr__(result, '_index', self._index) return result def __copy__(self): return self def __deepcopy__(self, _): return self
@declarative(Token) @with_meta class ContainerBase(object): pass class TokenContainerMeta(ContainerBase.__class__): def __init__(cls, name, bases, dct): super(TokenContainerMeta, cls).__init__(name, bases, dct) prefix = getattr(cls.get_meta(), 'prefix', cls.__name__) all_tokens = [] for token_name, token in cls.get_declared().items(): if ( token_name in cls.__dict__ and any(token_name in base.get_declared() for base in bases) and not token.__override__ ): raise TypeError('Illegal enum value override. Use __override__=True parameter to override.') overrides = Struct() if token.name is None: overrides.name = token_name else: assert token.name == token_name if prefix: assert 'prefix' in token, 'You must define a token attribute called "prefix"' if token.prefix is None: overrides.prefix = prefix new_token = token.duplicate(**overrides) if token != new_token: setattr(cls, token_name, new_token) token = new_token all_tokens.append(token) cls.tokens = OrderedDict((t.name, t) for t in all_tokens) cls.set_declared(cls.tokens) def __iter__(cls): return iter(cls.tokens.values()) def __contains__(cls, item): return item in cls.tokens.values() def __len__(cls): return len(cls.tokens.values()) def __getitem__(cls, key): return cls.tokens[key]
[docs]def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" # This requires a bit of explanation: the basic idea is to make a dummy # metaclass for one level of class instantiation that replaces itself with # the actual metaclass. class metaclass(meta): def __new__(cls, name, this_bases, d): return meta(name, bases, d) return type.__new__(metaclass, 'temporary_class', (), {}) # pragma: no mutate
class TokenContainer(with_metaclass(TokenContainerMeta, ContainerBase)): class Meta: prefix = '' documentation_columns = ['name'] documentation_sort_key = None @classmethod # pragma: no mutate def __iter__(cls): # pragma: no cover # Done in the metaclass, only here as a comfort blanket for PyCharm raise Exception("Not implemented here") # pragma: no mutate @classmethod # pragma: no mutate def __len__(cls): # pragma: no cover # Done in the metaclass, only here as a comfort blanket for PyCharm raise Exception("Not implemented here") # pragma: no mutate @classmethod def get(cls, key, default=None): try: return cls[key] except KeyError: return default @classmethod def in_documentation_order(cls, sort_key=None): tokens = list(cls) if sort_key is None: sort_key = cls.get_meta().documentation_sort_key if sort_key is not None: tokens.sort(key=sort_key) return tokens @classmethod def to_csv(cls, columns=None, sort_key=None): out = StringIO() w = csv.writer(out) if columns is None: columns = cls.get_meta().documentation_columns w.writerow(columns) for token in cls.in_documentation_order(sort_key): w.writerow([(getattr(token, a) or '') for a in columns]) return out.getvalue() @classmethod def to_confluence(cls, columns=None, sort_key=None): out = StringIO() if columns is None: columns = cls.get_meta().documentation_columns out.write('||' + '||'.join(columns) + '||\n') for token in cls.in_documentation_order(sort_key): out.write('|' + '|'.join((getattr(token, a) or ' ') for a in columns) + '|\n') return out.getvalue() @classmethod def to_rst(cls, columns=None, sort_key=None): input = StringIO(cls.to_csv(columns, sort_key)) from prettytable import from_csv table = from_csv(input) lines = table.get_string(hrules=True).splitlines() # Special separator between header and rows in RST lines[2] = lines[2].replace('-', '=') return '\n'.join(lines) @classmethod def to_excel(cls, columns=None, sort_key=None): from xlwt import Workbook wb = Workbook(encoding="utf8") sheet = wb.add_sheet('Attributes') if columns is None: columns = cls.get_meta().documentation_columns for i, heading in enumerate(columns): sheet.write(0, i, heading) for row, field in enumerate(cls.in_documentation_order(sort_key)): for col, heading in enumerate(columns): value = getattr(field, heading) if value: sheet.write(row + 1, col, value) result = BytesIO() wb.save(result) return result.getvalue() def generate_documentation(token_container): # pragma: no cover import argparse parser = argparse.ArgumentParser(description='Generate documentation of fields.') # pragma: no mutate group = parser.add_mutually_exclusive_group(required=True) # pragma: no mutate group.add_argument('-c', '--csv', action='store_true', help='Generate a csv description of all fields') # pragma: no mutate group.add_argument('-w', '--wiki', action='store_true', help='Generate a confluence wiki markup description of all fields') # pragma: no mutate group.add_argument('-r', '--rst', action='store_true', help='Generate a RST table of all fields') # pragma: no mutate group.add_argument('-e', '--excel', action='store_true', help='Generate a excel table of all fields') # pragma: no mutate args = parser.parse_args() if args.csv: print(token_container.to_csv()) if args.wiki: print(token_container.to_confluence()) if args.rst: print(token_container.to_rst()) if args.excel: print(token_container.to_excel())