
    AҐi)                     T    d Z ddlZddlZddlmZmZ  G d d      Zd Zd Zd	 Z	d
 Z
y)z
Dirty Application Base Class

Provides the DirtyApp base class that all dirty applications must inherit from,
and utilities for loading dirty apps from import paths.
    N   )DirtyAppErrorDirtyAppNotFoundErrorc                   &    e Zd ZdZdZd Zd Zd Zy)DirtyAppaf
  
    Base class for dirty applications.

    Dirty applications are loaded once when the dirty worker starts and
    persist in memory for the lifetime of the worker. They are designed
    for stateful resources like ML models, connection pools, etc.

    Lifecycle
    ---------
    1. ``__init__()``: Called when the app is instantiated (once per worker)
    2. ``init()``: Called after instantiation to initialize resources
    3. ``__call__()``: Called for each request from HTTP workers
    4. ``close()``: Called when the worker shuts down

    State Persistence
    -----------------
    Instance variables persist across requests. This is the key feature
    that enables loading heavy resources once and reusing them::

        class MLApp(DirtyApp):
            def init(self):
                self.model = load_model()  # Loaded once, reused forever

            def predict(self, data):
                return self.model.predict(data)  # Same model for all requests

    Thread Safety
    -------------
    With ``dirty_threads=1`` (default): Only one request runs at a time,
    so no thread safety concerns.

    With ``dirty_threads > 1``: Multiple requests may run concurrently
    in the same worker. Your app MUST be thread-safe. Options:

    - Use locks: ``threading.Lock()`` for shared state
    - Use thread-local: ``threading.local()`` for per-thread state
    - Use read-only state: Load models once in init(), never mutate

    Example::

        import threading

        class ThreadSafeMLApp(DirtyApp):
            def __init__(self):
                self.models = {}
                self._lock = threading.Lock()

            def init(self):
                self.models['default'] = load_model('base-model')

            def load_model(self, name):
                with self._lock:
                    if name not in self.models:
                        self.models[name] = load_model(name)
                return {"loaded": True, "name": name}

    Worker Allocation
    -----------------
    By default, all dirty workers load all apps. For apps that consume
    significant memory (like large ML models), you can limit how many
    workers load the app by setting the ``workers`` class attribute::

        class HeavyModelApp(DirtyApp):
            workers = 2  # Only 2 workers will load this app

            def init(self):
                self.model = load_10gb_model()

    Subclasses should implement:
        - init(): Called once at worker startup to initialize resources
        - __call__(action, *args, **kwargs): Handle requests from HTTP workers
        - close(): Called at worker shutdown to cleanup resources
    Nc                      y)az  
        Initialize the application.

        Called once when the dirty worker starts, after the app instance
        is created. Use this for expensive initialization like loading
        ML models, establishing database connections, etc.

        This method is called in the child process after fork, so it's
        safe to initialize non-fork-safe resources here.
        N selfs    K/var/www/descvideos/venv/lib/python3.12/site-packages/gunicorn/dirty/app.pyinitzDirtyApp.initb           c                 n    t        | |d      }||j                  d      rt        d|        ||i |S )a  
        Handle a request from an HTTP worker.

        Args:
            action: The action/method name to execute
            *args: Positional arguments for the action
            **kwargs: Keyword arguments for the action

        Returns:
            The result of the action (must be JSON-serializable)

        Raises:
            ValueError: If the action is unknown
            Any exception: Will be caught and returned as DirtyAppError
        N_zUnknown action: )getattr
startswith
ValueError)r   actionargskwargsmethods        r   __call__zDirtyApp.__call__n   sG      vt,>V..s3/x899t&v&&r   c                      y)z
        Cleanup resources.

        Called when the dirty worker is shutting down. Use this to
        release resources like database connections, unload models, etc.
        Nr	   r
   s    r   closezDirtyApp.close   r   r   )__name__
__module____qualname____doc__workersr   r   r   r	   r   r   r   r      s    HZ G
'*r   r   c                 \   d| vrt        d|  d|       | j                  d      }t        |      dk(  r| dfS t        |      dk(  r5|\  }}}| d| }	 t        |      }|dk  rt        d|  d| |       ||fS t        d|  d|       # t        $ r t        d|  d	| d
|       w xY w)a  
    Parse a dirty app specification.

    Supports two formats:
    - ``"module:Class"`` - standard format, all workers load the app
    - ``"module:Class:N"`` - worker-limited format, only N workers load the app

    Args:
        spec: The app specification string

    Returns:
        tuple: (import_path, worker_count)
            - import_path: The "module:Class" part for importing
            - worker_count: Integer limit or None for all workers

    Raises:
        DirtyAppError: If the spec format is invalid or worker_count is < 1

    Examples::

        >>> parse_dirty_app_spec("myapp:App")
        ("myapp:App", None)

        >>> parse_dirty_app_spec("myapp:App:2")
        ("myapp:App", 2)

        >>> parse_dirty_app_spec("myapp.sub:App:1")
        ("myapp.sub:App", 1)
    :Invalid import path format: z?. Expected 'module.path:ClassName' or 'module.path:ClassName:N'app_path   N   zInvalid worker count in spec: z. Expected integer, got ''r   z!. Worker count must be >= 1, got )r   splitlenintr   )specpartsmodule_path
class_name	count_strimport_pathworker_counts          r   parse_dirty_app_specr3      s'   < $*4& 1L M
 	
 JJsOE 5zQd| 5zQ-2*Z$Qzl3	y>L !0 722>A  \** 
&tf -H 	I #  	0 7**3A7 	s   B B+c                 n   d| vrt        d|  d|       | j                  dd      \  }}	 |t        j                  v rt        j                  |   }nt	        j
                  |      }	 t        ||      }t        |t              st        |  d|       	  |       }g d
}|D ]5  }t        ||      rt        t        ||            r%t        |  d| |        |S # t        $ r}t        |       |d}~ww xY w# t        $ r t        |       dw xY w# t        $ r}t        d|  d	| |       |d}~ww xY w)aO  
    Load a dirty app class from an import path.

    Args:
        import_path: String in format 'module.path:ClassName'

    Returns:
        An instance of the dirty app class

    Raises:
        DirtyAppNotFoundError: If the module or class cannot be found
        DirtyAppError: If the class is not a valid DirtyApp subclass
    r"   r#   ". Expected 'module.path:ClassName'r$   r   N is not a classzFailed to instantiate z: )r   r   r   z is missing required method: )r   rsplitsysmodules	importlibimport_moduleImportErrorr   r   AttributeError
isinstancetype	Exceptionhasattrcallable)	r1   r.   r/   modulee	app_classapprequired_methodsmethod_names	            r   load_dirty_apprI      s    +*;- 8/ 0 
 	
 *00a8K8#++%[[-F,,[9F
;FJ/	
 i&m?+ 
 	
k 5' sK(k9R0S-<[MJ$  JE  8#K0a78  ;#K0d:;  $[MA37 
 	s;   ;C (C9 D 	C6%C11C69D	D4D//D4c                 4    i }| D ]  }t        |      ||<    |S )z
    Load multiple dirty apps from a list of import paths.

    Args:
        import_paths: List of import path strings

    Returns:
        dict: Mapping of import path to app instance

    Raises:
        DirtyAppError: If any app fails to load
    )rI   )import_pathsappsr1   s      r   load_dirty_appsrM     s,     D# 8*;7[8Kr   c                    d| vrt        d|  d|       | j                  dd      \  }}	 |t        j                  v rt        j                  |   }nt	        j
                  |      }	 t        ||      }t        |t              st        |  d|       t        |dd      S # t        $ r}t        |       |d}~ww xY w# t        $ r t        |       dw xY w)	a  
    Get the workers class attribute from a dirty app without instantiating it.

    This is used by the arbiter to determine how many workers should load
    an app based on the class attribute, without needing to actually load
    the app.

    Args:
        import_path: String in format 'module.path:ClassName'

    Returns:
        The workers class attribute value (int or None)

    Raises:
        DirtyAppNotFoundError: If the module or class cannot be found
        DirtyAppError: If the import path format is invalid
    r"   r#   r5   r$   r   Nr6   r    )r   r7   r8   r9   r:   r;   r<   r   r   r=   r>   r?   )r1   r.   r/   rC   rD   rE   s         r   get_app_workers_attributerO   ,  s    $ +*;- 8/ 0 
 	
 *00a8K8#++%[[-F,,[9F
;FJ/	
 i&m?+ 
 	
 9i..#  8#K0a78  ;#K0d:;s#   ;B! (B> !	B;*B66B;>C)r   r:   r8   errorsr   r   r   r3   rI   rM   rO   r	   r   r   <module>rQ      s9   
  
 8w wtHV?D&2/r   