1#! /usr/bin/env python
2# -*- coding: utf-8 -*-
3
4##############################################################################
5##  DendroPy Phylogenetic Computing Library.
6##
7##  Copyright 2010-2015 Jeet Sukumaran and Mark T. Holder.
8##  All rights reserved.
9##
10##  See "LICENSE.rst" for terms and conditions of usage.
11##
12##  If you use this work or any portion thereof in published work,
13##  please cite it as:
14##
15##     Sukumaran, J. and M. T. Holder. 2010. DendroPy: a Python library
16##     for phylogenetic computing. Bioinformatics 26: 1569-1571.
17##
18##############################################################################
19
20"""
21Infrastructure for phylogenetic data objects.
22"""
23
24import os
25import copy
26import sys
27import collections
28from dendropy.utility.textprocessing import StringIO
29if not (sys.version_info.major >= 3 and sys.version_info.minor >= 4):
30    from dendropy.utility.filesys import pre_py34_open as open
31from dendropy.utility import container
32from dendropy.utility import bibtex
33from dendropy.utility import textprocessing
34from dendropy.utility import urlio
35from dendropy.utility import error
36from dendropy.utility import deprecate
37
38##############################################################################
39## Keyword Processor
40
41def _extract_serialization_target_keyword(kwargs, target_type):
42    target_type_keywords = ["file", "path", "url", "data", "stream", "string"]
43    found_kw = []
44    for kw in target_type_keywords:
45        if kw in kwargs:
46            found_kw.append(kw)
47    if not found_kw:
48        raise TypeError("{} not specified; exactly one of the following keyword arguments required to be specified: {}".format(target_type, target_type_keywords))
49    if len(found_kw) > 1:
50        raise TypeError("{} specified multiple times: {}".format(target_type, found_kw))
51    target = kwargs.pop(found_kw[0])
52    if "schema" not in kwargs:
53        raise TypeError("Mandatory keyword argument 'schema' not specified")
54    schema = kwargs.pop("schema")
55    return found_kw[0], target, schema
56
57##############################################################################
58## DataObject
59
60class DataObject(object):
61
62    """
63    Base class for all phylogenetic data objects.
64    """
65
66    def __init__(self, label=None):
67        self._label = None
68        if label is not None:
69            self._set_label(label)
70
71    def _get_label(self):
72        return self._label
73    def _set_label(self, v):
74        # self._label = str(v) if v is not None else v
75        self._label = v
76    label = property(_get_label, _set_label)
77
78    def clone(self, depth=1):
79        """
80        Creates and returns a copy of ``self``.
81
82        Parameters
83        ----------
84        depth : integer
85            The depth of the copy:
86
87                - 0: shallow-copy: All member objects are references,
88                  except for :attr:``annotation_set`` of top-level object and
89                  member |Annotation| objects: these are full,
90                  independent instances (though any complex objects in the
91                  ``value`` field of |Annotation| objects are also
92                  just references).
93                - 1: taxon-namespace-scoped copy: All member objects are full
94                  independent instances, *except* for |TaxonNamespace|
95                  and |Taxon| instances: these are references.
96                - 2: Exhaustive deep-copy: all objects are cloned.
97        """
98        if depth == 0:
99            return copy.copy(self)
100        elif depth == 1:
101            return self.taxon_namespace_scoped_copy(memo=None)
102        elif depth == 2:
103            return copy.deepcopy(self)
104        else:
105            raise TypeError("Unsupported cloning depth: {}".format(depth))
106
107    def taxon_namespace_scoped_copy(self, memo=None):
108        """
109        Cloning level: 1.
110        Taxon-namespace-scoped copy: All member objects are full independent
111        instances, *except* for |TaxonNamespace| and |Taxon|
112        objects: these are preserved as references.
113        """
114        raise NotImplementedError
115
116##############################################################################
117## Deserializable
118
119class Deserializable(object):
120    """
121    Mixin class which all classes that require deserialization should subclass.
122    """
123
124    def _parse_and_create_from_stream(cls, stream, schema, **kwargs):
125        """
126        Subclasses need to implement this method to create
127        and return and instance of themselves read from the
128        stream.
129        """
130        raise NotImplementedError
131    _parse_and_create_from_stream = classmethod(_parse_and_create_from_stream)
132
133    def _get_from(cls, **kwargs):
134        """
135        Factory method to return new object of this class from an external
136        source by dispatching calls to more specialized ``get_from_*`` methods.
137        Implementing classes will have a publically-exposed method, ``get()``,
138        that wraps a call to this method. This allows for class-specific
139        documentation of keyword arguments. E.g.::
140
141            @classmethod
142            def get(cls, **kwargs):
143                '''
144                ... (documentation) ...
145                '''
146                return cls._get_from(**kwargs)
147
148        """
149        try:
150            src_type, src, schema = _extract_serialization_target_keyword(kwargs, "Source")
151        except Exception as e:
152            raise e
153        if src_type == "file" or src_type == "stream":
154            return cls.get_from_stream(src=src, schema=schema, **kwargs)
155        elif src_type == "path":
156            return cls.get_from_path(src=src, schema=schema, **kwargs)
157        elif src_type == "data" or src_type == "string":
158            return cls.get_from_string(src=src, schema=schema, **kwargs)
159        elif src_type == "url":
160            return cls.get_from_url(src=src, schema=schema, **kwargs)
161        else:
162            raise ValueError("Unsupported source type: {}".format(src_type))
163    _get_from = classmethod(_get_from)
164
165    def get_from_stream(cls, src, schema, **kwargs):
166        """
167        Factory method to return new object of this class from file-like object
168        ``src``.
169
170        Parameters
171        ----------
172        src : file or file-like
173            Source of data.
174        schema : string
175            Specification of data format (e.g., "nexus").
176        \*\*kwargs : keyword arguments, optional
177            Arguments to customize parsing, instantiation, processing, and
178            accession of objects read from the data source, including schema-
179            or format-specific handling. These will be passed to the underlying
180            schema-specific reader for handling.
181
182        Returns
183        -------
184        pdo : phylogenetic data object
185            New instance of object, constructed and populated from data given
186            in source.
187        """
188        return cls._parse_and_create_from_stream(stream=src,
189                schema=schema,
190                **kwargs)
191    get_from_stream = classmethod(get_from_stream)
192
193    def get_from_path(cls, src, schema, **kwargs):
194        """
195        Factory method to return new object of this class from file
196        specified by string ``src``.
197
198        Parameters
199        ----------
200        src : string
201            Full file path to source of data.
202        schema : string
203            Specification of data format (e.g., "nexus").
204        \*\*kwargs : keyword arguments, optional
205            Arguments to customize parsing, instantiation, processing, and
206            accession of objects read from the data source, including schema-
207            or format-specific handling. These will be passed to the underlying
208            schema-specific reader for handling.
209
210        Returns
211        -------
212        pdo : phylogenetic data object
213            New instance of object, constructed and populated from data given
214            in source.
215        """
216        with open(src, "r", newline=None) as fsrc:
217            return cls._parse_and_create_from_stream(stream=fsrc,
218                    schema=schema,
219                    **kwargs)
220    get_from_path = classmethod(get_from_path)
221
222    def get_from_string(cls, src, schema, **kwargs):
223        """
224        Factory method to return new object of this class from string ``src``.
225
226        Parameters
227        ----------
228        src : string
229            Data as a string.
230        schema : string
231            Specification of data format (e.g., "nexus").
232        \*\*kwargs : keyword arguments, optional
233            Arguments to customize parsing, instantiation, processing, and
234            accession of objects read from the data source, including schema-
235            or format-specific handling. These will be passed to the underlying
236            schema-specific reader for handling.
237
238        Returns
239        -------
240        pdo : phylogenetic data object
241            New instance of object, constructed and populated from data given
242            in source.
243        """
244        ssrc = StringIO(src)
245        return cls._parse_and_create_from_stream(stream=ssrc,
246                schema=schema,
247                **kwargs)
248    get_from_string = classmethod(get_from_string)
249
250    def get_from_url(cls, src, schema, strip_markup=False, **kwargs):
251        """
252        Factory method to return a new object of this class from
253        URL given by ``src``.
254
255        Parameters
256        ----------
257        src : string
258            URL of location providing source of data.
259        schema : string
260            Specification of data format (e.g., "nexus").
261        \*\*kwargs : keyword arguments, optional
262            Arguments to customize parsing, instantiation, processing, and
263            accession of objects read from the data source, including schema-
264            or format-specific handling. These will be passed to the underlying
265            schema-specific reader for handling.
266
267        Returns
268        -------
269        pdo : phylogenetic data object
270            New instance of object, constructed and populated from data given
271            in source.
272        """
273        text = urlio.read_url(src, strip_markup=strip_markup)
274        ssrc = StringIO(text)
275        try:
276            return cls._parse_and_create_from_stream(
277                    stream=ssrc,
278                    schema=schema,
279                    **kwargs)
280        except error.DataParseError:
281            sys.stderr.write(text)
282            raise
283    get_from_url = classmethod(get_from_url)
284
285
286##############################################################################
287## MultiReadabe
288
289class MultiReadable(object):
290    """
291    Mixin class which all classes that support multiple (e.g., aggregative) deserialization should subclass.
292    """
293
294    def _parse_and_add_from_stream(self, stream, schema, **kwargs):
295        """
296        Populates/constructs objects of this type from ``schema``-formatted
297        data in the file-like object source ``stream``.
298
299        Parameters
300        ----------
301        stream : file or file-like
302            Source of data.
303        schema : string
304            Specification of data format (e.g., "nexus").
305        \*\*kwargs : keyword arguments, optional
306            Arguments to customize parsing, instantiation, processing, and
307            accession of objects read from the data source, including schema-
308            or format-specific handling. These will be passed to the underlying
309            schema-specific reader for handling.
310
311        Returns
312        -------
313        n : ``int`` or ``tuple`` [``int``]
314            A value indicating size of data read, where "size" depends on
315            the object:
316
317                - |Tree|: **undefined**
318                - |TreeList|: number of trees
319                - |CharacterMatrix|: number of sequences
320                - |DataSet|: ``tuple`` (number of taxon namespaces, number of tree lists, number of matrices)
321
322        """
323        raise NotImplementedError
324
325    def _read_from(self, **kwargs):
326        """
327        Add data to objects of this class from an external
328        source by dispatching calls to more specialized ``read_from_*`` methods.
329        Implementing classes will have a publically-exposed method, ``read()``,
330        that wraps a call to this method. This allows for class-specific
331        documentation of keyword arguments. E.g.::
332
333            def read(self, **kwargs):
334                '''
335                ... (documentation) ...
336                '''
337                return MultiReadable._read_from(self, **kwargs)
338
339        """
340        try:
341            src_type, src, schema = _extract_serialization_target_keyword(kwargs, "Source")
342        except Exception as e:
343            raise e
344        if src_type == "file" or src_type == "stream":
345            return self.read_from_stream(src=src, schema=schema, **kwargs)
346        elif src_type == "path":
347            return self.read_from_path(src=src, schema=schema, **kwargs)
348        elif src_type == "data" or src_type == "string":
349            return self.read_from_string(src=src, schema=schema, **kwargs)
350        elif src_type == "url":
351            return self.read_from_url(src=src, schema=schema, **kwargs)
352        else:
353            raise ValueError("Unsupported source type: {}".format(src_type))
354
355    def read_from_stream(self, src, schema, **kwargs):
356        """
357        Reads from file (exactly equivalent to just ``read()``, provided
358        here as a separate method for completeness.
359
360        Parameters
361        ----------
362        fileobj : file or file-like
363            Source of data.
364        schema : string
365            Specification of data format (e.g., "nexus").
366        \*\*kwargs : keyword arguments, optional
367            Arguments to customize parsing, instantiation, processing, and
368            accession of objects read from the data source, including schema-
369            or format-specific handling. These will be passed to the underlying
370            schema-specific reader for handling.
371
372        Returns
373        -------
374        n : ``tuple`` [integer]
375            A value indicating size of data read, where "size" depends on
376            the object:
377
378                - |Tree|: **undefined**
379                - |TreeList|: number of trees
380                - |CharacterMatrix|: number of sequences
381                - |DataSet|: ``tuple`` (number of taxon namespaces, number of tree lists, number of matrices)
382
383        """
384        return self._parse_and_add_from_stream(stream=src, schema=schema, **kwargs)
385
386    def read_from_path(self, src, schema, **kwargs):
387        """
388        Reads data from file specified by ``filepath``.
389
390        Parameters
391        ----------
392        filepath : file or file-like
393            Full file path to source of data.
394        schema : string
395            Specification of data format (e.g., "nexus").
396        \*\*kwargs : keyword arguments, optional
397            Arguments to customize parsing, instantiation, processing, and
398            accession of objects read from the data source, including schema-
399            or format-specific handling. These will be passed to the underlying
400            schema-specific reader for handling.
401
402        Returns
403        -------
404        n : ``tuple`` [integer]
405            A value indicating size of data read, where "size" depends on
406            the object:
407
408                - |Tree|: **undefined**
409                - |TreeList|: number of trees
410                - |CharacterMatrix|: number of sequences
411                - |DataSet|: ``tuple`` (number of taxon namespaces, number of tree lists, number of matrices)
412        """
413        with open(src, "r", newline=None) as fsrc:
414            return self._parse_and_add_from_stream(stream=fsrc, schema=schema, **kwargs)
415
416    def read_from_string(self, src, schema, **kwargs):
417        """
418        Reads a string.
419
420        Parameters
421        ----------
422        src_str : string
423            Data as a string.
424        schema : string
425            Specification of data format (e.g., "nexus").
426        \*\*kwargs : keyword arguments, optional
427            Arguments to customize parsing, instantiation, processing, and
428            accession of objects read from the data source, including schema-
429            or format-specific handling. These will be passed to the underlying
430            schema-specific reader for handling.
431
432        Returns
433        -------
434        n : ``tuple`` [integer]
435            A value indicating size of data read, where "size" depends on
436            the object:
437
438                - |Tree|: **undefined**
439                - |TreeList|: number of trees
440                - |CharacterMatrix|: number of sequences
441                - |DataSet|: ``tuple`` (number of taxon namespaces, number of tree lists, number of matrices)
442        """
443        s = StringIO(src)
444        return self._parse_and_add_from_stream(stream=s, schema=schema, **kwargs)
445
446    def read_from_url(self, src, schema, **kwargs):
447        """
448        Reads a URL source.
449
450        Parameters
451        ----------
452        src : string
453            URL of location providing source of data.
454        schema : string
455            Specification of data format (e.g., "nexus").
456        \*\*kwargs : keyword arguments, optional
457            Arguments to customize parsing, instantiation, processing, and
458            accession of objects read from the data source, including schema-
459            or format-specific handling. These will be passed to the underlying
460            schema-specific reader for handling.
461
462        Returns
463        -------
464        n : ``tuple`` [integer]
465            A value indicating size of data read, where "size" depends on
466            the object:
467
468                - |Tree|: **undefined**
469                - |TreeList|: number of trees
470                - |CharacterMatrix|: number of sequences
471                - |DataSet|: ``tuple`` (number of taxon namespaces, number of tree lists, number of matrices)
472        """
473        src_str = urlio.read_url(src)
474        s = StringIO(src_str)
475        return self._parse_and_add_from_stream(stream=s, schema=schema, **kwargs)
476
477##############################################################################
478## NonMultiReadable
479
480class NonMultiReadable(object):
481    """
482    Mixin to enforce transition from DendroPy 3 to DendroPy 4 API
483    """
484
485    def error(self, funcname):
486        read_from_func = funcname
487        get_from_func = funcname.replace("read", "get")
488        raise TypeError(("\n".join((
489                "The '{classname}' class no longer supports           ",
490                "(re-)population by re-reading data from an external  ",
491                "source. Instantiate a new object using, for example, ",
492                "'{classname}.{get_from_func}()' and bind it to",
493                "the variable name instead. That is, instead of:",
494                "",
495                "    x.{read_from_func}(...)",
496                "",
497                "use:",
498                "",
499                "    x = {classname}.{get_from_func}(...)",
500                "",
501                "",
502                ))).format(classname=self.__class__.__name__, get_from_func=get_from_func, read_from_func=read_from_func))
503    def read(self, stream, schema, **kwargs):
504        raise NotImplementedError()
505    def read_from_stream(self, fileobj, schema, **kwargs):
506        self.error("read_from_stream")
507    def read_from_path(self, filepath, schema, **kwargs):
508        self.error("read_from_path")
509    def read_from_string(self, src_str, schema, **kwargs):
510        self.error("read_from_string")
511    def read_from_url(self, url, schema, **kwargs):
512        self.error("read_from_url")
513
514##############################################################################
515## Serializable
516
517class Serializable(object):
518    """
519    Mixin class which all classes that require serialization should subclass.
520    """
521
522    def _format_and_write_to_stream(self, stream, schema, **kwargs):
523        """
524        Writes the object to the file-like object ``stream`` in ``schema``
525        schema.
526        """
527        raise NotImplementedError
528
529    def _write_to(self, **kwargs):
530        """
531        Write this object to an external resource by dispatching calls to more
532        specialized ``write_to_*`` methods. Implementing classes will have a
533        publically-exposed method, ``write()``, that wraps a call to this
534        method. This allows for class-specific documentation of keyword
535        arguments. E.g.::
536
537            def write(self, **kwargs):
538                '''
539                ... (documentation) ...
540                '''
541                return Serializable._write_to(self, **kwargs)
542
543        """
544        try:
545            dest_type, dest, schema = _extract_serialization_target_keyword(kwargs, "Destination")
546        except Exception as e:
547            raise e
548        if dest_type == "file":
549            return self.write_to_stream(dest=dest, schema=schema, **kwargs)
550        elif dest_type == "path":
551            return self.write_to_path(dest=dest, schema=schema, **kwargs)
552        else:
553            raise ValueError("Unsupported source type: {}".format(dest_type))
554
555    def write(self, **kwargs):
556        """
557        Writes out ``self`` in ``schema`` format.
558
559        **Mandatory Destination-Specification Keyword Argument (Exactly One of the Following Required):**
560
561            - **file** (*file*) -- File or file-like object opened for writing.
562            - **path** (*str*) -- Path to file to which to write.
563
564        **Mandatory Schema-Specification Keyword Argument:**
565
566            - **schema** (*str*) -- Identifier of format of data. See
567              "|Schemas|" for more details.
568
569        **Optional Schema-Specific Keyword Arguments:**
570
571            These provide control over how the data is formatted, and supported
572            argument names and values depend on the schema as specified by the
573            value passed as the "``schema``" argument. See "|Schemas|" for more
574            details.
575
576        Examples
577        --------
578
579        ::
580
581                d.write(path="path/to/file.dat",
582                        schema="nexus",
583                        preserve_underscores=True)
584                f = open("path/to/file.dat")
585                d.write(file=f,
586                        schema="nexus",
587                        preserve_underscores=True)
588
589        """
590        return Serializable._write_to(self, **kwargs)
591
592    def write_to_stream(self, dest, schema, **kwargs):
593        """
594        Writes to file-like object ``dest``.
595        """
596        return self._format_and_write_to_stream(stream=dest, schema=schema, **kwargs)
597
598    def write_to_path(self, dest, schema, **kwargs):
599        """
600        Writes to file specified by ``dest``.
601        """
602        with open(os.path.expandvars(os.path.expanduser(dest)), "w") as f:
603            return self._format_and_write_to_stream(stream=f, schema=schema, **kwargs)
604
605    def as_string(self, schema, **kwargs):
606        """
607        Composes and returns string representation of the data.
608
609        **Mandatory Schema-Specification Keyword Argument:**
610
611            - **schema** (*str*) -- Identifier of format of data. See
612              "|Schemas|" for more details.
613
614        **Optional Schema-Specific Keyword Arguments:**
615
616            These provide control over how the data is formatted, and supported
617            argument names and values depend on the schema as specified by the
618            value passed as the "``schema``" argument. See "|Schemas|" for more
619            details.
620
621        """
622        s = StringIO()
623        self._format_and_write_to_stream(stream=s, schema=schema, **kwargs)
624        return s.getvalue()
625
626##############################################################################
627## Annotable
628
629class Annotable(object):
630    """
631    Mixin class which all classes that need to persist object attributes
632    or other information as metadata should subclass.
633    """
634
635    def _get_annotations(self):
636        if not hasattr(self, "_annotations"):
637            self._annotations = AnnotationSet(self)
638        return self._annotations
639    def _set_annotations(self, annotations):
640        if hasattr(self, "_annotations") \
641                and annotations is self._annotations \
642                and self._annotations.target is self:
643            return
644        if not isinstance(annotations, AnnotationSet):
645            raise ValueError("Cannot set 'annotations' to object of type '{}'".format(type(annotations)))
646        old_target = annotations.target
647        self._annotations = annotations
648        self._annotations.target = self
649        for a in self._annotations:
650            if a.is_attribute and a._value[0] is old_target:
651                a.target = self
652    annotations = property(_get_annotations, _set_annotations)
653
654    def _has_annotations(self):
655        return hasattr(self, "_annotations") and len(self._annotations) > 0
656    has_annotations = property(_has_annotations)
657
658    def copy_annotations_from(self,
659            other,
660            attribute_object_mapper=None):
661        """
662        Copies annotations from ``other``, which must be of |Annotable|
663        type.
664
665        Copies are deep-copies, in that the |Annotation| objects added
666        to the ``annotation_set`` |AnnotationSet| collection of ``self`` are
667        independent copies of those in the ``annotate_set`` collection of
668        ``other``. However, dynamic bound-attribute annotations retain references
669        to the original objects as given in ``other``, which may or may not be
670        desirable. This is handled by updated the objects to which attributes
671        are bound via mappings found in ``attribute_object_mapper``.
672        In dynamic bound-attribute annotations, the ``_value`` attribute of the
673        annotations object (:attr:`Annotation._value`) is a tuple consisting of
674        "``(obj, attr_name)``", which instructs the |Annotation| object to
675        return "``getattr(obj, attr_name)``" (via: "``getattr(*self._value)``")
676        when returning the value of the Annotation. "``obj``" is typically the object
677        to which the |AnnotationSet| belongs (i.e., ``self``). When a copy
678        of |Annotation| is created, the object reference given in the
679        first element of the ``_value`` tuple of dynamic bound-attribute
680        annotations are unchanged, unless the id of the object reference is fo
681
682        Parameters
683        ----------
684
685        ``other`` : |Annotable|
686            Source of annotations to copy.
687
688        ``attribute_object_mapper`` : dict
689            Like the ``memo`` of ``__deepcopy__``, maps object id's to objects. The
690            purpose of this is to update the parent or owner objects of dynamic
691            attribute annotations.
692            If a dynamic attribute |Annotation|
693            gives object ``x`` as the parent or owner of the attribute (that is,
694            the first element of the :attr:`Annotation._value` tuple is
695            ``other``) and ``id(x)`` is found in ``attribute_object_mapper``,
696            then in the copy the owner of the attribute is changed to
697            ``attribute_object_mapper[id(x)]``.
698            If ``attribute_object_mapper`` is |None| (default), then the
699            following mapping is automatically inserted: ``id(other): self``.
700            That is, any references to ``other`` in any |Annotation|
701            object will be remapped to ``self``.  If really no reattribution
702            mappings are desired, then an empty dictionary should be passed
703            instead.
704
705        """
706        if hasattr(other, "_annotations"):
707            if attribute_object_mapper is None:
708                attribute_object_mapper = {id(object):self}
709            for a1 in other._annotations:
710                a2 = a1.clone(attribute_object_mapper=attribute_object_mapper)
711                if a2.is_attribute and a2._value[0] is other:
712                    a2._value = (attribute_object_mapper.get(id(other), other), a2._value[1])
713                self.annotations.add(a2)
714
715    def deep_copy_annotations_from(self, other, memo=None):
716        """
717        Note that all references to ``other`` in any annotation value (and
718        sub-annotation, and sub-sub-sub-annotation, etc.) will be
719        replaced with references to ``self``. This may not always make sense
720        (i.e., a reference to a particular entity may be absolute regardless of
721        context).
722        """
723        if hasattr(other, "_annotations"):
724            # if not isinstance(self, other.__class__) or not isinstance(other, self.__class__):
725            if type(self) is not type(other):
726                raise TypeError("Cannot deep-copy annotations from different type (unable to assume object equivalence in dynamic or nested annotations)")
727            if memo is None:
728                memo = {}
729            for a1 in other._annotations:
730                a2 = copy.deepcopy(a1, memo=memo)
731                memo[id(a1)] = a2
732                if a2.is_attribute and a1._value[0] is other:
733                    a2._value = (self, a1._value[1])
734                self.annotations.add(a2)
735            if hasattr(self, "_annotations"):
736                memo[id(other._annotations)] = self._annotations
737
738    # def __copy__(self):
739    #     o = self.__class__.__new__(self.__class__)
740    #     for k in self.__dict__:
741    #         if k == "_annotations":
742    #             continue
743    #         o.__dict__[k] = self.__dict__[k]
744    #     o.copy_annotations_from(self)
745
746    def __copy__(self, memo=None):
747        """
748        Cloning level: 0.
749        :attr:``annotation_set`` of top-level object and member |Annotation|
750        objects are full, independent instances. All other member objects (include
751        objects referenced by dynamically-bound attribute values of
752        |Annotation| objects) are references.
753        All member objects are references, except for
754        """
755        if memo is None:
756            memo = {}
757        other = self.__class__()
758        memo[id(self)] = other
759        for k in self.__dict__:
760            if k == "_annotations":
761                continue
762            other.__dict__[k] = copy.copy(self.__dict__[k])
763            memo[id(self.__dict__[k])] = other.__dict__[k]
764        self.deep_copy_annotations_from(other, memo=memo)
765
766    def __deepcopy__(self, memo=None):
767        # ensure clone map
768        if memo is None:
769            memo = {}
770        # get or create clone of self
771        try:
772            other = memo[id(self)]
773        except KeyError:
774            # create object without initialization
775            # other = type(self).__new__(self.__class__)
776            other = self.__class__.__new__(self.__class__)
777            # store
778            memo[id(self)] = other
779        # copy other attributes first, skipping annotations
780        for k in self.__dict__:
781            if k == "_annotations":
782                continue
783            if k in other.__dict__:
784                continue
785            other.__dict__[k] = copy.deepcopy(self.__dict__[k], memo)
786            memo[id(self.__dict__[k])] = other.__dict__[k]
787            # assert id(self.__dict__[k]) in memo
788        # create annotations
789        other.deep_copy_annotations_from(self, memo)
790        # return
791        return other
792
793##############################################################################
794## Annotation
795
796class Annotation(Annotable):
797    """
798    Metadata storage, composition and persistance, with the following attributes:
799
800        * ``name``
801        * ``value``
802        * ``datatype_hint``
803        * ``name_prefix``
804        * ``namespace``
805        * ``annotate_as_reference``
806        * ``is_hidden``
807        * ``real_value_format_specifier`` - format specifier for printing or rendering
808          values as string, given in Python's format specification
809          mini-language. E.g., '.8f', '4E', '>04d'.
810
811    """
812
813    def __init__(self,
814            name,
815            value,
816            datatype_hint=None,
817            name_prefix=None,
818            namespace=None,
819            name_is_prefixed=False,
820            is_attribute=False,
821            annotate_as_reference=False,
822            is_hidden=False,
823            label=None,
824            real_value_format_specifier=None,
825            ):
826        self._value = value
827        self.is_attribute = is_attribute
828        if name_is_prefixed:
829            self.prefixed_name = name
830            if name_prefix is not None:
831                self._name_prefix = name_prefix
832        else:
833            self.name = name
834            self._name_prefix = name_prefix
835        self.datatype_hint = datatype_hint
836        self._namespace = None
837        self.namespace = namespace
838        self.annotate_as_reference = annotate_as_reference
839        self.is_hidden = is_hidden
840        self.real_value_format_specifier = real_value_format_specifier
841
842    def __eq__(self, o):
843        return self is o
844        # if not isinstance(o, self.__class__):
845        #     return False
846        # if self._value != o._value:
847        #     return False
848        # if self.is_attribute != o.is_attribute:
849        #     return False
850        # if self.is_attribute and o.is_attribute:
851        #     if getattr(*self._value) != getattr(*o._value):
852        #         return False
853        # # at this point, we have established that the values
854        # # are equal
855        # return (self.name == o.name
856        #         and self._name_prefix == o._name_prefix
857        #         and self.datatype_hint == o.datatype_hint
858        #         and self._namespace == o._namespace
859        #         and self.annotate_as_reference == o.annotate_as_reference
860        #         and self.is_hidden == o.is_hidden
861        #         and ( ((not hasattr(self, "_annotations")) and (not hasattr(o, "_annotations")))
862        #             or (hasattr(self, "_annotations") and hasattr(o, "_annotations") and self._annotations == o._annotations)))
863
864    def __hash__(self):
865        return id(self)
866
867    def __str__(self):
868        return "{}='{}'".format(self.name, self.value)
869
870    def __copy__(self):
871        return self.clone()
872
873    # def __deepcopy__(self, memo=None):
874    #     if memo is None:
875    #         memo = {}
876    #     o = self.__class__.__new__(self.__class__)
877    #     memo[id(self)] = o
878    #     for k in self.__dict__:
879    #         # if k not in o.__dict__: # do not add attributes already added by base class
880    #         print("--->{}: {}".format(id(o), k))
881    #         o.__dict__[k] = copy.deepcopy(self.__dict__[k], memo)
882    #         memo[id(self.__dict__[k])] = o.__dict__[k]
883    #     return o
884
885    def clone(self, attribute_object_mapper=None):
886        """
887        Essentially a shallow-copy, except that any objects in the ``_value``
888        field with an ``id`` found in ``attribute_object_mapper`` will be replaced
889        with ``attribute_object_mapper[id]``.
890        """
891        o = self.__class__.__new__(self.__class__)
892        if attribute_object_mapper is None:
893            attribute_object_mapper = {id(self):o}
894        if hasattr(self, "_annotations"):
895            o.copy_annotations_from(self)
896        for k in self.__dict__:
897            if k == "_annotations":
898                continue
899            o.__dict__[k] = self.__dict__[k]
900        return o
901
902    def is_match(self, **kwargs):
903        match = True
904        for k, v in kwargs.items():
905            if k == "name_prefix":
906                if self.name_prefix != v:
907                    return False
908            elif k == "prefixed_name":
909                if self.prefixed_name != v:
910                    return False
911            elif k == "namespace":
912                if self.namespace != v:
913                    return False
914            elif k == "value":
915                if self.value != v:
916                    return False
917            elif hasattr(self, k):
918                if getattr(self, k) != v:
919                    return False
920        return True
921
922    def _get_value(self):
923        if self.is_attribute:
924            return getattr(*self._value)
925        else:
926            return self._value
927    def _set_value(self, value):
928        self._value = value
929    value = property(_get_value, _set_value)
930
931    def _get_name_prefix(self):
932        if self._name_prefix is None:
933            self._name_prefix = "dendropy"
934        return self._name_prefix
935    def _set_name_prefix(self, prefix):
936        self._name_prefix = prefix
937    name_prefix = property(_get_name_prefix, _set_name_prefix)
938
939    def _get_namespace(self):
940        if self._namespace is None:
941            self._namespace = "http://packages.python.org/DendroPy/"
942        return self._namespace
943    def _set_namespace(self, prefix):
944        self._namespace = prefix
945    namespace = property(_get_namespace, _set_namespace)
946
947    def _get_prefixed_name(self):
948        return "{}:{}".format(self.name_prefix, self.name)
949    def _set_prefixed_name(self, prefixed_name):
950        self._name_prefix, self.name = textprocessing.parse_curie_standard_qualified_name(prefixed_name)
951    prefixed_name = property(_get_prefixed_name, _set_prefixed_name)
952
953##############################################################################
954## AnnotationSet
955
956class AnnotationSet(container.OrderedSet):
957
958    def __init__(self, target, *args):
959        container.OrderedSet.__init__(self, *args)
960        self.target = target
961
962    def __eq__(self, other):
963        if not isinstance(other, self.__class__):
964            return False
965        return (container.OrderedSet.__eq__(self, other))
966                #and self.target is other.target) # we consider two
967                # AnnotationSet objects equal even if their targets are
968                # different; this is because (a) the target is convenience
969                # artifact, so client code calls to ``add_bound_attribute`` do
970                # not need to specify an owner, and (b) the target is not part
971                # of the contents of the AnnotationSet
972
973    def __str__(self):
974        return "AnnotationSet([{}])".format(( ", ".join(str(a) for a in self)))
975
976    def __deepcopy__(self, memo):
977        try:
978            o = self.__class__(target=memo[id(self.target)])
979        except KeyError:
980            raise KeyError("deepcopy error: object id {} not found: {}".format(id(self.target), repr(self.target)))
981        memo[id(self)] = o
982        for a in self:
983            x = copy.deepcopy(a, memo)
984            memo[id(a)] = x
985            o.add(x)
986        return o
987
988    def __getitem__(self, name):
989        """
990        Experimental! Inefficient! Volatile! Subject to change!
991        """
992        if isinstance(name, int):
993            return container.OrderedSet.__getitem__(self, name)
994        for a in self:
995            if a.name == name:
996                return a
997        a = self.add_new(name, "")
998        return a
999
1000    def __setitem__(self, name, value):
1001        """
1002        Experimental! Inefficient! Volatile! Subject to change!
1003        """
1004        if isinstance(name, int):
1005            container.OrderedSet.__setitem__(self, name, value)
1006        for a in self:
1007            if a.name == name:
1008                a.value = value
1009                return
1010        self.add_new(name=name, value=value)
1011
1012    def add_new(self,
1013            name,
1014            value,
1015            datatype_hint=None,
1016            name_prefix=None,
1017            namespace=None,
1018            name_is_prefixed=False,
1019            is_attribute=False,
1020            annotate_as_reference=False,
1021            is_hidden=False,
1022            real_value_format_specifier=None,
1023            ):
1024        """
1025        Add an annotation.
1026
1027        Parameters
1028        ----------
1029        name : string
1030            The property/subject/field of the annotation (e.g. "color",
1031            "locality", "dc:citation")
1032        value: string
1033            The content of the annotation.
1034        datatype_hint : string, optional
1035            Mainly for NeXML output (e.g. "xsd:string").
1036        namespace_prefix : string, optional
1037            Mainly for NeXML output (e.g. "dc:").
1038        namespace : string, optional
1039            Mainly for NeXML output (e.g. "http://www.w3.org/XML/1998/namespace").
1040        name_is_prefixed : string, optional
1041            Mainly for NeXML *input*: name will be split into prefix and local part
1042            before storage (e.g., "dc:citations" will result in prefix = "dc" and
1043            name="citations")
1044        is_attribute : boolean, optional
1045            If value is passed as a tuple of (object, "attribute_name") and this
1046            is True, then actual content will be the result of calling
1047            ``getattr(object, "attribute_name")``.
1048        annotate_as_reference : boolean, optional
1049            The value should be interpreted as a URI that points to content.
1050        is_hidden : boolean, optional
1051            Do not write or print this annotation when writing data.
1052        real_value_format_specifier : str
1053          Format specifier for printing or rendering values as string, given
1054          in Python's format specification mini-language. E.g., '.8f', '4E',
1055          '>04d'.
1056
1057        Returns
1058        -------
1059        annotation : |Annotation|
1060            The new |Annotation| created.
1061        """
1062        if not name_is_prefixed:
1063            if name_prefix is None and namespace is None:
1064                name_prefix = "dendropy"
1065                namespace = "http://packages.python.org/DendroPy/"
1066            elif name_prefix is None:
1067                raise TypeError("Cannot specify 'name_prefix' for unqualified name without specifying 'namespace'")
1068            elif namespace is None:
1069                raise TypeError("Cannot specify 'namespace' for unqualified name without specifying 'name_prefix'")
1070        else:
1071            if namespace is None:
1072                raise TypeError("Cannot specify qualified name without specifying 'namespace'")
1073        annote = Annotation(
1074                name=name,
1075                value=value,
1076                datatype_hint=datatype_hint,
1077                name_prefix=name_prefix,
1078                namespace=namespace,
1079                name_is_prefixed=name_is_prefixed,
1080                is_attribute=is_attribute,
1081                annotate_as_reference=annotate_as_reference,
1082                is_hidden=is_hidden,
1083                real_value_format_specifier=real_value_format_specifier,
1084                )
1085        return self.add(annote)
1086
1087    def add_bound_attribute(self,
1088            attr_name,
1089            annotation_name=None,
1090            datatype_hint=None,
1091            name_prefix=None,
1092            namespace=None,
1093            name_is_prefixed=False,
1094            annotate_as_reference=False,
1095            is_hidden=False,
1096            real_value_format_specifier=None,
1097            owner_instance=None,
1098            ):
1099        """
1100        Add an attribute of an object as a dynamic annotation. The value of the
1101        annotation will be dynamically bound to the value of the attribute.
1102
1103        Parameters
1104        ----------
1105        attr_name : string
1106            The (string) name of the attribute to be used as the source of the
1107            content or value of the annotation.
1108        annotation_name : string, optional
1109            Use this string as the annotation field/name rather than the attribute
1110            name.
1111        datatype_hint : string, optional
1112            Mainly for NeXML output (e.g. "xsd:string").
1113        namespace_prefix : string, optional
1114            Mainly for NeXML output (e.g. "dc:").
1115        namespace : string, optional
1116            Mainly for NeXML output (e.g. "http://www.w3.org/XML/1998/namespace").
1117        name_is_prefixed : string, optional
1118            Mainly for NeXML *input*: name will be split into prefix and local part
1119            before storage (e.g., "dc:citations" will result in prefix = "dc" and
1120            name="citations")
1121        annotate_as_reference : bool, optional
1122            The value should be interpreted as a URI that points to content.
1123        is_hidden : bool, optional
1124            Do not write or print this annotation when writing data.
1125        owner_instance : object, optional
1126            The object whose attribute is to be used as the value of the
1127            annotation. Defaults to ``self.target``.
1128
1129        Returns
1130        -------
1131        annotation : |Annotation|
1132            The new |Annotation| created.
1133        """
1134        if annotation_name is None:
1135            annotation_name = attr_name
1136        if owner_instance is None:
1137            owner_instance = self.target
1138        if not hasattr(owner_instance, attr_name):
1139            raise AttributeError(attr_name)
1140        if not name_is_prefixed:
1141            if name_prefix is None and namespace is None:
1142                name_prefix = "dendropy"
1143                namespace = "http://packages.python.org/DendroPy/"
1144            elif name_prefix is None:
1145                raise TypeError("Cannot specify 'name_prefix' for unqualified name without specifying 'namespace'")
1146            elif namespace is None:
1147                raise TypeError("Cannot specify 'namespace' for unqualified name without specifying 'name_prefix'")
1148        else:
1149            if namespace is None:
1150                raise TypeError("Cannot specify qualified name without specifying 'namespace'")
1151        annote = Annotation(
1152                name=annotation_name,
1153                value=(owner_instance, attr_name),
1154                datatype_hint=datatype_hint,
1155                name_prefix=name_prefix,
1156                namespace=namespace,
1157                name_is_prefixed=name_is_prefixed,
1158                is_attribute=True,
1159                annotate_as_reference=annotate_as_reference,
1160                is_hidden=is_hidden,
1161                real_value_format_specifier=real_value_format_specifier,
1162                )
1163        return self.add(annote)
1164
1165    def add_citation(self,
1166            citation,
1167            read_as="bibtex",
1168            store_as="bibtex",
1169            name_prefix=None,
1170            namespace=None,
1171            is_hidden=False):
1172        """
1173        Add a citation as an annotation.
1174
1175        Parameters
1176        ----------
1177        citation : string or dict or `BibTexEntry`
1178            The citation to be added. If a string, then it must be a
1179            BibTex-formatted entry. If a dictionary, then it must have
1180            BibTex fields as keys and contents as values.
1181        read_as : string, optional
1182            Specifies the format/schema/structure of the citation. Currently
1183            only supports 'bibtex'.
1184        store_as : string, optional
1185            Specifies how to record the citation, with one of the
1186            following strings as values: "bibtex" (a set of annotations, where
1187            each BibTex field becomes a separate annotation); "prism"
1188            (a set of PRISM [Publishing Requirements for Industry Standard
1189            Metadata] annotations); "dublin" (A set of of Dublic Core
1190            annotations). Defaults to "bibtex".
1191        name_prefix : string, optional
1192            Mainly for NeXML output (e.g. "dc:").
1193        namespace : string, optional
1194            Mainly for NeXML output (e.g. "http://www.w3.org/XML/1998/namespace").
1195        is_hidden : boolean, optional
1196            Do not write or print this annotation when writing data.
1197
1198        Returns
1199        -------
1200        annotation : |Annotation|
1201            The new |Annotation| created.
1202        """
1203        if read_as == "bibtex":
1204            return self.add_bibtex(citation=citation,
1205                    store_as=store_as,
1206                    name_prefix=name_prefix,
1207                    namespace=namespace,
1208                    is_hidden=is_hidden)
1209        else:
1210            raise ValueError("Source format '{}' is not supported".format(read_as))
1211
1212    def add_bibtex(self,
1213            citation,
1214            store_as="bibtex",
1215            name_prefix=None,
1216            namespace=None,
1217            is_hidden=False):
1218        """
1219        Add a citation as an annotation.
1220
1221        Parameters
1222        ----------
1223        citation : string or dict or `BibTexEntry`
1224            The citation to be added. If a string, then it must be a
1225            BibTex-formatted entry. If a dictionary, then it must have
1226            BibTex fields as keys and contents as values.
1227        store_as : string, optional
1228            Specifies how to record the citation, with one of the
1229            following strings as values: "bibtex" (a set of annotations, where
1230            each BibTex field becomes a separate annotation); "prism"
1231            (a set of PRISM [Publishing Requirements for Industry Standard
1232            Metadata] annotations); "dublin" (A set of of Dublic Core
1233            annotations). Defaults to "bibtex".
1234        name_prefix : string, optional
1235            Mainly for NeXML output (e.g. "dc:").
1236        namespace : string, optional
1237            Mainly for NeXML output (e.g. "http://www.w3.org/XML/1998/namespace").
1238        is_hidden : boolean, optional
1239            Do not write or print this annotation when writing data.
1240
1241        Returns
1242        -------
1243        annotation : |Annotation|
1244            The new |Annotation| created.
1245        """
1246        bt = bibtex.BibTexEntry(citation)
1247        bt_dict = bt.fields_as_dict()
1248
1249        if name_prefix is None and namespace is not None:
1250            raise TypeError("Cannot specify 'name_prefix' for unqualified name without specifying 'namespace'")
1251        elif namespace is None and name_prefix is not None:
1252            raise TypeError("Cannot specify 'namespace' for unqualified name without specifying 'name_prefix'")
1253
1254        if store_as.lower().startswith("bibtex"):
1255            if name_prefix is None and namespace is None:
1256                name_prefix = "bibtex"
1257                namespace = "http://www.edutella.org/bibtex#"
1258            self.add_new(
1259                    name="bibtype",
1260                    value=bt.bibtype,
1261                    datatype_hint="xsd:string",
1262                    name_prefix=name_prefix,
1263                    namespace=namespace,
1264                    name_is_prefixed=False,
1265                    is_attribute=False,
1266                    annotate_as_reference=False,
1267                    is_hidden=is_hidden)
1268            self.add_new(
1269                    name="citekey",
1270                    value=bt.citekey,
1271                    datatype_hint="xsd:string",
1272                    name_prefix=name_prefix,
1273                    namespace=namespace,
1274                    name_is_prefixed=False,
1275                    is_attribute=False,
1276                    annotate_as_reference=False,
1277                    is_hidden=is_hidden)
1278            for entry_key, entry_value in bt_dict.items():
1279                self.add_new(
1280                        name=entry_key,
1281                        value=entry_value,
1282                        datatype_hint="xsd:string",
1283                        name_prefix=name_prefix,
1284                        namespace=namespace,
1285                        name_is_prefixed=False,
1286                        is_attribute=False,
1287                        annotate_as_reference=False,
1288                        is_hidden=is_hidden)
1289        # elif store_as.lower().startswith("bibtex-record"):
1290        #     if name_prefix is None and namespace is None:
1291        #         name_prefix = "dendropy"
1292        #         namespace = "http://packages.python.org/DendroPy/"
1293        #     self.add_new(
1294        #             name="bibtex",
1295        #             value=bt.as_compact_bibtex(),
1296        #             datatype_hint="xsd:string",
1297        #             name_is_prefixed=False,
1298        #             name_prefix=name_prefix,
1299        #             namespace=namespace,
1300        #             is_attribute=False,
1301        #             annotate_as_reference=False,
1302        #             is_hidden=is_hidden)
1303        elif store_as.lower().startswith("prism"):
1304            prism_map = {
1305                    'volume': bt_dict.get('volume', None),
1306                    'publicationName':  bt_dict.get('journal', None),
1307                    'pageRange': bt_dict.get('pages', None),
1308                    'publicationDate': bt_dict.get('year', None),
1309                    }
1310            if name_prefix is None and namespace is None:
1311                name_prefix = "prism"
1312                namespace = "http://prismstandard.org/namespaces/1.2/basic/"
1313            for field, value in prism_map.items():
1314                if value is None:
1315                    continue
1316                self.add_new(
1317                        name=field,
1318                        value=value,
1319                        datatype_hint="xsd:string",
1320                        name_prefix=name_prefix,
1321                        namespace=namespace,
1322                        name_is_prefixed=False,
1323                        is_attribute=False,
1324                        annotate_as_reference=False,
1325                        is_hidden=is_hidden)
1326        elif store_as.lower().startswith("dublin"):
1327            dc_map = {
1328                    'title': bt_dict.get('title', None),
1329                    'creator':  bt_dict.get('author', None),
1330                    'publisher': bt_dict.get('journal', None),
1331                    'date': bt_dict.get('year', None),
1332                    }
1333            if name_prefix is None and namespace is None:
1334                name_prefix = "dc"
1335                namespace = "http://purl.org/dc/elements/1.1/"
1336            for field, value in dc_map.items():
1337                if value is None:
1338                    continue
1339                self.add_new(
1340                        name=field,
1341                        value=value,
1342                        datatype_hint="xsd:string",
1343                        name_is_prefixed=False,
1344                        name_prefix=name_prefix,
1345                        namespace=namespace,
1346                        is_attribute=False,
1347                        annotate_as_reference=False,
1348                        is_hidden=is_hidden)
1349        else:
1350            raise ValueError("Unrecognized composition specification: '{}'".format(store_as))
1351
1352    def findall(self, **kwargs):
1353        """
1354        Returns AnnotationSet of Annotation objects associated with self.target
1355        that match based on *all* criteria specified in keyword arguments::
1356
1357            >>> notes = tree.annotations.findall(name="color")
1358            >>> notes = tree.annotations.findall(namespace="http://packages.python.org/DendroPy/")
1359            >>> notes = tree.annotations.findall(namespace="http://packages.python.org/DendroPy/",
1360                                          name="color")
1361            >>> notes = tree.annotations.findall(name_prefix="dc")
1362            >>> notes = tree.annotations.findall(prefixed_name="dc:color")
1363
1364        If no matches are found, the return AnnotationSet is empty.
1365
1366        If no keyword arguments are given, *all* annotations are returned::
1367
1368            >>> notes = tree.annotations.findall()
1369
1370        Returns
1371        -------
1372        results : |AnnotationSet| or |None|
1373            |AnnotationSet| containing |Annotation| objects that
1374            match criteria, or |None| if no matching annotations found.
1375        """
1376        results = []
1377        for a in self:
1378            if a.is_match(**kwargs):
1379                results.append(a)
1380        results = AnnotationSet(self.target, results)
1381        return results
1382
1383    def find(self, **kwargs):
1384        """
1385        Returns the *first* Annotation associated with self.target
1386        which matches based on *all* criteria specified in keyword arguments::
1387
1388            >>> note = tree.annotations.find(name="color")
1389            >>> note = tree.annotations.find(name_prefix="dc", name="color")
1390            >>> note = tree.annotations.find(prefixed_name="dc:color")
1391
1392        If no match is found, None is returned.
1393
1394        If no keyword arguments are given, a TypeError is raised.
1395
1396        Returns
1397        -------
1398        results : |Annotation| or |None|
1399            First |Annotation| object found that matches criteria, or
1400            |None| if no matching annotations found.
1401        """
1402        if "default" in kwargs:
1403            default = kwargs["default"]
1404            del kwargs["default"]
1405        else:
1406            default = None
1407        if not kwargs:
1408            raise TypeError("Search criteria not specified")
1409        for a in self:
1410            if a.is_match(**kwargs):
1411                return a
1412        return default
1413
1414    def get_value(self, name, default=None):
1415        """
1416        Returns the *value* of the *first* Annotation associated with
1417        self.target which has ``name`` in the name field.
1418
1419        If no match is found, then ``default`` is returned.
1420
1421        Parameters
1422        ----------
1423        name : string
1424            Name of |Annotation| object whose value is to be returned.
1425
1426        default : any, optional
1427            Value to return if no matching |Annotation| object found.
1428
1429        Returns
1430        -------
1431        results : |Annotation| or |None|
1432            ``value`` of first |Annotation| object found that matches
1433            criteria, or |None| if no matching annotations found.
1434        """
1435        for a in self:
1436            if a.is_match(name=name):
1437                return a.value
1438        return default
1439
1440    def require_value(self, name):
1441        """
1442        Returns the *value* of the *first* Annotation associated with
1443        self.target which has ``name`` in the name field.
1444
1445        If no match is found, then KeyError is raised.
1446
1447        Parameters
1448        ----------
1449        name : string
1450            Name of |Annotation| object whose value is to be returned.
1451
1452        Returns
1453        -------
1454        results : |Annotation| or |None|
1455            ``value`` of first |Annotation| object found that matches
1456            criteria.
1457        """
1458        v = self.get_value(name, default=None)
1459        if v is None:
1460            raise KeyError(name)
1461        return v
1462
1463    def drop(self, **kwargs):
1464        """
1465        Removes Annotation objects that match based on *all* criteria specified
1466        in keyword arguments.
1467
1468        Remove all annotation objects with ``name`` ==
1469        "color"::
1470
1471            >>> tree.annotations.drop(name="color")
1472
1473        Remove all annotation objects with ``namespace`` ==
1474        "http://packages.python.org/DendroPy/"::
1475
1476            >>> tree.annotations.drop(namespace="http://packages.python.org/DendroPy/")
1477
1478        Remove all annotation objects with ``namespace`` ==
1479        "http://packages.python.org/DendroPy/" *and* ``name`` == "color"::
1480
1481            >>> tree.annotations.drop(namespace="http://packages.python.org/DendroPy/",
1482                    name="color")
1483
1484        Remove all annotation objects with ``name_prefix`` == "dc"::
1485
1486            >>> tree.annotations.drop(name_prefix="dc")
1487
1488        Remove all annotation objects with ``prefixed_name`` == "dc:color"::
1489
1490            >>> tree.annotations.drop(prefixed_name="dc:color")
1491
1492        If no keyword argument filter criteria are given, *all* annotations are
1493        removed::
1494
1495            >>> tree.annotations.drop()
1496
1497        Returns
1498        -------
1499        results : |AnnotationSet|
1500            |AnnotationSet| containing |Annotation| objects that
1501            were removed.
1502        """
1503        to_remove = []
1504        for a in self:
1505            if a.is_match(**kwargs):
1506                to_remove.append(a)
1507        for a in to_remove:
1508            self.remove(a)
1509        return AnnotationSet(self.target, to_remove)
1510
1511    def values_as_dict(self, **kwargs):
1512        """
1513        Returns annotation set as a dictionary. The keys and values for the dictionary will
1514        be generated based on the following keyword arguments:
1515
1516        Keyword Arguments
1517        -----------------
1518        key_attr : string
1519            String specifying an Annotation object attribute name to be used
1520            as keys for the dictionary.
1521        key_fn : string
1522            Function that takes an Annotation object as an argument and returns
1523            the value to be used as a key for the dictionary.
1524        value_attr : string
1525            String specifying an Annotation object attribute name to be used
1526            as values for the dictionary.
1527        value_fn : string
1528            Function that takes an Annotation object as an argument and returns
1529            the value to be used as a value for the dictionary.
1530
1531        At most one of ``key_attr`` or ``key_fn`` can be specified. If neither
1532        is specified, then by default the keys are generated from Annotation.name.
1533        At most one of ``value_attr`` or ``value_fn`` can be specified. If neither
1534        is specified, then by default the values are generated from Annotation.value.
1535        Key collisions will result in the dictionary entry for that key being
1536        overwritten.
1537
1538        Returns
1539        -------
1540        values : dict
1541        """
1542        if "key_attr" in kwargs and "key_fn" in kwargs:
1543            raise TypeError("Cannot specify both 'key_attr' and 'key_fn'")
1544        elif "key_attr" in kwargs:
1545            key_attr = kwargs["key_attr"]
1546            key_fn = lambda a: getattr(a, key_attr)
1547        elif "key_fn" in kwargs:
1548            key_fn = kwargs["key_fn"]
1549        else:
1550            key_fn = lambda a: a.name
1551        if "value_attr" in kwargs and "value_fn" in kwargs:
1552            raise TypeError("Cannot specify both 'value_attr' and 'value_fn'")
1553        elif "value_attr" in kwargs:
1554            value_attr = kwargs["value_attr"]
1555            value_fn = lambda a: getattr(a, value_attr)
1556        elif "value_fn" in kwargs:
1557            value_fn = kwargs["value_fn"]
1558        else:
1559            value_fn = lambda a: a.value
1560        d = {}
1561        for a in self:
1562            d[key_fn(a)] = value_fn(a)
1563        return d
1564
1565