diff --git a/docs/conf.py b/docs/conf.py index 6b7df99d..ab983912 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,7 +44,12 @@ todo_add_to_theme_to_keep_menus_expanded = """ - + """ @@ -111,7 +116,7 @@ if on_rtd: html_theme = 'default' -else: # only import and set the theme if we're building docs locally +else: # only import and set the theme if we're building docs locally import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = ['_themes', sphinx_rtd_theme.get_html_theme_path()] diff --git a/glom/cli.py b/glom/cli.py index 46ea63e0..ff76eafb 100644 --- a/glom/cli.py +++ b/glom/cli.py @@ -35,13 +35,7 @@ import sys import json -from face import (Command, - Flag, - face_middleware, - PosArgSpec, - PosArgDisplay, - CommandLineError, - UsageError) +from face import Command, face_middleware, PosArgSpec, UsageError from face.utils import isatty import glom @@ -49,6 +43,7 @@ PY3 = (sys.version_info[0] == 3) + def glom_cli(target, spec, indent, debug, inspect): """Command-line interface to the glom library, providing nested data access and data restructuring with the power of Python. @@ -104,7 +99,8 @@ def console_main(): sys.exit(main(sys.argv) or 0) except Exception: if _enable_debug: - import pdb;pdb.post_mortem() + import pdb + pdb.post_mortem() raise @@ -135,14 +131,12 @@ def mw_handle_target(target_text, target_format): else: raise UsageError('expected target-format to be one of python, json, or yaml') - try: target = load_func(target_text) except Exception as e: raise UsageError('could not load target data, got: %s: %s' % (e.__class__.__name__, e)) - return target diff --git a/glom/core.py b/glom/core.py index a903b912..37435de3 100644 --- a/glom/core.py +++ b/glom/core.py @@ -25,16 +25,15 @@ import copy import weakref import operator -from abc import ABCMeta from pprint import pprint +from abc import ABCMeta import string from collections import OrderedDict import traceback from face.helpers import get_wrap_width from boltons.typeutils import make_sentinel -from boltons.iterutils import is_iterable -#from boltons.funcutils import format_invocation + PY2 = (sys.version_info[0] == 2) if PY2: @@ -55,7 +54,7 @@ _type_type = type _MISSING = make_sentinel('_MISSING') -SKIP = make_sentinel('SKIP') +SKIP = make_sentinel('SKIP') SKIP.__doc__ = """ The ``SKIP`` singleton can be returned from a function or included via a :class:`~glom.Val` to cancel assignment into the output @@ -105,7 +104,7 @@ in case of exceptions """ -MODE = make_sentinel('MODE') +MODE = make_sentinel('MODE') CHILD_ERRORS = make_sentinel('CHILD_ERRORS') CHILD_ERRORS.__doc__ = """ @@ -121,6 +120,7 @@ _PKG_DIR_PATH = os.path.dirname(os.path.abspath(__file__)) + class GlomError(Exception): """The base exception for all the errors that might be raised from :func:`glom` processing logic. @@ -231,6 +231,7 @@ def format_target_spec_trace(scope, root_error, width=TRACE_WIDTH, depth=0, prev segments = [] indent = " " + "|" * depth tick = "| " if depth else "- " + def mk_fmt(label, t=None): pre = indent + (t or tick) + label + ": " fmt_width = width - len(pre) @@ -1588,7 +1589,6 @@ def _format_t(path, root=T): class Val(object): """Val objects are specs which evaluate to the wrapped *value*. - >>> target = {'a': {'b': 'c'}} >>> spec = {'a': 'a.b', 'readability': Val('counts')} >>> pprint(glom(target, spec)) @@ -1717,6 +1717,7 @@ def __repr__(self): class _AbstractIterable(_AbstractIterableBase): __metaclass__ = ABCMeta + @classmethod def __subclasshook__(cls, C): if C in (str, bytes): @@ -2109,12 +2110,12 @@ def chain_child(scope): return nxt_in_chain -unbound_methods = set([type(str.__len__)]) #, type(Ref.glomit)]) +unbound_methods = set([type(str.__len__)]) # , type(Ref.glomit)]) def _has_callable_glomit(obj): glomit = getattr(obj, 'glomit', None) - return callable(glomit) and not isinstance(obj, type) + return callable(glomit) and not isinstance(obj, type) def _glom(target, spec, scope): diff --git a/glom/grouping.py b/glom/grouping.py index cae8fca9..8f9ae39e 100644 --- a/glom/grouping.py +++ b/glom/grouping.py @@ -274,7 +274,6 @@ def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.size) - class Limit(object): """ Limits the number of values passed to sub-accumulator diff --git a/glom/matching.py b/glom/matching.py index 9446fce9..887be00e 100644 --- a/glom/matching.py +++ b/glom/matching.py @@ -9,7 +9,6 @@ import re import sys -from pprint import pprint from boltons.iterutils import is_iterable from boltons.typeutils import make_sentinel @@ -195,12 +194,18 @@ def __repr__(self): sorted(e and e.__name__ or "None" for e in _RE_VALID_FUNCS)))) _RE_TYPES = () -try: re.match(u"", u"") -except Exception: pass # pragma: no cover -else: _RE_TYPES += (type(u""),) -try: re.match(b"", b"") -except Exception: pass # pragma: no cover -else: _RE_TYPES += (type(b""),) +try: + re.match(u"", u"") +except Exception: + pass # pragma: no cover +else: + _RE_TYPES += (type(u""),) +try: + re.match(b"", b"") +except Exception: + pass # pragma: no cover +else: + _RE_TYPES += (type(b""),) class Regex(object): @@ -251,7 +256,7 @@ def __repr__(self): return self.__class__.__name__ + args -#TODO: combine this with other functionality elsewhere? +# TODO: combine this with other functionality elsewhere? def _bool_child_repr(child): if child is M: return repr(child) @@ -567,7 +572,6 @@ def glomit(self, target, spec): M = _MType() - class Optional(object): """Used as a :class:`dict` key in a :class:`~glom.Match()` spec, marks that a value match key which would otherwise be required is @@ -843,17 +847,16 @@ def __init__(self, cases, default=_MISSING): % (self.__class__.__name__, self.cases)) return - def glomit(self, target, scope): for keyspec, valspec in self.cases: try: scope[glom](target, keyspec, scope) - except GlomError as ge: + except GlomError: continue return scope[glom](target, valspec, chain_child(scope)) if self.default is not _MISSING: return self.default - raise MatchError("no matches for target in %s" % self.__class__.__name__) + raise MatchError("no matches for target in %s" % self.__class__.__name__) def __repr__(self): return '%s(%s)' % (self.__class__.__name__, bbrepr(self.cases)) diff --git a/glom/mutation.py b/glom/mutation.py index 4a113c6b..65b7add1 100644 --- a/glom/mutation.py +++ b/glom/mutation.py @@ -10,7 +10,7 @@ import operator from pprint import pprint -from .core import Path, T, S, Spec, glom, UnregisteredTarget, GlomError, PathAccessError, UP +from .core import Path, S, Spec, glom, PathAccessError, UP, T from .core import TType, register_op, TargetRegistry, bbrepr, PathAssignError try: @@ -46,7 +46,6 @@ def get_message(self): % (self.dest_name, self.path, self.exc)) - class Assign(object): """*New in glom 18.3.0* @@ -288,7 +287,7 @@ def glomit(self, target, scope): dest_path = self.path try: dest = scope[glom](dest_target, dest_path, scope) - except PathAccessError as pae: + except PathAccessError: if not self.ignore_missing: raise else: diff --git a/glom/reduction.py b/glom/reduction.py index 7be12439..1290e064 100644 --- a/glom/reduction.py +++ b/glom/reduction.py @@ -1,7 +1,6 @@ - +from pprint import pprint import operator import itertools -from pprint import pprint from boltons.typeutils import make_sentinel @@ -310,7 +309,6 @@ def _fold(self, iterator): return ret - def _agg(self, target, tree): if self not in tree: acc = tree[self] = self.init() diff --git a/glom/streaming.py b/glom/streaming.py index 9b83abf7..83d1107a 100644 --- a/glom/streaming.py +++ b/glom/streaming.py @@ -24,6 +24,7 @@ from .core import glom, T, STOP, SKIP, _MISSING, Path, TargetRegistry, Call, Spec, S, bbrepr, format_invocation from .matching import Check + class Iter(object): """``Iter()`` is glom's counterpart to Python's built-in :func:`iter()` function. Given an iterable target, ``Iter()`` yields the result @@ -257,7 +258,6 @@ def unique(self, key=T): (key,), lambda it, scope: unique_iter(it, key=lambda t: scope[glom](t, key, scope))) - def slice(self, *args): """Returns a new :class:`Iter()` spec which trims iterables in the same manner as :func:`itertools.islice`. diff --git a/glom/test/test_basic.py b/glom/test/test_basic.py index 2c561c5f..d0aa6449 100644 --- a/glom/test/test_basic.py +++ b/glom/test/test_basic.py @@ -4,14 +4,13 @@ import pytest -from glom import glom, SKIP, STOP, Path, Inspect, Coalesce, CoalesceError, Val, Call, T, S, Invoke, Spec, Ref -from glom import Auto, Fill, Iter, A, Vars, Val, Literal, GlomError, Pipe +from glom import glom, SKIP, STOP, Inspect, Coalesce, Val, Call, T, S, Invoke, Spec, Ref +from glom import Fill, Iter, Literal, Pipe import glom.core as glom_core -from glom.core import UP, ROOT, bbformat, bbrepr -from glom.mutation import PathAssignError +from glom.core import UP, bbformat, bbrepr -from glom import OMIT, Let, Literal # backwards compat +from glom import OMIT # backwards compat def test_initial_integration(): @@ -101,7 +100,6 @@ def test_coalesce(): Coalesce(bad_kwarg=True) - def test_skip(): assert OMIT is SKIP # backwards compat @@ -197,6 +195,7 @@ def __init__(s, a, b, c): s.a, s.b, s.c = a, b, c assert repr(call_f) val = glom(1, call_f) assert (val.a, val.b, val.c) == (1, 1, 1) + class F(object): def __init__(s, a): s.a = a val = glom({'one': F('two')}, Call(F, args=(T['one'].a,))) @@ -219,6 +218,7 @@ def __init__(s, a): s.a = a def test_invoke(): args = [] + def test(*a, **kw): args.append(a) args.append(kw) @@ -231,9 +231,9 @@ def test(*a, **kw): 'kwargs': {'a': 'a'}, 'c': 'C', } - spec = Invoke(test).star(args='args' - ).constants(3, b='b').specs(c='c' - ).star(args='args2', kwargs='kwargs') + spec = (Invoke(test).star(args='args') + .constants(3, b='b').specs(c='c') + .star(args='args2', kwargs='kwargs')) repr(spec) # no exceptions assert repr(Invoke(len).specs(T)) == 'Invoke(len).specs(T)' assert (repr(Invoke.specfunc(next).constants(len).constants(1)) @@ -245,9 +245,9 @@ def test(*a, **kw): args = [] assert glom(test, Invoke.specfunc(T)) == 'test' assert args == [(), {}] - repr_spec = Invoke.specfunc(T).star(args='args' - ).constants(3, b='b').specs(c='c' - ).star(args='args2', kwargs='kwargs') + repr_spec = (Invoke.specfunc(T).star(args='args') + .constants(3, b='b').specs(c='c') + .star(args='args2', kwargs='kwargs')) assert repr(eval(repr(repr_spec), locals(), globals())) == repr(repr_spec) with pytest.raises(TypeError, match='expected func to be a callable or Spec instance'): @@ -281,7 +281,6 @@ def ret_args(*a, **kw): assert glom(target, spec) == 'hi' - def test_spec_and_recursion(): assert repr(Spec('a.b.c')) == "Spec('a.b.c')" @@ -350,7 +349,6 @@ def test_python_native(): target = {'system': {'planets': [{'name': 'earth', 'moons': 1}, {'name': 'jupiter', 'moons': 69}]}} - output = glom(target, {'moon_count': ('system.planets', ['moons'], sum)}) assert output == {'moon_count': 70} @@ -409,7 +407,7 @@ def test_ref(): assert repr(Ref('item', (T[1], Ref('item')))) == "Ref('item', (T[1], Ref('item')))" etree2dicts = Ref('ElementTree', - {"tag": "tag", "text": "text", "attrib": "attrib", "children": (iter, [Ref('ElementTree')])}) + {"tag": "tag", "text": "text", "attrib": "attrib", "children": (iter, [Ref('ElementTree')])}) etree2tuples = Fill(Ref('ElementTree', (T.tag, Iter(Ref('ElementTree')).all()))) etree = ElementTree.fromstring(''' @@ -430,6 +428,8 @@ def test_pipe(): _IS_PYPY = '__pypy__' in sys.builtin_module_names + + @pytest.mark.skipif(_IS_PYPY, reason='pypy othertype.__repr__ is never object.__repr__') def test_api_repr(): import glom diff --git a/glom/test/test_check.py b/glom/test/test_check.py index f498442f..d56a8012 100644 --- a/glom/test/test_check.py +++ b/glom/test/test_check.py @@ -42,18 +42,20 @@ def test_check_basic(): assert glom(target, [Check(validate=(int, float))]) assert glom(target, [Check()]) # bare check does a truthy check - failing_checks = [({'a': {'b': 1}}, {'a': ('a', 'b', Check(type=str))}, - '''target at path ['a', 'b'] failed check, got error: "expected type to be 'str', found type 'int'"'''), - ({'a': {'b': 1}}, {'a': ('a', Check('b', type=str))}, - '''target at path ['a'] failed check, subtarget at 'b' got error: "expected type to be 'str', found type 'int'"'''), - (1, Check(type=(unicode, bool))), - (1, Check(instance_of=unicode)), - (1, Check(instance_of=(unicode, bool))), - (1, Check(equal_to=0)), - (1, Check(one_of=(0,))), - (1, Check(one_of=(0, 2))), - ('-3.14', Check(validate=int)), - ('', Check(validate=lambda x: False)),] + failing_checks = [ + ({'a': {'b': 1}}, {'a': ('a', 'b', Check(type=str))}, + '''target at path ['a', 'b'] failed check, got error: "expected type to be 'str', found type 'int'"'''), + ({'a': {'b': 1}}, {'a': ('a', Check('b', type=str))}, + '''target at path ['a'] failed check, subtarget at 'b' got ''' + '''error: "expected type to be 'str', found type 'int'"'''), + (1, Check(type=(unicode, bool))), + (1, Check(instance_of=unicode)), + (1, Check(instance_of=(unicode, bool))), + (1, Check(equal_to=0)), + (1, Check(one_of=(0,))), + (1, Check(one_of=(0, 2))), + ('-3.14', Check(validate=int)), + ('', Check(validate=lambda x: False))] for fc in failing_checks: if len(fc) == 2: diff --git a/glom/test/test_error.py b/glom/test/test_error.py index 4c51b5e2..a101ed3f 100644 --- a/glom/test/test_error.py +++ b/glom/test/test_error.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import os import re -import sys import traceback import pytest diff --git a/glom/test/test_match.py b/glom/test/test_match.py index 31767776..ef659a71 100644 --- a/glom/test/test_match.py +++ b/glom/test/test_match.py @@ -8,7 +8,7 @@ from glom.matching import ( Match, M, MatchError, TypeMatchError, And, Or, Not, Optional, Required, Regex) -from glom.core import Auto, SKIP, Ref +from glom.core import Auto, SKIP try: unicode @@ -16,12 +16,12 @@ unicode = str - def _chk(spec, good_target, bad_target): glom(good_target, spec) with pytest.raises(MatchError): glom(bad_target, spec) + def test_basic(): _chk(Match(1), 1, 2) _chk(Match(int), 1, 1.0) @@ -41,7 +41,7 @@ def test_basic(): glom(1.0, M >= 1) glom(1.0, M < 2) glom(1.0, M <= 1) - glom(1.0, M != None) + glom(1.0, M != None) # noqa: E711 glom(1.0, (M > 0) & float) glom(1.0, (M > 100) | float) @@ -66,7 +66,6 @@ def test_basic(): with pytest.raises(TypeError): Or('a', bad_kwarg=True) - _chk(Match(Or("a", "b")), "a", "c") glom({None: 1}, Match({object: object})) _chk(Match((int, str)), (1, "cat"), (1, 2)) @@ -165,12 +164,11 @@ def test_and_or_reduction(): def test_precedence(): """test corner cases of dict key precedence""" glom({(0, 1): 3}, - Match({ - (0, 1): Val(1), # this should match - (0, int): Val(2), # optional - (0, M == 1): Val(3), # optional - }) - ) + Match({ + (0, 1): Val(1), # this should match + (0, int): Val(2), # optional + (0, M == 1): Val(3), # optional + })) with pytest.raises(ValueError): Optional(int) # int is already optional so not valid to wrap @@ -273,10 +271,11 @@ def test_sample(): 'name': str, Optional('date_added'): datetime.datetime, 'desc': str, - 'tags': [str,]}) + 'tags': [str]}) def good(): glom(data, spec) + def bad(): with pytest.raises(MatchError): glom(data, spec) @@ -337,7 +336,7 @@ def none_or(sub_schema): 'allow None or sub_schema' return Match(Or(None, sub_schema)) - assert glom(None, none_or('abc')) == None + assert glom(None, none_or('abc')) == None # noqa: E711 assert glom('abc', none_or('abc')) == 'abc' with pytest.raises(MatchError): glom(123, none_or('abc')) @@ -353,7 +352,7 @@ def in_range(sub_schema, _min, _max): def default_if_none(sub_schema, default_factory): return Or( - And(M == None, Auto(lambda t: default_factory())), sub_schema) + And(M == None, Auto(lambda t: default_factory())), sub_schema) # noqa: E711 assert glom(1, default_if_none(T, list)) == 1 assert glom(None, default_if_none(T, list)) == [] @@ -387,14 +386,15 @@ def test_nested_struct(): """adapted from use case""" import json - _json = lambda spec: Auto((json.loads, _str_json, Match(spec))) + def _json(spec): + return Auto((json.loads, _str_json, Match(spec))) _str_json = Ref('json', - Match(Or( - And(dict, {Ref('json'): Ref('json')}), - And(list, [Ref('json')]), - And(type(u''), Auto(str)), - object))) + Match(Or( + And(dict, {Ref('json'): Ref('json')}), + And(list, [Ref('json')]), + And(type(u''), Auto(str)), + object))) rule_spec = Match({ 'rule_id': Or('', Regex(r'\d+')), @@ -454,9 +454,11 @@ def test_check_ported_tests(): assert glom(target, M(T)) == ['1'] failing_checks = [({'a': {'b': 1}}, {'a': ('a', 'b', Match(str))}, - '''expected type str, not int'''), # TODO: bbrepr at least, maybe include path like Check did + # TODO: bbrepr at least, maybe include path like Check did + '''expected type str, not int'''), ({'a': {'b': 1}}, {'a': ('a', Match({'b': str}))}, - '''expected type str, not int'''), # TODO: include subspec path ('b') + # TODO: include subspec path ('b') + '''expected type str, not int'''), (1, Match(Or(unicode, bool))), (1, Match(unicode)), (1, Match(0)), @@ -464,7 +466,7 @@ def test_check_ported_tests(): ('-3.14', Match(lambda x: int(x) > 0)), # ('-3.14', M(lambda x: int(x) > 0)), # TODO: M doesn't behave quite like Match because it's mode-free - ] + ] for fc in failing_checks: if len(fc) == 2: @@ -495,11 +497,11 @@ def test_switch(): assert glom(None, Switch(cases, default=4)) == 4 assert glom(None, Switch({'z': 26}, default=4)) == 4 with pytest.raises(MatchError): - glom(None, Switch(cases)) + glom(None, Switch(cases)) with pytest.raises(ValueError): - Switch({}) + Switch({}) with pytest.raises(TypeError): - Switch("wrong type") + Switch("wrong type") assert glom(None, Switch({S(a=lambda t: 1): S['a']})) == 1 repr(Switch(cases)) diff --git a/glom/test/test_mutation.py b/glom/test/test_mutation.py index 9631613d..3912a221 100644 --- a/glom/test/test_mutation.py +++ b/glom/test/test_mutation.py @@ -16,6 +16,7 @@ class Foo(object): assert glom({}, Assign('a', 1)) == {'a': 1} assert glom(Foo(), Assign('a', 1)).a == 1 assert glom({'a': Foo()}, Assign('a.a', 1))['a'].a == 1 + def r(): r = {} r['r'] = r @@ -110,12 +111,10 @@ def test_assign_missing_dict(): target = {} val = object() - from itertools import count - counter = count() def debugdict(): ret = dict() - #ret['id'] = id(ret) - #ret['inc'] = counter.next() + # ret['id'] = id(ret) + # ret['inc'] = counter.next() return ret assign(target, 'a.b.c.d', val, missing=debugdict) @@ -125,6 +124,7 @@ def debugdict(): def test_assign_missing_object(): val = object() + class Container(object): pass @@ -169,6 +169,7 @@ def test_assign_missing_unassignable(): class Tarjay(object): init_count = 0 + def __init__(self): self.__class__.init_count += 1 @@ -266,7 +267,10 @@ def __delattr__(self, name): raise Exception("and you trusted me?") spec = Delete('a') - ok_target = lambda: None + + def ok_target(): + return None + ok_target.a = 1 glom(ok_target, spec) assert not hasattr(ok_target, 'a') diff --git a/glom/test/test_streaming.py b/glom/test/test_streaming.py index 18f94c5f..63cd9605 100644 --- a/glom/test/test_streaming.py +++ b/glom/test/test_streaming.py @@ -1,10 +1,10 @@ import pytest -from itertools import count, dropwhile, chain +from itertools import count from glom import Iter -from glom import glom, SKIP, STOP, T, Call, Spec, Glommer, Check, SKIP +from glom import glom, SKIP, STOP, T, Glommer, Check RANGE_5 = list(range(5)) diff --git a/glom/test/test_target_types.py b/glom/test/test_target_types.py index 97c7c0f8..0b021e9a 100644 --- a/glom/test/test_target_types.py +++ b/glom/test/test_target_types.py @@ -11,18 +11,23 @@ class A(object): pass + class B(object): pass + class C(A): pass + class D(B): pass + class E(C, D, A): pass + class F(E): pass @@ -39,10 +44,12 @@ def test_types_leave_one_out(): obj = cur_t() treg = glommer.scope[TargetRegistry] - assert treg._get_closest_type(obj, treg._op_type_tree['get']) == obj.__class__.mro()[1] + assert treg._get_closest_type(obj, treg._op_type_tree['get']) == ( + obj.__class__.mro()[1]) if cur_t is E: - assert glommer.scope[TargetRegistry]._get_closest_type(obj, treg._op_type_tree['get']) is C # sanity check + assert glommer.scope[TargetRegistry]._get_closest_type( + obj, treg._op_type_tree['get']) is C # sanity check return @@ -56,7 +63,8 @@ def test_types_bare(): # test that bare glommers can't glom anything with pytest.raises(UnregisteredTarget) as exc_info: glommer.glom(object(), {'object_repr': '__class__.__name__'}) - assert repr(exc_info.value) == "UnregisteredTarget('get', , OrderedDict(), ('__class__',))" + assert repr(exc_info.value) == ( + "UnregisteredTarget('get', , OrderedDict(), ('__class__',))") assert str(exc_info.value).find( "glom() called without registering any types for operation 'get'." " see glom.register() or Glommer's constructor for details.") != -1 @@ -225,7 +233,6 @@ def _autodiscover_raise(type_obj): treg.register_op('lol', exact=False) assert treg._op_type_tree.get('lol') - def _autodiscover_faulty_return(type_obj): return 'hideeho' @@ -237,7 +244,8 @@ def _autodiscover_sneaky(type_obj): if type_obj is set: return 'this should have been False or a callable, but was intentionally a string' if type_obj is frozenset: - raise ValueError('this should have been False or a callable, but was intentionally a ValueError') + raise ValueError( + 'this should have been False or a callable, but was intentionally a ValueError') return False treg.register_op('sneak', _autodiscover_sneaky) diff --git a/glom/tutorial.py b/glom/tutorial.py index efc19f73..bb87cea1 100644 --- a/glom/tutorial.py +++ b/glom/tutorial.py @@ -430,15 +430,17 @@ declarative, but flexible, an ideal balance for maintainability. """ -import json import datetime from itertools import count from collections import OrderedDict from pprint import pprint +import json + import attr from attr import Factory -from glom import glom, Coalesce +from glom import glom + _email_autoincrement = lambda c=count(1): next(c) _contact_autoincrement = lambda c=count(1): next(c) @@ -464,7 +466,6 @@ def get(self, contact_id): return CONTACTS.get(contact_id) - @attr.s class Contact(object): id = attr.ib(Factory(_contact_autoincrement), init=False) diff --git a/setup.py b/setup.py index b801c687..89e935b6 100644 --- a/setup.py +++ b/setup.py @@ -54,7 +54,7 @@ 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ] - ) + ) """ A brief checklist for release: