Skip to content

Commit

Permalink
feat: add cli
Browse files Browse the repository at this point in the history
  • Loading branch information
Goldziher committed Jul 14, 2024
1 parent 314f122 commit aaa78ee
Show file tree
Hide file tree
Showing 41 changed files with 981 additions and 661 deletions.
4 changes: 1 addition & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ repos:
additional_dependencies:
- tomli
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.13.0
rev: v2.14.0
hooks:
- id: pretty-format-yaml
args: [--autofix, --indent, '2']
Expand All @@ -41,8 +41,6 @@ repos:
hooks:
- id: mypy
name: mypy
#currently awaiting mypy 3.11 release
# entry: pdm run mypy --enable-incomplete-feature=NewGenericSyntax
entry: pdm run mypy
require_serial: true
language: system
Expand Down
4 changes: 4 additions & 0 deletions gitmind/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from gitmind.cli import cli

if __name__ == "__main__":
cli()
3 changes: 3 additions & 0 deletions gitmind/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from gitmind.cli.main import cli

__all__ = ["cli"]
26 changes: 26 additions & 0 deletions gitmind/cli/_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import TypedDict, cast

from pygit2 import Repository
from rich_click import Context

from gitmind.config import GitMindSettings


class CLIContext(TypedDict):
"""The CLI context. This dict is set on the cli 'ctx.obj' attribute."""

settings: GitMindSettings
"""The gitmind settings instance."""
repo: Repository


def get_settings_from_context(ctx: Context) -> CLIContext:
"""Get settings from context.
Args:
ctx (Context): The context.
Returns:
GitMindSettings: The settings
"""
return cast(CLIContext, ctx.obj)
3 changes: 3 additions & 0 deletions gitmind/cli/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .commit import commit

__all__ = ["commit"]
34 changes: 34 additions & 0 deletions gitmind/cli/commands/commit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from rich_click import Context, argument, echo, group, pass_context

from gitmind.utils.sync import run_as_sync


@group()
def commit(sha: str) -> None:
"""Commit commands."""
echo(f"Commit {sha}")


@commit.command()
@argument("commit_hash", required=True, type=str)
@pass_context
@run_as_sync
async def grade(ctx: Context, commit_hash: str) -> None:
echo(f"Commit {commit_hash}")


@commit.command()
@argument("commit_hash", required=True, type=str)
@pass_context
@run_as_sync
async def describe(ctx: Context, commit_hash: str) -> None:
"""Describe a commit."""
echo(f"Commit {commit_hash}")
# try:
# settings = get_settings_from_context(ctx)
# repo = get_repo(getcwd())
# commit = get_commit(repo=repo, hexsha=commit_hash)
# echo(settings.model_dump_json())
# echo(f"Commit {commit_hash}: {commit.message}")
# except BadName as e:
# raise UsageError(f"Invalid commit hash: {commit_hash}", ctx=ctx) from e
48 changes: 48 additions & 0 deletions gitmind/cli/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from contextlib import suppress
from typing import Any

from pydantic import ValidationError
from rich_click import Choice, Context, UsageError, group, option, pass_context, rich_click

from gitmind.cli.commands import commit
from gitmind.config import GitMindSettings

rich_click.USE_RICH_MARKUP = True
rich_click.SHOW_ARGUMENTS = True
rich_click.GROUP_ARGUMENTS_OPTIONS = True
rich_click.STYLE_ERRORS_SUGGESTION = "magenta italic"
rich_click.SHOW_METAVARS_COLUMN = True
rich_click.APPEND_METAVARS_HELP = True


@group() # type: ignore[arg-type]
@option("--provider", help="The LLM provider to use.", type=Choice(["openai", "groq"]), required=False)
@option("--model", help="The name of the model.", type=str, required=False)
@option("--provider-key", help="The API key for the provider.", type=str, required=False)
@option("--cache", help="The API key for the provider.", type=Choice(["memory", "file"]), required=False)
@option("--repo", help="The URL or path to the target repository", type=str, required=False)
@option("--retries", help="The maximum number of retries for LLM requests.", type=int, required=False)
@option("--debug", help="Enable debug logging.", type=bool, required=False)
@option("--silent", help="Run silently.", type=bool, required=False)
@pass_context
def cli(ctx: Context, **kwargs: Any) -> None:
"""GitMind CLI."""
if ctx.obj is None:
with suppress(ImportError):
from dotenv import find_dotenv, load_dotenv

if dotenv_file := find_dotenv():
load_dotenv(dotenv_file)
try:
ctx.obj = GitMindSettings(**{k: v for k, v in kwargs.items() if v is not None})
except ValidationError as e:
missing_parameters = "\n".join([f"-\t{msg["loc"][0]}" for msg in e.errors()])
error_message = (
f"The following required parameters are missing:\n\n{missing_parameters}\n\nMake sure to "
f"either pass them as command line options, define them in a config file or set them "
f"as env variables."
)
raise UsageError(error_message, ctx=ctx) from e


cli.add_command(commit)
107 changes: 0 additions & 107 deletions gitmind/commit_processing/commit.py

This file was deleted.

121 changes: 121 additions & 0 deletions gitmind/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from re import Pattern
from re import compile as compile_regex
from typing import Annotated, Final, Literal

from pydantic import BaseModel, DirectoryPath, Field
from pydantic_settings import (
BaseSettings,
JsonConfigSettingsSource,
PydanticBaseSettingsSource,
PyprojectTomlConfigSettingsSource,
SettingsConfigDict,
TomlConfigSettingsSource,
YamlConfigSettingsSource,
)

try:
from openai.types import ChatModel as OpenAIModel
except ImportError:
OpenAIModel = str # type: ignore[misc]


CONFIG_FILE_NAME: Final[str] = "gitmind-config"

git_regex: Pattern[str] = compile_regex(r"((git|ssh|http(s)?)|(git@[\w\.-]+))(:(//)?)([\w\.@\:/\-~]+)(\.git)(/)?")


class BaseGitMindProviderConfig(BaseModel):
"""Base options for GitMind clients."""

api_key: str
"""The API key for the provider."""
max_retries: int = 0
"""The maximum number of retries for requests."""


class OpenAIProviderConfig(BaseGitMindProviderConfig):
"""Configuration for the OpenAI LLM provider."""

name: Literal["openai"] = "openai"
"""The LLM provider."""
model: OpenAIModel = "gpt-4o"
"""The model to use for completions."""
base_url: str | None = None
"""The base URL for the API."""


class AzureOpenAIProviderConfig(BaseGitMindProviderConfig):
"""Configuration for the Azure OpenAI LLM provider."""

name: Literal["azure-openai"] = "azure-openai"
"""The LLM provider."""
model: OpenAIModel = "gpt-4o"
"""The model to use for completions."""
endpoint: str
"""The Azure endpoint for the API."""
deployment: str | None = None
"""The Azure deployment for the API."""
api_version: str | None = None
"""The API version for the Azure API."""
ad_token: str | None = None
"""The Azure AD token."""


class GroqProviderConfig(BaseGitMindProviderConfig):
"""Base options for Groq clients."""

name: Literal["groq"] = "groq"
"""The LLM provider."""
base_url: str | None = None
"""The base URL for the Groq model."""
model: str = "llama3-8b-8192"
"""The default model to use for generating completions."""


class GitMindSettings(BaseSettings):
"""Configuration for the GitMind Application."""

model_config = SettingsConfigDict(
arbitrary_types_allowed=True,
regex_engine="python-re",
env_prefix="GITMIND_",
env_file=".env",
json_file=f"{CONFIG_FILE_NAME}.json",
pyproject_toml_table_header=("tool", "gitmind"),
toml_file=f"{CONFIG_FILE_NAME}.toml",
yaml_file=[f"{CONFIG_FILE_NAME}.yaml", f"{CONFIG_FILE_NAME}.yml"],
)
provider: OpenAIProviderConfig | AzureOpenAIProviderConfig | GroqProviderConfig
"""The LLM provider configuration object."""
cache_type: Literal["memory", "file"] = "memory"
"""The cache type to use."""
repo_url: Annotated[str, Field(pattern=git_regex)] | DirectoryPath
"""The target repository. The value can be either a URL or a directory path."""
debug: bool = False
"""Whether to enable debug mode."""
silent: bool = False
"""Whether to enable silent mode."""

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
"""Customise the settings sources for the GitMindSettings class to load multiple setting types.
See: https://docs.pydantic.dev/latest/concepts/pydantic_settings/#customise-settings-sources
"""
return (
init_settings,
JsonConfigSettingsSource(settings_cls),
YamlConfigSettingsSource(settings_cls),
TomlConfigSettingsSource(settings_cls),
PyprojectTomlConfigSettingsSource(settings_cls),
env_settings,
dotenv_settings,
file_secret_settings,
)
Loading

0 comments on commit aaa78ee

Please sign in to comment.