daggerml.util

View source
  1import json
  2import logging
  3import os
  4import time
  5import urllib.request
  6from dataclasses import dataclass
  7from random import randint
  8
  9import boto3
 10from botocore.client import Config
 11from botocore.exceptions import BotoCoreError, NoRegionError
 12
 13logger = logging.getLogger(__name__)
 14
 15
 16def current_time_millis():
 17    return round(time.time() * 1000)
 18
 19
 20@dataclass
 21class BackoffWithJitter:
 22    min: int = 100
 23    max: int = 10000
 24    k: int = 2
 25    state: int = 0
 26
 27    def __call__(self):
 28        if self.state == 0:
 29            self.state = randint(self.min, self.min * 2)
 30            return self.state
 31        self.state = min(self.max, randint(self.min, max(self.min, self.state) * self.k))
 32        return self.state
 33
 34
 35def _get_region_from_metadata():
 36    """
 37    Attempts to retrieve the AWS region from ECS or EC2 metadata.
 38
 39    Returns
 40    -------
 41    Optional[str]
 42        The AWS region string if found, otherwise None.
 43    """
 44    # ECS (used in AWS Batch)
 45    metadata_uri = os.environ.get("ECS_CONTAINER_METADATA_URI_V4") or os.environ.get("ECS_CONTAINER_METADATA_URI")
 46    if metadata_uri:
 47        try:
 48            with urllib.request.urlopen(metadata_uri, timeout=2) as response:
 49                metadata = json.load(response)
 50            cluster_label = metadata.get("Labels", {}).get("com.amazonaws.ecs.cluster", "")
 51            region = cluster_label.split(":")[0] if ":" in cluster_label else None
 52            if region:
 53                return region
 54        except Exception as e:
 55            logger.warning("Failed to get region from ECS metadata: %s", e)
 56    # EC2 fallback: use IMDSv2
 57    try:
 58        token_req = urllib.request.Request(
 59            "http://169.254.169.254/latest/api/token",
 60            method="PUT",
 61            headers={"X-aws-ec2-metadata-token-ttl-seconds": "60"},
 62        )
 63        with urllib.request.urlopen(token_req, timeout=2) as token_response:
 64            token = token_response.read().decode()
 65        region_req = urllib.request.Request(
 66            "http://169.254.169.254/latest/dynamic/instance-identity/document",
 67            headers={"X-aws-ec2-metadata-token": token},
 68        )
 69        with urllib.request.urlopen(region_req, timeout=2) as region_response:
 70            identity_doc = json.load(region_response)
 71        return identity_doc.get("region")
 72    except Exception as e:
 73        logger.warning("Failed to get region from EC2 metadata: %s", e)
 74    return
 75
 76
 77def get_client(
 78    name,
 79    region=None,
 80    default_region="us-east-1",
 81    *,
 82    connection_timeout=5,
 83    read_timeout=60,
 84    max_attempts=5,
 85    retry_mode="adaptive",
 86    max_pool_connections=20,
 87):
 88    """
 89    Creates a robust boto3 client, determining the AWS region in the following order:
 90        1. Explicit argument
 91        2. AWS_REGION / AWS_DEFAULT_REGION environment variables
 92        3. boto3/botocore session
 93        4. ECS/EC2 metadata
 94        5. Fallback default region (us-east-1)
 95
 96    Parameters
 97    ----------
 98    name : str
 99        The name of the AWS service client.
100    region : Optional[str], default=None
101        The AWS region to use.
102    default_region : str, default="us-east-1"
103        The fallback AWS region.
104    connection_timeout : int | float, default=5
105        The connection timeout in seconds.
106    read_timeout : int | float, default=60
107        The read timeout in seconds.
108    max_attempts : int, default=5
109        The maximum number of retry attempts for retriable AWS requests.
110    retry_mode : str, default="adaptive"
111        The botocore retry mode.
112    max_pool_connections : int, default=20
113        The maximum number of pooled HTTP connections.
114
115    Returns
116    -------
117    boto3.client
118        A boto3 client for the specified service.
119    """
120    # Step 1–3: Try common boto3 config methods
121    region = region or os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION")
122    if not region:
123        try:
124            region = boto3.Session().region_name
125        except (BotoCoreError, NoRegionError, ConnectionRefusedError):
126            logger.debug("could not instantiate boto client...")
127            pass
128    # Step 4: Metadata if still no region
129    if not region:
130        logger.debug("inferring aws region from metadata")
131        region = _get_region_from_metadata()
132    # Step 5: Fallback default
133    if not region:
134        logger.warning(f"falling back to default region '{default_region}'")
135        region = default_region
136    config = Config(
137        region_name=region,
138        connect_timeout=connection_timeout,
139        read_timeout=read_timeout,
140        retries={"max_attempts": max_attempts, "mode": retry_mode},
141        max_pool_connections=max_pool_connections,
142    )
143    return boto3.client(name, config=config)

logger

logger= <Logger daggerml.util (WARNING)>

current_time_millis

def current_time_millis():
View source
17def current_time_millis():
18    return round(time.time() * 1000)

BackoffWithJitter

@dataclass
class BackoffWithJitter:
View source
21@dataclass
22class BackoffWithJitter:
23    min: int = 100
24    max: int = 10000
25    k: int = 2
26    state: int = 0
27
28    def __call__(self):
29        if self.state == 0:
30            self.state = randint(self.min, self.min * 2)
31            return self.state
32        self.state = min(self.max, randint(self.min, max(self.min, self.state) * self.k))
33        return self.state

BackoffWithJitter.__init__

BackoffWithJitter(min: int = 100, max: int = 10000, k: int = 2, state: int = 0)

BackoffWithJitter.min

min: int= 100

BackoffWithJitter.max

max: int= 10000

BackoffWithJitter.k

k: int= 2

BackoffWithJitter.state

state: int= 0

get_client

def get_client( name, region=None, default_region='us-east-1', *, connection_timeout=5, read_timeout=60, max_attempts=5, retry_mode='adaptive', max_pool_connections=20):
View source
 78def get_client(
 79    name,
 80    region=None,
 81    default_region="us-east-1",
 82    *,
 83    connection_timeout=5,
 84    read_timeout=60,
 85    max_attempts=5,
 86    retry_mode="adaptive",
 87    max_pool_connections=20,
 88):
 89    """
 90    Creates a robust boto3 client, determining the AWS region in the following order:
 91        1. Explicit argument
 92        2. AWS_REGION / AWS_DEFAULT_REGION environment variables
 93        3. boto3/botocore session
 94        4. ECS/EC2 metadata
 95        5. Fallback default region (us-east-1)
 96
 97    Parameters
 98    ----------
 99    name : str
100        The name of the AWS service client.
101    region : Optional[str], default=None
102        The AWS region to use.
103    default_region : str, default="us-east-1"
104        The fallback AWS region.
105    connection_timeout : int | float, default=5
106        The connection timeout in seconds.
107    read_timeout : int | float, default=60
108        The read timeout in seconds.
109    max_attempts : int, default=5
110        The maximum number of retry attempts for retriable AWS requests.
111    retry_mode : str, default="adaptive"
112        The botocore retry mode.
113    max_pool_connections : int, default=20
114        The maximum number of pooled HTTP connections.
115
116    Returns
117    -------
118    boto3.client
119        A boto3 client for the specified service.
120    """
121    # Step 1–3: Try common boto3 config methods
122    region = region or os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION")
123    if not region:
124        try:
125            region = boto3.Session().region_name
126        except (BotoCoreError, NoRegionError, ConnectionRefusedError):
127            logger.debug("could not instantiate boto client...")
128            pass
129    # Step 4: Metadata if still no region
130    if not region:
131        logger.debug("inferring aws region from metadata")
132        region = _get_region_from_metadata()
133    # Step 5: Fallback default
134    if not region:
135        logger.warning(f"falling back to default region '{default_region}'")
136        region = default_region
137    config = Config(
138        region_name=region,
139        connect_timeout=connection_timeout,
140        read_timeout=read_timeout,
141        retries={"max_attempts": max_attempts, "mode": retry_mode},
142        max_pool_connections=max_pool_connections,
143    )
144    return boto3.client(name, config=config)

Creates a robust boto3 client, determining the AWS region in the following order: 1. Explicit argument 2. AWS_REGION / AWS_DEFAULT_REGION environment variables 3. boto3/botocore session 4. ECS/EC2 metadata 5. Fallback default region (us-east-1)

Parameters
  • name (str): The name of the AWS service client.
  • region (Optional[str], default=None): The AWS region to use.
  • default_region (str, default="us-east-1"): The fallback AWS region.
  • connection_timeout (int | float, default=5): The connection timeout in seconds.
  • read_timeout (int | float, default=60): The read timeout in seconds.
  • max_attempts (int, default=5): The maximum number of retry attempts for retriable AWS requests.
  • retry_mode (str, default="adaptive"): The botocore retry mode.
  • max_pool_connections (int, default=20): The maximum number of pooled HTTP connections.
Returns
  • boto3.client: A boto3 client for the specified service.