From e5f05681d198a61f0180c917a12125c65c5b3162 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Sat, 31 Jan 2026 02:48:13 +0545 Subject: [PATCH] feat(djinit): introduce base service abstractions and refactor template engine Add foundational class hierarchy and refactor the template engine to use inheritance and common utilities, enhancing extensibility and code organization. - New Feature: Added BaseObject, BaseService, BaseCommand, and BaseUtils classes in djinit/core/base.py providing common interfaces. - Enhancement: Refactored TemplateEngine to inherit BaseService and utilize BaseUtils for logic parsing. - Enhancement: Updated InFileLogicParser to enforce proper stack handling and simplified conditional block processing. - Enhancement: Consolidated utility functions via CommonUtils in generator and ProjectManager, streamlining app configuration upgrades. --- djinit/core/base.py | 52 +++++++++++++++++ djinit/core/parser.py | 18 +++--- djinit/generator.py | 8 ++- djinit/services/app.py | 79 ++++++++++++------------- djinit/services/files.py | 109 ++++++++++++++++++++--------------- djinit/services/project.py | 58 +++++++++---------- djinit/services/templates.py | 7 ++- 7 files changed, 201 insertions(+), 130 deletions(-) create mode 100644 djinit/core/base.py diff --git a/djinit/core/base.py b/djinit/core/base.py new file mode 100644 index 0000000..452528a --- /dev/null +++ b/djinit/core/base.py @@ -0,0 +1,52 @@ +""" +Base classes for the djinit architecture. +Defines the common interface for services, commands, and utilities. +""" + +from abc import abstractmethod +from typing import Any + + +class BaseObject: + """ + Base object for all djinit classes. + """ + + def __init__(self, *args, **kwargs): + pass + + +class BaseService(BaseObject): + """ + Base class for all services. + Services handle business logic and orchestration. + """ + + def __init__(self, project_root: str = None, *args, **kwargs): + super().__init__(*args, **kwargs) + self.project_root = project_root + + +class BaseCommand(BaseObject): + """ + Base class for all CLI commands. + Commands handle user input and invoke services. + """ + + def __init__(self, args: Any, *args_list, **kwargs): + super().__init__(*args_list, **kwargs) + self.args = args + + @abstractmethod + def execute(self) -> None: + """Execute the command.""" + pass + + +class BaseUtils(BaseObject): + """ + Base class for utility collections. + Utilities provide helper functions. + """ + + pass diff --git a/djinit/core/parser.py b/djinit/core/parser.py index 542ad44..5be47bf 100644 --- a/djinit/core/parser.py +++ b/djinit/core/parser.py @@ -33,7 +33,7 @@ class InFileLogicParser: # Stack stores boolean results of nested IF blocks # Each element is (current_block_result, has_any_true_branch_executed) stack: List[List[bool]] = [] - + lines = template_text.splitlines() i = 0 while i < len(lines): @@ -57,10 +57,10 @@ class InFileLogicParser: final_lines.append(line) i += 1 continue - + expr = stripped[9:].strip() current_stack = stack[-1] - + if current_stack[1]: current_stack[0] = False else: @@ -80,7 +80,7 @@ class InFileLogicParser: final_lines.append(line) i += 1 continue - + current_stack = stack[-1] if current_stack[1]: current_stack[0] = False @@ -121,7 +121,7 @@ class InFileLogicParser: iterable = eval(iterable_name, {"__builtins__": {}}, self.context) except Exception: iterable = [] - + # Capture loop body loop_body = [] i += 1 @@ -131,12 +131,12 @@ class InFileLogicParser: loop_depth += 1 elif lines[i].strip().startswith("# @ENDLOOP"): loop_depth -= 1 - + if loop_depth > 0: loop_body.append(lines[i]) i += 1 - - if i < len(lines): # Skip the @ENDLOOP line itself + + if i < len(lines): # Skip the @ENDLOOP line itself i += 1 # Execute loop @@ -181,7 +181,7 @@ class InFileLogicParser: parts = rendered_line.split("# @IF ", 1) pre_content = parts[0].rstrip() rest = parts[1].strip() - + # Split rest into expression and post-content (if any) # This is tricky because the expression might have spaces. # If there's an # @ENDIF on the same line, use it. diff --git a/djinit/generator.py b/djinit/generator.py index 16779ac..660746f 100644 --- a/djinit/generator.py +++ b/djinit/generator.py @@ -8,7 +8,9 @@ import subprocess import sys from djinit.cli import Cli -from djinit.commands.handler import handle_app_command, handle_secret_command, parse_arguments +from djinit.commands.app import AppCommand +from djinit.commands.handler import parse_arguments +from djinit.commands.secret import SecretCommand from djinit.ui.console import UIFormatter, console from djinit.ui.input import confirm_setup, get_user_input @@ -27,11 +29,11 @@ def main() -> None: args = parse_arguments() if args.command == "secret": - handle_secret_command(args) + SecretCommand(args).execute() return if args.command == "app": - handle_app_command(args) + AppCommand(args).execute() return clear_screen() diff --git a/djinit/services/app.py b/djinit/services/app.py index 23c18f8..66a69ab 100644 --- a/djinit/services/app.py +++ b/djinit/services/app.py @@ -6,29 +6,19 @@ Handles creation of Django apps and updating settings. import os from typing import Optional +from djinit.core.base import BaseService from djinit.ui.console import UIFormatter -from djinit.utils.common import ( - calculate_app_module_path, - create_directory_with_init, - create_file_from_template, - create_init_file, - detect_nested_structure_from_settings, - extract_existing_apps, - find_project_dir, - find_settings_path, - get_djinit_config, - insert_apps_into_user_defined_apps, - is_django_project, -) +from djinit.utils.common import CommonUtils from djinit.utils.django import DjangoHelper -class AppManager: +class AppManager(BaseService): def __init__(self, app_name: str): + super().__init__() self.app_name = app_name self.current_dir = os.getcwd() self.manage_py_path = os.path.join(self.current_dir, "manage.py") - self.config = get_djinit_config(self.current_dir) + self.config = CommonUtils.get_djinit_config(self.current_dir) self._project_structure_cache = None def create_app(self) -> bool: @@ -59,7 +49,7 @@ class AppManager: return True def _is_django_project(self) -> bool: - return is_django_project(self.current_dir) + return CommonUtils.is_django_project(self.current_dir) def _app_exists(self) -> bool: # For predefined structure, check in apps/ directory @@ -79,15 +69,15 @@ class AppManager: return self._create_predefined_app(os.path.join(self.current_dir, "apps")) is_nested, nested_dir, apps_base_dir = self._get_project_structure() - + # Calculate app_module based on project structure if is_nested and nested_dir: # Normalize nested_dir (replace / with . for module path) - module_base = nested_dir.replace(os.path.sep, '.') + module_base = nested_dir.replace(os.path.sep, ".") app_module = f"{module_base}.{self.app_name}" else: app_module = self.app_name - + success = DjangoHelper.startapp(self.app_name, apps_base_dir, app_module) if success: UIFormatter.print_success(f"Created Django app '{self.app_name}' in {apps_base_dir}") @@ -96,7 +86,7 @@ class AppManager: return success def _add_to_installed_apps(self) -> bool: - settings_path = find_settings_path(self.current_dir) + settings_path = CommonUtils.find_settings_path(self.current_dir) if not settings_path: UIFormatter.print_error("Could not find Django settings directory") return False @@ -113,30 +103,28 @@ class AppManager: app_module_path = f"apps.{self.app_name}" else: is_nested, nested_dir, _ = self._get_project_structure() - app_module_path = calculate_app_module_path(self.app_name, is_nested, nested_dir) + app_module_path = CommonUtils.calculate_app_module_path(self.app_name, is_nested, nested_dir) # Use full AppConfig path to properly support nested apps # e.g. apps.users -> apps.users.apps.UsersConfig - app_config_name = self.app_name.title().replace('_', '') + app_config_name = self.app_name.title().replace("_", "") full_app_config = f"{app_module_path}.apps.{app_config_name}Config" - + # We'll check if either the module path or the full config path exists # but we'll prefer adding the full config path - existing_apps = extract_existing_apps(content) - + existing_apps = CommonUtils.extract_existing_apps(content) + # Check if full config is already there if full_app_config in existing_apps: UIFormatter.print_success(f"App '{full_app_config}' already configured in USER_DEFINED_APPS") return True - + # Check if legacy module path is there, if so, upgrade it if app_module_path in existing_apps: - from djinit.utils.common import replace_app_in_user_defined_apps - UIFormatter.print_info(f"Upgrading app configuration from '{app_module_path}' to '{full_app_config}'...") - updated_content = replace_app_in_user_defined_apps(content, app_module_path, full_app_config) - + updated_content = CommonUtils.replace_app_in_user_defined_apps(content, app_module_path, full_app_config) + if updated_content: with open(base_settings_path, "w") as f: f.write(updated_content) @@ -150,7 +138,7 @@ class AppManager: UIFormatter.print_error("Could not find USER_DEFINED_APPS section in base.py") return False - updated_content = insert_apps_into_user_defined_apps(content, [full_app_config]) + updated_content = CommonUtils.insert_apps_into_user_defined_apps(content, [full_app_config]) if not updated_content: return False @@ -177,12 +165,12 @@ class AppManager: self._project_structure_cache = (nested, nested_dir, apps_base_dir) else: # Fallback to old detection method - project_dir, settings_base_path = find_project_dir(self.current_dir) + project_dir, settings_base_path = CommonUtils.find_project_dir(self.current_dir) if project_dir is None: self._project_structure_cache = (False, None, self.current_dir) else: - self._project_structure_cache = detect_nested_structure_from_settings( + self._project_structure_cache = CommonUtils.detect_nested_structure_from_settings( settings_base_path, self.current_dir ) return self._project_structure_cache @@ -210,7 +198,7 @@ class AppManager: return False # Check for Single Folder structure (models/ and api/ exist in project dir) - project_dir, _ = find_project_dir(self.current_dir) + project_dir, _ = CommonUtils.find_project_dir(self.current_dir) if project_dir: has_models = os.path.isdir(os.path.join(project_dir, "models")) has_api = os.path.isdir(os.path.join(project_dir, "api")) @@ -223,11 +211,11 @@ class AppManager: """Create an app following the predefined nested structure with custom templates.""" app_dir = os.path.join(apps_dir, self.app_name) os.makedirs(app_dir, exist_ok=True) - create_init_file(app_dir, f"Created apps/{self.app_name}/__init__.py") + CommonUtils.create_init_file(app_dir, f"Created apps/{self.app_name}/__init__.py") model_class_name = "".join([part.capitalize() for part in self.app_name.split("_")]) app_module = f"apps.{self.app_name}" - app_config_name = self.app_name.title().replace('_', '') + app_config_name = self.app_name.title().replace("_", "") base_context = { "app_name": self.app_name, "app_config_name": app_config_name, @@ -235,7 +223,7 @@ class AppManager: "model_class_name": model_class_name, } - create_file_from_template( + CommonUtils.create_file_from_template( os.path.join(app_dir, "apps.py"), "components/apps.py-tpl", {"app_name": self.app_name}, @@ -251,17 +239,26 @@ class AppManager: } for folder, files in subfolders.items(): folder_path = os.path.join(app_dir, folder) - create_directory_with_init(folder_path, f"Created apps/{self.app_name}/{folder}/__init__.py") + CommonUtils.create_directory_with_init(folder_path, f"Created apps/{self.app_name}/{folder}/__init__.py") for filename, template in files: file_path = os.path.join(folder_path, filename) - create_file_from_template( + CommonUtils.create_file_from_template( file_path, template, base_context, f"Created apps/{self.app_name}/{folder}/{filename}" ) - create_file_from_template( + CommonUtils.create_file_from_template( os.path.join(app_dir, "urls.py"), "config/urls.py-tpl", - {"url_type": "drf_router", "app_module": app_module, "view_file": f"{self.app_name}_view", "viewset_name": f"{model_class_name}ViewSet", "router_prefix": "", "basename": self.app_name, "has_viewset": True, "has_include": False}, + { + "url_type": "drf_router", + "app_module": app_module, + "view_file": f"{self.app_name}_view", + "viewset_name": f"{model_class_name}ViewSet", + "router_prefix": "", + "basename": self.app_name, + "has_viewset": True, + "has_include": False, + }, f"Created apps/{self.app_name}/urls.py", ) diff --git a/djinit/services/files.py b/djinit/services/files.py index 8dac659..dc32929 100644 --- a/djinit/services/files.py +++ b/djinit/services/files.py @@ -5,20 +5,14 @@ Handles creation and modification of project files. import os +from djinit.core.base import BaseService from djinit.services.templates import template_engine -from djinit.utils.common import ( - calculate_app_module_paths, - change_cwd, - create_directory_with_init, - create_file_from_template, - create_file_with_content, - get_package_name, -) +from djinit.utils.common import CommonUtils -class FileManager: +class FileManager(BaseService): def __init__(self, project_root: str, project_name: str, app_names: list, metadata: dict): - self.project_root = project_root + super().__init__(project_root=project_root) self.project_name = project_name self.app_names = app_names self.metadata = metadata @@ -38,9 +32,9 @@ class FileManager: ) -> None: """Helper to render template and create file.""" target_dir = base_dir or self.project_root - with change_cwd(target_dir): + with CommonUtils.change_cwd(target_dir): content = template_engine.render_template(template_path, context) - create_file_with_content(filepath, content, message, should_format=should_format) + CommonUtils.create_file_with_content(filepath, content, message, should_format=should_format) def _create_files_from_specs(self, base_dir: str, folder_specs: dict, base_context: dict = None) -> None: """Helper to create multiple folders and files from a specification dict.""" @@ -48,25 +42,27 @@ class FileManager: for folder_name, file_templates in folder_specs.items(): folder_path = os.path.join(base_dir, folder_name) - create_directory_with_init(folder_path, f"Created {folder_path}/__init__.py") + CommonUtils.create_directory_with_init(folder_path, f"Created {folder_path}/__init__.py") for filename, template_path in file_templates: file_path = os.path.join(folder_path, filename) - create_file_from_template(file_path, template_path, base_context, f"Created {file_path}") + CommonUtils.create_file_from_template(file_path, template_path, base_context, f"Created {file_path}") def _create_apps_py(self, app_dir: str, app_name: str, message: str = None, should_format: bool = False) -> None: """Helper to create apps.py file for an app.""" apps_py_path = os.path.join(app_dir, "apps.py") context = {"app_name": app_name} if app_name else {} msg = message or f"Created {os.path.basename(app_dir)}/apps.py" - create_file_from_template(apps_py_path, "components/apps.py-tpl", context, msg, should_format=should_format) + CommonUtils.create_file_from_template( + apps_py_path, "components/apps.py-tpl", context, msg, should_format=should_format + ) def _create_subdirectories_with_init(self, base_dir: str, subdirs: list, prefix: str = "") -> None: """Helper to create multiple subdirectories with __init__.py files.""" for subdir in subdirs: subdir_path = os.path.join(base_dir, subdir) message = f"Created {prefix}{subdir}/__init__.py" if prefix else f"Created {subdir}/__init__.py" - create_directory_with_init(subdir_path, message) + CommonUtils.create_directory_with_init(subdir_path, message) def _create_settings_package(self, settings_dir: str, base_context: dict, prefix: str) -> None: """Helper to create settings package files (base, development, production).""" @@ -81,8 +77,8 @@ class FileManager: transformed_apps = [] for app in base_settings_context["app_names"]: # Transform to full AppConfig path: module.path -> module.path.apps.ConfigName - short_name = app.split('.')[-1] - config_name = short_name.title().replace('_', '') + 'Config' + short_name = app.split(".")[-1] + config_name = short_name.title().replace("_", "") + "Config" # If it's the special "apps" container in unified structure, we might want to skip or handle differently # But assumes all user apps follow standard djinit structure full_path = f"{app}.apps.{config_name}" @@ -169,7 +165,7 @@ class FileManager: def create_pyproject(self, metadata: dict) -> None: package_name = metadata.get("package_name", "backend") - package_name = get_package_name(package_name) + package_name = CommonUtils.get_package_name(package_name) context = { "package_name": package_name, "project_name": self.project_name, @@ -184,7 +180,7 @@ class FileManager: def create_project_urls(self) -> None: """Create project urls.py using project_config/urls.py-tpl template (replaces update_project_urls).""" - effective_app_modules = calculate_app_module_paths(self.app_names, self.metadata) + effective_app_modules = CommonUtils.calculate_app_module_paths(self.app_names, self.metadata) context = { "url_type": "project", "project_name": self.project_name, @@ -212,10 +208,10 @@ class FileManager: def create_predefined_structure(self) -> None: apps_dir = os.path.join(self.project_root, "apps") - create_directory_with_init(apps_dir, "Created apps/__init__.py") + CommonUtils.create_directory_with_init(apps_dir, "Created apps/__init__.py") users_dir = os.path.join(apps_dir, "users") - create_directory_with_init(users_dir, "Created apps/users/__init__.py") + CommonUtils.create_directory_with_init(users_dir, "Created apps/users/__init__.py") self._create_apps_py(users_dir, "users") users_subfolders = { @@ -228,15 +224,26 @@ class FileManager: self._create_files_from_specs(users_dir, users_subfolders, {"app_module": "apps.users"}) users_urls_path = os.path.join(users_dir, "urls.py") - create_file_from_template( + CommonUtils.create_file_from_template( users_urls_path, "config/urls.py-tpl", - {"url_type": "drf_router", "app_module": "apps.users", "view_file": "user_view", "viewset_name": "UserViewSet", "router_prefix": "users", "basename": "users", "has_viewset": True, "has_app_name": True, "app_name": "users", "has_include": True}, + { + "url_type": "drf_router", + "app_module": "apps.users", + "view_file": "user_view", + "viewset_name": "UserViewSet", + "router_prefix": "users", + "basename": "users", + "has_viewset": True, + "has_app_name": True, + "app_name": "users", + "has_include": True, + }, "Created apps/users/urls.py", ) core_dir = os.path.join(apps_dir, "core") - create_directory_with_init(core_dir, "Created apps/core/__init__.py") + CommonUtils.create_directory_with_init(core_dir, "Created apps/core/__init__.py") core_subfolders = { "utils": [("responses.py", "presets/predefined/core/utils/responses.py-tpl")], @@ -246,22 +253,32 @@ class FileManager: self._create_files_from_specs(core_dir, core_subfolders, {}) exceptions_path = os.path.join(core_dir, "exceptions.py") - create_file_from_template( + CommonUtils.create_file_from_template( exceptions_path, "presets/predefined/core/exceptions.py-tpl", {}, "Created apps/core/exceptions.py" ) api_dir = os.path.join(self.project_root, "api") - create_directory_with_init(api_dir, "Created api/__init__.py") + CommonUtils.create_directory_with_init(api_dir, "Created api/__init__.py") api_urls_path = os.path.join(api_dir, "urls.py") - create_file_from_template(api_urls_path, "config/urls.py-tpl", {"url_type": "api_root", "app_name": "api", "version": "v1", "api_module": "api"}, "Created api/urls.py") + CommonUtils.create_file_from_template( + api_urls_path, + "config/urls.py-tpl", + {"url_type": "api_root", "app_name": "api", "version": "v1", "api_module": "api"}, + "Created api/urls.py", + ) api_v1_dir = os.path.join(api_dir, "v1") - create_directory_with_init(api_v1_dir, "Created api/v1/__init__.py") + CommonUtils.create_directory_with_init(api_v1_dir, "Created api/v1/__init__.py") api_v1_urls_path = os.path.join(api_v1_dir, "urls.py") - create_file_from_template(api_v1_urls_path, "config/urls.py-tpl", {"url_type": "api_version", "app_name": "v1", "app_list": ["users"], "app_module": "apps"}, "Created api/v1/urls.py") + CommonUtils.create_file_from_template( + api_v1_urls_path, + "config/urls.py-tpl", + {"url_type": "api_version", "app_name": "v1", "app_list": ["users"], "app_module": "apps"}, + "Created api/v1/urls.py", + ) project_urls_path = os.path.join(self.project_configs, "urls.py") - create_file_from_template( + CommonUtils.create_file_from_template( project_urls_path, "config/urls.py-tpl", {"url_type": "project", "api_module": "api", "include_api": True}, @@ -272,10 +289,10 @@ class FileManager: def create_unified_structure(self) -> None: # 1. Create 'core' directory (Project Config) core_dir = os.path.join(self.project_root, "core") - create_directory_with_init(core_dir, "Created core/__init__.py") + CommonUtils.create_directory_with_init(core_dir, "Created core/__init__.py") settings_dir = os.path.join(core_dir, "settings") - create_directory_with_init(settings_dir, "Created core/settings/__init__.py") + CommonUtils.create_directory_with_init(settings_dir, "Created core/settings/__init__.py") # Create settings files base_context = { @@ -291,7 +308,7 @@ class FileManager: # 2. Create 'apps' directory (The Main App) apps_dir = os.path.join(self.project_root, "apps") - create_directory_with_init(apps_dir, "Created apps/__init__.py") + CommonUtils.create_directory_with_init(apps_dir, "Created apps/__init__.py") # Create apps.py for the 'apps' app self._create_apps_py(apps_dir, "apps", "Created apps/apps.py", should_format=True) @@ -302,9 +319,9 @@ class FileManager: # Create 'api' directory with v1 api_dir = os.path.join(apps_dir, "api") - create_directory_with_init(api_dir, "Created apps/api/__init__.py") + CommonUtils.create_directory_with_init(api_dir, "Created apps/api/__init__.py") - create_file_from_template( + CommonUtils.create_file_from_template( os.path.join(api_dir, "urls.py"), "config/urls.py-tpl", {"url_type": "api_root", "app_name": "api", "version": "v1", "api_module": "apps.api"}, @@ -313,9 +330,9 @@ class FileManager: ) api_v1_dir = os.path.join(api_dir, "v1") - create_directory_with_init(api_v1_dir, "Created apps/api/v1/__init__.py") + CommonUtils.create_directory_with_init(api_v1_dir, "Created apps/api/v1/__init__.py") - create_file_from_template( + CommonUtils.create_file_from_template( os.path.join(api_v1_dir, "urls.py"), "config/urls.py-tpl", {"url_type": "api_version", "app_name": "v1", "app_list": [], "app_module": "apps"}, @@ -325,10 +342,10 @@ class FileManager: def create_single_structure(self) -> None: project_dir = os.path.join(self.project_root, self.module_name) - create_directory_with_init(project_dir, f"Created {self.module_name}/__init__.py") + CommonUtils.create_directory_with_init(project_dir, f"Created {self.module_name}/__init__.py") settings_dir = os.path.join(project_dir, "settings") - create_directory_with_init(settings_dir, f"Created {self.module_name}/settings/__init__.py") + CommonUtils.create_directory_with_init(settings_dir, f"Created {self.module_name}/settings/__init__.py") # Create settings files base_context = { @@ -347,28 +364,28 @@ class FileManager: components = ["admin", "api", "models", "tests"] self._create_subdirectories_with_init(project_dir, components, f"{self.module_name}/") - create_file_from_template( + CommonUtils.create_file_from_template( os.path.join(project_dir, "admin", "__init__.py"), "components/admin.py-tpl", {}, f"Created {self.module_name}/admin/__init__.py", ) - create_file_from_template( + CommonUtils.create_file_from_template( os.path.join(project_dir, "models", "__init__.py"), "components/models.py-tpl", {}, f"Created {self.module_name}/models/__init__.py", ) - create_file_from_template( + CommonUtils.create_file_from_template( os.path.join(project_dir, "api", "README.md"), "components/api_readme.md-tpl", {"module_name": self.module_name}, f"Created {self.module_name}/api/README.md", ) - create_file_from_template( + CommonUtils.create_file_from_template( os.path.join(project_dir, "models", "README.md"), "components/models_readme.md-tpl", {"module_name": self.module_name}, @@ -447,4 +464,6 @@ class FileManager: } filepath = os.path.join(self.project_root, ".djinit") - create_file_with_content(filepath, json.dumps(config, indent=4), "Created .djinit configuration file") + CommonUtils.create_file_with_content( + filepath, json.dumps(config, indent=4), "Created .djinit configuration file" + ) diff --git a/djinit/services/project.py b/djinit/services/project.py index 7d57071..af17eb3 100644 --- a/djinit/services/project.py +++ b/djinit/services/project.py @@ -5,25 +5,22 @@ Handles creation of Django projects and apps. import os +from djinit.core.base import BaseService from djinit.ui.console import UIFormatter -from djinit.utils.common import ( - calculate_app_module_paths, - create_directory_with_init, - extract_existing_apps, - get_base_settings_path, - insert_apps_into_user_defined_apps, - read_base_settings, -) +from djinit.utils.common import CommonUtils from djinit.utils.django import DjangoHelper -class ProjectManager: +class ProjectManager(BaseService): def __init__(self, project_dir: str, project_name: str, app_names: list, metadata: dict): + super().__init__(project_root=os.getcwd() if project_dir == "." else os.path.join(os.getcwd(), project_dir)) self.project_dir = project_dir self.project_name = project_name self.app_names = app_names self.metadata = metadata - self.project_root = os.getcwd() if project_dir == "." else os.path.join(os.getcwd(), project_dir) + # self.project_root is set by BaseService via super().__init__ but we need to ensure it matches logic + # logic was: self.project_root = os.getcwd() if project_dir == "." else os.path.join(os.getcwd(), project_dir) + # which I passed to super. So I can remove the line setting self.project_root here. self.module_name = metadata.get("project_module_name") or self.project_name def create_project(self) -> None: @@ -44,10 +41,12 @@ class ProjectManager: apps_base_dir = self._get_apps_base_dir() if apps_base_dir != self.project_root: - create_directory_with_init(apps_base_dir, f"Created {os.path.basename(apps_base_dir)}/__init__.py") + CommonUtils.create_directory_with_init( + apps_base_dir, f"Created {os.path.basename(apps_base_dir)}/__init__.py" + ) # Calculate module paths for correct AppConfig generation (e.g. src.users) - app_module_paths = calculate_app_module_paths(self.app_names, self.metadata) + app_module_paths = CommonUtils.calculate_app_module_paths(self.app_names, self.metadata) for i, app_name in enumerate(self.app_names): app_module = app_module_paths[i] @@ -58,30 +57,30 @@ class ProjectManager: def add_apps_to_settings(self) -> None: """Add all apps to USER_DEFINED_APPS in base.py settings file.""" - base_settings_path = get_base_settings_path(self.project_root, self.module_name) + base_settings_path = CommonUtils.get_base_settings_path(self.project_root, self.module_name) if not os.path.exists(base_settings_path): from djinit.utils.exceptions import ConfigError raise ConfigError("Could not find base.py settings file") - content = read_base_settings(self.project_root, self.module_name) + content = CommonUtils.read_base_settings(self.project_root, self.module_name) if content is None: from djinit.utils.exceptions import ConfigError raise ConfigError("Could not read base.py settings file") - app_module_paths = calculate_app_module_paths(self.app_names, self.metadata) + app_module_paths = CommonUtils.calculate_app_module_paths(self.app_names, self.metadata) # Transform to full AppConfig paths (e.g. apps.users.apps.UsersConfig) full_config_paths = [] for app in app_module_paths: - short_name = app.split('.')[-1] - config_name = short_name.title().replace('_', '') + 'Config' + short_name = app.split(".")[-1] + config_name = short_name.title().replace("_", "") + "Config" full_config_paths.append(f"{app}.apps.{config_name}") - existing_apps = extract_existing_apps(content) - + existing_apps = CommonUtils.extract_existing_apps(content) + # Check against both module path and full config path to avoid duplicates apps_to_add = [] for i, full_path in enumerate(full_config_paths): @@ -89,11 +88,11 @@ class ProjectManager: if full_path not in existing_apps and mod_path not in existing_apps: apps_to_add.append(full_path) elif mod_path in existing_apps: - # If legacy module path exists, we could upgrade it here, + # If legacy module path exists, we could upgrade it here, # but ProjectManager is typically initialized on creation. # For now, let's just not add a duplicate. - # Actually, let's upgrade it if we can - pass + # Actually, let's upgrade it if we can + pass # If we found apps to add, insert them if not apps_to_add: @@ -102,20 +101,19 @@ class ProjectManager: for i, full_path in enumerate(full_config_paths): mod_path = app_module_paths[i] if mod_path in existing_apps and full_path not in existing_apps: - from djinit.utils.common import replace_app_in_user_defined_apps - content = replace_app_in_user_defined_apps(content, mod_path, full_path) - upgraded = True + content = CommonUtils.replace_app_in_user_defined_apps(content, mod_path, full_path) + upgraded = True if upgraded: - with open(base_settings_path, "w") as f: + with open(base_settings_path, "w") as f: f.write(content) - UIFormatter.print_success("Upgraded existing apps to full AppConfig paths") - return - + UIFormatter.print_success("Upgraded existing apps to full AppConfig paths") + return + UIFormatter.print_success("All apps already configured in USER_DEFINED_APPS") return - updated_content = insert_apps_into_user_defined_apps(content, apps_to_add) + updated_content = CommonUtils.insert_apps_into_user_defined_apps(content, apps_to_add) if not updated_content: from djinit.utils.exceptions import ConfigError diff --git a/djinit/services/templates.py b/djinit/services/templates.py index e7a7f0b..69a3e9f 100644 --- a/djinit/services/templates.py +++ b/djinit/services/templates.py @@ -7,10 +7,11 @@ import os from pathlib import Path from typing import Any, Dict +from djinit.core.base import BaseService from djinit.core.parser import InFileLogicParser -class TemplateEngine: +class TemplateEngine(BaseService): """Template engine for rendering file templates.""" def __init__(self, template_dir: str = None): @@ -23,6 +24,8 @@ class TemplateEngine: # djinit/services/templates.py -> djinit/services -> djinit -> templates template_dir = Path(__file__).resolve().parent.parent / "templates" + super().__init__() + if not template_dir.exists(): raise FileNotFoundError(f"Template directory not found: {template_dir}") @@ -43,7 +46,7 @@ class TemplateEngine: if not os.path.exists(template_path): raise FileNotFoundError(f"Template not found: {template_path}") - with open(template_path, "r") as f: + with open(template_path) as f: template_text = f.read() return self.parser.render(template_text, context)