a
    ߙfb'G                     @   s:  d Z ddlmZ ddlZddlmZ ddlmZ ddlm	Z	 ddl
ZddlmZ ddlmZ g d	ZG d
d deZG dd deZg Zdd ZG dd deZG dd dedZG dd deZG dd deZdd Zdd ZG dd dZdd  Zd!d" Zd#d$ Z dd%ee fd&d'Z!G d(d) d)Z"G d*d+ d+Z#dS ),zJ
This module contains helper functions and classes for handling metadata.
    )wrapsN)OrderedDict)Mapping)deepcopy)AstropyWarning)dtype_bytes_or_chars)MergeConflictErrorMergeConflictWarningMERGE_STRATEGIEScommon_dtype	MergePlusMergeNpConcatenateMergeStrategyMergeStrategyMetaenable_merge_strategiesmergeMetaDataMetaAttributec                   @   s   e Zd ZdS )r   N__name__
__module____qualname__ r   r   5lib/python3.9/site-packages/astropy/utils/metadata.pyr      s   r   c                   @   s   e Zd ZdS )r	   Nr   r   r   r   r   r	      s   r	   c                    s   dd  t jt jt jt jt jft fdd| D }t|dkrn fdd| D }td| }||_	| fd	d| D } t
| D ]8\}}|jjd
v r|jjdkrdndt|j g| |< qt dd | D }|jjS )a  
    Use numpy to find the common dtype for a list of ndarrays.

    Only allow arrays within the following fundamental numpy data types:
    ``np.bool_``, ``np.object_``, ``np.number``, ``np.character``, ``np.void``

    Parameters
    ----------
    arrs : list of ndarray
        Arrays for which to find the common dtype

    Returns
    -------
    dtype_str : str
        String representation of dytpe (dtype ``str`` attribute)
    c                 S   s   t | dtdS )NdtypeO)getattrnpr   arrr   r   r   r   5   s    zcommon_dtype.<locals>.dtypec                 3   s&   | ] t  fd dD V  qdS )c                 3   s   | ]}t  j|V  qd S N)
issubclasstype).0Znp_type)r   r   r   r   	<genexpr>9       z)common_dtype.<locals>.<genexpr>.<genexpr>N)tuple)r#   r   Znp_typesr   r   r$   9   s   zcommon_dtype.<locals>.<genexpr>   c                    s   g | ]} |j qS r   )namer#   r   r   r   r   
<listcomp>=   r%   z common_dtype.<locals>.<listcomp>zArrays have incompatible types c                    s   g | ]}t jd  |dqS )r(   r+   )r   emptyr*   r+   r   r   r,   B   r%   )SUr/   0   0c                 S   s   g | ]}|d  qS )r   r   r*   r   r   r   r,   K   r%   )r   Zbool_Zobject_Znumber	characterZvoidsetlenr   Z_incompat_types	enumerater   kindr   Zarraystr)ZarrsZ
uniq_typesZincompat_typesZtmeir   Z
arr_commonr   r'   r   r   $   s$    r   c                       s    e Zd ZdZ fddZ  ZS )r   zc
    Metaclass that registers MergeStrategy subclasses into the
    MERGE_STRATEGIES registry.
    c           	         s   t  | |||}d|v rPt|d trP|d j t  fdd}t||_d|v r|d }t|trp|g}t|D ]\}}t	
d|||f qx|S )Nr   c              
      s@   z | ||W S  t y: } zt|W Y d }~n
d }~0 0 d S r    )	Exceptionr   )clsleftrighterrZ
orig_merger   r   r   ]   s    z(MergeStrategyMeta.__new__.<locals>.mergetypesr   )super__new__
isinstanceclassmethod__func__r   r   r&   reversedr
   insert)	mclsr)   basesmembersr:   r   r?   r;   r<   	__class__r>   r   rA   U   s    


zMergeStrategyMeta.__new__)r   r   r   __doc__rA   __classcell__r   r   rJ   r   r   O   s   r   c                   @   s   e Zd ZdZdZdS )r   a^	  
    Base class for defining a strategy for merging metadata from two
    sources, left and right, into a single output.

    The primary functionality for the class is the ``merge(cls, left, right)``
    class method.  This takes ``left`` and ``right`` side arguments and
    returns a single merged output.

    The first class attribute is ``types``.  This is defined as a list of
    (left_types, right_types) tuples that indicate for which input types the
    merge strategy applies.  In determining whether to apply this merge
    strategy to a pair of (left, right) objects, a test is done:
    ``isinstance(left, left_types) and isinstance(right, right_types)``.  For
    example::

      types = [(np.ndarray, np.ndarray),  # Two ndarrays
               (np.ndarray, (list, tuple)),  # ndarray and (list or tuple)
               ((list, tuple), np.ndarray)]  # (list or tuple) and ndarray

    As a convenience, ``types`` can be defined as a single two-tuple instead of
    a list of two-tuples, e.g. ``types = (np.ndarray, np.ndarray)``.

    The other class attribute is ``enabled``, which defaults to ``False`` in
    the base class.  By defining a subclass of ``MergeStrategy`` the new merge
    strategy is automatically registered to be available for use in
    merging. However, by default the new merge strategy is *not enabled*.  This
    prevents inadvertently changing the behavior of unrelated code that is
    performing metadata merge operations.

    In most cases (particularly in library code that others might use) it is
    recommended to leave custom strategies disabled and use the
    `~astropy.utils.metadata.enable_merge_strategies` context manager to locally
    enable the desired strategies.  However, if one is confident that the
    new strategy will not produce unexpected behavior, then one can globally
    enable it by setting the ``enabled`` class attribute to ``True``.

    Examples
    --------
    Here we define a custom merge strategy that takes an int or float on
    the left and right sides and returns a list with the two values.

      >>> from astropy.utils.metadata import MergeStrategy
      >>> class MergeNumbersAsList(MergeStrategy):
      ...     types = ((int, float), (int, float))  # (left_types, right_types)
      ...
      ...     @classmethod
      ...     def merge(cls, left, right):
      ...         return [left, right]

    FN)r   r   r   rL   enabledr   r   r   r   r   q   s   4r   )	metaclassc                   @   s0   e Zd ZdZeefeefgZdZedd Z	dS )r   z
    Merge ``left`` and ``right`` objects using the plus operator.  This
    merge strategy is globally enabled by default.
    Tc                 C   s   || S r    r   r:   r;   r<   r   r   r   r      s    zMergePlus.mergeN)
r   r   r   rL   listr&   r?   rN   rC   r   r   r   r   r   r      s
   r   c                   @   sF   e Zd ZdZejejfejeeffeefejfgZdZ	e
dd ZdS )r   z
    Merge ``left`` and ``right`` objects using np.concatenate.  This
    merge strategy is globally enabled by default.

    This will upcast a list or tuple to np.ndarray and the output is
    always ndarray.
    Tc                 C   s0   t |t | }}t||g t ||gS r    )r   Z
asanyarrayr   ZconcatenaterP   r   r   r   r      s    zMergeNpConcatenate.mergeN)r   r   r   rL   r   ZndarrayrQ   r&   r?   rN   rC   r   r   r   r   r   r      s   
r   c                 C   s   t | |ot ||S r    )rB   )r;   r<   r:   r   r   r   _both_isinstance   s    rR   c                 C   s(   zt | |kW S  ty"   Y dS 0 d S NT)boolr9   )r;   r<   r   r   r   
_not_equal   s    rU   c                   @   s$   e Zd Zdd Zdd Zdd ZdS )_EnableMergeStrategiesc                 G   s<   || _ i | _tD ]&\}}}t||r|j| j|< d|_qd S rS   )merge_strategiesorig_enabledr
   r!   rN   )selfrW   	left_type
right_typemerge_strategyr   r   r   __init__   s    
z_EnableMergeStrategies.__init__c                 C   s   d S r    r   rY   r   r   r   	__enter__   s    z _EnableMergeStrategies.__enter__c                 C   s   | j  D ]\}}||_q
d S r    )rX   itemsrN   )rY   r"   valuetbr\   rN   r   r   r   __exit__   s    z_EnableMergeStrategies.__exit__N)r   r   r   r]   r_   rc   r   r   r   r   rV      s   rV   c                  G   s   t |  S )a5  
    Context manager to temporarily enable one or more custom metadata merge
    strategies.

    Examples
    --------
    Here we define a custom merge strategy that takes an int or float on
    the left and right sides and returns a list with the two values.

      >>> from astropy.utils.metadata import MergeStrategy
      >>> class MergeNumbersAsList(MergeStrategy):
      ...     types = ((int, float),  # left side types
      ...              (int, float))  # right side types
      ...     @classmethod
      ...     def merge(cls, left, right):
      ...         return [left, right]

    By defining this class the merge strategy is automatically registered to be
    available for use in merging. However, by default new merge strategies are
    *not enabled*.  This prevents inadvertently changing the behavior of
    unrelated code that is performing metadata merge operations.

    In order to use the new merge strategy, use this context manager as in the
    following example::

      >>> from astropy.table import Table, vstack
      >>> from astropy.utils.metadata import enable_merge_strategies
      >>> t1 = Table([[1]], names=['a'])
      >>> t2 = Table([[2]], names=['a'])
      >>> t1.meta = {'m': 1}
      >>> t2.meta = {'m': 2}
      >>> with enable_merge_strategies(MergeNumbersAsList):
      ...    t12 = vstack([t1, t2])
      >>> t12.meta['m']
      [1, 2]

    One can supply further merge strategies as additional arguments to the
    context manager.

    As a convenience, the enabling operation is actually done by checking
    whether the registered strategies are subclasses of the context manager
    arguments.  This means one can define a related set of merge strategies and
    then enable them all at once by enabling the base class.  As a trivial
    example, *all* registered merge strategies can be enabled with::

      >>> with enable_merge_strategies(MergeStrategy):
      ...    t12 = vstack([t1, t2])

    Parameters
    ----------
    *merge_strategies : `~astropy.utils.metadata.MergeStrategy`
        Merge strategies that will be enabled.

    )rV   )rW   r   r   r   r      s    8r   c                 C   s   d | t|t||}|S )NzECannot merge meta key {0!r} types {1!r} and {2!r}, choosing {0}={3!r})formatr"   keyr;   r<   outr   r   r   _warn_str_func#  s    rh   c                 C   s"   d| dt |dt |}|S )NzCannot merge meta key z types z and )r"   re   r   r   r   _error_str_func*  s    ri   warnc                 C   s  t | |tstdt| }| D ]\}}||vrDt|||< q$t | | || trvt| | || ||d||< q$zx|du rtD ]J\}	}
}|jsqt| | |	rt|| |
r|| | || ||<  qqtn|| | || ||< W q$ ty   | | du r|| ||< n|| du r6| | ||< nt	| | || r|dkrrt
||| | || t n6|dkrt||| | || n|dkrtd|| ||< n|| ||< Y q$0 q$|S )z
    Merge the ``left`` and ``right`` metadata objects.

    This is a simplistic and limited implementation at this point.
    z%Can only merge two dict-based objects)metadata_conflictsNrj   errorZsilentzGmetadata_conflicts argument must be one of "silent", "warn", or "error")rR   dictr   r   r`   r   r
   rN   rB   rU   warningsrj   r	   
ValueError)r;   r<   Z
merge_funcrk   Zwarn_str_funcZerror_str_funcrg   rf   valrZ   r[   Z	merge_clsr   r   r   r   /  sP    


r   c                   @   s*   e Zd ZdZdddZdd Zdd	 Zd
S )r   a  
    A descriptor for classes that have a ``meta`` property.

    This can be set to any valid `~collections.abc.Mapping`.

    Parameters
    ----------
    doc : `str`, optional
        Documentation for the attribute of the class.
        Default is ``""``.

        .. versionadded:: 1.2

    copy : `bool`, optional
        If ``True`` the the value is deepcopied before setting, otherwise it
        is saved as reference.
        Default is ``True``.

        .. versionadded:: 1.2
     Tc                 C   s   || _ || _d S r    )rL   copy)rY   docrr   r   r   r   r]     s    zMetaData.__init__c                 C   s$   |d u r| S t |dst |_|jS )N_meta)hasattrr   rt   )rY   instanceownerr   r   r   __get__  s
    
zMetaData.__get__c                 C   sB   |d u rt  |_n,t|tr6| jr.t||_q>||_ntdd S )Nz meta attribute must be dict-like)r   rt   rB   r   rr   r   	TypeError)rY   rv   ra   r   r   r   __set__  s    

zMetaData.__set__N)rq   T)r   r   r   rL   r]   rx   rz   r   r   r   r   r   s  s   
r   c                   @   sB   e Zd ZdZdddZdd Zdd Zd	d
 Zdd Zdd Z	dS )r   aZ  
    Descriptor to define custom attribute which gets stored in the object
    ``meta`` dict and can have a defined default.

    This descriptor is intended to provide a convenient way to add attributes
    to a subclass of a complex class such as ``Table`` or ``NDData``.

    This requires that the object has an attribute ``meta`` which is a
    dict-like object.  The value of the MetaAttribute will be stored in a
    new dict meta['__attributes__'] that is created when required.

    Classes that define MetaAttributes are encouraged to support initializing
    the attributes via the class ``__init__``.  For example::

        for attr in list(kwargs):
            descr = getattr(self.__class__, attr, None)
            if isinstance(descr, MetaAttribute):
                setattr(self, attr, kwargs.pop(attr))

    The name of a ``MetaAttribute`` cannot be the same as any of the following:

    - Keyword argument in the owner class ``__init__``
    - Method or attribute of the "parent class", where the parent class is
      taken to be ``owner.__mro__[1]``.

    :param default: default value

    Nc                 C   s
   || _ d S r    )default)rY   r{   r   r   r   r]     s    zMetaAttribute.__init__c                 C   s   |d u r| S | j d u r.| j|jdi vr.d S |jdi }z|| j }W n8 ty   | j d urrt| j || j< || j}Y n0 |S NZ__attributes__)r{   r)   metaget
setdefaultKeyErrorr   )rY   rv   rw   
attributesra   r   r   r   rx     s    

zMetaAttribute.__get__c                 C   s   |j di }||| j< d S r|   )r}   r   r)   )rY   rv   ra   r   r   r   r   rz     s    zMetaAttribute.__set__c                 C   s6   d|j v r2|j d }| j|v r&|| j= |s2|j d= d S r|   )r}   r)   )rY   rv   attrsr   r   r   
__delete__  s    


zMetaAttribute.__delete__c                    s^   dd l   fdd |j D }||v s>t|jd |rTt| d| jj || _	d S )Nr   c                    s(   g | ] }|j  jj jjfvr|jqS r   )r6   Z	ParameterZVAR_KEYWORDZVAR_POSITIONALr)   )r#   Zparaminspectr   r   r,     s   
z.MetaAttribute.__set_name__.<locals>.<listcomp>r(   z not allowed as )
r   Z	signature
parametersvaluesru   __mro__ro   rK   r   r)   )rY   rw   r)   paramsr   r   r   __set_name__  s
    zMetaAttribute.__set_name__c                 C   s    d| j j d| j d| j dS )N<z name=z	 default=>)rK   r   r)   r{   r^   r   r   r   __repr__  s    zMetaAttribute.__repr__)N)
r   r   r   rL   r]   rx   rz   r   r   r   r   r   r   r   r     s   

r   )$rL   	functoolsr   rn   collectionsr   collections.abcr   rr   r   Znumpyr   Zastropy.utils.exceptionsr   Zastropy.utils.miscr   __all__ry   r   r	   r
   r   r"   r   r   r   r   rR   rU   rV   r   rh   ri   r   r   r   r   r   r   r   <module>   s:   +":;
D.