Skip to content

Commit

Permalink
implement stricter 1-to-1 checking + many fixes + improvements
Browse files Browse the repository at this point in the history
See the changelog for full details.

Closes #21.
  • Loading branch information
jab committed Dec 21, 2015
1 parent 20083b0 commit 81f86da
Show file tree
Hide file tree
Showing 55 changed files with 990 additions and 591 deletions.
7 changes: 6 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
- repo: git://github.com/pre-commit/pre-commit-hooks
sha: 0ff8620e03bf7acfb4dfdc86de14baf032c604f5
sha: v0.4.2
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: debug-statements
- id: double-quote-string-fixer

#- repo: git://github.com/FalconSocial/pre-commit-mirrors-pep257
# sha: v0.3.2
# hooks:
# - id: pep257
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
language: python
python:
- "2.7"
- "3.3"
- "3.4"
- "3.5"
- "pypy"
Expand All @@ -12,8 +13,8 @@ install:
- pip install tox-travis

script:
- tox
- py.test --cov bidict tests
- python setup.py test
- pep257 bidict

after_success:
coveralls
Expand Down
7 changes: 4 additions & 3 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,13 @@ Besides creating issues and pull requests, there are other ways to contribute.
If you read the code and learned something new, let us know and it'll give us the warm fuzzies.
If you're using bidict in a project you work on, blog about your experience.
If you come across other people who could find it useful, spread the word.
If bidict has helped you accomplish work you've been paid for,
please `support bidict <https://gumroad.com/l/XGXTp>`_
If bidict has helped you accomplish your work,
especially work you've been paid for,
please `support bidict <https://gumroad.com/l/bidict>`_
and/or ask your organization to do the same.

.. image:: ./docs/_static/support-on-gumroad.png
:target: https://gumroad.com/l/XGXTp
:target: https://gumroad.com/l/bidict
:alt: Support bidict

You can also use `Bountysource <https://www.bountysource.com/teams/jab>`_
Expand Down
7 changes: 4 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ bidict
Efficient, Pythonic bidirectional map implementation and related functionality.

.. image:: ./docs/_static/logo.png
:target: https://bidict.readthedocs.org/
:alt: bidict logo


Expand Down Expand Up @@ -47,7 +48,7 @@ Status
:alt: License

.. image:: https://img.shields.io/badge/gumroad-support%20bidict-brightgreen.svg
:target: https://gum.co/XGXTp
:target: https://gumroad.com/l/bidict
:alt: Support bidict

.. image:: https://img.shields.io/badge/Paypal-Buy%20a%20Drink-blue.svg
Expand Down Expand Up @@ -86,12 +87,12 @@ talk about your use case.

bidict is the result of hundreds of hours of voluntary, unpaid work.
If bidict has helped you accomplish work you've been paid for,
please `support bidict <https://gumroad.com/l/XGXTp>`_
please `support bidict <https://gumroad.com/l/bidict>`_
and/or ask your organization to do the same.
Your support directly contributes to bidict's sustainability.

.. image:: ./docs/_static/support-on-gumroad.png
:target: https://gumroad.com/l/XGXTp
:target: https://gumroad.com/l/bidict
:alt: Support bidict

Check out
Expand Down
2 changes: 1 addition & 1 deletion bidict/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.10.0.dev
0.10.0.dev0
17 changes: 10 additions & 7 deletions bidict/__init__.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
# -*- coding: utf-8 -*-

"""
Efficient, Pythonic bidirectional map implementation
and related functionality.
Efficient, Pythonic bidirectional map implementation and related functionality.
See https://bidict.readthedocs.org/ for comprehensive documentation.
.. :copyright: (c) 2015 Joshua Bronson.
.. :license: ISCL. See LICENSE for details.
"""

from ._common import BidirectionalMapping, CollapseException
from ._common import BidirectionalMapping, BidictException, KeyExistsException, ValueExistsException
from ._bidict import bidict
from ._collapsing import collapsingbidict
from ._loose import loosebidict
from ._frozen import frozenbidict
from ._named import namedbidict
from .util import pairs, inverted

__all__ = (
'BidirectionalMapping',
'CollapseException',
'BidictException',
'KeyExistsException',
'ValueExistsException',
'bidict',
'collapsingbidict',
'loosebidict',
'frozenbidict',
'namedbidict',
'pairs',
Expand All @@ -30,6 +33,6 @@
try:
from pkg_resources import resource_string
__version__ = resource_string(__name__, 'VERSION').decode('ascii').strip()
except Exception as e:
except Exception as e: # pragma: no cover
from warnings import warn
warn('Failed to read/set version: %r' % e)
116 changes: 78 additions & 38 deletions bidict/_bidict.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,115 @@
from ._common import BidirectionalMapping, _missing
"""Implements :class:`bidict.bidict`, the mutable bidirectional map type."""

from ._common import BidirectionalMapping
from .util import pairs
from collections import MutableMapping


class bidict(BidirectionalMapping, MutableMapping):
"""
Mutable bidirectional map type.
"""
"""Mutable bidirectional map type."""

def _del(self, key):
val = self._fwd[key]
del self._fwd[key]
del self._bwd[val]
del self._inv[val]
return val

def __delitem__(self, key):
"""Like :py:meth:`dict.__delitem__`, keeping bidirectionality intact."""
self._del(key)

def __setitem__(self, key, val):
self._put(key, val)
"""
Set the value for *key* to *val*.
If *key* is already associated with a different value,
the old value will be replaced with *val*.
Use :attr:`put` to raise an exception in this case instead.
If *val* is already associated with a different key,
an exception is raised
to protect against accidentally replacing the existing mapping.
If *key* is already associated with *val*, this is a no-op.
Use :attr:`forceput` to unconditionally associate *key* with *val*,
replacing any existing mappings as necessary to preserve uniqueness.
:raises bidict.ValueExistsException: if attempting to set a mapping
with a non-unique value.
"""
self._put(key, val, overwrite_key=False, overwrite_val=True)

def put(self, key, val):
"""
Alternative to using the setitem syntax to insert a mapping.
Associate *key* with *val* iff *key* and *val* are both unique.
That is, insert the given mapping iff
*key* is not already associated with an existing value and
*val* is not already associated with an existing key.
If *key* is already associated with *val*, this is a no-op.
:raises bidict.KeyExistsException: if attempting to insert a mapping
with the same key as an existing mapping.
:raises bidict.ValueExistsException: if attempting to insert a mapping
with the same value as an existing mapping.
"""
self._put(key, val)
self._put(key, val, overwrite_key=False, overwrite_val=False)

def forceput(self, key, val):
"""
Like :attr:`bidict.bidict.put` but silently removes any existing
mapping that would otherwise cause a :class:`bidict.CollapseException`
before inserting the given mapping::
>>> b = bidict({0: 'zero', 1: 'one'})
>>> b.put(0, 'one') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
CollapseException: ((0, 'zero'), (1, 'one'))
>>> b.forceput(0, 'one')
>>> b
bidict({0: 'one'})
Associate *key* with *val* unconditionally.
Replace any existing mappings containing key *key* or value *val*
as necessary to preserve uniqueness.
"""
oldval = self._fwd.get(key, _missing)
oldkey = self._bwd.get(val, _missing)
if oldval is not _missing:
del self._bwd[oldval]
if oldkey is not _missing:
del self._fwd[oldkey]
self._fwd[key] = val
self._bwd[val] = key
self._put(key, val, overwrite_key=True, overwrite_val=True)

def clear(self):
"""Remove all items."""
self._fwd.clear()
self._bwd.clear()
self._inv.clear()

def pop(self, key, *args):
val = self._fwd.pop(key, *args)
del self._bwd[val]
return val
"""Like :py:meth:`dict.pop`, keeping bidirectionality intact."""
ln = len(args) + 1
if ln > 2:
raise TypeError('pop expected at most 2 arguments, got %d' % ln)
try:
return self._del(key)
except KeyError:
if args:
return args[0]
raise

def popitem(self):
if not self._fwd:
"""Like :py:meth:`dict.popitem`, keeping bidirectionality intact."""
if not self:
raise KeyError('popitem(): %s is empty' % self.__class__.__name__)
key, val = self._fwd.popitem()
del self._bwd[val]
del self._inv[val]
return key, val

def setdefault(self, key, default=None):
val = self._fwd.setdefault(key, default)
self._bwd[val] = key
return val
"""Like :py:meth:`dict.setdefault`, keeping bidirectionality intact."""
if key not in self:
self[key] = default
return self[key]

def update(self, *args, **kw):
"""
Like :py:meth:`dict.update`, keeping bidirectionality intact.
Similar to calling :attr:`__setitem__` for each mapping given.
:raises bidict.ValueExistsException: if attempting to insert a mapping
with a non-unique value.
"""
return self._update(*args, **kw)

def forceupdate(self, *args, **kw):
"""Call :attr:`forceput` for each mapping given."""
for k, v in pairs(*args, **kw):
self._put(k, v)
self.forceput(k, v)
9 changes: 0 additions & 9 deletions bidict/_collapsing.py

This file was deleted.

Loading

0 comments on commit 81f86da

Please sign in to comment.