#!/usr/bin/env python
__copyright__ = "Copyright (c) 2018-2025 Alex Laird"
__license__ = "MIT"
import logging
from typing import Any, Dict, List, Optional
from pyngrok import conf
from pyngrok.conf import PyngrokConfig
from pyngrok.ngrok import get_ngrok_process, api_request
logger = logging.getLogger(__name__)
[docs]
class NgrokAgent:
"""
An object containing information about a ``ngrok`` agent.
"""
def __init__(self,
data: Dict[str, Any]) -> None:
#: The original tunnel data.
self.data: Dict[str, Any] = data
#: The status of the agent.
self.status: Optional[str] = data.get("status")
#: The version of the agent.
self.agent_version: Optional[str] = data.get("agent_version")
#: The session details for the agent.
self.session: Any = data.get("session")
#: The URI of the agent.
self.uri: Optional[str] = data.get("uri")
def __repr__(self) -> str:
return f"<NgrokAgent: \"{self.uri}\">"
def __str__(self) -> str: # pragma: no cover
return f"NgrokAgent: \"{self.uri}\""
[docs]
class CapturedRequest:
"""
An object containing a Captured Request from a ``ngrok`` tunnel.
"""
def __init__(self,
data: Dict[str, Any]) -> None:
#: The original tunnel data.
self.data: Dict[str, Any] = data
#: The URI of the captured request.
self.id: Optional[str] = data.get("id")
self.uri: Optional[str] = data.get("uri")
self.tunnel_name: Optional[str] = data.get("tunnel_name")
self.remote_addr: Optional[str] = data.get("remote_addr")
self.start: Optional[str] = data.get("start")
self.duration: Optional[int] = data.get("duration")
self.request: Any = data.get("request")
self.response: Any = data.get("response")
def __repr__(self) -> str:
return f"<CapturedRequest: \"{self.id}\">"
def __str__(self) -> str: # pragma: no cover
return f"CapturedRequest: \"{self.id}\""
[docs]
def get_agent_status(pyngrok_config: Optional[PyngrokConfig] = None, ) -> NgrokAgent:
"""
Get the ``ngrok`` agent status.
If ``ngrok`` is not installed at :class:`~pyngrok.conf.PyngrokConfig`'s ``ngrok_path``, calling this method
will first download and install ``ngrok``.
If ``ngrok`` is not running, calling this method will first start a process with
:class:`~pyngrok.conf.PyngrokConfig`.
:param pyngrok_config: A ``pyngrok`` configuration to use when interacting with the ``ngrok`` binary,
overriding :func:`~pyngrok.conf.get_default()`.
:return: The requests made to the tunnels.
"""
if pyngrok_config is None:
pyngrok_config = conf.get_default()
api_url = get_ngrok_process(pyngrok_config).api_url
return NgrokAgent(api_request(f"{api_url}/api/status", "GET",
timeout=pyngrok_config.request_timeout))
[docs]
def get_requests(tunnel_name: Optional[str] = None,
pyngrok_config: Optional[PyngrokConfig] = None, ) -> List[CapturedRequest]:
"""
Get the list of requests made to either all tunnels, or the given tunnel name.
If ``ngrok`` is not installed at :class:`~pyngrok.conf.PyngrokConfig`'s ``ngrok_path``, calling this method
will first download and install ``ngrok``.
If ``ngrok`` is not running, calling this method will first start a process with
:class:`~pyngrok.conf.PyngrokConfig`.
:param tunnel_name: The tunnel name to filter by.
:param pyngrok_config: A ``pyngrok`` configuration to use when interacting with the ``ngrok`` binary,
overriding :func:`~pyngrok.conf.get_default()`.
:return: The requests made to the tunnels.
"""
if pyngrok_config is None:
pyngrok_config = conf.get_default()
params = {"tunnel_name": tunnel_name} if tunnel_name else None
api_url = get_ngrok_process(pyngrok_config).api_url
requests = []
for request in api_request(f"{api_url}/api/requests/http", "GET",
params=params,
timeout=pyngrok_config.request_timeout)["requests"]:
requests.append(CapturedRequest(request))
return requests
[docs]
def get_request(request_id: str,
pyngrok_config: Optional[PyngrokConfig] = None, ) -> CapturedRequest:
"""
Get the given request made.
If ``ngrok`` is not installed at :class:`~pyngrok.conf.PyngrokConfig`'s ``ngrok_path``, calling this method
will first download and install ``ngrok``.
If ``ngrok`` is not running, calling this method will first start a process with
:class:`~pyngrok.conf.PyngrokConfig`.
:param request_id: The ID of the request to fetch.
:param pyngrok_config: A ``pyngrok`` configuration to use when interacting with the ``ngrok`` binary,
overriding :func:`~pyngrok.conf.get_default()`.
:return: The request made to the tunnel.
"""
if pyngrok_config is None:
pyngrok_config = conf.get_default()
api_url = get_ngrok_process(pyngrok_config).api_url
return CapturedRequest(api_request(f"{api_url}/api/requests/http/{request_id}", "GET",
timeout=pyngrok_config.request_timeout))
[docs]
def replay_request(request_id: str,
tunnel_name: Optional[str] = None,
pyngrok_config: Optional[PyngrokConfig] = None, ) -> None:
"""
Replay a given request through its original tunnel, or through a different given tunnel.
If ``ngrok`` is not installed at :class:`~pyngrok.conf.PyngrokConfig`'s ``ngrok_path``, calling this method
will first download and install ``ngrok``.
If ``ngrok`` is not running, calling this method will first start a process with
:class:`~pyngrok.conf.PyngrokConfig`.
:param request_id: The request ID.
:param tunnel_name: The name of tunnel to replay the request through.
:param pyngrok_config: A ``pyngrok`` configuration to use when interacting with the ``ngrok`` binary,
overriding :func:`~pyngrok.conf.get_default()`.
"""
if pyngrok_config is None:
pyngrok_config = conf.get_default()
api_url = get_ngrok_process(pyngrok_config).api_url
api_request(f"{api_url}/api/requests/http", "POST",
data={"id": request_id, "tunnel_name": tunnel_name},
timeout=pyngrok_config.request_timeout)
[docs]
def delete_requests(pyngrok_config: Optional[PyngrokConfig] = None) -> None:
"""
Delete request history.
If ``ngrok`` is not installed at :class:`~pyngrok.conf.PyngrokConfig`'s ``ngrok_path``, calling this method
will first download and install ``ngrok``.
If ``ngrok`` is not running, calling this method will first start a process with
:class:`~pyngrok.conf.PyngrokConfig`.
:param pyngrok_config: A ``pyngrok`` configuration to use when interacting with the ``ngrok`` binary,
overriding :func:`~pyngrok.conf.get_default()`.
"""
if pyngrok_config is None:
pyngrok_config = conf.get_default()
api_url = get_ngrok_process(pyngrok_config).api_url
api_request(f"{api_url}/api/requests/http", "DELETE",
timeout=pyngrok_config.request_timeout)