diff --git a/src/cookiecutter_python/backend/helpers.py b/src/cookiecutter_python/backend/helpers.py index acadaecc..8f9d8411 100644 --- a/src/cookiecutter_python/backend/helpers.py +++ b/src/cookiecutter_python/backend/helpers.py @@ -65,15 +65,15 @@ def parse_context(config_file: str): user_default_context = {} user_interpreters = cook_json['interpreters'] - context_defaults = dict( - cook_json, - **{k: v for k, v in user_default_context.items()}, - **{k: v[0] for k, v in choices.items() if k not in user_default_context}, - ) + # Optimize: Build context_defaults in a single pass + context_defaults = dict(cook_json, **user_default_context) + for k, v in choices.items(): + if k not in context_defaults: + context_defaults[k] = v[0] # Render cookiecutter.json again with context to resolve derived fields # This ensures derived fields like pkg_name get computed properly - template = env.get_template('cookiecutter.json') + # Optimize: Reuse template object instead of getting it again rendered_with_context = template.render({'cookiecutter': context_defaults}) resolved_cook_json: t.Mapping[str, t.Any] = json.loads(rendered_with_context) diff --git a/src/cookiecutter_python/backend/hosting_services/extract_name.py b/src/cookiecutter_python/backend/hosting_services/extract_name.py index 5be80ed3..a489ad75 100644 --- a/src/cookiecutter_python/backend/hosting_services/extract_name.py +++ b/src/cookiecutter_python/backend/hosting_services/extract_name.py @@ -31,20 +31,16 @@ def __call__(self, config_file: str) -> str: try: return self.name_extractor(context_data) except KeyError as error: + # Optimize: Sort before converting to string, remove unnecessary indent and sort_keys + available_vars = ', '.join(str(x) for x in sorted(context_data.keys())) raise ContextVariableDoesNotExist( "{msg}: {data}".format( - msg="Attempted to retrieve non-existant variable", + msg="Attempted to retrieve non-existent variable", data=json.dumps( { 'variable_name': str(self.name_extractor), - 'available_variables': '[{keys}]'.format( - keys=', '.join( - tuple(sorted([str(x) for x in context_data.keys()])) - ), - ), + 'available_variables': f'[{available_vars}]', }, - indent=4, - sort_keys=True, ), ), ) from error diff --git a/src/cookiecutter_python/backend/proxy.py b/src/cookiecutter_python/backend/proxy.py index 4ec1e2a7..788e2a3c 100644 --- a/src/cookiecutter_python/backend/proxy.py +++ b/src/cookiecutter_python/backend/proxy.py @@ -18,6 +18,4 @@ def log_info_args(message: str, *args, **kwargs) -> Tuple[str, str]: 'keyword_args': {k: str(v) for k, v in kwargs.items()}, 'positional_args': [str(arg_value) for arg_value in args], }, - indent=4, - sort_keys=True, ) diff --git a/src/cookiecutter_python/utils.py b/src/cookiecutter_python/utils.py index 6252e27c..80937fca 100644 --- a/src/cookiecutter_python/utils.py +++ b/src/cookiecutter_python/utils.py @@ -49,9 +49,9 @@ def load(interface: t.Type[T], module: t.Optional[str] = None) -> t.List[t.Type[ '{package}.{module}'.format(package=dotted_lib_path, module=module_name) ) - for attribute_name in dir(module_object): - attribute = getattr(module_object, attribute_name) - + # Optimize: Use vars() instead of dir() + getattr() for better performance + # Note: For module objects, vars() returns the same attributes as dir() (excluding builtins) + for attribute_name, attribute in vars(module_object).items(): if ( attribute != interface and isclass(attribute)