#!/usr/bin/env python3
# --------------------( LICENSE                            )--------------------
# Copyright (c) 2014-2025 Beartype authors.
# See "LICENSE" for further details.

'''
**Beartype import hook API.**

This subpackage publishes :pep:`302`- and :pep:`451`-compliant import hooks
enabling external callers to automatically decorate well-typed third-party
packages and modules with runtime type-checking dynamically generated by the
:func:`beartype.beartype` decorator in a single line of code.
'''

# ....................{ TODO                               }....................
#FIXME: [QA] We need to begin maintaining and supporting an internal beforelist.
#An beforelist is like a blacklist or a whitelist. Rather than simply marking a
#third-party attribute as "bad" or "good", an beforelist marks that the @beartype
#decorator must appear *AFTER* (rather than before, as is the default) another
#third-party decorator. An beforelist is required to handle both Celery and Typer
#(and probably other) third-party decorators, which are so PEP-noncompliant,
#non-standard, and hostile to runtime type-checking that the @beartype decorator
#must appear *AFTER* Celery and Typer decorators.
#
#Supporting an beforelist requires pure static analysis driven by static
#analysis. The core idea is that we need to begin tracking imports *AND* type
#instantiations for these problematic decorators.
#
#En-route to detailing how exactly we implement an beforelist, let's first talk
#about lexical scopes from the perspective of tracking imports. Broadly
#speaking, Python has three types of lexical scopes:
#* Global (i.e., module) scope.
#* Local (i.e., function) scope.
#* Class scope.
#
#For the purposes of tracking imports for problematic decorators:
#* *ONLY* globally scoped imports are of interest. Local and class scopes are
#  almost entirely ignorable. Celery and Typer decorators are *ONLY* applied to
#  globally-scoped functions in real-world code. Thankfully, we already track
#  lexical scopes in our AST transformer with a clever "_scopes_node_type"
#  list. One less thing to worry about.
#* *ONLY* fully-qualified module names are of interest. Since modules can import
#  arbitrary attributes defined by other modules, tracking imports relative to
#  modules is *MUCH* less helpful than tracking absolute imports. The latter can
#  be used to infer the object graph across the entire app. The former cannot.
#  Ergo, the data structures defined below *ONLY* store the fully-qualified
#  names of modules -- enabling detection across module boundaries.
#
#Now, here are the new data structures we require:
#    _BEFORELIST_SCHEMA_MODULE_TO_TYPE_TO_DECORATOR_NAME: (
#        Dict[str, Dict[str, str]]) = FrozenDict({
#        'celery': {'Celery': 'task'},  # <---- unsure if this is quite right!
#        'typer': {'Typer': 'command'},  # <-- unsure if this is quite right!
#    })
#    '''
#    **Beforelist** (i.e., frozen dictionary mapping from the fully-qualified
#    name of each third-party module to the unqualified basename of each type in
#    that module to the unqualified basename of each decorator method of that
#    type which the :func:`beartype.beartype` decorator *must* appear after
#    within the chain of decorators for objects decorated by that decorator).
#
#    This scheme defines the core read-only input to the beforelist algorithm.
#    '''
#
#
#    _BEFORELIST_HOOKED_MODULE_TO_TYPE_TO_DECORATOR_NAME: (
#        Dict[str, Dict[str, str]]) = {}
#    '''
#    **Beforelist types** (i.e., mutable dictionary mapping from the
#    fully-qualified name of each third-party module automatically decorated by
#    a :mod:`beartype.claw` import hook to the unqualified basename of each type
#    defined by another third-party module imported inside the former module to
#    the unqualified basename of each decorator method of that type which the
#    :func:`beartype.beartype` decorator *must* appear after within the chain of
#    decorators for objects decorated by that decorator).
#    '''
#
#
#    _BEFORELIST_HOOKED_MODULE_TO_INSTANCE_TO_DECORATOR_NAME: (
#        Dict[str, Dict[str, str]]) = {}
#    '''
#    **Beforelist instances** (i.e., mutable dictionary mapping from the
#    fully-qualified name of each third-party module automatically decorated by
#    a :mod:`beartype.claw` import hook to the unqualified basename of each
#    global variable instantiating a type defined by another third-party module
#    imported inside the former module to the unqualified basename of each
#    decorator method of that type which the :func:`beartype.beartype` decorator
#    *must* appear after within the chain of decorators for objects decorated by
#    that decorator).
#    '''
#
#*GREAT*. Those are the only three data structures we require. Now, we briefly
#outline how our AST transform leverages these structures:
#1. Detect imports that occur at global scope.
#2. If an import either matches the fully-qualified name of a key of
#   "_BEFORELIST_SCHEMA_MODULE_TO_TYPE_TO_DECORATOR_NAME" (e.g., "import celery")
#   *OR* both the fully-qualified name of a key of that dictionary and the
#   unqualified basename of the corresponding value (e.g., "from celery import
#   Celery"), then:
#   * Add a new key-value pair to
#     "_BEFORELIST_HOOKED_MODULE_TO_TYPE_TO_DECORATOR_NAME" whose:
#     * Key is the fully-qualified name of the *CURRENT* module being visited by
#       this AST transform. Note this is *NOT* the fully-qualified name of the
#       source module being imported from. The latter is now irrelevant and
#       entirely ignorable. Only the *CURRENT* module name matters.
#     * Value is the nested dictionary copied as a shallow reference from
#       "_BEFORELIST_SCHEMA_MODULE_TO_TYPE_TO_DECORATOR_NAME".
#   For example, if a third-party module named "muh_package.muh_submodule"
#   hooked by a "beartype.claw" import hook contains a problematic import "from
#   celery import Celery", then
#   "_BEFORELIST_HOOKED_MODULE_TO_TYPE_TO_DECORATOR_NAME" will now contain a
#   key-value pair resembling:
#       _BEFORELIST_HOOKED_MODULE_TO_TYPE_TO_DECORATOR_NAME = {
#           ...,
#           'muh_package.muh_submodule': {'Celery': 'task'},
#       }
#
#   The nested dictionary "{'Celery': 'task'}" above is simply a shallow
#   reference to the same nested dictionary in
#   "_BEFORELIST_SCHEMA_MODULE_TO_TYPE_TO_DECORATOR_NAME". Easy-peasy so far,
#   right? Thankfully, it doesn't get any harder, either.
#3. Detect variable assignments that occur at global scope.
#4. If an assignment matches the outermost key, innermost key, and associated
#   value of a nested entry of
#   "_BEFORELIST_HOOKED_MODULE_TO_TYPE_TO_DECORATOR_NAME" (e.g., an
#   assignment of the form "app = Celery(...)" in the third-party module
#   "muh_package.muh_submodule"), then add a new key-value pair to
#   "_BEFORELIST_HOOKED_MODULE_TO_INSTANCE_TO_DECORATOR_NAME" in the usual way.
#5. Detect decorator applications that occur at *ANY* scope. Scope no longer
#   matters here. Nested scopes implicitly access global scopes. For decorator
#   detection purposes, there's no benefit to confining our consideration to
#   merely decorators applied at global scope.
#6. If a decorator application matches the outermost key, innermost key, and
#   associated value of a nested entry of
#   "_BEFORELIST_HOOKED_MODULE_TO_INSTANCE_TO_DECORATOR_NAME" (e.g., a
#   decoration of the form "@app.task" in the third-party module
#   "muh_package.muh_submodule"), then this decoration is known to be
#   problematic. Inject the @beartype decorator *AFTER* rather than before this
#   problematic third-party decorator.
#
#Our initial implementation of this functionality will *NOT* be configurable by
#end users. We can consider doing that later. For now, global private
#dictionaries are totally fine. *shrug*
#
#Awesome stuff, huh? We agree. Tracking imports is surprisingly non-trivial. Of
#course, there's a great deal of non-triviality in edge cases -- but the
#approach algorithm demonstrates that this isn't *NEARLY* as awful as we
#initially assumed it would be. Common edge cases include:
#* Annotated variable assignments should be detected. The above simplistic
#  detection of global variable assignments will break down in obvious edge
#  cases (e.g., if the caller defines a utility factory function that creates
#  and returns a "celery.Celery" instance rather than directly calling the
#  celery.Celery() instantiator). To handle this, we should allow users to type
#  global variables as their expected types: e.g.,
#      # @beartype should also detect this as an instance of "celery.Celery":
#      app: Celery = make_celery_app()
#* Likewise, annotated callable returns should also be detected. This is
#  considerably harder. So, our initial implementation should just support
#  annotated variable assignments. Still, we eventually want to handle common
#  edge cases like:
#      def make_celery_app() -> Celery: ...
#
#      # @beartype should also detect this as an instance of "celery.Celery"
#      # *WITHOUT* needing to be explicitly told that "app" is now an instance
#      # of "celery.Celery":
#      app = make_celery_app()
#
#That's about it, though. Fun stuff. Let's do this, bois! \o/
#FIXME: Actually... that's not about it. We weren't far off, but there is a
#*BIT* more we want to do for a first-draft implementation. Notably, we need to
#track instantiated problematic objects and types across modules as well.
#Thankfully, this isn't *TOO* hard. We just need to chain map all of the above:
#    from collections import ChainMap
#    _BEFORELIST_MODULE_TO_ATTR_TO_DECORATOR_NAME: (
#        ChainMap[str, Dict[str, str]]) = ChainMap(
#        _BEFORELIST_SCHEMA_MODULE_TO_TYPE_TO_DECORATOR_NAME,
#        _BEFORELIST_HOOKED_MODULE_TO_TYPE_TO_DECORATOR_NAME,
#        _BEFORELIST_HOOKED_MODULE_TO_INSTANCE_TO_DECORATOR_NAME,
#    )
#
#What does that give us? A highly efficient O(1) constant-time means of
#detecting problematic objects and types. Given that, we revise the above AST
#transform algorithm as follows:
#1. Detect imports that occur at global scope.
#2. If an import matches the outermost key, innermost key, and associated
#   value of a nested entry of "_BEFORELIST_MODULE_TO_ATTR_TO_DECORATOR_NAME",
#   then that import is problematic. We now need to differentiate which of the
#   three above kinds of problematic import that import corresponds to, which we
#   do by:
#   1. Iterating over items of the public
#      "_BEFORELIST_MODULE_TO_ATTR_TO_DECORATOR_NAME.maps" list, each of which is
#      one of the lower-level dictionary globals declared above. Thankfully,
#      each of these globals has a *VERY* similar data structure.
#   2. If the current import matches the outermost key, innermost key, and
#      associated value of a nested entry of the currently iterated dictionary
#      global, perform the associated task already detailed above. Then break!
#   3. Else, silently continue to the next iterated dictionary.
#
#   See also the official "ChainMap" docos at:
#       https://docs.python.org/3/library/collections.html#collections.ChainMap
#3. Else, the current import is *NOT* problematic. Silently ignore this import.
#FIXME: Oh -- and one last thing! No, really. We also need to track problematic
#*MODULE* imports (e.g., "import celery"). Thankfully, this is surprisingly
#trivial. Given the above data structures, we just leverage the existing
#"_BEFORELIST_SCHEMA_MODULE_TO_TYPE_TO_DECORATOR_NAME" dictionary.
#
#If you consider it, an import of a problematic module "import celery" inside
#another previously non-problematic submodule "muh_package.muh_submodule"
#effectively renders the latter into a problematic module as well. That is,
#problematic modules are infectious. This infectiousness can be tracked by
#simply considering each problematic import to add one additional alias to the
#"_BEFORELIST_SCHEMA_MODULE_TO_TYPE_TO_DECORATOR_NAME". For example, after
#detecting and handling the aforementioned problematic import, the
#"_BEFORELIST_SCHEMA_MODULE_TO_TYPE_TO_DECORATOR_NAME" dictionary will resemble:
#    _BEFORELIST_SCHEMA_MODULE_TO_TYPE_TO_DECORATOR_NAME = FrozenDict({
#        'celery': {'Celery': 'task'},
#        'typer': {'Typer': 'command'},
#
#        # This previously non-problematic module has now become problematic!
#        'muh_package.muh_submodule': {'Celery': 'task'},  # <-- so bad, bro
#    })
#
#And... that's literally it. Awesome, eh? \o/
#FIXME: Actually... that's literally *NOT* it. We were close, but didn't quite
#get that right. Module imports effectively dynamically define a new submodule
#of the current source submodule whose name is that submodule. Thus, for
#example, an import of a problematic module "import celery" inside another
#previously non-problematic submodule "muh_package.muh_submodule" effectively
#dynamically defines a new problematic "muh_package.muh_submodule.celery"
#submodule. This can be trivially handled by just adding new entries resembling:
#    _BEFORELIST_SCHEMA_MODULE_TO_TYPE_TO_DECORATOR_NAME = FrozenDict({
#        'celery': {'Celery': 'task'},
#        'typer': {'Typer': 'command'},
#
#        # This new submodule is also known to be problematic!
#        'muh_package.muh_submodule.celery': {'Celery': 'task'},  # <-- so bad, bro
#    })

#FIXME: [QA] Technically, we're not quite done here. The "beartype.claw" API
#currently silently ignores attempts to subject the "beartype" package itself to
#@beartyping. Ideally, that API should instead raise human-readable exceptions
#when users explicitly attempt to do so when calling either the
#beartype_package() or beartype_packages() functions. After implementing that
#functionality, assert that in our test suite, please.

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# WARNING: To avoid polluting the public module namespace, external attributes
# should be locally imported at module scope *ONLY* under alternate private
# names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather
# than merely "from argparse import ArgumentParser").
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from beartype.claw._clawmain import (
    beartype_all as beartype_all,
    beartype_package as beartype_package,
    beartype_packages as beartype_packages,
    beartype_this_package as beartype_this_package,
)
from beartype.claw._package.clawpkgcontext import (
    beartyping as beartyping,
)
