Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,12 @@
"dpytools"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
"python.testing.pytestEnabled": true,
"python.testing.unittestArgs": [
"-v",
"-s",
"./tests",
"-p",
"test_*.py"
],
}
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

fmt: ## (Format) - runs black and isort against the codebase
poetry run black ./src/*
poetry run isort ./src/*
poetry run black ./dpytools/*
poetry run isort ./dpytools/*

lint: ## Run the ruff python linter
poetry run ruff ./src/*
poetry run ruff ./dpytools/*

test: ## Run pytest and check test coverage
poetry run pytest --cov-report term-missing --cov=dpytools
41 changes: 23 additions & 18 deletions dpytools/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@
from .properties.intproperty import IntegerProperty
from .properties.string import StringProperty


class Config:

def __init__(self):
self._properties_to_validate: List[BaseProperty] = []

@staticmethod
def from_env(config_dict: Dict[str, Dict[str, Any]]) -> Config:

config = Config()

for env_var_name, value in config_dict.items():

value_for_property = os.environ.get(env_var_name, None)
assert value_for_property is not None, f'Required envionrment value "{env_var_name}" could not be found.'
assert (
value_for_property is not None
), f'Required envionrment value "{env_var_name}" could not be found.'

if value["class"] == StringProperty:
if value["kwargs"]:
Expand All @@ -31,13 +31,13 @@ def from_env(config_dict: Dict[str, Dict[str, Any]]) -> Config:
regex = None
min_len = None
max_len = None

stringprop = StringProperty(
_name = value["property"],
_value = value_for_property,
regex = regex,
min_len = min_len,
max_len = max_len
_name=value["property"],
_value=value_for_property,
regex=regex,
min_len=min_len,
max_len=max_len,
)

prop_name = value["property"]
Expand All @@ -53,10 +53,10 @@ def from_env(config_dict: Dict[str, Dict[str, Any]]) -> Config:
max_val = None

intprop = IntegerProperty(
_name = value["property"],
_value = value_for_property,
min_val = min_val,
max_val = max_val
_name=value["property"],
_value=value_for_property,
min_val=min_val,
max_val=max_val,
)

prop_name = value["property"]
Expand All @@ -65,10 +65,11 @@ def from_env(config_dict: Dict[str, Dict[str, Any]]) -> Config:

else:
prop_type = value["class"]
raise TypeError(f"Unsupported property type specified via 'property' field, got {prop_type}. Should be of type StringProperty or IntegerProperty")

return config
raise TypeError(
f"Unsupported property type specified via 'property' field, got {prop_type}. Should be of type StringProperty or IntegerProperty"
)

return config

def assert_valid_config(self):
"""
Expand All @@ -79,4 +80,8 @@ def assert_valid_config(self):
property.type_is_valid()
property.secondary_validation()

self._properties_to_validate = []
self._properties_to_validate = []

# For each of the properties you imbided above, run
# self.type_is_valid()
# self.secondary_validation()
2 changes: 1 addition & 1 deletion dpytools/config/properties/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .intproperty import IntegerProperty
from .string import StringProperty
from .intproperty import IntegerProperty
15 changes: 10 additions & 5 deletions dpytools/config/properties/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from abc import ABCMeta, abstractmethod
from dataclasses import dataclass
from typing import Any, Union, Tuple, Optional
from typing import Any


@dataclass
class BaseProperty(metaclass=ABCMeta):
Expand All @@ -13,21 +14,25 @@ def name(self):

@name.setter
def name(self, value):
raise ValueError(f"Trying to change name property to value {value} but you cannot change a property name after instantiation.")
raise ValueError(
f"Trying to change name property to value {value} but you cannot change a property name after instantiation."
)

@property
def value(self):
return self._value

@value.setter
def value(self, value):
raise ValueError(f"Trying to change value to {value} but you cannot change a property value after instantiation.")
raise ValueError(
f"Trying to change value to {value} but you cannot change a property value after instantiation."
)

@abstractmethod
def type_is_valid(self):
"""
Validate that the property looks like
its of the correct type
its of the correct type
"""
...

Expand All @@ -39,4 +44,4 @@ def secondary_validation(self):
Non type based validation you might want to
run against a configuration value.
"""
...
...
18 changes: 13 additions & 5 deletions dpytools/config/properties/intproperty.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from typing import Optional
from dataclasses import dataclass
from typing import Optional

from .base import BaseProperty


@dataclass
class IntegerProperty(BaseProperty):
min_val: Optional[int]
Expand All @@ -10,12 +12,14 @@ class IntegerProperty(BaseProperty):
def type_is_valid(self):
"""
Validate that the property looks like
its of the correct type
its of the correct type
"""
try:
int(self._value)
except Exception as err:
raise Exception(f"Cannot cast {self._name} value {self._value} to integer.") from err
raise Exception(
f"Cannot cast {self._name} value {self._value} to integer."
) from err

def secondary_validation(self):
"""
Expand All @@ -26,7 +30,11 @@ def secondary_validation(self):
raise ValueError(f"Integer value for {self._name} does not exist.")

if self.min_val and self._value < self.min_val:
raise ValueError(f"Integer value for {self._name} is lower than allowed minimum.")
raise ValueError(
f"Integer value for {self._name} is lower than allowed minimum."
)

if self.max_val and self._value > self.max_val:
raise ValueError(f"Integer value for {self._name} is higher than allowed maximum.")
raise ValueError(
f"Integer value for {self._name} is higher than allowed maximum."
)
27 changes: 18 additions & 9 deletions dpytools/config/properties/string.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from typing import Optional
import re
from dataclasses import dataclass
from typing import Optional

from .base import BaseProperty

import re

@dataclass
class StringProperty(BaseProperty):
Expand All @@ -13,32 +14,40 @@ class StringProperty(BaseProperty):
def type_is_valid(self):
"""
Validate that the property looks like
its of the correct type
its of the correct type
"""
try:
str(self._value)
except Exception as err:
raise Exception(f"Cannot cast {self.name} value {self._value} to string.") from err
raise Exception(
f"Cannot cast {self.name} value {self._value} to string."
) from err

def secondary_validation(self):
"""
Non type based validation you might want to
run against a configuration value of this kind.
"""

if len(self._value) == 0:
raise ValueError(f"Str value for {self.name} is an empty string")

if self.regex:
# TODO - confirm the value matches the regex
regex_search = re.search(self.regex, self._value)
if not regex_search:
raise ValueError(f"Str value for {self.name} does not match the given regex.")
raise ValueError(
f"Str value for {self.name} does not match the given regex."
)

if self.min_len:
if len(self._value) < self.min_len:
raise ValueError(f"Str value for {self.name} is shorter than minimum length {self.min_len}")
raise ValueError(
f"Str value for {self.name} is shorter than minimum length {self.min_len}"
)

if self.max_len:
if len(self._value) > self.max_len:
raise ValueError(f"Str value for {self.name} is longer than maximum length {self.max_len}")
raise ValueError(
f"Str value for {self.name} is longer than maximum length {self.max_len}"
)
40 changes: 20 additions & 20 deletions dpytools/http_clients/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import logging

import backoff
import requests
from requests.exceptions import HTTPError
import logging


# Function to log retry attempts
Expand All @@ -15,19 +16,14 @@ def __init__(self, backoff_max=30):
self.backoff_max = backoff_max

# GET request method with exponential backoff
@backoff.on_exception(
backoff.expo,
HTTPError,
max_time=30,
on_backoff=log_retry
)
@backoff.on_exception(backoff.expo, HTTPError, max_time=30, on_backoff=log_retry)
def get(self, url, *args, **kwargs):
"""
Sends a GET request to the specified URL with optional extra arguments.

This method is a thin wrapper around `requests.get()`. Any additional arguments
are passed directly to `requests.get()`. For more information on the available
arguments, refer to the `requests.get()` documentation:
This method is a thin wrapper around `requests.get()`. Any additional arguments
are passed directly to `requests.get()`. For more information on the available
arguments, refer to the `requests.get()` documentation:
https://docs.python-requests.org/en/latest/api/#requests.get

Args:
Expand All @@ -40,22 +36,22 @@ def get(self, url, *args, **kwargs):
Raises:
HTTPError: If the request fails for a network-related reason.
"""
return self._handle_request('GET', url, *args, **kwargs)
return self._handle_request("GET", url, *args, **kwargs)

# POST request method with exponential backoff
@backoff.on_exception(
backoff.expo,
backoff.expo,
HTTPError,
max_time=30,
max_time=30,
on_backoff=log_retry,
)
def post(self, url, *args, **kwargs):
"""
Sends a POST request to the specified URL with optional extra arguments.

This method is a thin wrapper around `requests.post()`. Any additional arguments
are passed directly to `requests.post()`. For more information on the available
arguments, refer to the `requests.post()` documentation:
This method is a thin wrapper around `requests.post()`. Any additional arguments
are passed directly to `requests.post()`. For more information on the available
arguments, refer to the `requests.post()` documentation:
https://docs.python-requests.org/en/latest/api/#requests.post

Args:
Expand All @@ -69,8 +65,8 @@ def post(self, url, *args, **kwargs):
Raises:
HTTPError: If the request fails for a network-related reason.
"""
return self._handle_request('POST', url, *args, **kwargs)
return self._handle_request("POST", url, *args, **kwargs)

# Method to handle requests for GET and POST
def _handle_request(self, method, url, *args, **kwargs):
logging.info(f"Sending {method} request to {url}")
Expand All @@ -80,8 +76,12 @@ def _handle_request(self, method, url, *args, **kwargs):
return response

except HTTPError as http_err:
logging.error(f"HTTP error occurred: {http_err} when sending a {method} to {url} with headers {kwargs.get('headers')}")
logging.error(
f"HTTP error occurred: {http_err} when sending a {method} to {url} with headers {kwargs.get('headers')}"
)
raise http_err
except Exception as err:
logging.error(f"Other error occurred: {err} when sending a {method} to {url} with headers {kwargs.get('headers')}")
logging.error(
f"Other error occurred: {err} when sending a {method} to {url} with headers {kwargs.get('headers')}"
)
raise err
7 changes: 4 additions & 3 deletions dpytools/logger/logger.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from typing import Dict, List, Optional, Union
import structlog
import traceback
import json
import traceback
from datetime import datetime, timezone
from typing import Dict, List, Optional

import structlog


def level_to_severity(level: int) -> int:
Expand Down
Loading