/
liquid-g
/
liquid-code
Обзор
Документация
Войти
/
liquid-g
/
liquid-code
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop-0.4
src/liquidcode/commands/make/crud.py
243 строки
10 KB
User
v0.4.16: middleware priorities docs, code refactoring, logging and reserved words check
07 июл 2026, 00:02
07 июл 2026, 00:02
6c94e1e
Код
Авторство
О чём код?
"""Генератор CRUD-контроллера для LiquidCode. Команда: lcc make:crud <entity> [--directory DIR] [--force] [--no-interactive] [--fields FIELD...] Интерактивный режим позволяет выбрать методы. Особенности: - Имя контроллера формируется из имени сущности (например, Product → ProductController). - Если сущность не найдена, выводится ошибка и команда завершается. - Пустой ввод имени сущности завершает команду. - Поля извлекаются из __init__ сущности с использованием get_type_hints для корректной обработки Optional, List, Dict и других Generic-типов. """ import argparse import importlib import inspect from typing import Optional, get_args, get_origin, \ get_type_hints from ..base import ( generate_from_template, TemplateError, normalize_class_name, normalize_route, ask_methods, normalize_field_type, ) from ..core.naming_utils import CONTROLLER_SUFFIX def configure_parser(subparser: argparse.ArgumentParser) -> None: """ Добавляет аргументы для команды make:crud. Args: subparser: Объект ArgumentParser для команды. """ subparser.add_argument( 'entity', nargs='?', help='Имя сущности (например, Product). Контроллер будет назван ProductController.' ) subparser.add_argument( '--fields', nargs='*', help='Поля для сериализатора (например, name:string price:float). Если не указаны, будут извлечены из сущности.' ) subparser.add_argument( '--directory', default='src/Controller', help='Целевая директория для контроллера (по умолчанию: src/Controller)' ) subparser.add_argument( '--force', action='store_true', help='Перезаписать файл, если он существует' ) subparser.add_argument( '--no-interactive', action='store_true', help='Отключить интерактивный режим (использовать значения по умолчанию)' ) def run(args: argparse.Namespace) -> None: """ Выполняет команду make:crud. """ try: # 0. Проверка: в неинтерактивном режиме обязательно указать имя сущности if args.no_interactive and not args.entity: print( "[ERROR] В неинтерактивном режиме обязательно укажите имя сущности через аргумент.") return # 1. Запрашиваем имя сущности, пока не будет введено корректное или пустое entity_name = args.entity while True: if not entity_name and not args.no_interactive: entity_name = input( "Введите имя сущности для CRUD (например, Product) или оставьте пустым для выхода: ").strip() if not entity_name: print("Выход.") return try: normalized_entity = normalize_class_name(entity_name) break except ValueError as e: print(f"[ERROR] Ошибка: {e}") entity_name = None # сброс для повторного запроса if args.no_interactive: print( "[ERROR] Интерактивный режим отключен. Укажите корректное имя сущности через аргумент.") return # 2. Проверяем существование сущности и извлекаем поля entity_exists = False entity_fields = [] try: module_name = f"src.Entity.{normalized_entity}" entity_module = importlib.import_module(module_name) entity_class = None for name, obj in inspect.getmembers(entity_module, inspect.isclass): if obj.__name__ == normalized_entity: entity_class = obj break if entity_class: hints = get_type_hints(entity_class.__init__) sig = inspect.signature(entity_class.__init__) for param_name, param in sig.parameters.items(): if param_name == 'self': continue annotation = hints.get(param_name, param.annotation) nullable = False type_name = None if get_origin(annotation) is Optional: nullable = True args_list = get_args(annotation) if args_list: annotation = args_list[0] if isinstance(annotation, type): type_name = annotation.__name__ if hasattr(annotation, '__name__') else str( annotation) elif annotation is inspect.Parameter.empty: type_name = 'string' else: # Обработка List, Dict, etc. if get_origin(annotation) is list: inner = get_args(annotation) if inner: inner_type = inner[0] inner_name = inner_type.__name__ if hasattr( inner_type, '__name__') else str( inner_type) type_name = f"list[{inner_name}]" else: type_name = 'list' elif get_origin(annotation) is dict: type_name = 'dict' elif get_origin(annotation) is tuple: type_name = 'tuple' elif get_origin(annotation) is set: type_name = 'set' else: type_name = 'string' if not type_name: type_name = 'string' entity_fields.append({ 'name': param_name, 'type': type_name, 'nullable': nullable, 'length': None, }) entity_exists = True except (ImportError, ModuleNotFoundError, AttributeError): entity_exists = False if not entity_exists: print(f"[ERROR] Сущность {normalized_entity} не найдена в src.Entity.") print(" Сначала создайте сущность через make:entity.") return print(f"[OK] Найдена сущность {normalized_entity}. Использованы её поля.") # 3. Определяем поля для сериализатора from ..core.naming_utils import RESERVED_WORDS if args.fields: fields = [] for field in args.fields: if ':' in field: fname, ftype = field.split(':', 1) else: fname, ftype = field, 'string' # Проверка на зарезервированные слова if fname in RESERVED_WORDS: print(f"⚠️ Поле '{fname}' — зарезервированное слово Python. Будет преобразовано в '{fname}_field'.") fields.append({ 'name': fname, 'type': ftype, 'nullable': False, 'length': None, }) else: fields = entity_fields for field in fields: field['type'] = normalize_field_type(field['type'], field['nullable']) # 4. Выбор методов if not args.no_interactive: selected_methods = ask_methods() else: selected_methods = ['index', 'show', 'create', 'update', 'delete'] # Проверка: если ни один метод не выбран, используем все по умолчанию if not selected_methods: print( "[WARN] Не выбраны методы. Будут созданы все методы по умолчанию.") selected_methods = ['index', 'show', 'create', 'update', 'delete'] controller_name = normalized_entity route = controller_name.lower() normalized_route = normalize_route(route) extra = { 'name': controller_name, 'route': normalized_route, 'fields': fields, 'use_serializer': bool(fields), 'methods': selected_methods, } created = generate_from_template( template_name='crud', args=args, extra_kwargs=extra, default_filename='{name}Controller.py', default_dir='src/Controller', ) if created: print(f"[OK] Создан CRUD-контроллер: {controller_name}{CONTROLLER_SUFFIX}") if fields: print( f" Поля сериализатора: {', '.join(f['name'] for f in fields)}") if selected_methods: print(f" Методы: {', '.join(selected_methods)}") else: print( "[WARN] Файл уже существует. Используйте --force для перезаписи.") except (TypeError, ValueError, TemplateError, IOError) as e: print(f"[ERROR] Ошибка: {e}")