diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c6c631..bb0229c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,9 +35,6 @@ jobs: git config --global init.defaultBranch main PYTHON_VERSION=${{ matrix.python-version }} devcontainer up --workspace-folder . - - name: Lint package - run: devcontainer exec --workspace-folder . poe lint - - name: Test package run: devcontainer exec --workspace-folder . poe test diff --git a/chain.yaml b/chain.yaml new file mode 100644 index 0000000..8b53fee --- /dev/null +++ b/chain.yaml @@ -0,0 +1,148 @@ +TypedBuildCoreComponentsPrompt: + classes: + - TypedPrompt + - Chat + description: Develop core components of the DSL. + error_handling: Exception Handling + methods: + - execute + - validate + parsers: + - YAML Parser + - JSON Parser + performance_metrics: + - Speed + - Memory + source: Develop parsers {{ parsers }} for the YAML configurations. Implement classes + {{ classes }} and methods {{ methods }} for functionalities. Include {{ error_handling + }} and consider {{ performance_metrics }}. + title: Build Core Components +TypedDeploymentPrompt: + backup_plan: Daily Backups + ci_cd_pipelines: + - Jenkins + - GitLab + deployment_strategy: Blue-Green + description: Deploy the system. + monitoring_tools: + - Prometheus + - Grafana + rollback_procedures: + - Automated + - Manual + source: Choose an appropriate deployment strategy {{ deployment_strategy }}. Implement + CI/CD pipelines {{ ci_cd_pipelines }}, use monitoring tools {{ monitoring_tools + }}, have a backup plan {{ backup_plan }}, and prepare rollback procedures {{ rollback_procedures + }}. + title: Deployment +TypedDesignArchitecturePrompt: + components: + - Parser + - Executor + description: Design the DSL architecture. + interactions: + - Data Flow + - Control Flow + modularity: Modular + scalability: High + source: Plan how the DSL will interact with other system components {{ components + }}. Define the DSL's syntax {{ syntax }}, ensure {{ scalability }} and {{ modularity + }}, and outline component interactions {{ interactions }}. + syntax: YAML-based + title: Design Architecture +TypedDocumentationPrompt: + api_docs: + - Endpoints + - Examples + description: Create detailed documentation. + documentation_types: + - API + - User Guide + faqs: + - General + - Technical + source: Create detailed documentation types {{ documentation_types }} and offer + training sessions or materials to end-users including user guides {{ user_guides + }}, API documentation {{ api_docs }}, tutorials {{ tutorials }}, and FAQs {{ faqs + }}. + title: Documentation + tutorials: + - Video + - Text + user_guides: + - Getting Started + - Advanced +TypedImplementBusinessLogicPrompt: + algorithms: + - NLP + - ML + data_models: + - User + - Environment + description: Implement the business logic. + goal_setting: S.M.A.R.T + optimization_criteria: + - Efficiency + - Accuracy + source: Add logic for team composition {{ team_composition }}, goal setting {{ goal_setting + }}, use data models {{ data_models }}, apply algorithms {{ algorithms }}, and + meet optimization criteria {{ optimization_criteria }}. + team_composition: Cross-functional + title: Implement Business Logic +TypedMaintenancePrompt: + description: Maintain and update the system. + monitoring_metrics: + - CPU Usage + - Error Rate + patching_policy: Security First + source: Monitor system's usage and performance using metrics {{ monitoring_metrics + }}. Apply patches and updates as required following the update schedule {{ update_schedule + }} and patching policy {{ patching_policy }}. Provide support through channels + {{ support_channels }} and collect feedback via {{ user_feedback_mechanisms }}. + support_channels: + - Email + - Chat + title: Maintenance + update_schedule: Monthly + user_feedback_mechanisms: + - Survey + - Reviews +TypedRequirementAnalysisPrompt: + constraints: + - Time + - Budget + core_functionalities: + - Parsing + - Error Handling + description: Gather detailed requirements for the DSL. + source: Gather detailed requirements that the DSL needs to fulfill. Identify core + functionalities, consult with {{ stakeholders }}, consider {{ constraints }}, + choose appropriate {{ technologies }}, within the timeframe of {{ timeframe }}. + stakeholders: + - Product Manager + - Dev Team + - QA Team + technologies: + - Python + - YAML + timeframe: Q1 + title: Requirement Analysis +TypedTestingPrompt: + description: Conduct thorough testing. + integration_tests: + - test_end_to_end + source: Write unit tests {{ unit_tests }}, validate through integration tests {{ + integration_tests }}, perform stress tests {{ stress_tests }}, use test data {{ + test_data }} in various test environments {{ test_environments }}. + stress_tests: + - test_load + test_data: + - Sample YAML + - Sample JSON + test_environments: + - Local + - Staging + title: Testing + unit_tests: + - test_parser + - test_executor diff --git a/poetry.lock b/poetry.lock index 54294d8..0a233fa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1382,6 +1382,25 @@ files = [ docs = ["Sphinx", "furo"] test = ["objgraph", "psutil"] +[[package]] +name = "groq" +version = "0.4.1" +description = "The official Python library for the groq API" +optional = false +python-versions = ">=3.7" +files = [ + {file = "groq-0.4.1-py3-none-any.whl", hash = "sha256:2939ff96e3fc633416e5d9ab26bbfd63c70b2226338a507f8c2b0b3aa27c9dec"}, + {file = "groq-0.4.1.tar.gz", hash = "sha256:f2285c0a7d64abefcdec3d61e8bc1a61ff04d887ed30b991ac7fe53ae1e10251"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +typing-extensions = ">=4.7,<5" + [[package]] name = "gunicorn" version = "21.2.0" @@ -5265,4 +5284,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "4bdc172b18ea1bae17308051c3848966ece2709966feeee8e5b0df318c0c85d6" +content-hash = "b4495d13cabe3f7b92ba0953c4a0b12567cc859ac59cf3c2aec84cad8561b978" diff --git a/pyproject.toml b/pyproject.toml index 33268d4..03ece69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,11 +4,11 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] # https://python-poetry.org/docs/pyproject/ name = "dspygen" -version = "2024.2.25" +version = "2024.2.25.2" description = "A Ruby on Rails style framework for the DSPy (Demonstrate, Search, Predict) project for Language Models like GPT, BERT, and LLama." authors = ["Sean Chatman "] readme = "README.md" -repository = "https://github.com/user/my-package" +repository = "https://github.com/seanchatmangpt/dspygen" [tool.poetry.scripts] # https://python-poetry.org/docs/pyproject/#scripts dspygen = "dspygen.cli:app" @@ -33,6 +33,7 @@ openai = "^1.12.0" pyperclip = "^1.8.2" asyncer = "^0.0.5" loguru = "^0.7.2" +groq = "^0.4.1" [tool.poetry.group.test.dependencies] # https://python-poetry.org/docs/master/managing-dependencies/ coverage = { extras = ["toml"], version = ">=7.2.5" } diff --git a/src/dspygen/async_typer.py b/src/dspygen/async_typer.py new file mode 100644 index 0000000..3c8393e --- /dev/null +++ b/src/dspygen/async_typer.py @@ -0,0 +1,28 @@ +import inspect +from functools import partial, wraps + +import asyncer +from typer import Typer + + +class AsyncTyper(Typer): + @staticmethod + def maybe_run_async(decorator, f): + if inspect.iscoroutinefunction(f): + + @wraps(f) + def runner(*args, **kwargs): + return asyncer.runnify(f)(*args, **kwargs) + + decorator(runner) + else: + decorator(f) + return f + + def callback(self, *args, **kwargs): + decorator = super().callback(*args, **kwargs) + return partial(self.maybe_run_async, decorator) + + def command(self, *args, **kwargs): + decorator = super().command(*args, **kwargs) + return partial(self.maybe_run_async, decorator) diff --git a/src/dspygen/cli.py b/src/dspygen/cli.py index 1ff87a5..67ed3d1 100644 --- a/src/dspygen/cli.py +++ b/src/dspygen/cli.py @@ -1,12 +1,15 @@ """dspygen CLI.""" from importlib import import_module +import dspy import os from pathlib import Path import typer +from dspygen.utils.dspy_tools import init_dspy + app = typer.Typer() @@ -32,7 +35,68 @@ def init(project_name: str): typer.echo(f"Initializing {project_name}.") +README = """DSPyGen: Streamlining AI Development +DSPyGen, influenced by the efficiency and modularity of Ruby on Rails, is a powerful command-line interface (CLI) designed to revolutionize AI development by leveraging DSPy modules. This tool simplifies the process of creating, developing, and deploying language model (LM) pipelines, embodying the Ruby on Rails philosophy of "Convention over Configuration" for AI projects. + +Features +Quick Initialization: Set up your DSPyGen project in seconds, echoing Ruby on Rails' ease of starting new projects. +Modular Approach: Inspired by Ruby on Rails' modular design, DSPyGen allows for the easy generation and enhancement of DSPy modules. +Intuitive Command Structure: With user-friendly commands, managing your AI development workflow becomes as straightforward as web development with Ruby on Rails. +Embedded Chatbot Assistance: For guidance and support, DSPyGen includes a chatbot, making it easier to navigate through your development process.""" + + @app.command("help") def cli_help(question: str): """Answers the user questions with a helpful chatbot.""" - typer.echo(f"TODO: Answer {question}.") + chatbot(question, README) + + +def gbot(question, context): + from groq import Groq + + client = Groq( + api_key=os.environ.get("GROQ_API_KEY"), + ) + + chat_completion = client.chat.completions.create( + messages=[ + { + "role": "system", + "content": f"you are a helpful assistant. This is what you help with: {context}" + }, + { + "role": "user", + "content": f"{question}", + } + ], + model="llama2-70b-4096", + ) + + print(chat_completion.choices[0].message.content.rstrip()) + + +def chatbot(question, context, history=""): + init_dspy(max_tokens=2000) + + qa = dspy.ChainOfThought("question, context -> answer") + response = qa(question=question, context=context).answer + history += response + print(f"Chatbot: {response}") + confirmed = False + while not confirmed: + confirm = typer.prompt("Did this answer your question? [y/N]", default="N") + + if confirm.lower() in ["y", "yes"]: + confirmed = True + else: + want = typer.prompt("How can I help more?") + + question = f"{history}\n{want}" + question = question[-1000:] + + response = qa(question=question, context=README).answer + history += response + print(f"Chatbot: {response}") + + return history + diff --git a/src/dspygen/modules/gen_dspy_module.py b/src/dspygen/modules/gen_dspy_module.py index 805f306..b8c6223 100644 --- a/src/dspygen/modules/gen_dspy_module.py +++ b/src/dspygen/modules/gen_dspy_module.py @@ -5,7 +5,7 @@ from dspygen.modules.gen_pydantic_instance_module import gen_pydantic_instance_call from dspygen.modules.source_code_pep8_docs_module import source_code_docs_call from dspygen.utils.dspy_tools import init_dspy -from typetemp.functional import render +from dspygen.typetemp.functional import render app = Typer() diff --git a/src/dspygen/subcommands/__init__.py b/src/dspygen/subcommands/__init__.py new file mode 100644 index 0000000..96041fa --- /dev/null +++ b/src/dspygen/subcommands/__init__.py @@ -0,0 +1 @@ +"""dspygen package.""" diff --git a/src/dspygen/subcommands/command_cmd.py b/src/dspygen/subcommands/command_cmd.py index 32ec880..9d721ad 100644 --- a/src/dspygen/subcommands/command_cmd.py +++ b/src/dspygen/subcommands/command_cmd.py @@ -2,7 +2,7 @@ import typer -from typetemp.functional import render +from dspygen.typetemp.functional import render app = typer.Typer(help="""Generate new sub commands or add to existing ones.""") diff --git a/src/dspygen/subcommands/module_cmd.py b/src/dspygen/subcommands/module_cmd.py index fae0854..bc94761 100644 --- a/src/dspygen/subcommands/module_cmd.py +++ b/src/dspygen/subcommands/module_cmd.py @@ -4,12 +4,9 @@ import os import typer -from pydantic import BaseModel, Field from dspygen.modules.gen_dspy_module import gen_dspy_module_call -from dspygen.modules.gen_pydantic_instance_module import gen_pydantic_instance_call from dspygen.modules.file_name_module import file_name_call -from typetemp.functional import render from dspygen.utils.dspy_tools import init_dspy from dspygen.utils.file_tools import dspy_modules_dir, source_dir diff --git a/src/typetemp/__init__.py b/src/dspygen/typetemp/__init__.py similarity index 100% rename from src/typetemp/__init__.py rename to src/dspygen/typetemp/__init__.py diff --git a/src/typetemp/environment/__init__.py b/src/dspygen/typetemp/environment/__init__.py similarity index 100% rename from src/typetemp/environment/__init__.py rename to src/dspygen/typetemp/environment/__init__.py diff --git a/src/typetemp/environment/typed_environment.py b/src/dspygen/typetemp/environment/typed_environment.py similarity index 84% rename from src/typetemp/environment/typed_environment.py rename to src/dspygen/typetemp/environment/typed_environment.py index e5dc742..c887ca4 100644 --- a/src/typetemp/environment/typed_environment.py +++ b/src/dspygen/typetemp/environment/typed_environment.py @@ -1,7 +1,7 @@ from jinja2 import Environment, FileSystemLoader -from typetemp.extension.faker_extension import FakerExtension -from typetemp.extension.inflection_extension import InflectionExtension +from dspygen.typetemp.extension.faker_extension import FakerExtension +from dspygen.typetemp.extension.inflection_extension import InflectionExtension class TypedEnvironment(Environment): diff --git a/src/typetemp/environment/typed_native_environment.py b/src/dspygen/typetemp/environment/typed_native_environment.py similarity index 83% rename from src/typetemp/environment/typed_native_environment.py rename to src/dspygen/typetemp/environment/typed_native_environment.py index e5209aa..b94f7b1 100644 --- a/src/typetemp/environment/typed_native_environment.py +++ b/src/dspygen/typetemp/environment/typed_native_environment.py @@ -1,8 +1,8 @@ from jinja2 import FileSystemLoader from jinja2.nativetypes import NativeEnvironment -from typetemp.extension.faker_extension import FakerExtension -from typetemp.extension.inflection_extension import InflectionExtension +from dspygen.typetemp.extension.faker_extension import FakerExtension +from dspygen.typetemp.extension.inflection_extension import InflectionExtension class TypedNativeEnvironment(NativeEnvironment): diff --git a/src/typetemp/extension/__init__.py b/src/dspygen/typetemp/extension/__init__.py similarity index 100% rename from src/typetemp/extension/__init__.py rename to src/dspygen/typetemp/extension/__init__.py diff --git a/src/typetemp/extension/faker_extension.py b/src/dspygen/typetemp/extension/faker_extension.py similarity index 100% rename from src/typetemp/extension/faker_extension.py rename to src/dspygen/typetemp/extension/faker_extension.py diff --git a/src/typetemp/extension/inflection_extension.py b/src/dspygen/typetemp/extension/inflection_extension.py similarity index 100% rename from src/typetemp/extension/inflection_extension.py rename to src/dspygen/typetemp/extension/inflection_extension.py diff --git a/src/typetemp/functional.py b/src/dspygen/typetemp/functional.py similarity index 89% rename from src/typetemp/functional.py rename to src/dspygen/typetemp/functional.py index f30f1e1..8eb5f4f 100644 --- a/src/typetemp/functional.py +++ b/src/dspygen/typetemp/functional.py @@ -1,8 +1,8 @@ import os from pathlib import Path -from typetemp.environment.typed_environment import environment -from typetemp.environment.typed_native_environment import native_environment +from dspygen.typetemp.environment.typed_environment import environment +from dspygen.typetemp.environment.typed_native_environment import native_environment _env = environment _native_env = native_environment diff --git a/src/typetemp/template/__init__.py b/src/dspygen/typetemp/template/__init__.py similarity index 100% rename from src/typetemp/template/__init__.py rename to src/dspygen/typetemp/template/__init__.py diff --git a/src/typetemp/template/async_render_mixin.py b/src/dspygen/typetemp/template/async_render_mixin.py similarity index 87% rename from src/typetemp/template/async_render_mixin.py rename to src/dspygen/typetemp/template/async_render_mixin.py index ddd4f92..fbf61fe 100644 --- a/src/typetemp/template/async_render_mixin.py +++ b/src/dspygen/typetemp/template/async_render_mixin.py @@ -2,11 +2,11 @@ import anyio -from typetemp.environment.typed_environment import async_environment -from typetemp.environment.typed_native_environment import async_native_environment -from typetemp.template.render_funcs import arender_str -from utils.complete import acreate -from utils.file_tools import write +from dspygen.typetemp.environment.typed_environment import async_environment +from dspygen.typetemp.environment.typed_native_environment import async_native_environment +from dspygen.typetemp.template.render_funcs import arender_str +from dspygen.utils.complete import acreate +from dspygen.utils.file_tools import write class AsyncRenderMixin: diff --git a/src/typetemp/template/dsl_project.py b/src/dspygen/typetemp/template/dsl_project.py similarity index 99% rename from src/typetemp/template/dsl_project.py rename to src/dspygen/typetemp/template/dsl_project.py index caf15cf..5559c38 100644 --- a/src/typetemp/template/dsl_project.py +++ b/src/dspygen/typetemp/template/dsl_project.py @@ -5,7 +5,7 @@ import yaml -from typetemp.template.typed_prompt import TypedPrompt +from dspygen.typetemp.template.typed_prompt import TypedPrompt @dataclass diff --git a/src/typetemp/template/python b/src/dspygen/typetemp/template/python similarity index 100% rename from src/typetemp/template/python rename to src/dspygen/typetemp/template/python diff --git a/src/typetemp/template/render_funcs.py b/src/dspygen/typetemp/template/render_funcs.py similarity index 100% rename from src/typetemp/template/render_funcs.py rename to src/dspygen/typetemp/template/render_funcs.py diff --git a/src/typetemp/template/render_mixin.py b/src/dspygen/typetemp/template/render_mixin.py similarity index 100% rename from src/typetemp/template/render_mixin.py rename to src/dspygen/typetemp/template/render_mixin.py diff --git a/src/typetemp/template/smart_template.py b/src/dspygen/typetemp/template/smart_template.py similarity index 95% rename from src/typetemp/template/smart_template.py rename to src/dspygen/typetemp/template/smart_template.py index 4836608..138abbb 100644 --- a/src/typetemp/template/smart_template.py +++ b/src/dspygen/typetemp/template/smart_template.py @@ -7,9 +7,9 @@ import typer from dspygen.async_typer import AsyncTyper -from typetemp.template.async_render_mixin import AsyncRenderMixin -from utils.complete import LLMConfig -from utils.file_tools import write +from dspygen.typetemp.template.async_render_mixin import AsyncRenderMixin +from dspygen.utils.complete import LLMConfig +from dspygen.utils.file_tools import write app = AsyncTyper() diff --git a/src/typetemp/template/typed_injector.py b/src/dspygen/typetemp/template/typed_injector.py similarity index 98% rename from src/typetemp/template/typed_injector.py rename to src/dspygen/typetemp/template/typed_injector.py index 1be386a..e0e60d2 100644 --- a/src/typetemp/template/typed_injector.py +++ b/src/dspygen/typetemp/template/typed_injector.py @@ -3,7 +3,7 @@ import re from dataclasses import is_dataclass -from typetemp.environment.typed_environment import environment +from dspygen.typetemp.environment.typed_environment import environment class TypedInjector: diff --git a/src/typetemp/template/typed_prompt.py b/src/dspygen/typetemp/template/typed_prompt.py similarity index 95% rename from src/typetemp/template/typed_prompt.py rename to src/dspygen/typetemp/template/typed_prompt.py index ad5a199..5a71659 100644 --- a/src/typetemp/template/typed_prompt.py +++ b/src/dspygen/typetemp/template/typed_prompt.py @@ -1,7 +1,7 @@ from typing import Union -from typetemp.template.render_mixin import RenderMixin -from utils.complete import create +from dspygen.typetemp.template.render_mixin import RenderMixin +from dspygen.utils.complete import create class TypedPrompt(RenderMixin): diff --git a/src/typetemp/template/typed_python_source.py b/src/dspygen/typetemp/template/typed_python_source.py similarity index 91% rename from src/typetemp/template/typed_python_source.py rename to src/dspygen/typetemp/template/typed_python_source.py index b51a7a9..af98dc7 100644 --- a/src/typetemp/template/typed_python_source.py +++ b/src/dspygen/typetemp/template/typed_python_source.py @@ -3,9 +3,9 @@ from dataclasses import dataclass from typing import Optional -from typetemp.environment.typed_environment import TypedEnvironment -from typetemp.environment.typed_native_environment import TypedNativeEnvironment -from typetemp.template.render_mixin import RenderMixin +from dspygen.typetemp.environment.typed_environment import TypedEnvironment +from dspygen.typetemp.environment.typed_native_environment import TypedNativeEnvironment +from dspygen.typetemp.template.render_mixin import RenderMixin _env = TypedEnvironment() _native_env = TypedNativeEnvironment() diff --git a/src/typetemp/template/typed_template.py b/src/dspygen/typetemp/template/typed_template.py similarity index 93% rename from src/typetemp/template/typed_template.py rename to src/dspygen/typetemp/template/typed_template.py index 8bb485b..2ca9353 100644 --- a/src/typetemp/template/typed_template.py +++ b/src/dspygen/typetemp/template/typed_template.py @@ -1,4 +1,4 @@ -from typetemp.template.render_mixin import RenderMixin +from dspygen.typetemp.template.render_mixin import RenderMixin class TypedTemplate(RenderMixin): diff --git a/src/dspygen/utils/create_prompts.py b/src/dspygen/utils/create_prompts.py index 1a617c1..137acee 100644 --- a/src/dspygen/utils/create_prompts.py +++ b/src/dspygen/utils/create_prompts.py @@ -6,13 +6,11 @@ from typing import Optional import loguru -from icontract import ensure, require -from typetemp.template.typed_template import TypedTemplate -from utils.complete import achat, create -from utils.file_tools import write -from utils.models import get_model -from utils.radon_workbench import fix_code +from dspygen.typetemp.template.typed_template import TypedTemplate +from dspygen.utils.complete import achat, create +from dspygen.utils.file_tools import write +from dspygen.utils.models import get_model create_jinja_template = """ Objective: @@ -440,8 +438,6 @@ def create_tailwind_landing( f.write(markup) -@require(lambda prompt: isinstance(prompt, str)) -@require(lambda cls: issubclass(cls, object)) def create_data( prompt: str, cls: type, model: Optional[str] = None, max_tokens: int = 2000 ) -> dict: @@ -497,9 +493,6 @@ def create_data( return json.loads("{" + corrected_result.replace("\n", "")) -@require(lambda prompt: isinstance(prompt, str)) -@require(lambda cabal: isinstance(cabal, Callable)) -@ensure(lambda result: isinstance(result, dict)) def create_kwargs(prompt: str, cabal: Callable) -> dict: """Create a dict of data from a prompt that can be passed to the given class as kwargs""" instructions = dedent( @@ -630,7 +623,7 @@ class {class_name}(BaseModel): cls_code = f"""from pydantic import BaseModel\n\n class {class_name}(BaseModel):\n{result}""" - cls_code = fix_code(cls_code) + # cls_code = fix_code(cls_code) if file_path: write(contents=cls_code, filename=file_path) diff --git a/src/dspygen/utils/py_module.py b/src/dspygen/utils/py_module.py deleted file mode 100644 index a4cecfa..0000000 --- a/src/dspygen/utils/py_module.py +++ /dev/null @@ -1,296 +0,0 @@ -import ast -import logging -import os -from ast import NodeTransformer, fix_missing_locations, parse -from textwrap import dedent - -import astor -import autopep8 -from redbaron import RedBaron - -logging.basicConfig(filename="PyModule.log", level=logging.INFO) - - -class PyModule: - def __init__(self, filepath, source=None): - self.filepath = filepath - self.red = None - - if source: - self.from_source(source) - else: - self.load() - - def load(self): - if not os.path.exists(self.filepath): - with open(self.filepath, "w") as f: - f.write("") - with open(self.filepath) as f: - content = f.read() - if content.strip(): - self.red = RedBaron(content) - else: - self.red = RedBaron("") # Initialize with an empty RedBaron instance - - def save(self): - # fixed = fix_indentation(self.red.dumps()) - fixed = self.red.dumps() - - fixed = autopep8.fix_code(fixed) - - with open(self.filepath, "w") as f: - f.write(fixed) - # f.write(formatted_code) - # f.write(formatted_code) - - def __setitem__(self, key, value): - self.__setattr__(key, value) - - def __setattr__(self, key, value): - if not isinstance(value, str): - super().__setattr__(key, value) - return - elif isinstance(value, str) and not any( - keyword in value for keyword in ("def", "class", "import", "from") - ): - super().__setattr__(key, value) - return - - new_element = RedBaron(value).find_all(("def", "class")) - - if self.red: - for elem in new_element: - existing_element = self.red.find(elem.type, name=key) - if existing_element: - if "def" in elem.type: - self.replace_method( - existing_element.parent.actor_id, - existing_element.actor_id, - value, - ) - # else: - # existing_element.replace(elem) - # existing_element.replace(elem) - self.add_imports(value) - break - else: - if "def" in elem.type and "self" in value: - existing_class = self.red.find("class") - self.replace_method( - existing_class.actor_id, new_element.actor_id, value - ) - else: - self.red.append(elem) - self.add_imports(value) - break - else: - self.red = RedBaron(value) - - logging.info(f"Element '{key}' has been updated or added.") - self.save() - - def replace_method(self, parent_name, elem_name, value): - source_code = autopep8.fix_code(self.red.dumps()) - tree = parse(source_code) - transformer = AddOrReplaceFunctionInClass(parent_name, elem_name, value) - new_tree = transformer.visit(tree) - new_tree = fix_missing_locations(new_tree) - new_source = autopep8.fix_code(astor.to_source(new_tree)) - self.from_source(new_source) - self.save() - - def __getattr__(self, key): - found_class = self.red.find("class", name=key) - if found_class: - return PyClass(self, key) - else: - super().__getattribute__(key) - - def add_imports(self, value): - # Add any imports from the new code at the top if they don't already exist - for node in RedBaron(str(value)).find_all(("import", "from_import")): - import_str = node.dumps().strip() - existing_imports = [ - existing.dumps().strip() - for existing in self.red.find_all(("import", "from_import")) - ] - - if import_str not in existing_imports: - self.red.insert(0, node) - - def from_source(self, source): - self.red = RedBaron(source) - - def __repr__(self): - return f"PyModule('{self.filepath}')" - - def __str__(self): - return autopep8.fix_code(self.red.dumps()) - - -# I have IMPLEMENTED your PerfectPythonProductionCode® AGI enterprise innovative and opinionated best practice IMPLEMENTATION code of your requirements. - - -class PyClass: - def __init__(self, parent_module, class_name): - self.parent_module = parent_module - self.class_name = class_name - self.class_node = self.parent_module.red.find("class", name=self.class_name) - - def __setattr__(self, key, value): - if key in ["parent_module", "class_name", "class_node"]: - super().__setattr__(key, value) - return - - if not isinstance(value, str): - super().__setattr__(key, value) - return - - if isinstance(value, str) and not any( - keyword in value for keyword in ("def", "import", "from") - ): - super().__setattr__(key, value) - return - - new_element = RedBaron(value).find("def") - - if self.class_node: - existing_element = self.class_node.find("def", name=key) - if existing_element: - self.replace_method(new_element, existing_element, value) - else: - self.class_node.value.append(new_element) - self.add_imports(value) - else: - self.class_node = RedBaron(f"class {self.class_name}:\n pass\n").find( - "class" - ) - self.parent_module.red.append(self.class_node) - self.class_node.value.append(new_element) - self.add_imports(value) - - logging.info( - f"Method '{key}' in class '{self.class_name}' has been updated or added." - ) - self.parent_module.save() - - def add_imports(self, value): - # Add any imports from the new code at the top if they don't already exist - for node in RedBaron(str(value)).find_all(("import", "from_import")): - import_str = node.dumps().strip() - existing_imports = [ - existing.dumps().strip() - for existing in self.parent_module.red.find_all( - ("import", "from_import") - ) - ] - if import_str not in existing_imports: - self.parent_module.red.insert(0, node) - - def replace_method(self, new_element, existing_element, value): - source_code = autopep8.fix_code(self.parent_module.red.dumps()) - tree = parse(source_code) - transformer = AddOrReplaceFunctionInClass( - self.class_name, new_element.actor_id, value - ) - new_tree = transformer.visit(tree) - new_tree = fix_missing_locations(new_tree) - new_source = autopep8.fix_code(astor.to_source(new_tree)) - self.parent_module.from_source(new_source) - self.parent_module.save() - - def __repr__(self): - return f"PyClass('{self.class_name}') in module '{self.parent_module.filepath}'" - - def __str__(self): - return autopep8.fix_code(self.class_node.dumps()) - - -class AddOrReplaceFunctionInClass(NodeTransformer): - def __init__(self, class_name, func_name, func_body): - self.class_name = class_name - self.func_name = func_name - self.new_func = parse(func_body).body[0] - - def visit_ClassDef(self, node): - if node.name == self.class_name: - for idx, item in enumerate(node.body): - if isinstance(item, ast.FunctionDef) and item.name == self.func_name: - node.body[idx] = self.new_func - return node - if ( - isinstance(item, ast.AsyncFunctionDef) - and item.name == self.func_name - ): - node.body[idx] = self.new_func - return node - - # If function is not found, append it to the class body - node.body.append(self.new_func) - return node - - -# Example usage: - -code_str = """ -class MyClass: - def my_method(self): - print('Hello, world!') - - def another_method(self): - print('Incorrect indentation.') -""" - -# corrected_code_str = check_and_fix_indentation(code_str) -# print(corrected_code_str) - - -if __name__ == "__main__": - with open("demo_module3.py", "w") as f: - f.write("") - - module = PyModule("demo_module3.py") - - module.HelloWorld = dedent( - """class HelloWorld(): - def __init__(self, value): - self.name = value - - def hello_world(self): - print("Hello, world 123!") - """ - ) - - # my_class = module.MyClass - - module.HelloWorld.hello_world = dedent( - """def hello_world(self): - print("Hello, world 123!") - """ - ) - - module.HelloWorld.hello_world2 = dedent( - """def hello_world2(self): - print("Hello, world 123!") - """ - ) - - module.HelloWorld.hello_world3 = dedent( - """def hello_world2(self): - print("Hello, world 123!") - """ - ) - - module.HelloWorld.replace_method( - "__init__", - dedent( - """def __init__(self, value): - self.name = value - self.age = 3089234324 - """ - ), - ) - - # print(module.HelloWorld) - - print(module) diff --git a/src/dspygen/utils/radon_workbench.py b/src/dspygen/utils/radon_workbench.py deleted file mode 100644 index 33989c8..0000000 --- a/src/dspygen/utils/radon_workbench.py +++ /dev/null @@ -1,144 +0,0 @@ -# Here is your PerfectPythonProductionCode® AGI response -from textwrap import dedent - -# This Python module wraps around Radon's metrics functions to make them easier to use. -# We'll include functions for Cyclomatic Complexity, Raw Metrics, Halstead Metrics, and Maintainability Index. -# Note: You need to install radon library via pip for this to work. -from radon.complexity import cc_visit -from radon.metrics import h_visit, mi_visit -from radon.raw import analyze - -from utils.complete import create -from utils.py_module import PyModule - - -def get_cyclomatic_complexity(source_code: str) -> dict: - """Returns the Cyclomatic Complexity of the given source code.""" - return {func.actor_id: func.complexity for func in cc_visit(source_code)} - - -def get_raw_metrics(source_code: str) -> dict: - """Returns raw metrics (LOC, LLOC, SLOC, Comments, Multi, Blank, etc.) of the given source code.""" - return analyze(source_code)._asdict() - - -def get_halstead_metrics(source_code: str) -> dict: - """Returns the Halstead metrics (unique operators, unique operands, etc.) of the given source code.""" - return h_visit(source_code) - - -def get_maintainability_index(source_code: str) -> dict: - """Returns the Maintainability Index of the given source code.""" - return mi_visit(source_code, False) - - -def check_for_bugs_with_metrics(source_code: str, metrics: dict) -> str: - """Uses GPT-3.5-turbo to analyze the code and metrics for possible bugs.""" - # Create the prompt for GPT-3.5-turbo - prompt = ( - "Review the following Python code and its metrics to identify any bugs.\n\n" - ) - prompt += f"Code:\n```\n{source_code}\n```\n\n" - prompt += "Metrics:\n" - for metric_name, metric_value in metrics.items(): - prompt += f"- {metric_name}: {metric_value}\n" - prompt += ( - "\nAre there any bugs in this code, True or False? " - "If True, provide a description of the bug and how to fix it.\n\n" - ) - - # Make the API call - response = create(prompt=prompt, max_tokens=2100, stop=["\n\n", "False"]) - - return response - - -def fix_code(code: str, error: str = "", max_tokens=2000) -> str: - """Use GPT-3.5-turbo to fix the code.""" - # Create the prompt for GPT-3.5-turbo - prompt = ( - "Fix the following Python code and provide docstrings detailing fix. Do not add any functions " - "or classes\nYou are only fixing what you are given:\n\n" - ) - - if error != "": - prompt += f"Error:\n```\n{error}\n```\n\n" - - prompt += f"Code:\n```\n{code}\n```\n\n```python\n# Here is your PerfectPythonProductionCode® AGI fixed code\n" - - # Make the API call - response = create(prompt=prompt, stop=["\n```"], max_tokens=max_tokens) - - # Get the response from GPT-3.5-turbo - fixed_code = response - - return fixed_code - - -bad_add = dedent( - """ - def add(a, b): - asdljfaksdhfashkfdaskl;hfasdl;ihk -return a - b - - def sub(a, b): - saddaosjfads;klfjsd - return a - b12312390898327 - - - if __name__ == "__main__": -assert add(1, 2) == 3 - assert sub(1, 2) == -1 - """ -) - -bad_largest = dedent( - """df quiq_Sorttttt(nums: List[int]) -> int: - """ -) - - -def get_code_metrics(sample_code): - return { - "Cyclomatic Complexity": get_cyclomatic_complexity(sample_code), - "Raw Metrics": get_raw_metrics(sample_code), - "Halstead Metrics": get_halstead_metrics(sample_code), - "Maintainability Index": get_maintainability_index(sample_code), - } - - -def refactor_code(sample_code): - try: - all_metrics = get_code_metrics(sample_code) - print(all_metrics) - result = check_for_bugs_with_metrics(sample_code, all_metrics) - - if result == "": - return sample_code - else: - return fix_code(result) - except SyntaxError: - return fix_code(sample_code) - - -if __name__ == "__main__": - # result = "GPT-3.5-turbo Analysis: There appears to be a bug in the add function, where it is returning the wrong value (a - b instead of a + b). To fix this, we can simply change the return statement to return a + b." - result = refactor_code(bad_largest) - # result = refactor_code(bad_add) - print("GPT-3.5-turbo Analysis:", result) - - module = PyModule(source=result, filepath="demo_module.py") - - # print("Module:", module) - # print("Functions:", module.functions) - - # add_fixed = dedent("""def add(a, b): - # return a + b""") - add_fixed = fix_code(result) - - module.add = add_fixed - - print(str(module)) - - module.filepath = "demo_module.py" - module.save() diff --git a/tests/test_cli.py b/tests/test_cli.py index 0c4f705..e24568c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -7,9 +7,12 @@ runner = CliRunner() -def test_say() -> None: +def test_init() -> None: """Test that the say command works as expected.""" - message = "Hello world" - result = runner.invoke(app, ["--message", message]) + result = runner.invoke(app, ["init", "hello-world-project"]) assert result.exit_code == 0 - assert message in result.stdout + +def test_bad_init() -> None: + """Test that the say command works as expected.""" + result = runner.invoke(app, ["init"]) + assert result.exit_code == 2