AmmarFahmy
adding all files
105b369
from typing import Optional, Dict, Any, Union, List
from pydantic import field_validator, Field
from pydantic_core.core_schema import FieldValidationInfo
from phi.base import PhiBase
from phi.app.context import ContainerContext
from phi.resource.base import ResourceBase
from phi.utils.log import logger
class AppBase(PhiBase):
# -*- App Name (required)
name: str
# -*- Image Configuration
# Image can be provided as a DockerImage object
image: Optional[Any] = None
# OR as image_name:image_tag str
image_str: Optional[str] = None
# OR as image_name and image_tag
image_name: Optional[str] = None
image_tag: Optional[str] = None
# Entrypoint for the container
entrypoint: Optional[Union[str, List[str]]] = None
# Command for the container
command: Optional[Union[str, List[str]]] = None
# -*- Python Configuration
# Install python dependencies using a requirements.txt file
install_requirements: bool = False
# Path to the requirements.txt file relative to the workspace_root
requirements_file: str = "requirements.txt"
# Set the PYTHONPATH env var
set_python_path: bool = True
# Manually provide the PYTHONPATH.
# If None, PYTHONPATH is set to workspace_root
python_path: Optional[str] = None
# Add paths to the PYTHONPATH env var
# If python_path is provided, this value is ignored
add_python_paths: Optional[List[str]] = None
# -*- App Ports
# Open a container port if open_port=True
open_port: bool = False
# If open_port=True, port_number is used to set the
# container_port if container_port is None and host_port if host_port is None
port_number: int = 80
# Port number on the Container to open
# Preferred over port_number if both are set
container_port: Optional[int] = Field(None, validate_default=True)
# Port name for the opened port
container_port_name: str = "http"
# Port number on the Host to map to the Container port
# Preferred over port_number if both are set
host_port: Optional[int] = Field(None, validate_default=True)
# -*- Extra Resources created "before" the App resources
resources: Optional[List[ResourceBase]] = None
# -*- Other args
print_env_on_load: bool = False
# -*- App specific args. Not to be set by the user.
# Container Environment that can be set by subclasses
# which is used as a starting point for building the container_env
# Any variables set in container_env will be overriden by values
# in the env_vars dict or env_file
container_env: Optional[Dict[str, Any]] = None
# Variable used to cache the container context
container_context: Optional[ContainerContext] = None
# -*- Cached Data
cached_resources: Optional[List[Any]] = None
@field_validator("container_port", mode="before")
def set_container_port(cls, v, info: FieldValidationInfo):
port_number = info.data.get("port_number")
if v is None and port_number is not None:
v = port_number
return v
@field_validator("host_port", mode="before")
def set_host_port(cls, v, info: FieldValidationInfo):
port_number = info.data.get("port_number")
if v is None and port_number is not None:
v = port_number
return v
def get_app_name(self) -> str:
return self.name
def get_image_str(self) -> str:
if self.image:
return f"{self.image.name}:{self.image.tag}"
elif self.image_str:
return self.image_str
elif self.image_name and self.image_tag:
return f"{self.image_name}:{self.image_tag}"
elif self.image_name:
return f"{self.image_name}:latest"
else:
return ""
def build_resources(self, build_context: Any) -> Optional[Any]:
logger.debug(f"@build_resource_group not defined for {self.get_app_name()}")
return None
def get_dependencies(self) -> Optional[List[ResourceBase]]:
return (
[dep for dep in self.depends_on if isinstance(dep, ResourceBase)] if self.depends_on is not None else None
)
def add_app_properties_to_resources(self, resources: List[ResourceBase]) -> List[ResourceBase]:
updated_resources = []
app_properties = self.model_dump(exclude_defaults=True)
app_group = self.get_group_name()
app_output_dir = self.get_app_name()
app_skip_create = app_properties.get("skip_create", None)
app_skip_read = app_properties.get("skip_read", None)
app_skip_update = app_properties.get("skip_update", None)
app_skip_delete = app_properties.get("skip_delete", None)
app_recreate_on_update = app_properties.get("recreate_on_update", None)
app_use_cache = app_properties.get("use_cache", None)
app_force = app_properties.get("force", None)
app_debug_mode = app_properties.get("debug_mode", None)
app_wait_for_create = app_properties.get("wait_for_create", None)
app_wait_for_update = app_properties.get("wait_for_update", None)
app_wait_for_delete = app_properties.get("wait_for_delete", None)
app_save_output = app_properties.get("save_output", None)
for resource in resources:
resource_properties = resource.model_dump(exclude_defaults=True)
resource_skip_create = resource_properties.get("skip_create", None)
resource_skip_read = resource_properties.get("skip_read", None)
resource_skip_update = resource_properties.get("skip_update", None)
resource_skip_delete = resource_properties.get("skip_delete", None)
resource_recreate_on_update = resource_properties.get("recreate_on_update", None)
resource_use_cache = resource_properties.get("use_cache", None)
resource_force = resource_properties.get("force", None)
resource_debug_mode = resource_properties.get("debug_mode", None)
resource_wait_for_create = resource_properties.get("wait_for_create", None)
resource_wait_for_update = resource_properties.get("wait_for_update", None)
resource_wait_for_delete = resource_properties.get("wait_for_delete", None)
resource_save_output = resource_properties.get("save_output", None)
# If skip_create on resource is not set, use app level skip_create (if set on app)
if resource_skip_create is None and app_skip_create is not None:
resource.skip_create = app_skip_create
# If skip_read on resource is not set, use app level skip_read (if set on app)
if resource_skip_read is None and app_skip_read is not None:
resource.skip_read = app_skip_read
# If skip_update on resource is not set, use app level skip_update (if set on app)
if resource_skip_update is None and app_skip_update is not None:
resource.skip_update = app_skip_update
# If skip_delete on resource is not set, use app level skip_delete (if set on app)
if resource_skip_delete is None and app_skip_delete is not None:
resource.skip_delete = app_skip_delete
# If recreate_on_update on resource is not set, use app level recreate_on_update (if set on app)
if resource_recreate_on_update is None and app_recreate_on_update is not None:
resource.recreate_on_update = app_recreate_on_update
# If use_cache on resource is not set, use app level use_cache (if set on app)
if resource_use_cache is None and app_use_cache is not None:
resource.use_cache = app_use_cache
# If force on resource is not set, use app level force (if set on app)
if resource_force is None and app_force is not None:
resource.force = app_force
# If debug_mode on resource is not set, use app level debug_mode (if set on app)
if resource_debug_mode is None and app_debug_mode is not None:
resource.debug_mode = app_debug_mode
# If wait_for_create on resource is not set, use app level wait_for_create (if set on app)
if resource_wait_for_create is None and app_wait_for_create is not None:
resource.wait_for_create = app_wait_for_create
# If wait_for_update on resource is not set, use app level wait_for_update (if set on app)
if resource_wait_for_update is None and app_wait_for_update is not None:
resource.wait_for_update = app_wait_for_update
# If wait_for_delete on resource is not set, use app level wait_for_delete (if set on app)
if resource_wait_for_delete is None and app_wait_for_delete is not None:
resource.wait_for_delete = app_wait_for_delete
# If save_output on resource is not set, use app level save_output (if set on app)
if resource_save_output is None and app_save_output is not None:
resource.save_output = app_save_output
# If workspace_settings on resource is not set, use app level workspace_settings (if set on app)
if resource.workspace_settings is None and self.workspace_settings is not None:
resource.set_workspace_settings(self.workspace_settings)
# If group on resource is not set, use app level group (if set on app)
if resource.group is None and app_group is not None:
resource.group = app_group
# Always set output_dir on resource to app level output_dir
resource.output_dir = app_output_dir
app_dependencies = self.get_dependencies()
if app_dependencies is not None:
if resource.depends_on is None:
resource.depends_on = app_dependencies
else:
resource.depends_on.extend(app_dependencies)
updated_resources.append(resource)
return updated_resources
def get_resources(self, build_context: Any) -> List[ResourceBase]:
if self.cached_resources is not None and len(self.cached_resources) > 0:
return self.cached_resources
base_resources = self.resources or []
app_resources = self.build_resources(build_context)
if app_resources is not None:
base_resources.extend(app_resources)
self.cached_resources = self.add_app_properties_to_resources(base_resources)
# logger.debug(f"Resources: {self.cached_resources}")
return self.cached_resources
def matches_filters(self, group_filter: Optional[str] = None) -> bool:
if group_filter is not None:
group_name = self.get_group_name()
logger.debug(f"{self.get_app_name()}: Checking {group_filter} in {group_name}")
if group_name is None or group_filter not in group_name:
return False
return True
def should_create(self, group_filter: Optional[str] = None) -> bool:
if not self.enabled or self.skip_create:
return False
return self.matches_filters(group_filter)
def should_delete(self, group_filter: Optional[str] = None) -> bool:
if not self.enabled or self.skip_delete:
return False
return self.matches_filters(group_filter)
def should_update(self, group_filter: Optional[str] = None) -> bool:
if not self.enabled or self.skip_update:
return False
return self.matches_filters(group_filter)