# -*- coding: utf-8 -*-""""""# standard librarytry:importtyping_extensionsasTexcept:# pragma: no coverimporttypingasTimportstringfromfunctoolsimportcached_property# third party library (include vendor)froms3pathlibimportS3PathfrompydanticimportBaseModel,Field,ConfigDict,field_validatorfromwhich_env.apiimport(validate_env_name,CommonEnvNameEnum,)from.vendor.strutilsimportslugify# modules from this submodulefrom.constantsimportAwsTagKeyEnum,EnvVarNameEnum
[docs]defvalidate_project_name(project_name:str):""" Validate project name follows AWS-compatible naming conventions. Ensures project names work across AWS services and infrastructure tools. Rules enforce compatibility with S3 buckets, SSM parameters, and IAM resources. :after_param project_name: Project identifier to validate :raises ValueError: If name violates naming rules """ifproject_name[0]notinstring.ascii_lowercase:raiseValueError("first letter of project_name has to be a-z!")ifproject_name[-1]notin(string.ascii_lowercase+string.digits):raiseValueError("last letter of project_name has to be a-z, 0-9!")iflen(set(project_name).difference(string.ascii_lowercase+string.digits+"_-")):raiseValueError("project_name can only has a-z, 0-9, - or _!")
[docs]defnormalize_parameter_name(param_name:str)->str:""" Normalize parameter names to comply with AWS SSM Parameter Store restrictions. AWS SSM Parameter Store prohibits parameter names that start with "aws" or "ssm" reserved prefixes. This function automatically adds a "p-" prefix to avoid conflicts. **Why this is needed:** - Prevents runtime errors when deploying parameters - Ensures consistent naming across all AWS accounts - Handles edge cases where project names might conflict with AWS reserved words :after_param param_name: Original parameter name :return: AWS-compliant parameter name Example: - "my-project" -> "my-project" (unchanged) - "aws-project" -> "p-aws-project" (prefixed) - "ssm-config" -> "p-ssm-config" (prefixed) Reference: https://docs.aws.amazon.com/cli/latest/reference/ssm/put-parameter.html#options """ifparam_name.startswith("aws")orparam_name.startswith("ssm"):returnf"p-{param_name}"else:returnparam_name
classBaseEnv(BaseModel):""" """model_config=ConfigDict(frozen=True,extra="ignore",)project_name:str=Field(default=None)env_name:str=Field(default=None)aws_account_id:str|None=Field(default=None)aws_region:str|None=Field(default=None)s3uri_data:str|None=Field(default=None)s3uri_artifacts:str|None=Field(default=None)@field_validator("project_name",mode="before")@classmethoddef_check_project_name(cls,value:str)->str:validate_project_name(value)returnvalue@field_validator("env_name",mode="before")@classmethoddef_check_env_name(cls,value:str)->str:validate_env_name(value)returnvalue@classmethoddeffrom_dict(cls,data:dict[str,T.Any])->"T.Self":returncls(**data)defto_dict(self)->dict[str,T.Any]:returnself.model_dump()@cached_propertydefproject_name_slug(self)->str:""" Project name with hyphen delimiters for URLs and display. Example: "my_project" becomes "my-project" """returnslugify(self.project_name,delim="-")@cached_propertydefproject_name_snake(self)->str:""" Project name with underscore delimiters for code and systems. Example: "my-project" becomes "my_project" """returnslugify(self.project_name,delim="_")@cached_propertydefprefix_name_slug(self)->str:""" Combined project and environment name with hyphen delimiters. Used for resource naming and display purposes. Example: "my-project-dev" """returnf"{self.project_name_slug}-{self.env_name}"@cached_propertydefprefix_name_snake(self)->str:""" Combined project and environment name with mixed delimiters. Used for system identifiers and internal naming. Example: "my_project-dev" """returnf"{self.project_name_snake}-{self.env_name}"@cached_propertydefparameter_name(self)->str:""" AWS SSM Parameter Store name for this environment's configuration. Follows the pattern: "${project_name}-${env_name}" with automatic normalization to avoid AWS naming restrictions (e.g., "aws" prefix). Example: "my_project-dev" -> "my_project-dev" Example: "aws_project-prod" -> "p-aws_project-prod" .. note:: If you want to use "/path/to/parameter-name" style, you can override this property in your environment class to return a different value. """returnnormalize_parameter_name(self.prefix_name_snake)@propertydefs3dir_data(self)->S3Path:""" :class:`s3pathlib.S3Path` object of ``s3uri_data`` """returnS3Path.from_s3_uri(self.s3uri_data).to_dir()@propertydefs3dir_env_data(self)->S3Path:""" Environment specific s3 folder to store project data. """returnself.s3dir_data.joinpath("envs",self.env_name).to_dir()@propertydefs3dir_artifacts(self)->S3Path:# pragma: no cover""" Shared artifacts s3 dir for all environments. """returnS3Path.from_s3_uri(self.s3uri_artifacts).to_dir()@propertydefs3dir_env_artifacts(self)->S3Path:# pragma: no cover""" Env specific artifacts s3 dir. example: ``${s3dir_artifacts}/envs/${env_name}/ """returnself.s3dir_artifacts.joinpath("envs",self.env_name).to_dir()@propertydefs3dir_tmp_artifacts(self)->S3Path:""" Example: ``${s3dir_artifacts}/tmp/`` """returnself.s3dir_artifacts.joinpath("tmp").to_dir()@propertydefs3dir_config_artifacts(self)->S3Path:""" Example: ``${s3dir_artifacts}/config/`` """returnself.s3dir_artifacts.joinpath("config").to_dir()@propertydefenv_vars(self:"BaseEnv")->dict[str,str]:""" Common environment variable for all computational resources in this environment. It is primarily for "self awareness" (detect who I am, which environment I am in). """return{EnvVarNameEnum.PARAMETER_NAME.value:self.parameter_name,EnvVarNameEnum.PROJECT_NAME.value:self.project_name,EnvVarNameEnum.ENV_NAME.value:self.env_name,}@propertydefdevops_aws_tags(self:"BaseEnv")->dict[str,str]:""" Common AWS resources tags for all resources in devops environment. """return{AwsTagKeyEnum.tech_project_name.value:self.project_name,AwsTagKeyEnum.tech_env_name.value:CommonEnvNameEnum.devops.value,}@propertydefworkload_aws_tags(self:"BaseEnv")->dict[str,str]:""" Common AWS resources tags for all resources in workload environment. """return{AwsTagKeyEnum.tech_project_name.value:self.project_name,AwsTagKeyEnum.tech_env_name.value:self.env_name,}@propertydefcloudformation_stack_name(self:"BaseEnv")->str:""" Cloudformation stack name. """returnself.prefix_name_slugT_BASE_ENV=T.TypeVar("T_BASE_ENV",bound=BaseEnv)