Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update dependencies with reported vulnerabilities #572

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
447 changes: 266 additions & 181 deletions poetry.lock

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ include = ["README.md", "CHANGELOG.md"]

[tool.poetry.dependencies]
python = ">=3.9,<3.13"
pydantic = "^1.9.0"
pydantic = "^2.4"
loguru = "^0.6.0"
httpx = "^0.23.0"
python-dotenv = "^1.0.0"
Expand All @@ -41,6 +41,7 @@ python-dateutil = "^2.8.2"
Authlib = "^1.1.0"
watchfiles = "^0.18.1"
python-ulid = "^2.4.0.post0"
pydantic-settings = "^2.2.1"

[tool.poetry.dev-dependencies]
pytest = "^7.4.1"
Expand Down Expand Up @@ -74,7 +75,7 @@ flake8-annotations-complexity = "^0.0.7"
flake8-annotations = "^3.0.0"
flake8-markdown = "^0.4.0"
flake8-bandit = "^4.1.1"
fastapi = "^0.95.0"
fastapi = "^0.103"
uvicorn = "^0.21.1"
pytest-profiling = "^1.7.0"
pytest-sugar = "^0.9.6"
Expand All @@ -85,7 +86,7 @@ pytest-xdist = "^3.2.0"
pytest-vscodedebug = "^0.1.0"
pytest-html = "^4.0.0rc0"
bandit = "^1.7.0"
statesman = "^1.0.4"
statesman = "^1.0.5"
types-PyYAML = "^6.0.4"
types-setuptools = "^63.4.1"
types-python-dateutil = "^2.8.2"
Expand All @@ -94,7 +95,7 @@ types-pytz = "^2022.7.1"
types-toml = "^0.10.2"
types-aiofiles = "^23.1.0"
types-tabulate = "^0.9.0"
black = "^23.1.0"
black = "^24.4"
coverage = "^7.2.2"
pytest-timeout = "^2.1.0"

Expand Down
4 changes: 2 additions & 2 deletions servo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,5 @@ def __get_version() -> Optional[str]:
from .utilities import *

# Resolve forward references
servo.events.EventResult.update_forward_refs()
servo.events.EventHandler.update_forward_refs()
servo.events.EventResult.model_rebuild()
servo.events.EventHandler.model_rebuild()
73 changes: 40 additions & 33 deletions servo/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import copy
import enum
import time
from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING
from typing import Annotated, Any, Dict, List, Optional, Union, TYPE_CHECKING

from authlib.integrations.httpx_client import AsyncOAuth2Client
import curlify2
Expand Down Expand Up @@ -103,26 +103,27 @@ def response_event(self) -> Events:

class Request(pydantic.BaseModel):
event: Union[Events, str] # TODO: Needs to be rethought -- used adhoc in some cases
param: Optional[Dict[str, Any]] # TODO: Switch to a union of supported types
param: Optional[Dict[str, Any]] = None # TODO: Switch to a union of supported types
servo_uid: Union[str, None] = None

class Config:
json_encoders = {
Events: lambda v: str(v),
}
@pydantic.field_serializer("event")
def event_str(self, event: Events | str) -> str:
if isinstance(event, Events):
return str(event)
return event


class Status(pydantic.BaseModel):
status: Statuses
message: Optional[str] = None
other_messages: Optional[
list[str]
] = None # other lower priority error in exception group
additional_messages: Optional[list[str]] = (
None # other lower priority error in exception group
)
reason: Optional[str] = None
state: Optional[Dict[str, Any]] = None
descriptor: Optional[Dict[str, Any]] = None
metrics: Union[dict[str, Any], None] = None
annotations: Union[dict[str, str], None] = None
metrics: Union[Dict[str, Any], None] = None
annotations: Union[Dict[str, str], None] = None
command_uid: Union[str, None] = pydantic.Field(default=None, alias="cmd_uid")

@classmethod
Expand Down Expand Up @@ -162,17 +163,18 @@ def from_error(

return cls(status=status, message=str(error), reason=reason, **kwargs)

def dict(
def model_dump(
self,
*,
exclude_unset: bool = True,
by_alias: bool = True,
**kwargs,
) -> DictStrAny:
return super().dict(exclude_unset=exclude_unset, by_alias=by_alias, **kwargs)
return super().model_dump(
exclude_unset=exclude_unset, by_alias=by_alias, **kwargs
)

class Config:
allow_population_by_field_name = True
model_config = pydantic.ConfigDict(populate_by_name=True)


class SleepResponse(pydantic.BaseModel):
Expand All @@ -182,38 +184,43 @@ class SleepResponse(pydantic.BaseModel):
# SleepResponse '{"cmd": "SLEEP", "param": {"duration": 60, "data": {"reason": "no active optimization pipeline"}}}'


def metric_name(v: servo.Metric | str) -> str:
if isinstance(v, servo.Metric):
return v.name

return v


# Instructions from servo on what to measure
class MeasureParams(pydantic.BaseModel):
metrics: List[str]
metrics: List[
Annotated[
str,
pydantic.Field(validate_default=True),
pydantic.BeforeValidator(metric_name),
]
]
control: servo.types.Control

@pydantic.validator("metrics", always=True, pre=True)
@pydantic.field_validator("metrics", mode="before")
@classmethod
def coerce_metrics(cls, value) -> List[str]:
if isinstance(value, dict):
return list(value.keys())

return value

@pydantic.validator("metrics", each_item=True, pre=True)
def _map_metrics(cls, v) -> str:
if isinstance(v, servo.Metric):
return v.name

return v


class CommandResponse(pydantic.BaseModel):
command: Commands = pydantic.Field(alias="cmd")
command_uid: Union[str, None] = pydantic.Field(alias="cmd_uid")
param: Optional[
Union[MeasureParams, Dict[str, Any]]
] # TODO: Switch to a union of supported types, remove isinstance check from ServoRunner.measure when done
param: Optional[Union[MeasureParams, Dict[str, Any]]] = (
None # TODO: Switch to a union of supported types, remove isinstance check from ServoRunner.measure when done
)

class Config:
json_encoders = {
Commands: lambda v: v.value,
}
@pydantic.field_serializer("command")
def cmd_str(self, cmd: Commands) -> str:
return cmd.value


def descriptor_to_adjustments(descriptor: dict) -> List[servo.types.Adjustment]:
Expand Down Expand Up @@ -290,7 +297,7 @@ def get_api_client_for_optimizer(
if "Bearer" not in auth_header_value:
auth_header_value = f"Bearer {auth_header_value}"
return httpx.AsyncClient(
base_url=optimizer.url,
base_url=str(optimizer.url),
headers={
"Authorization": auth_header_value,
"User-Agent": user_agent(),
Expand All @@ -302,7 +309,7 @@ def get_api_client_for_optimizer(
)
elif isinstance(optimizer, servo.configuration.AppdynamicsOptimizer):
api_client = AsyncOAuth2Client(
base_url=optimizer.url,
base_url=str(optimizer.url),
headers={
"User-Agent": user_agent(),
"Content-Type": "application/json",
Expand Down
43 changes: 22 additions & 21 deletions servo/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import pydantic
import pydantic.json
import pydantic_settings
import yaml

import servo.configuration
Expand Down Expand Up @@ -69,7 +70,7 @@ class Assembly(pydantic.BaseModel):
of the schema family of methods. See the method docstrings for specific details.
"""

config_file: Optional[pathlib.Path]
config_file: Optional[pathlib.Path] = None
servos: List[servo.servo.Servo]
_context_token: Optional[contextvars.Token] = pydantic.PrivateAttr(None)

Expand Down Expand Up @@ -115,7 +116,7 @@ async def assemble(
# TODO: Needs to be public / have a better name
# TODO: We need to index the env vars here for multi-servo
servo_config_model, routes = _create_config_model(config=config, env=env)
servo_config = servo_config_model.parse_obj(config)
servo_config = servo_config_model.model_validate(config)

telemetry = servo.telemetry.Telemetry()

Expand Down Expand Up @@ -278,9 +279,9 @@ def _create_config_model_from_routes(
require_fields: bool = True,
) -> Type[servo.configuration.BaseServoConfiguration]:
# Create Pydantic fields for each active route
connector_versions: Dict[
Type[servo.connector.BaseConnector], str
] = {} # use dict for uniquing and ordering
connector_versions: Dict[Type[servo.connector.BaseConnector], str] = (
{}
) # use dict for uniquing and ordering
setting_fields: Dict[
str, Tuple[Type[servo.configuration.BaseConfiguration], Any]
] = {}
Expand All @@ -290,28 +291,32 @@ def _create_config_model_from_routes(

for name, connector_class in routes.items():
config_model = _derive_config_model_for_route(name, connector_class)
config_model.__config__.title = (
config_model.model_config["title"] = (
f"{connector_class.full_name} Settings (named {name})"
)
setting_fields[name] = (config_model, default_value)
connector_versions[
connector_class
] = f"{connector_class.full_name} v{connector_class.version}"
setting_fields[name] = (Optional[config_model], default_value)
connector_versions[connector_class] = (
f"{connector_class.full_name} v{connector_class.version}"
)

# Create our model
servo_config_model = pydantic.create_model(
"ServoConfiguration",
__base__=servo.configuration.BaseServoConfiguration,
**setting_fields,
servo_config_model: servo.configuration.BaseServoConfiguration = (
pydantic.create_model(
"ServoConfiguration",
__base__=servo.configuration.BaseServoConfiguration,
**setting_fields,
)
)

connectors_series = servo.utilities.join_to_series(
list(connector_versions.values())
)
servo_config_model.__config__.title = "Servo Configuration Schema"
servo_config_model.__config__.schema_extra = {
servo_config_model.model_config["title"] = "Servo Configuration Schema"
servo_config_model.model_config["json_schema_extra"] = {
"description": f"Schema for configuration of Servo v{servo.Servo.version} with {connectors_series}"
}
servo_config_model.model_config["env_nested_delimiter"] = "_"
servo_config_model.model_config["extra"] = "ignore"

return servo_config_model

Expand All @@ -337,6 +342,7 @@ def _create_config_model(
routes = servo.connector._routes_for_connectors_descriptor(connectors_value)
require_fields = True

print(f"require_fields {require_fields}")
servo_config_model = _create_config_model_from_routes(
routes, require_fields=require_fields
)
Expand Down Expand Up @@ -413,9 +419,4 @@ def _derive_config_model_for_route(
)
__config_models_cache__.append(cache_entry)

# Traverse across all the fields and update the env vars
for field_name, field in config_model.__fields__.items():
field.field_info.extra.pop("env", None)
field.field_info.extra["env_names"] = {f"SERVO_{name}_{field_name}".upper()}

return config_model
Loading
Loading