o
    ,&]i=9                     @  s   U d Z ddlmZ ddlZddlmZmZmZmZ ddl	m
Z
 er-ddlmZ ddlmZ e
eZded	< G d
d deZG dd dZdS )a  Component file watching utilities.

This module provides the `ComponentFileWatcher`, a utility that watches
component asset directories for changes and notifies a caller-provided callback
with the affected component names. It abstracts the underlying path-watcher
implementation and ensures exception-safe startup and cleanup.

Why this exists
---------------
Streamlit supports advanced Custom Components that ship a package of static
assets (for example, a Vite/Webpack build output). While a user develops their
app, those frontend files may change. The component registry for Custom
Components v2 must stay synchronized with the on-disk assets so that the server
can resolve the up-to-date files.

This watcher exists to keep the registry in sync by listening for changes in
component asset roots and notifying a higher-level manager that can re-resolve
the affected component definitions.

Notes
-----
- Watching is directory-based with a recursive glob ("**/*").
- Common noisy directories (e.g., ``node_modules``) are ignored in callbacks.
- Startup is exception-safe and does not leak partially created watchers.

See Also
--------
- :class:`streamlit.watcher.local_sources_watcher.LocalSourcesWatcher` - watches
  app source files per session to trigger reruns.
- :class:`streamlit.components.v2.component_registry.BidiComponentRegistry` -
  the server-side store of Custom Component v2 definitions that reacts to
  watcher notifications.
    )annotationsN)TYPE_CHECKINGFinalProtocolcast)
get_logger)CallablePathr   _LOGGERc                   @  s   e Zd ZdddZdS )	_HasClosereturnNonec                 C  s   d S N selfr   r   l/var/www/html/IGF-ODF-V3/venv/lib/python3.10/site-packages/streamlit/components/v2/component_file_watcher.pycloseB   s    z_HasClose.closeNr   r   )__name__
__module____qualname__r   r   r   r   r   r   A   s    r   c                   @  s   e Zd ZdZd5ddZed6d	d
Zd7ddZd8ddZd7ddZ	d9ddZ
d:ddZd;ddZd<d"d#Zd=d%d&Zd>d*d+Zd?d.d/Zd@d2d3Zd4S )AComponentFileWatchera#  Handle file watching for component asset directories.

    Parameters
    ----------
    component_update_callback : Callable[[list[str]], None]
        Callback invoked when files change under any watched directory. It
        receives a list of component names affected by the change.
    component_update_callbackCallable[[list[str]], None]r   r   c                 C  s2   || _ t | _i | _g | _d| _i | _d| _dS )a  Initialize the file watcher.

        Parameters
        ----------
        component_update_callback : Callable[[list[str]], None]
            Callback function to call when components under watched roots change.
            Signature: (affected_component_names)
        F)__pycache__z.cachez.gitz.hgz.mypy_cachez.pytest_cachez.ruff_cachez.svnz.swcz.yarncoveragenode_modulesvenvN)	_component_update_callback	threadingLock_lock_watched_directories_path_watchers_watching_active_asset_watch_roots_ignored_dirs)r   r   r   r   r   __init__O   s   	

zComponentFileWatcher.__init__boolc                 C  s   | j S )zCheck if file watching is currently active.

        Returns
        -------
        bool
            True if file watching is active, False otherwise
        )r&   r   r   r   r   is_watching_activev   s   	z'ComponentFileWatcher.is_watching_activeasset_watch_rootsdict[str, Path]c                 C  s   |    | | dS )a{  Start file watching for asset roots.

        Parameters
        ----------
        asset_watch_roots : dict[str, Path]
            Mapping of component names to asset root directories to watch.

        Notes
        -----
        The method is idempotent: it stops any active watchers first, then
        re-initializes watchers for the provided ``asset_watch_roots``.
        N)stop_file_watching_start_file_watching)r   r,   r   r   r   start_file_watching   s   z(ComponentFileWatcher.start_file_watchingc              
   C  s   | j F | js	 W d   dS | jD ]}z|  W q ty)   td Y qw | j  | j  | j	  d| _t
d W d   dS 1 sLw   Y  dS )zStop file watching and clean up watchers.

        Notes
        -----
        This method is safe to call multiple times and will no-op if
        watching is not active.
        NzFailed to close path watcherFz,Stopped file watching for component registry)r#   r&   r%   r   	Exceptionr   	exceptionclearr$   r'   debug)r   watcherr   r   r   r.      s    



"z'ComponentFileWatcher.stop_file_watchingc              	   C  s   | j k | jr	 W d   dS |s td 	 W d   dS z/|  }|du r2W W d   dS | |}| ||\}}|rI| ||| ntd W n ty]   t	d Y n	w W d   dS W d   dS 1 sqw   Y  dS )a  Internal method to start file watching with the given roots.

        This method is exception-safe: in case of failures while creating
        watchers, any previously created watcher instances are closed and no
        internal state is committed.
        NzNo asset roots to watchz-No directories were watched; staying inactivezFailed to start file watching)
r#   r&   r   r4   _get_default_path_watcher_class_prepare_directories_to_watch_build_watchers_for_directories_commit_watch_stater1   r2   )r   r,   path_watcher_classdirectories_to_watchnew_watchersnew_watched_dirsr   r   r   r/      s@   

"z)ComponentFileWatcher._start_file_watchingtype | Nonec                 C  s0   ddl m}m} | }||u rtd dS |S )a  Return the default path watcher class.

        Returns
        -------
        type | None
            The concrete path watcher class to instantiate, or ``None`` if
            the NoOp watcher is configured and file watching should be
            skipped.
        r   )NoOpPathWatcherget_default_path_watcher_classz8NoOpPathWatcher in use; skipping component file watchingN)streamlit.watcher.path_watcherr?   r@   r   r4   )r   r?   r@   r:   r   r   r   r6      s   

z4ComponentFileWatcher._get_default_path_watcher_classdict[str, list[str]]c                 C  sP   i }|  D ]\}}t| }||vrg ||< ||| vr%|| | q|S )a  Build a mapping of directory to component names.

        Parameters
        ----------
        asset_watch_roots : dict[str, Path]
            Mapping of component names to their asset root directories.

        Returns
        -------
        dict[str, list[str]]
            A map from absolute directory path to a deduplicated list of
            component names contained in that directory.
        )itemsstrresolveappend)r   r,   r;   	comp_nameroot	directoryr   r   r   r7      s   z2ComponentFileWatcher._prepare_directories_to_watchr:   typer;   ,tuple[list[_HasClose], dict[str, list[str]]]c           	   	   C  s   g }i }|  D ]5\}}z$| t|}|||ddd}|td| |||< td|| W q ty=   | |  w ||fS )a  Create watchers for directories with rollback on failure.

        Parameters
        ----------
        path_watcher_class : type
            The path watcher class to instantiate for each directory.
        directories_to_watch : dict[str, list[str]]
            A map of directory to the associated component name list.

        Returns
        -------
        tuple[list[_HasClose], dict[str, list[str]]]
            The list of created watcher instances and the watched directory
            mapping.

        Raises
        ------
        Exception
            Propagates any exception during watcher creation after closing
            already-created watchers.
        z**/*F)glob_patternallow_nonexistentr   z2Prepared watcher for directory %s (components: %s))	rC   _make_directory_callbacktuplerF   r   r   r4   r1   _rollback_watchers)	r   r:   r;   r<   r=   rI   component_namescbr5   r   r   r   r8     s.   
z4ComponentFileWatcher._build_watchers_for_directoriesr<   list[_HasClose]r=   c                 C  s2   || _ || _t|| _d| _tdt| j dS )a  Commit created watchers and mark watching active.

        Parameters
        ----------
        new_watchers : list[_HasClose]
            Fully initialized watcher instances.
        new_watched_dirs : dict[str, list[str]]
            Mapping from directory to component names.
        asset_watch_roots : dict[str, Path]
            The asset roots used to initialize watchers; stored for reference.
        Tz(Started file watching for %d directoriesN)r%   r$   dictr'   r&   r   r4   len)r   r<   r=   r,   r   r   r   r9   6  s   

z(ComponentFileWatcher._commit_watch_statewatchersc              	   C  s6   |D ]}z|   W q ty   td Y qw dS )zClose any created watchers when setup fails.

        Parameters
        ----------
        watchers : list[_HasClose]
            Watcher instances that were successfully created before a failure.
        z,Failed to close path watcher during rollbackN)r   r1   r   r2   )r   rV   wr   r   r   rP   O  s   z'ComponentFileWatcher._rollback_watcherscompstuple[str, ...]Callable[[str], None]c                   s   d fdd}|S )	zHCreate a callback for a directory watcher that captures component names.changed_pathrD   r   r   c                   s:    | rtd|  d S td|   t  d S )Nz&Ignoring change in noisy directory: %sz6Directory change detected: %s, checking components: %s)_is_in_ignored_directoryr   r4   _handle_component_changelist)r[   rX   r   r   r   callback`  s   
z?ComponentFileWatcher._make_directory_callback.<locals>.callbackN)r[   rD   r   r   r   )r   rX   r`   r   r_   r   rN   ]  s   z-ComponentFileWatcher._make_directory_callbackaffected_components	list[str]c                 C  s:   | j sdS z| | W dS  ty   td Y dS w )zHandle component changes for both directory and file events.

        Parameters
        ----------
        affected_components : list[str]
            List of component names affected by the change
        Nz Component update callback raised)r&   r    r1   r   r2   )r   ra   r   r   r   r]   m  s   z-ComponentFileWatcher._handle_component_changer[   rD   c                   sN   zddl m} t|| j t fdd| jD W S  ty&   Y dS w )ae  Return True if the changed path is inside an ignored directory.

        Parameters
        ----------
        changed_path : str
            The filesystem path that triggered the change event.

        Returns
        -------
        bool
            True if the path is located inside one of the ignored directories,
            False otherwise.
        r   r	   c                 3  s    | ]}| v V  qd S r   r   ).0ignoredpartsr   r   	<genexpr>  s    z@ComponentFileWatcher._is_in_ignored_directory.<locals>.<genexpr>F)pathlibr
   setrE   rf   anyr(   r1   )r   r[   _Pathr   re   r   r\     s   z-ComponentFileWatcher._is_in_ignored_directoryN)r   r   r   r   )r   r*   )r,   r-   r   r   r   )r   r>   )r,   r-   r   rB   )r:   rJ   r;   rB   r   rK   )r<   rS   r=   rB   r,   r-   r   r   )rV   rS   r   r   )rX   rY   r   rZ   )ra   rb   r   r   )r[   rD   r   r*   )r   r   r   __doc__r)   propertyr+   r0   r.   r/   r6   r7   r8   r9   rP   rN   r]   r\   r   r   r   r   r   E   s     
	'




'


4


r   )rl   
__future__r   r!   typingr   r   r   r   streamlit.loggerr   collections.abcr   rh   r
   r   r   __annotations__r   r   r   r   r   r   <module>   s   "