2023-07-05 21:21:08 +00:00
from os . path import abspath , dirname , join
2023-07-24 10:49:57 +00:00
from pathlib import Path
from typing import Optional
2023-07-05 21:21:08 +00:00
from dynaconf import Dynaconf
2023-08-01 11:43:26 +00:00
from starlette_context import context
2023-07-05 21:21:08 +00:00
2023-07-24 10:49:57 +00:00
PR_AGENT_TOML_KEY = ' pr-agent '
2023-07-05 21:21:08 +00:00
current_dir = dirname ( abspath ( __file__ ) )
2025-10-28 19:41:16 +00:00
dynconf_kwargs = { ' core_loaders ' : [ ] , # DISABLE default loaders, otherwise will load toml files more than once.
' loaders ' : [ ' pr_agent.custom_merge_loader ' , ' dynaconf.loaders.env_loader ' ] , # Use a custom loader to merge sections, but overwrite their overlapping values. Also support ENV variables to take precedence.
' root_path ' : join ( current_dir , " settings " ) , #Used for Dynaconf.find_file() - So that root path points to settings folder, since we disabled all core loaders.
' merge_enabled ' : True # In case more than one file is sent, merge them. Must be set to True, otherwise, a .toml file with section [XYZ] overwrites the entire section of a previous .toml file's [XYZ] and we want it to only overwrite the overlapping fields under such section
}
2023-08-01 11:43:26 +00:00
global_settings = Dynaconf (
2023-07-05 21:21:08 +00:00
envvar_prefix = False ,
2025-10-28 19:41:16 +00:00
load_dotenv = False , # Security: Don't load .env files
2023-07-05 21:21:08 +00:00
settings_files = [ join ( current_dir , f ) for f in [
2023-08-01 11:43:26 +00:00
" settings/configuration.toml " ,
2023-10-05 14:43:35 +00:00
" settings/ignore.toml " ,
2025-06-25 14:39:14 +00:00
" settings/generated_code_ignore.toml " ,
2023-08-01 11:43:26 +00:00
" settings/language_extensions.toml " ,
2023-10-05 14:43:35 +00:00
" settings/pr_reviewer_prompts.toml " ,
2023-10-06 10:03:36 +00:00
" settings/pr_questions_prompts.toml " ,
2024-02-15 12:25:22 +00:00
" settings/pr_line_questions_prompts.toml " ,
2023-10-06 10:03:36 +00:00
" settings/pr_description_prompts.toml " ,
2025-03-11 14:46:53 +00:00
" settings/code_suggestions/pr_code_suggestions_prompts.toml " ,
" settings/code_suggestions/pr_code_suggestions_prompts_not_decoupled.toml " ,
" settings/code_suggestions/pr_code_suggestions_reflect_prompts.toml " ,
2023-10-06 10:03:36 +00:00
" settings/pr_information_from_user_prompts.toml " ,
2023-08-01 11:43:26 +00:00
" settings/pr_update_changelog_prompts.toml " ,
2023-10-26 20:28:33 +00:00
" settings/pr_custom_labels.toml " ,
2023-10-06 10:03:36 +00:00
" settings/pr_add_docs.toml " ,
2024-09-21 13:58:37 +00:00
" settings/custom_labels.toml " ,
" settings/pr_help_prompts.toml " ,
2025-03-20 14:32:16 +00:00
" settings/pr_help_docs_prompts.toml " ,
2025-04-03 08:51:26 +00:00
" settings/pr_help_docs_headings_prompts.toml " ,
2025-01-02 09:16:21 +00:00
" settings/.secrets.toml " ,
2023-10-26 20:28:33 +00:00
" settings_prod/.secrets.toml " ,
2025-10-28 19:41:16 +00:00
] ] ,
* * dynconf_kwargs
2023-07-05 21:21:08 +00:00
)
2023-07-24 10:49:57 +00:00
2025-02-24 09:46:12 +00:00
def get_settings ( use_context = False ) :
2024-06-03 15:58:31 +00:00
"""
Retrieves the current settings .
This function attempts to fetch the settings from the starlette_context ' s context object. If it fails,
it defaults to the global settings defined outside of this function .
Returns :
Dynaconf : The current settings object , either from the context or the global default .
"""
2023-08-01 11:43:26 +00:00
try :
return context [ " settings " ]
except Exception :
return global_settings
2023-07-24 10:49:57 +00:00
# Add local configuration from pyproject.toml of the project being reviewed
2024-06-03 15:58:31 +00:00
def _find_repository_root ( ) - > Optional [ Path ] :
2023-07-24 10:49:57 +00:00
"""
Identify project root directory by recursively searching for the . git directory in the parent directories .
"""
cwd = Path . cwd ( ) . resolve ( )
no_way_up = False
while not no_way_up :
no_way_up = cwd == cwd . parent
if ( cwd / " .git " ) . is_dir ( ) :
return cwd
cwd = cwd . parent
2023-07-25 13:36:58 +00:00
return None
2023-07-24 10:49:57 +00:00
2023-08-01 11:43:26 +00:00
2023-07-24 10:49:57 +00:00
def _find_pyproject ( ) - > Optional [ Path ] :
"""
Search for file pyproject . toml in the repository root .
"""
2023-07-25 13:41:29 +00:00
repo_root = _find_repository_root ( )
if repo_root :
2024-06-03 15:58:31 +00:00
pyproject = repo_root / " pyproject.toml "
2023-07-25 13:41:29 +00:00
return pyproject if pyproject . is_file ( ) else None
return None
2023-07-24 10:49:57 +00:00
2023-08-01 11:43:26 +00:00
2023-07-24 10:49:57 +00:00
pyproject_path = _find_pyproject ( )
if pyproject_path is not None :
2023-08-01 11:43:26 +00:00
get_settings ( ) . load_file ( pyproject_path , env = f ' tool. { PR_AGENT_TOML_KEY } ' )
2025-05-29 03:42:05 +00:00
def apply_secrets_manager_config ( ) :
"""
Retrieve configuration from AWS Secrets Manager and override existing settings
"""
try :
2025-05-29 04:17:31 +00:00
# Dynamic imports to avoid circular dependency (secret_providers imports config_loader)
2025-05-29 03:42:05 +00:00
from pr_agent . secret_providers import get_secret_provider
from pr_agent . log import get_logger
secret_provider = get_secret_provider ( )
if not secret_provider :
return
if ( hasattr ( secret_provider , ' get_all_secrets ' ) and
get_settings ( ) . get ( " CONFIG.SECRET_PROVIDER " ) == ' aws_secrets_manager ' ) :
try :
secrets = secret_provider . get_all_secrets ( )
if secrets :
apply_secrets_to_config ( secrets )
get_logger ( ) . info ( " Applied AWS Secrets Manager configuration " )
except Exception as e :
get_logger ( ) . error ( f " Failed to apply AWS Secrets Manager config: { e } " )
except Exception as e :
try :
from pr_agent . log import get_logger
get_logger ( ) . debug ( f " Secret provider not configured: { e } " )
except :
# Fail completely silently if log module is not available
pass
def apply_secrets_to_config ( secrets : dict ) :
"""
Apply secret dictionary to configuration
"""
try :
2025-05-29 04:17:31 +00:00
# Dynamic import to avoid potential circular dependency
2025-05-29 03:42:05 +00:00
from pr_agent . log import get_logger
except :
def get_logger ( ) :
class DummyLogger :
def debug ( self , msg ) : pass
return DummyLogger ( )
for key , value in secrets . items ( ) :
if ' . ' in key : # nested key like "openai.key"
parts = key . split ( ' . ' )
if len ( parts ) == 2 :
section , setting = parts
section_upper = section . upper ( )
setting_upper = setting . upper ( )
# Set only when no existing value (prioritize environment variables)
current_value = get_settings ( ) . get ( f " { section_upper } . { setting_upper } " )
if current_value is None or current_value == " " :
get_settings ( ) . set ( f " { section_upper } . { setting_upper } " , value )
get_logger ( ) . debug ( f " Set { section } . { setting } from AWS Secrets Manager " )