o
    i5                     @   s*  d 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	m
Z
mZmZmZmZmZmZmZ G dd deZG dd	 d	eZG d
d deZG dd dZG dd dZe Zdadd Zdd Zdd Zdd Zd,ddZdd Zd,ddZ dd Z!d d! Z"d"d# Z#d,d$d%Z$d&d' Z%d(d) Z&d*d+ Z'dS )-aL  
Stash - Global Shared State for Dirty Workers

Provides simple key-value tables stored in the arbiter process.
All workers can read and write to the same tables.

Usage::

    from gunicorn.dirty import stash

    # Basic operations - table is auto-created on first access
    stash.put("sessions", "user:1", {"name": "Alice", "role": "admin"})
    user = stash.get("sessions", "user:1")
    stash.delete("sessions", "user:1")

    # Dict-like interface
    sessions = stash.table("sessions")
    sessions["user:1"] = {"name": "Alice"}
    user = sessions["user:1"]
    del sessions["user:1"]

    # Query operations
    keys = stash.keys("sessions")
    keys = stash.keys("sessions", pattern="user:*")

    # Table management
    stash.ensure("cache")           # Explicit creation (idempotent)
    stash.clear("sessions")         # Delete all entries
    stash.delete_table("sessions")  # Delete the table itself
    tables = stash.tables()         # List all tables

Declarative usage in DirtyApp::

    class MyApp(DirtyApp):
        stashes = ["sessions", "cache"]  # Auto-created on arbiter start

        def __call__(self, action, *args, **kwargs):
            # Tables are ready to use
            stash.put("sessions", "key", "value")

Note: Tables are stored in the arbiter process and are ephemeral.
If the arbiter restarts, all data is lost.
    N   )
DirtyError)DirtyProtocolSTASH_OP_PUTSTASH_OP_GETSTASH_OP_DELETESTASH_OP_KEYSSTASH_OP_CLEARSTASH_OP_INFOSTASH_OP_ENSURESTASH_OP_DELETE_TABLESTASH_OP_TABLESSTASH_OP_EXISTSmake_stash_messagec                   @   s   e Zd ZdZdS )
StashErrorz$Base exception for stash operations.N)__name__
__module____qualname____doc__ r   r   H/home/ubuntu/.local/lib/python3.10/site-packages/gunicorn/dirty/stash.pyr   E   s    r   c                           e Zd ZdZ fddZ  ZS )StashTableNotFoundErrorz#Raised when a table does not exist.c                    s   || _ t d|  d S )NzStash table not found: )
table_namesuper__init__)selfr   	__class__r   r   r   L   s   z StashTableNotFoundError.__init__r   r   r   r   r   __classcell__r   r   r   r   r   I       r   c                       r   )StashKeyNotFoundErrorz,Raised when a key does not exist in a table.c                    s(   || _ || _t d| d|  d S )NzKey not found in z: )r   keyr   r   )r   r   r#   r   r   r   r   T   s   zStashKeyNotFoundError.__init__r   r   r   r   r   r"   Q   r!   r"   c                   @   s   e Zd ZdZd*ddZdd Zdd Zd	d
 Zd+ddZdd Z	d,ddZ
dd Zd,ddZdd Zdd Zdd Zd,ddZdd Zd d! Zd"d# Zd$d% Zd&d' Zd(d) ZdS )-StashClientzl
    Client for stash operations.

    Communicates with the arbiter which stores all tables in memory.
          >@c                 C   s    || _ || _d| _t | _dS )z
        Initialize the stash client.

        Args:
            socket_path: Path to the dirty arbiter's Unix socket
            timeout: Default timeout for operations in seconds
        N)socket_pathtimeout_sock	threadingLock_lock)r   r&   r'   r   r   r   r   a   s   zStashClient.__init__c                 C   s   t t S )zGenerate a unique request ID.)struuiduuid4r   r   r   r   _get_request_idn      zStashClient._get_request_idc              
   C   s   ddl }| jdurdS z| |j|j| _| j| j | j| j W dS  |jt	fy? } zd| _t
d| |d}~ww )z Establish connection to arbiter.r   NzFailed to connect to arbiter: )socketr(   AF_UNIXSOCK_STREAM
settimeoutr'   connectr&   errorOSErrorr   )r   r2   er   r   r   _connectr   s   
zStashClient._connectc                 C   s:   | j durz| j   W n	 ty   Y nw d| _ dS dS )zClose the connection.N)r(   close	Exceptionr/   r   r   r   _close   s   

zStashClient._closeNc                 C   s6  | j  | jdu r|   |  }t||||||d}zWt| j| t| j}|d}	|	tj	kr@|dW W  d   S |	tj
krl|di }
|
dd}|
dd	}|d
kr_t||dkrht||t|td|	  ty } z|   t|tr td| |d}~ww 1 sw   Y  dS )a6  
        Execute a stash operation.

        Args:
            op: Operation code (STASH_OP_*)
            table: Table name
            key: Optional key
            value: Optional value
            pattern: Optional pattern for keys operation

        Returns:
            Result from the operation
        N)r#   valuepatterntyperesultr7   
error_typer   messagezUnknown errorr   r"   zUnexpected response type: zStash operation failed: )r+   r(   r:   r0   r   r   write_messageread_messagegetMSG_TYPE_RESPONSEMSG_TYPE_ERRORr   r"   r   r<   r=   
isinstance)r   optabler#   r>   r?   
request_idrC   responsemsg_type
error_inforB   	error_msgr9   r   r   r   _execute   sB   






zStashClient._executec                 C   s   | j t|||d dS )z
        Store a value in a table.

        The table is automatically created if it doesn't exist.

        Args:
            table: Table name
            key: Key to store under
            value: Value to store (must be serializable)
        )r#   r>   N)rQ   r   )r   rK   r#   r>   r   r   r   put   s   zStashClient.putc                 C   s*   z	| j t||dW S  ty   | Y S w )z
        Retrieve a value from a table.

        Args:
            table: Table name
            key: Key to retrieve
            default: Default value if key not found

        Returns:
            The stored value, or default if not found
        r#   )rQ   r   r"   )r   rK   r#   defaultr   r   r   rF      s
   zStashClient.getc                 C      | j t||dS )z
        Delete a key from a table.

        Args:
            table: Table name
            key: Key to delete

        Returns:
            True if key was deleted, False if it didn't exist
        rS   )rQ   r   r   rK   r#   r   r   r   delete      zStashClient.deletec                 C   rU   )z
        Get all keys in a table, optionally filtered by pattern.

        Args:
            table: Table name
            pattern: Optional glob pattern (e.g., "user:*")

        Returns:
            List of keys
        r?   )rQ   r   )r   rK   r?   r   r   r   keys   rX   zStashClient.keysc                 C      |  t| dS )z]
        Delete all entries in a table.

        Args:
            table: Table name
        N)rQ   r	   r   rK   r   r   r   clear      zStashClient.clearc                 C   s   |  t|S )z
        Get information about a table.

        Args:
            table: Table name

        Returns:
            Dict with table info (size, etc.)
        )rQ   r
   r\   r   r   r   info   s   
zStashClient.infoc                 C   r[   )z
        Ensure a table exists (create if not exists).

        This is idempotent - calling it multiple times is safe.

        Args:
            table: Table name
        N)rQ   r   r\   r   r   r   ensure  s   	zStashClient.ensurec                 C   rU   )z
        Check if a table or key exists.

        Args:
            table: Table name
            key: Optional key to check within the table

        Returns:
            True if exists, False otherwise
        rS   )rQ   r   rV   r   r   r   exists  rX   zStashClient.existsc                 C   r[   )zV
        Delete an entire table.

        Args:
            table: Table name
        N)rQ   r   r\   r   r   r   delete_table$  r^   zStashClient.delete_tablec                 C   s   |  tdS )zT
        List all tables.

        Returns:
            List of table names
         )rQ   r   r/   r   r   r   tables-  s   zStashClient.tablesc                 C   s
   t | |S )z
        Get a dict-like interface to a table.

        Args:
            name: Table name

        Returns:
            StashTable instance
        )
StashTable)r   namer   r   r   rK   6  s   

zStashClient.tablec                 C   s4   | j  |   W d   dS 1 sw   Y  dS )zClose the client connection.N)r+   r=   r/   r   r   r   r;   B  s   
"zStashClient.closec                 C   s   | S Nr   r/   r   r   r   	__enter__G  s   zStashClient.__enter__c                 C   s   |    d S rg   )r;   )r   exc_typeexc_valexc_tbr   r   r   __exit__J  s   zStashClient.__exit__)r%   )NNNrg   )r   r   r   r   r   r0   r:   r=   rQ   rR   rF   rW   rZ   r]   r_   r`   ra   rb   rd   rK   r;   rh   rl   r   r   r   r   r$   Z   s*    

	6

	
		r$   c                   @   s   e Zd ZdZdd Zedd Zdd Zdd	 Zd
d Z	dd Z
dd Zdd ZdddZdddZdd Zdd Zdd ZdS )re   a2  
    Dict-like interface to a stash table.

    Example::

        sessions = stash.table("sessions")
        sessions["user:1"] = {"name": "Alice"}
        user = sessions["user:1"]
        del sessions["user:1"]

        # Iteration
        for key in sessions:
            print(key, sessions[key])
    c                 C   s   || _ || _d S rg   )_client_name)r   clientrf   r   r   r   r   ^  s   
zStashTable.__init__c                 C   s   | j S )zTable name.)rn   r/   r   r   r   rf   b  s   zStashTable.namec                 C   s4   | j | j|}|d u r| j | j|st||S rg   )rm   rF   rn   ra   KeyError)r   r#   rA   r   r   r   __getitem__g  s
   zStashTable.__getitem__c                 C   s   | j | j|| d S rg   )rm   rR   rn   )r   r#   r>   r   r   r   __setitem__o  s   zStashTable.__setitem__c                 C   s   | j | j|st|d S rg   )rm   rW   rn   rp   r   r#   r   r   r   __delitem__r  s   zStashTable.__delitem__c                 C   s   | j | j|S rg   )rm   ra   rn   rs   r   r   r   __contains__v  s   zStashTable.__contains__c                 C   s   t | j| jS rg   )iterrm   rZ   rn   r/   r   r   r   __iter__y  s   zStashTable.__iter__c                 C   s   | j | j}|ddS )Nsizer   )rm   r_   rn   rF   )r   r_   r   r   r   __len__|  s   zStashTable.__len__Nc                 C   s   | j | j||S )zGet value with default.)rm   rF   rn   )r   r#   rT   r   r   r   rF        zStashTable.getc                 C   s   | j j| j|dS )z-Get all keys, optionally filtered by pattern.rY   )rm   rZ   rn   )r   r?   r   r   r   rZ     rz   zStashTable.keysc                 C   s   | j | j dS )zDelete all entries.N)rm   r]   rn   r/   r   r   r   r]     rz   zStashTable.clearc                 c   s0    | j | jD ]}|| j | j|fV  qdS )z Iterate over (key, value) pairs.Nrm   rZ   rn   rF   rs   r   r   r   items  s   zStashTable.itemsc                 c   s,    | j | jD ]}| j | j|V  qdS )zIterate over values.Nr{   rs   r   r   r   values  s   zStashTable.valuesrg   )r   r   r   r   r   propertyrf   rq   rr   rt   ru   rw   ry   rF   rZ   r]   r|   r}   r   r   r   r   re   N  s     


re   c                 C   s   | a dS )z@Set the global stash socket path (called during initialization).N)_stash_socket_path)pathr   r   r   set_stash_socket_path  s   r   c                  C   s0   ddl } tdu r| jd}|r|S tdtS )zGet the stash socket path.r   NGUNICORN_DIRTY_SOCKETz\Stash socket path not configured. Make sure dirty_workers > 0 and dirty_apps are configured.)osr   environrF   r   )r   r   r   r   r   get_stash_socket_path  s   r   c                  C   s,   t tdd} | du rt }t|} | t_| S )z*Get or create a thread-local stash client.stash_clientN)getattr_thread_localr   r$   r   )ro   r&   r   r   r   _get_client  s   r   c                 C   s   t  | || dS )zStore a value in a table.N)r   rR   )rK   r#   r>   r   r   r   rR     s   rR   c                 C   s   t  | ||S )zRetrieve a value from a table.)r   rF   )rK   r#   rT   r   r   r   rF        rF   c                 C      t  | |S )zDelete a key from a table.)r   rW   rK   r#   r   r   r   rW        rW   c                 C   r   )zGet all keys in a table.)r   rZ   )rK   r?   r   r   r   rZ     r   rZ   c                 C      t  |  dS )zDelete all entries in a table.N)r   r]   rK   r   r   r   r]     r   r]   c                 C      t  | S )zGet information about a table.)r   r_   r   r   r   r   r_     r1   r_   c                 C   r   )zEnsure a table exists.N)r   r`   r   r   r   r   r`     r   r`   c                 C   r   )zCheck if a table or key exists.)r   ra   r   r   r   r   ra     r   ra   c                 C   r   )zDelete an entire table.N)r   rb   r   r   r   r   rb     r   rb   c                   C   s
   t   S )zList all tables.)r   rd   r   r   r   r   rd     s   
rd   c                 C   r   )z%Get a dict-like interface to a table.)r   rK   )rf   r   r   r   rK     r1   rK   rg   )(r   r)   r-   errorsr   protocolr   r   r   r   r   r	   r
   r   r   r   r   r   r   r   r"   r$   re   localr   r   r   r   r   rR   rF   rW   rZ   r]   r_   r`   ra   rb   rd   rK   r   r   r   r   <module>   s6   ,8	 uN


