
    ^j                     ,   d Z ddlZddlZddlZddlZddlZddlZddlZddlm	Z	 ddl
mZmZmZmZ ddlmZ ddlmZmZ ddlmZmZmZmZmZ erddlmZ dd	lmZmZmZ dd
lm Z m!Z! ddl"m#Z#  G d de$ej                        Z% G d de$ej                        Z&de$de$fdZ'de$ddfdZ( G d de      Z) ed       G d d             Z*e*jV                  Z, ee,      de*de-de-ddfd       Z.e.e*_+        d e/e$ef   de$fd!Z0d"ede1e/e$ef      fd#Z2d$ede*fd%Z3d$ed&e$ddfd'Z4d(e$de$fd)Z5d*e1e*   d&e$de1e$   fd+Z6	 	 	 dPd,eee$f   d&ee$   d-e7d.e7de1e$   f
d/Z8	 	 dQd,eee$f   d-e7d.e7de1e*   fd0Z9d,eee$f   d&ee$   de/ee$   ed   f   fd1Z:de$ded   fd2Z;d3e*de$fd4Z<d5ede$fd6Z=ddd7d8ed3e*d9ee$   d:ee$   de$f
d;Z>d3e*de$fd<Z?d3e*de/e$e$e$e7e$f   fd=Z@d>d?dee$   d@e$de$fdAZAd3e*d&ed   de&fdBZBdde%j                  dfddCd,eee$f   d&ee$   d-e7dDee%e$f   d.e7dEee1d      de$fdFZD	 dRd,eee$f   d-e7de1e*   fdGZEddHlFmGZG  eGe3dIdJK      d$ede*fdL       ZH eGe9dIdJK      	 dRd,eee$f   d-e7de1e*   fdM       ZI e!e*dIdJK       G dN dO             ZJy)Su  Audit tools for deprecation lifecycle management.

This module provides three complementary utilities for verifying the health of deprecated callables across a codebase.
All three are designed to be called from pytest or a CI script against an imported package.

**Wrapper configuration** (:func:`~deprecate.audit.validate_deprecation_wrapper`,
:func:`~deprecate.audit.find_deprecation_wrappers`):
    Detect wrappers that have zero impact — invalid ``args_mapping`` keys, identity mappings, empty mappings, or a
    ``target`` pointing back to the same wrapper.

**Expiry enforcement** (:func:`~deprecate.audit.validate_deprecation_expiry`):
    Detect wrappers whose ``remove_in`` version has been reached or passed, preventing zombie code from shipping past
    its scheduled removal deadline.

**Chain detection** (:func:`~deprecate.audit.validate_deprecation_chains`):
    Detect wrappers whose ``target`` is itself a deprecated callable, forming a chain that users traverse
    unnecessarily. Two chain kinds are reported via :class:`~deprecate.audit.ChainType`: ``TARGET`` (forwarding chain)
    and ``STACKED`` (composed argument mappings).

**Report generation** (:func:`~deprecate.audit.generate_deprecation_table`):
    Generate a docs-friendly markdown summary from wrapper metadata.

Results are returned as :class:`~deprecate.audit.DeprecationWrapperInfo` dataclasses, which carry both
identification info and structured validation results for programmatic processing.

!!! note
    :func:`~deprecate.audit.validate_deprecation_expiry` requires the ``packaging`` library for PEP 440
    version comparison. Install with: ``pip install pyDeprecate[audit]``

Copyright (C) 2020-2026 Jiri Borovec <6035284+Borda@users.noreply.github.com>

    N)suppress)	dataclassfieldis_dataclassreplace)Enum)cached_propertywraps)TYPE_CHECKINGAnyCallableOptionalUnion)Version)DeprecationConfig
TargetMode_has_deprecation_meta)_DeprecatedProxydeprecated_class)!get_func_arguments_types_defaultsc                       e Zd ZdZdZdZy)
TableStylezVMarkdown table layout produced by :func:`~deprecate.audit.generate_deprecation_table`.compactmatrixN)__name__
__module____qualname____doc__COMPACTMATRIX     Z/var/www/ramen.bs-engineer-server.com/venv/lib/python3.12/site-packages/deprecate/audit.pyr   r   >   s    `GFr"   r   c                   0    e Zd ZdZdZdZdZdZdZdZ	dZ
d	Zy
)DeprecationStatusuL  Lifecycle status labels used in the deprecation report's *Current Status* column.

    Each member's value is the full display string (emoji + text) rendered in the table.
    Using a ``str`` enum means members compare equal to their string values and can be
    returned wherever a plain string is expected.

    Members are ordered from least to most urgent for easy visual scanning:

    Examples:
        >>> DeprecationStatus.ACTIVE_WARNING.value
        '📢 Deprecation Active'
        >>> DeprecationStatus.PAST_REMOVAL_DATE > DeprecationStatus.ACTIVE_WARNING
        False

    u   🕒 Scheduled Deprecationu   ℹ️ No Removal Targetu   ⚪ Status Unknownu   ⚪ Invalid Removal Targetu   📢 Deprecation Activeu   ⏰ Removal Imminentu   🔔 Remove Before Releaseu   💥 Past Removal DateN)r   r   r   r   SCHEDULED_DEPRECATIONNO_REMOVAL_TARGETSTATUS_UNKNOWNINVALID_REMOVAL_TARGETACTIVE_WARNINGREMOVAL_IMMINENTREMOVE_BEFORE_RELEASEPAST_REMOVAL_DATEr!   r"   r#   r%   r%   E   s7      92)N9.N-80r"   r%   versionreturnc                     | j                  d      }t        j                  dt        j                        }|j	                  d |      S )u  Normalize non-standard version strings before PEP 440 parsing.

    Newer ``packaging`` (>=22) is strict PEP 440 and rejects real-world strings that omit trailing digits
    on pre/post/dev release labels (e.g. ``"1.8.0.dev"``, ``"1.8.0dev"``, ``"1.8.0.post"``). This helper
    performs the minimum normalization needed to make such strings parseable, then defers everything else
    (label aliasing like ``alpha`` -> ``a``, case folding, separator handling) to ``packaging.Version``.

    The transformation is conservative:

    1. Strip a single leading ``v`` or ``V`` prefix (``packaging`` accepts this, but stripping defensively
       keeps the normalized output stable for downstream callers).
    2. Append ``0`` to bare pre/post/dev labels that lack a trailing digit. Labels recognized:
       ``dev``, ``rc``, ``a``, ``b``, ``c``, ``alpha``, ``beta``, ``preview``, ``post``.

    No other transformations are applied — case, separators, and label aliases pass through unchanged
    so ``packaging.Version`` can apply its own canonicalization.

    Args:
        version: Raw version string, possibly missing trailing digits on labels.

    Returns:
        Normalized version string ready to be passed to ``packaging.version.Version``.

    Examples:
        >>> _normalize_version_string("1.8.0.dev")
        '1.8.0.dev0'
        >>> _normalize_version_string("1.8.0dev")
        '1.8.0dev0'
        >>> _normalize_version_string("1.8.0.post")
        '1.8.0.post0'
        >>> _normalize_version_string("v1.2.3")
        '1.2.3'
        >>> _normalize_version_string("1.8.0.RC1")
        '1.8.0.RC1'
        >>> _normalize_version_string("1.2.3")
        '1.2.3'

    vVzJ(?P<sep>\.?)(?P<label>alpha|beta|preview|post|dev|rc|a|b|c)(?![A-Za-z0-9])c                 L    | j                  d       | j                  d       dS )Nseplabel0)group)ms    r#   <lambda>z+_normalize_version_string.<locals>.<lambda>   s$    AGGEN#3AGGG4D3EQ!G r"   )lstriprecompile
IGNORECASEsub)r.   
normalizedpatterns      r#   _normalize_version_stringr@   `   sA    N %J jjU
G ;;GTTr"   version_stringr   c                     	 ddl m}m} 	  |t	        |             S # t        $ r}t        d      |d}~ww xY w# |$ r}t        d|  d|       |d}~ww xY w)a  Parse a version string using the packaging library (PEP 440 compliant).

    This function requires the 'packaging' library, which is available as an optional dependency via the 'audit'
    extra: ``pip install pyDeprecate[audit]``

    The packaging library provides robust PEP 440 version parsing and comparison, supporting pre-releases
    (alpha/beta/rc), stable releases, post-releases, and development releases with proper ordering.

    Inputs are first passed through :func:`_normalize_version_string`, which appends ``0`` to bare
    pre/post/dev labels (e.g. ``"1.8.0.dev"`` becomes ``"1.8.0.dev0"``) so non-canonical-but-common
    strings parse successfully under strict ``packaging`` (>=22).

    Args:
        version_string: Version string (e.g., "1.2.3", "2.0", "1.5.0a1", "1.5.0rc1", "1.5.0.post1").

    Returns:
        packaging.version.Version object that supports comparison operations.

    Raises:
        ImportError: If the packaging library is not installed.
        ValueError: If the version string is not valid per PEP 440
            (wraps ``packaging.version.InvalidVersion`` with additional context).

    Example:
        >>> import importlib; importlib.import_module("packaging")  # doctest: +ELLIPSIS
        <module 'packaging' ...>
        >>> v1 = _parse_version("1.2.3")
        >>> v2 = _parse_version("2.0")
        >>> v1 < v2
        True
        >>> _parse_version("1.5.0a1") < _parse_version("1.5.0")
        True
        >>> _parse_version("1.8.0.dev") < _parse_version("1.8.0")
        True
        >>> _parse_version("1.8.0.post") > _parse_version("1.8.0")
        True

    !!! note
        Install the audit extra to use version comparison features:
        ``pip install pyDeprecate[audit]``

    r   )InvalidVersionr   zaVersion comparison requires the 'packaging' library. Install with: pip install pyDeprecate[audit]NzFailed to parse version 'zE'. Expected PEP 440 format (e.g., '1.2.3', '2.0', '1.5.0a1'). Error: )packaging.versionrC   r   ImportErrorr@   
ValueError)rA   rC   r   errs       r#   _parse_versionrH      s    V=0@AA  o
	  ''7 899<?
 	s$    9 	616AAAc                       e Zd ZdZdZdZy)	ChainTypeu   Type of deprecation chain detected by :func:`~deprecate.audit.validate_deprecation_chains`.

    Attributes:
        TARGET: The ``target`` argument is itself a callable decorated with :func:`~deprecate.deprecated`
            (a forwarding chain). Fix by pointing directly to the final non-deprecated target.
        STACKED: Arg mappings chain and must be composed/collapsed. Two sub-cases:
            (a) Callable ``target`` is itself ``@deprecated(True, args_mapping=...)`` — the
            caller's mapping feeds into the target's self-renaming, so both hops must be
            collapsed into one. (b) Multiple ``@deprecated(True, args_mapping=...)`` decorators
            are stacked on the same function and should be merged into a single decorator.

    targetstackedN)r   r   r   r   TARGETSTACKEDr!   r"   r#   rJ   rJ      s     FGr"   rJ   T)frozenc                   ^   e Zd ZU dZdZeed<   dZeed<    ee	      Z
e	ed<    ee      Zee   ed<   dZeed	<    ee      Zee   ed
<   dZeed<   dZeed<   dZeed<   dZeed<   dZee   ed<    edd      Zeed<    edd      Zeed<   ddZedefd       Zedee   fd       Zy)DeprecationWrapperInfoa  Information about a deprecated wrapper and its validation results.

    This dataclass represents a deprecated wrapper (a :func:`~deprecate.deprecated`-decorated function or a
    :func:`~deprecate.proxy.deprecated_class`/:func:`~deprecate.proxy.deprecated_instance` proxy), containing both
    identification info and validation results from :func:`~deprecate.audit.validate_deprecation_wrapper` or
    :func:`~deprecate.audit.find_deprecation_wrappers`.

    Attributes:
        module: Module name where the wrapper is defined (empty for direct validation).
        function: Wrapper name.
        deprecated_info: The ``__deprecated__`` attribute from the decorator,
            as a :class:`~deprecate._types.DeprecationConfig`.
        invalid_args: List of ``args_mapping`` keys that don't exist in the wrapper's signature.
        empty_args_mapping: True if ``args_mapping`` is None or empty (no argument remapping).
        identity_args_mapping: List of args where key equals value (e.g., ``{'arg': 'arg'}``).
        self_reference: True if target points to the same wrapper.
        no_effect: True if wrapper has zero impact (combines all checks).
        all_identity: True when every configured mapping is an identity mapping (key == value, non-empty).
        chain_type: The kind of deprecation chain detected, or ``None`` if no chain.
            See :class:`~deprecate.audit.ChainType` for values
            (:attr:`~deprecate.audit.ChainType.TARGET` or :attr:`~deprecate.audit.ChainType.STACKED`).
        misconfigured_target: True when the wrapper has an invalid target configuration:
            ``target=False``, :attr:`~deprecate._types.TargetMode.NOTIFY` with ``args_mapping``, or
            :attr:`~deprecate._types.TargetMode.ARGS_REMAP` with empty ``args_mapping``.
        empty_deprecated_in: True when ``deprecated_in`` is empty. Missing ``remove_in`` alone is a valid use case
            (many libraries deprecate without a scheduled removal date), so only the absence of ``deprecated_in``
            is treated as a misconfiguration signal. CI pipelines can filter on this field to surface wrappers
            that lack the introductory version metadata without crashing callers.
        api_type: Inferred deprecated API type for report generation.
            Possible values: ``callable``, ``args``, ``class``, ``dataclass``, ``dataclass attributes``,
            ``data``, ``class constructor``, ``class constructor args``, ``class method``, ``class method args``,
            ``classmethod``, ``classmethod args``, ``staticmethod``, ``staticmethod args``.

    Example:
        >>> info = DeprecationWrapperInfo(
        ...     module="my_package.module",
        ...     function="old_function",
        ...     deprecated_info=DeprecationConfig(deprecated_in="1.0", remove_in="2.0"),
        ...     invalid_args=["nonexistent"],
        ...     no_effect=True,
        ... )
        >>> info.function
        'old_function'
        >>> info.invalid_args
        ['nonexistent']

     modulefunction)default_factorydeprecated_infoinvalid_argsFempty_args_mappingidentity_args_mappingself_reference	no_effectmisconfigured_targetall_identityN
chain_type)initdefaultempty_deprecated_in)reprr`   api_typer/   c                 \    t         j                  | d| j                  j                          y)zMDerive ``empty_deprecated_in`` from ``deprecated_info`` to keep them in sync.ra   N)object__setattr__rV   deprecated_inselfs    r#   __post_init__z$DeprecationWrapperInfo.__post_init__!  s$    4!6D<P<P<^<^8^_r"   c                 R    t        j                  dt        d       | j                  S )a  Deprecated alias for :attr:`~deprecate.audit.DeprecationWrapperInfo.empty_args_mapping`.

        !!! warning "Deprecated in 0.8"
            Renamed to :attr:`~deprecate.audit.DeprecationWrapperInfo.empty_args_mapping`.
            Will be removed in v1.0.

        Note:
            Python's default warning filter deduplicates per ``(message, category, module, lineno)``,
            so accessing this property in a loop from the same call site emits at most one warning.

        zV'empty_mapping' was renamed to 'empty_args_mapping' in 0.8 and will be removed in 1.0.   
stacklevel)warningswarnDeprecationWarningrX   rh   s    r#   empty_mappingz$DeprecationWrapperInfo.empty_mapping%  s'     	d	

 &&&r"   c                 R    t        j                  dt        d       | j                  S )a  Deprecated alias for :attr:`~deprecate.audit.DeprecationWrapperInfo.identity_args_mapping`.

        !!! warning "Deprecated in 0.8"
            Renamed to :attr:`~deprecate.audit.DeprecationWrapperInfo.identity_args_mapping`.
            Will be removed in v1.0.

        Note:
            Python's default warning filter deduplicates per ``(message, category, module, lineno)``,
            so accessing this property in a loop from the same call site emits at most one warning.

        z\'identity_mapping' was renamed to 'identity_args_mapping' in 0.8 and will be removed in 1.0.rl   rm   )ro   rp   rq   rY   rh   s    r#   identity_mappingz'DeprecationWrapperInfo.identity_mapping9  s'     	j	

 )))r"   )r/   N)r   r   r   r   rS   str__annotations__rT   r   r   rV   listrW   rX   boolrY   rZ   r[   r\   r]   r^   r   rJ   ra   rc   rj   propertyrr   rt   r!   r"   r#   rQ   rQ      s    .` FCHc).?P)QO&Q#D9L$s)9$$',T'B49B ND It!&$&L$&*J#* %5% @@ub1Hc1` 't ' '& *$s) * *r"   rQ   ri   argskwargsc                     dD ]X  \  }}||v st        j                  d| d| dt        d       |j                  |      }||v r|j                  |       |||<   Z t	        | g|i | y)zIWrap the auto-generated ``__init__`` to accept legacy constructor kwargs.))rr   rX   )rt   rY   'z' was renamed to 'zJ' in 0.8 and will be removed in 1.0. Update your code to use the new name.rl   rm   N)ro   rp   rq   pop_dwi_orig_init)ri   rz   r{   oldnew	old_values         r#   _dwi_compat_initr   c  s     $S &=MMC5*3% 09 9"	 

3If} 

3#F3K#$$ 4)$)&)r"   itemc                     | d   S )z$Extract the member name for sorting.r   r!   )r   s    r#   _member_name_keyr   ~  s    7Nr"   objc           	      *   t        t        dd      }t        |      r ||       S t        |       }g }|D ]A  }t	        t
              5  |j                  |t        j                  | |      f       ddd       C t        |t              S # 1 sw Y   ^xY w)zReturn members without triggering dynamic ``getattr`` side effects.

    Uses ``inspect.getmembers_static`` when available (Python 3.11+). For Python
    3.9/3.10 compatibility, falls back to ``dir()`` + ``inspect.getattr_static``.

    getmembers_staticNkey)
getattrinspectcallabledirr   AttributeErrorappendgetattr_staticsortedr   )r   r   namesmembersnames        r#   _getmembers_static_compatr     s      )<dC!" %%HE%'G Fn% 	FNND'"8"8d"CDE	F 	FF '/00	F 	Fs   (B		B	funcc                    t        |       st        dt        | d|        d      | j                  }|j                  }|j
                  }g }| }g }||| u nd}|t        j                  u }|t        j                  u }	d}
t        |      rVt        |      rK|j                  j
                  }|t        j                  u }|rt        j                  nt        j                  }
nX|rVt        | dd      }|Gt        |      r<|j                  j
                  }|du s|t        j                  u rt        j                  }
d}|rt        | t              rg }n/t        |       D cg c]  }|d   	 }}|D cg c]	  }||vs| }}|j!                         D cg c]  \  }}||k(  s| }}}t#        |      t#        |      k(  xr t#        |      dkD  }|xs |}|xs
 |xr |xs |}t%        t        |d	d            xs |	xr t%        |      xs |xr |}|j&                  xs t        | dt)        |             }t+        ||||||||||


      S c c}w c c}w c c}}w )a  Validate if a deprecated wrapper configuration is effective.

    This is a development tool to check if deprecated wrappers are configured correctly and will have the intended
    effect. It examines the ``__deprecated__`` attribute set by the :func:`~deprecate.deprecated` decorator and
    identifies
    configurations that would result in zero impact:

    - args_mapping keys that don't exist in the function's signature
    - Empty or None args_mapping (no argument remapping)
    - Identity mappings where key equals value (e.g., {'arg': 'arg'})
    - Target pointing to the same function (self-reference)
    - target=None with no args_mapping (just warns, no forwarding)

    Args:
        func: The decorated function to validate. Must have a ``__deprecated__`` attribute set by the ``@deprecated``
            decorator.

    Returns:
        :class:`~deprecate.audit.DeprecationWrapperInfo`: Dataclass with validation results:
            - function: Name of the wrapper being validated
            - deprecated_info: The typed :class:`~deprecate._types.DeprecationConfig` metadata from ``__deprecated__``
            - invalid_args: List of args_mapping keys not in wrapper signature
            - empty_args_mapping: True if args_mapping is None or empty
            - identity_args_mapping: List of args where key equals value (no effect)
            - self_reference: True if target is the same as the wrapper
            - no_effect: True if wrapper has zero impact (all checks combined)
            - empty_deprecated_in: True when ``deprecated_in`` is absent or empty

    Raises:
        ValueError: If the wrapper has missing or invalid ``__deprecated__`` metadata (expected
            :class:`~deprecate._types.DeprecationConfig`).

    Example:
        >>> from deprecate import deprecated, validate_deprecation_wrapper
        >>> def new_implementation(value: int) -> int:
        ...     return value * 2
        >>>
        >>> @deprecated(target=new_implementation, deprecated_in="1.0", args_mapping={"old_val": "value"})
        ... def old_func(old_val: int) -> int:
        ...     pass
        >>>
        >>> # Valid mapping to different function - has effect
        >>> result = validate_deprecation_wrapper(old_func)
        >>> result.no_effect
        False
        >>> result.invalid_args
        []

        >>> @deprecated(target=True, deprecated_in="1.0", args_mapping={"arg": "arg"})
        ... def identity_func(arg: int) -> int:
        ...     return arg
        >>>
        >>> # Identity mapping with self-deprecation - no effect
        >>> result = validate_deprecation_wrapper(identity_func)
        >>> result.identity_args_mapping
        ['arg']
        >>> result.no_effect
        True

    Note:
        Use this function during development or in CI to ensure deprecation decorators are configured meaningfully.
        Invalid configurations won't cause runtime errors but will silently have no effect.

    z	Function r   z{ has missing or invalid `__deprecated__` metadata. Expected `DeprecationConfig`; ensure it is decorated with `@deprecated`.NF__wrapped__Tr   misconfigured)
rT   rV   rW   rX   rY   rZ   r[   r\   r]   r^   )r   rF   r   __deprecated__args_mappingrK   r   
ARGS_REMAPNOTIFYr   rJ   rN   rM   
isinstancer   r   itemslenrx   r   ru   rQ   )r   dep_infor   rK   rW   rX   rY   rZ   _is_args_remap
_is_notifyr^   wrp_depr_tgt
is_stackedwrappederp_depr_tgtr]   arg	func_argsvalis_self_deprecationr[   r\   rT   s                          r#   validate_deprecation_wrapperr     s   D !&j$78 9W W
 	

 ""H((L__F L))')'-'9Vt^uN z444N:,,,J&*J1&9,,33 "Z%:%::
*4Y&&):J:J
	$t4#8#A"1188Lt#|z7L7L'L&..
Ld,-L+LT+RSCQSIS+7PC3i;OCPLP5A5G5G5I XcSTWZ X X01S5FF`3|K\_`K` ):N`#6#_<N<^R^I 	WX67 	3-4-	311  }}Dj#d) DH! !-3%1! 7 TP Xs   I	.	I8IIIcurrent_versionc           	         t        |       }|j                  j                  }|st        d|j                   d      	 t        |      }	 t        |      }||k\  rt        d|j                   d	| d
| d      y# t        $ r}t        d| d|       |d}~ww xY w# t        $ r$}t        d| d|j                   d|       |d}~ww xY w)a5  Check if a deprecated wrapper has passed its scheduled removal version.

    This is an internal helper function used by :func:`~deprecate.audit.validate_deprecation_expiry`.
    It verifies that deprecated code is actually removed when it reaches its scheduled removal deadline.

    The function validates that the wrapper is properly decorated, extracts the removal version from its metadata,
    and compares it against the current version using semantic versioning. If the current version is greater than or
    equal to the scheduled removal version, it raises an AssertionError indicating the code must be deleted.

    Args:
        func: The deprecated callable to check. Must have a ``__deprecated__`` attribute set by the ``@deprecated``
            decorator.
        current_version: The current version of the package (e.g., "2.0.0"). Should follow PEP 440 versioning
            conventions.

    Raises:
        ValueError: If the wrapper has missing or invalid ``__deprecated__`` metadata (expected
            :class:`~deprecate._types.DeprecationConfig`).
        ValueError: If the ``remove_in`` field is missing from the deprecation metadata.
        AssertionError: If the current version is greater than or equal to the scheduled removal version, indicating
            the code should have been removed.

    
Callable `zL` does not have a 'remove_in' version specified in its deprecation metadata.Invalid current_version '': NzInvalid remove_in 'z' for callable `z`: '` was scheduled for removal in version  but still exists in version %. Please delete this deprecated code.)r   rV   	remove_inrF   rT   rH   AssertionError)r   r   infor   current_verrG   
remove_vers          r#    _check_deprecated_wrapper_expiryr   ,  s   2 (-D $$..I'st
 	
Y$_5l#I.

 j 'Nyk Z++:*;;`b
 	
 !  Y4_4ESNOUXXY
  l.yk9I$--X[\_[`abhkkls/   A: 
B :	BBB	C
&CC
package_namec                 `   t        t              5  t        j                  j	                  |       cddd       S # 1 sw Y   nxY wt        t              5  t        j
                  |       }t        |d      r|j                  cddd       S 	 ddd       n# 1 sw Y   nxY wt        d|  d      )au  Auto-detect the installed version of a package.

    This private helper function attempts to retrieve the version of an installed package using importlib.metadata,
    with a fallback to checking the package's ``__version__`` attribute. This is useful for automatically detecting
    the current version of a package when checking deprecation expiry.

    Args:
        package_name: Name of the package to get the version for (e.g., "numpy", "mypackage").

    Returns:
        The version string of the installed package.

    Raises:
        ImportError: If the package is not installed or version cannot be determined.

    N__version__z)Could not determine version for package 'z<'. Ensure the package is installed and has version metadata.)	r   	Exception	importlibmetadatar.   import_modulehasattrr   rE   )r   rS   s     r#   _get_package_versionr   b  s    $ 
)	 8!!)),78 8 8 
)	 &((66=)%%& &)& & & 
3L> BD 	E s   9A-BBresultsc           
          t        |      }g }| D ]R  }|j                  j                  }|s	 t        |      }||k\  s.|j	                  d|j
                   d| d| d       T |S # t        $ r Y bw xY w)a  Apply expiry comparison to pre-scanned wrapper results.

    Shared implementation used by :func:`validate_deprecation_expiry` and the CLI's single-scan path. Keeps the
    error message format in one place.

    Args:
        results: Pre-scanned wrapper info list.
        current_version: Current package version string for comparison (PEP 440).

    Returns:
        List of expiry error messages for callables that have passed their removal deadline.

    Raises:
        ImportError: If the ``packaging`` library is not installed.

    r   r   r   r   )rH   rV   r   rF   r   rT   )r   r   r   expiredr   r   r   s          r#   _check_expiry_for_callablesr     s    " !1KG ((22		'	2J *$NNT]]O+RS\R]//@@eg N  		s   A''	A32A3rS   	recursiveinclude_membersc                 t   t        | t              r| nt        | dd      }|,|st        d      |j	                  d      d   }t        |      }	 t        |       t        | t              rt        j                  |       } t        t        | ||      |      S # t        $ r}t        d| d|       |d}~ww xY w)	a
  Check all deprecated callables in a module/package for expired removal deadlines.

    This enforcement tool scans an entire module or package for deprecated functions and checks if any have passed
    their scheduled removal version. It's designed for CI/CD pipelines to automatically detect and report zombie code
    across a codebase.

    The function uses :func:`~deprecate.audit.find_deprecation_wrappers` to discover all deprecated wrappers,
    then checks each one against
    the current version. Any wrappers that have reached or passed their removal deadline are collected and reported.

    Args:
        module: A Python module or package to scan. Can be:
            - Imported module object (e.g., ``import my_package; validate_deprecation_expiry(my_package, "2.0")``)
            - String module path (e.g., ``validate_deprecation_expiry("my_package.submodule", "2.0")``)
        current_version: The current version of your package to compare against removal deadlines (e.g., ``"2.0.0"``).
            If None, attempts to auto-detect the version using the package name from the module path (e.g.,
            ``"mypackage"`` extracts ``mypackage`` as package name).
        recursive: If True (default), recursively scan submodules. If False, only scan the top-level module.
        include_members: If True, also scan deprecated class members (methods, constructors).

    Returns:
        List of error messages for callables that have expired (past their removal deadline).
        Empty list if all deprecated callables are still within their deprecation period.

    Example:
        >>> # Check a specific module with version before any deadlines
        >>> from deprecate import validate_deprecation_expiry
        >>> expired = validate_deprecation_expiry("tests.collection_deprecate", "0.1", recursive=False)
        >>> len(expired)
        0

        >>> # Check with version past some removal deadlines
        >>> expired = validate_deprecation_expiry("tests.collection_deprecate", "0.5", recursive=False)
        >>> print(len(expired))  # Some functions have remove_in="0.5"
        28

    !!! note
        - Skips callables without a ``remove_in`` field (warnings only, no removal deadline)
        - Skips callables that cannot be imported or accessed
        - Silently skips callables with invalid ``remove_in`` version formats
        - Uses semantic versioning comparison (e.g., "1.2.3" vs "2.0.0")
        - Intended for automated checks in CI/CD pipelines
        - Can be integrated into test suites or pre-commit hooks

    r   NzoCannot auto-detect version: module object has no __name__ attribute. Please provide current_version explicitly..r   r   r   r   r   )r   ru   r   rF   splitr   rH   r   r   r   find_deprecation_wrappers)rS   r   r   r   module_namer   rG   s          r#   validate_deprecation_expiryr     s    h 'vs3&UY9ZK = 
 #((-a0.|<Y'
 &#((0&!&I_ap   Y4_4ESNOUXXYs   B 	B7 B22B7c                   	
 g t        | t              rt        j                  |       } ddddt        dt        dt        dt
        t           dt
        t           ddffd		d
t        dt        dt        ddf	fd
dt        ddf
fd} ||        |rt        | d      r	 t        t        j                  | j                  | j                  dz   d             }|D ]@  \  }}}t        t        t              5  t        j                  |      } ||       ddd       B S # t        t        f$ r g }Y Zw xY w# 1 sw Y   exY w)a
  Scan a module or package for deprecated wrappers and validate them.

    This is a development/CI tool to scan a codebase for all wrappers created with :func:`~deprecate.deprecated`,
    :func:`~deprecate.deprecated_class`, or :func:`~deprecate.deprecated_instance` and validate that each wrapper
    configuration is meaningful.
    Returns comprehensive information about each deprecated wrapper including validation results that help identify
    misconfigured wrappers.

    Args:
        module: A Python module or package to scan for deprecated wrappers. Can be:
            - Imported module object (e.g., ``import my_package; find_deprecation_wrappers(my_package)``)
            - String module path (e.g., ``find_deprecation_wrappers("my_package.submodule")``)
        recursive: If True (default), recursively scan submodules. If False, only scan the top-level module.
        include_members: If True, also scan deprecated methods and constructors defined on classes.

    Returns:
        List of :class:`~deprecate.audit.DeprecationWrapperInfo` dataclasses, one per deprecated wrapper found.
        Each contains:
            - module: Module name where the wrapper is defined
            - function: Wrapper name
            - deprecated_info: DeprecationConfig metadata from the decorator (``__deprecated__`` attribute)
            - invalid_args: List of args_mapping keys not in wrapper signature
            - empty_args_mapping: True if args_mapping is None or empty
            - identity_args_mapping: List of identity mappings (key == value)
            - self_reference: True if target points to same wrapper
            - no_effect: True if wrapper has zero impact

    Example:
        >>> from deprecate import find_deprecation_wrappers
        >>> from tests import collection_deprecate as my_package
        >>>
        >>> results = find_deprecation_wrappers(my_package)
        >>> print(len(results) > 0)  # Should find deprecated wrappers
        True
        >>> # Also works with string module paths
        >>> results = find_deprecation_wrappers("tests.collection_deprecate")
        >>> print(len(results) > 0)
        True

        >>> # Filter to find only problematic wrappers
        >>> problematic = [r for r in results if r.invalid_args or r.no_effect]
        >>> print(len(results) > 0)  # May or may not have problematic ones
        True

    Note:
        - Requires that the module be importable
        - Inspects the ``__deprecated__`` attribute set by the :func:`~deprecate.deprecated` decorator
        - Skips private/magic attributes and imports from other modules
        - Uses static member inspection to avoid scan-time side effects from dynamic attribute access

    Nmember_namedescriptor_kindr   r   qualified_namer   r   r/   c                    t        |       r;t        |       }t        | |||      }t        ||||      }j	                  |       yy)z=Emit a result if ``obj`` carries ``__deprecated__`` metadata.r   rS   rT   rc   N)r   r   _classify_wrapper_api_typer   r   )r   r   r   r   r   r   rc   r   s          r#   _scan_callablez1find_deprecation_wrappers.<locals>._scan_callable5  sJ     !%/4D1#tfuvH4nW_`DNN4 	 &r"   clscls_namec                    	 t        |       }|D ]  \  }}|j                  d      r|dk7  r| d| }t	        |t
        t        f      r,t	        |t
              rdnd} |j                  ||||       ft	        |t              r$|j                   |j                  |||       t	        |t              r |j                  |||        ||||        y# t        t        f$ r Y yw xY w)	z0Scan class members, peeking through descriptors.N___init__r   classmethodstaticmethodr   )r   )r   r   	TypeError
startswithr   r   r   __func__ry   fgetr	   r   )	r   r   r   r   	attr_namer   	qualifiedkindr   s	           r#   _scan_classz.find_deprecation_wrappers.<locals>._scan_classD  s    	/4G & 	SNIs##C(Y*-D#*Ai[1I#\:;(23(D}.s||[)QZlpqC*88'"388[)QZ[C1sxxiYWsK	R	S 	* 		s   C   C21C2modc                    	 t        |       }t	        | d      r| j
                  n
t        |       }|D ]  \  }}|j                  d      rt        |      r8t        |      }t        ||      }t        ||||      }	j                  |       [s^t        j                  |      stt        |dd      |k(  s |||        y# t        t        t        f$ r Y yw xY w)z@Scan a single module for deprecated functions and class members.Nr   r   r   r   )r   r   r   rE   r   r   ru   r   r   r   r   r   r   r   isclassr   )
r   r   mod_namer   r   r   rc   r   r   r   s
          r#   _scan_modulez/find_deprecation_wrappers.<locals>._scan_moduleZ  s    	/4G $+3
#;3<<S  	1ID#s#$S)3C85c4@tHthWt$ W__S%9gc<Y]>^bj>jC40	1	 	;7 		s   C CC__path__r   c                      y Nr!   )xs    r#   r8   z+find_deprecation_wrappers.<locals>.<lambda>w  s    r"   )pathprefixonerror)r   ru   r   r   r   r   r   rw   pkgutilwalk_packagesr   r   OSErrorrE   r   ModuleNotFoundError)rS   r   r   r   packages	_importermodname_ispkgsubmodr   r   r   s     `      @@@r#   r   r     sl   p -/G &#((0 &*)-!!! !
 c]! "#! 
!S S3 S# S$ S,1# 1$ 1.  WVZ0	%%6??6??UXCXbpqH +3 	%&Iw+':; %"009V$% %	%
 N % 	H	% %s   %9D% ;D<%D98D9<E	c                h   t        | t              r| nt        | dd      }|}|7|r5t        t              5  t        |j                  d      d         }ddd       |y	 |t        |      fS # 1 sw Y   xY w# t        $ r |dfcY S t        $ r"}|t        d| d|       ||dfcY d}~S d}~ww xY w)zAResolve report version string and optional parsed version object.r   Nr   r   )NNr   r   )	r   ru   r   r   rE   r   r   rH   rF   )rS   r   r   resolved_versionrG   s        r#   _resolve_table_versionr    s     'vs3&UY9ZK&Kk" 	O3K4E4Ec4J14MN	O &0@!AAA	O 	O  &%% &&88ISERSY\\%%&s/   A,A8 ,A58B1B1B,&B1,B1c                 J    | sy	 t        |       S # t        t        f$ r Y yw xY w)z8Best-effort version parser for report status evaluation.N)rH   rE   rF   )r.   s    r#   _safe_parse_versionr    s/    g&&$ s   
 ""r   c                 h    | j                   r| j                    d| j                   S | j                  S )z6Return a stable fully-qualified label for report rows.r   )rS   rT   r   s    r#   _format_report_symbolr    s*    /3{{dkk]!DMM?+MMr"   rK   c                    | | t         j                  u ryt        | t               r| j                  S t	        |       r9t        | dd      }t        | dt        | dt        |                   }|r| d| S |S t        |       S )z/Format replacement target name for report rows.   —r   rR   r   r   r   )r   r   r   valuer   r   ru   )rK   target_moduletarget_names      r#   _format_report_targetr    s    ~:#4#44&*%||b9fngfjRUV\R]6^_3@-+/QkQv;r"   r   wrapped_objr   r   c                p   t        |j                  j                        }|'|dk(  r|rdS dS |dk(  r|rdS dS |dk(  r|rdS dS |rdS d	S t        | t              r4| j
                  }t        j                  |      rt        |      r|rd
S dS yyt        j                  |       rt        |       r|rd
S dS y|ryy)z/Classify wrapper kind for markdown report rows.r   zclass constructor argszclass constructorr   zclassmethod argsr   zstaticmethod argszclass method argszclass methodzdataclass attributesr   classdatarz   r   )	rx   rV   r   r   r   r   r   r   r   )r  r   r   r   has_mapping
source_objs         r#   r   r     s     t++889K*$/:+S@SSm+)4%G-Gn,*5&I>I&1"E~E+/0 ((
??:&J'1<-M+M{#$-8)IkIr"   c                 f    | j                   r| j                   S | j                  j                  rdS dS )z2Return api_type with backward-compatible fallback.rz   r   )rc   rV   r   r  s    r#   _format_report_api_typer    s,    }}}}))666FJFr"   c                     | j                   xs d}|r|j                  dd      d   nd}t        |       }| j                  xs d|||j	                  d      |fS )zMSort report rows by module and symbol family, keeping args-variants adjacent.rR   r      )maxsplitr   z args)rT   r   r  rS   endswith)r   rT   	top_levelrc   s       r#   _report_row_sort_keyr    s[    }}"H6>sQ/2BI&t,HKK2y(H4E4Eg4NPXYYr"   r	  )missingr  c                2    | s|S d| j                  d       S )zCFormat version values with a stable ``v`` prefix for report output.vr1   )r9   )r.   r  s     r#   _format_versionr    s!    w~~d#$%%r"   c                    |6| j                   j                  st        j                  S t        j                  S t        | j                   j                        }|||k  rt        j                  S | j                   j                  }|st        j                  S t        |      }|t        j                  S ||k\  rt        j                  S |j                  rq	 t        |      } ||j                         ||j                        k(  }|r>|j                  "|j                  d   dk(  rt        j                  S t        j                   S t        j"                  S # t        $ r d}Y ]w xY w)z>Classify one deprecated symbol into a report lifecycle status.Fr   rc)rV   r   r%   r'   r(   r  rg   r&   r)   r-   is_prereleasetypebase_versionr   prer,   r+   r*   )r   r   rg   r   remove_version_VersionType	same_bases          r#   _get_deprecation_statusr)    sR    ''11 //	
 #11	
 ((<(<(J(JKM _}%D 666$$..I 222(3N 777.( 222
 $$	0L$_%A%ABlSaSnSnFooI "".?3F3Fq3IT3Q(>>>$555+++  	I	s   0E E$#E$)	_wrappersstyler*  c                   	 t        |      }t        | |      \  }}|t	        | ||      }t        |t        	      }	|t         j                  k(  rd
dg}
|	D ]  }|
j                  dt        |       dt        |       dt        |j                  j                         dt        |j                  j                         dt        |j                  j                          dt#        ||      j$                   d        ni |	D ]I  }|j                  j                  |j                  j                   fD ]  }|s|vst'        |      |<    K t        fd	      }|D cg c]  }t        |       }}ddj                  |      z   dz   }ddj                  d |D              z   dz   }t)        |      D ci c]  \  }}||
 }}}t+        |      }||g}
|	D ]  }dg|z  }|j                  j                  }|j                  j                   }|r||v rd|||   <   |r||v r||   }||   dk(  rdnd||<   |
j                  dt        |       dt        |       dt        |j                  j                         ddj                  |      z   dz           ||
j-                  dd| d       dj                  |
      S # t        $ r2}t        d|ddj                  d t         D               d      |d}~ww xY wc c}w c c}}w )u  Generate a markdown table summarizing deprecated wrappers.

    The table is derived from ``__deprecated__`` metadata and includes both
    top-level wrappers and deprecated class members (methods/constructors).

    Args:
        module: Imported module/package object or string module path to scan.
        current_version: Optional current package version for lifecycle status
            evaluation in compact style. If ``None``, auto-detection is attempted
            via the package name; status falls back to ``"⚪ Status Unknown"`` when
            ``packaging`` is not installed.
        recursive: If True (default), include submodules in the scan.
        style: Table format — ``"compact"`` or ``"matrix"``.
            - ``"compact"``: ``Original API | API Type | New API | Deprecated | Remove | Current Status``
            - ``"matrix"``: ``Original API | API Type | New API | <all versions...>``, with markers
              ``D`` (deprecated) and ``R`` (remove) in version columns.
        include_members: If True (default), include deprecated class members (methods, constructors).

    Returns:
        Markdown string containing a formatted table. When a version is
        resolvable (either from current_version or auto-detected), the
        first line is an HTML comment <!-- Current version: X.Y -->
        followed by the header row and alignment row. When no version can be
        resolved, the first line is the header row directly.

    Raises:
        ValueError: If ``style`` is not ``"compact"`` or ``"matrix"``, or if
            ``current_version`` is supplied but is not a valid PEP 440 version
            string and ``packaging`` is installed.

    Example:
        >>> from tests import collection_deprecate as pkg
        >>> report = generate_deprecation_table(pkg, recursive=False)
        >>> report.splitlines()[0]
        '| Original API | API Type | New API | Deprecated | Remove | Current Status |'

    zInvalid style z. Expected one of: z, c              3   4   K   | ]  }|j                     y wr   )r
  ).0ss     r#   	<genexpr>z-generate_deprecation_table.<locals>.<genexpr>Q  s     B_q177B_s   r   N)r   r   r   zL| Original API | API Type | New API | Deprecated | Remove | Current Status |z-| :--- | :--- | :--- | :---: | :---: | :--- |z| `z` | z | `z | z |c                 ,    |    d u |    |    fS | fS r   r!   )r.   version_maps    r#   r8   z,generate_deprecation_table.<locals>.<lambda>u  s3    G$,(3G(<(HG$! NU! r"   z&| Original API | API Type | New API | z| :--- | :--- | :--- | c              3       K   | ]  }d   yw)z:---:Nr!   )r.  r   s     r#   r0  z-generate_deprecation_table.<locals>.<genexpr>|  s     <^W<^s    DRzD/Rr   z<!-- Current version: z -->
)r   rF   joinr  r   r   r  r   r   r  r  r  rV   rK   r  rg   r   r)  r
  r  	enumerater   insert)rS   r   r   r+  r   r*  rG   r  parsed_versionwrappersrowsr   r.   sorted_versionsversion_headers
header_rowdivider_rowir  col_idx
n_versionsmarkersdep_inrem_inr2  s                           @r#   generate_deprecation_tablerH    s}   \5! (>fVe'f$n-f	[jk	 H
 
"""Z;

  		DKK)$/0*401 2)$*>*>*E*EFGt"4#7#7#E#EFGs"4#7#7#A#ABC3*4@FFGrK		 57 	HD 00>>@T@T@^@^_ Hwk9+>w+GK(H	H
 !
 DSS?73SS=

?@[[^bb
/%**<^o<^2^^aee$-o$>?DAq1a4??)
K( 	D"%!3G))77F))33F&G++.(&G+FO$+AJ#$5S5
KK)$/0*401 2)$*>*>*E*EFGtM PUzzZaObc fjj	  #A/0@/AFG99T?K  UI%8B_T^B_9_8``ab
	V T @s#   K $L;L	K>-K99K>c                 \    t        | |      D cg c]  }|j                  | c}S c c}w )u  Validate that deprecated functions don't form chains with other deprecated code.

    This is a developer utility that scans a module or package for deprecated functions that form chains in two ways:

    1. **TARGET chains**: The ``target`` argument points to another deprecated callable instead of the final
       non-deprecated implementation.
    2. **STACKED chains**: Multiple ``@deprecated(True, ...)`` decorators are stacked on the same function with
       argument mappings that should be collapsed, or a callable ``target`` is itself a self-deprecation
       (``target=True``) requiring mapping composition.

    Both types are wasteful: wrappers should point directly to the final (non-deprecated) implementation with
    composed argument mappings.

    Detection is based purely on decorator metadata (``__deprecated__`` attributes) — no source-code or AST
    inspection is performed.

    Args:
        module: A Python module or package to scan for deprecation chains. Can be:
            - Imported module object (e.g., ``import my_package; validate_deprecation_chains(my_package)``)
            - String module path (e.g., ``validate_deprecation_chains("my_package.submodule")``)
        recursive: If True (default), recursively scan submodules. If False, only scan the top-level module.

    Returns:
        List of :class:`~deprecate.audit.DeprecationWrapperInfo` where ``chain_type`` is not ``None``, i.e. every
        deprecated wrapper that forms a chain (``ChainType.TARGET`` or ``ChainType.STACKED``).

    Example:
        >>> from deprecate import validate_deprecation_chains
        >>> import tests.collection_chains as test_module
        >>>
        >>> issues = validate_deprecation_chains(test_module, recursive=False)
        >>> len(issues) > 0  # Should find chains
        True

    Note:
        - Only flags callees using the :func:`~deprecate.deprecated` decorator
        - Uses :func:`~deprecate.audit.find_deprecation_wrappers` and inspects ``chain_type`` to detect chains

    )r   )r   r^   )rS   r   r   s      r#   validate_deprecation_chainsrJ    s,    V 7vSsTW[WfWfWrDssss   )))
deprecatedz0.6z1.0)rK   rg   r   c                     t        |       S )zBUse :func:`~deprecate.audit.validate_deprecation_wrapper` instead.)r   )r   s    r#   validate_deprecated_callablerM    s     (--r"   c                     t        | |      S )z?Use :func:`~deprecate.audit.find_deprecation_wrappers` instead.)r   )rS   r   s     r#   find_deprecated_callablesrO    s     %VY77r"   c                       e Zd ZdZy)DeprecatedCallableInfozWDeprecated name for :class:`~deprecate.audit.DeprecationWrapperInfo`, use that instead.N)r   r   r   r   r!   r"   r#   rQ  rQ    s    ar"   rQ  )NTF)TT)T)Kr   enumr   importlib.metadatar   r   r:   ro   
contextlibr   dataclassesr   r   r   r   r   	functoolsr	   r
   typingr   r   r   r   r   rD   r   deprecate._typesr   r   r   deprecate.proxyr   r   deprecate.utilsr   ru   r   r%   r@   rH   rJ   rQ   r   r   re   r   tupler   rw   r   r   r   r   r   rx   r   r   r  r  r  r  r   r  r  r  r)  r   rH  rJ  deprecate.deprecationrK  rM  rO  rQ  r!   r"   r#   <module>r]     s  P      	   ? ?  , @ @) Q Q > =dii 1TYY 161Us 1Us 1Uh83 89 8v $ $i* i* i*| (00 ~*1 *& *F *W[ * *. #3  5c? s 
13 14c3h+@ 1&Sx S4J Sl3
8 3
c 3
d 3
ls s D .D)E  X[  `deh`i  J &*!	M#s(OMc]M M 	M
 
#YMd  J#s(OJJ J 

 !	JZ&#s(O& c]& 8C=(9--.	&4 ))< N 6 N3 N

# 
# 
" "&%)##
 # #	#
 c]# 	#LG"8 GS GZ5 Z%S#tUX@X:Y Z ?D &Xc] & & &&,"8 &,8T]K^ &,ct &,V &*$.$6$6 u ;?u#s(Ouc]u u S!	u
 u 567u 	ut +t#s(O+t+t 

 !+th - /uPUV.x .4J . W.
 ,EUS 8#s(O88 

 !8 T8 /uPUVb b Wbr"   