gofannon 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
gofannon/config.py ADDED
@@ -0,0 +1,58 @@
1
+ import os
2
+ from dotenv import load_dotenv
3
+ import logging
4
+
5
+
6
+ class ToolConfig:
7
+ _instance = None
8
+
9
+ def __init__(self):
10
+ load_dotenv()
11
+ self.config = {
12
+ 'github_api_key': os.getenv('GITHUB_API_KEY'),
13
+ 'deepinfra_api_key': os.getenv('DEEPINFRA_API_KEY'),
14
+ 'arxiv_api_key': os.getenv('ARXIV_API_KEY')
15
+ }
16
+
17
+ @classmethod
18
+ def get(cls, key):
19
+ if not cls._instance:
20
+ cls._instance = ToolConfig()
21
+ return cls._instance.config.get(key)
22
+
23
+ class FunctionRegistry:
24
+ _tools = {}
25
+
26
+ @classmethod
27
+ def register(cls, tool_class):
28
+ cls._tools[tool_class().definition['function']['name']] = tool_class
29
+ return tool_class
30
+
31
+ @classmethod
32
+ def get_tools(cls):
33
+ return [cls._tools[name]().definition for name in cls._tools]
34
+
35
+ def setup_logging():
36
+ """Configure logging for the gofannon package."""
37
+ logger = logging.getLogger('')
38
+ log_level = os.getenv('GOFANNON_LOG_LEVEL', 'WARNING').upper()
39
+ level = getattr(logging, log_level, logging.WARNING)
40
+
41
+ # Clear existing handlers if any
42
+ if logger.handlers:
43
+ for handler in logger.handlers:
44
+ logger.removeHandler(handler)
45
+
46
+ # Configure new handler
47
+ handler = logging.StreamHandler()
48
+ formatter = logging.Formatter(
49
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
50
+ )
51
+ handler.setFormatter(formatter)
52
+ logger.addHandler(handler)
53
+
54
+ logger.setLevel(level)
55
+ logger.propagate = False
56
+
57
+ # Initialize logging when config is imported
58
+ setup_logging()
@@ -0,0 +1,11 @@
1
+
2
+ from.create_issue import CreateIssue
3
+ from.get_repo_contents import GetRepoContents
4
+ from.commit_file import CommitFile
5
+ from.commit_files import CommitFiles
6
+ from.search import SearchRepos
7
+ from .read_issue import ReadIssue
8
+
9
+
10
+
11
+
@@ -0,0 +1,77 @@
1
+
2
+ import requests
3
+ import json
4
+
5
+ from..base import BaseTool
6
+ from ..config import FunctionRegistry
7
+ import logging
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ @FunctionRegistry.register
12
+ class CommitFile(BaseTool):
13
+ def __init__(self,
14
+ api_key=None,
15
+ name="commit_file",):
16
+ super().__init__()
17
+ self.api_key = api_key
18
+ self.name = name
19
+ self.API_SERVICE = 'github'
20
+
21
+ @property
22
+ def definition(self):
23
+ return {
24
+ "type": "function",
25
+ "function": {
26
+ "name": self.name,
27
+ "description": "Commit a file to a GitHub repository",
28
+ "parameters": {
29
+ "type": "object",
30
+ "properties": {
31
+ "repo_url": {
32
+ "type": "string",
33
+ "description": "The URL of the repository, e.g. https://github.com/The-AI-Alliance//gofannon"
34
+ },
35
+ "file_path": {
36
+ "type": "string",
37
+ "description": "The path of the file in the repository, e.g. example.txt"
38
+ },
39
+ "file_contents": {
40
+ "type": "string",
41
+ "description": "The contents of the file as a string"
42
+ },
43
+ "commit_message": {
44
+ "type": "string",
45
+ "description": "The commit message, e.g. 'Added example.txt'"
46
+ }
47
+ },
48
+ "required": ["repo_url", "file_path", "file_contents", "commit_message"]
49
+ }
50
+ }
51
+ }
52
+
53
+ def fn(self, repo_url,
54
+ file_path,
55
+ file_contents,
56
+ commit_message)-> str:
57
+ logger.debug(f"Committing file {file_path} to {repo_url}")
58
+ # Extracting the owner and repo name from the URL
59
+ repo_parts = repo_url.rstrip('/').split('/')
60
+ owner = repo_parts[-2]
61
+ repo = repo_parts[-1]
62
+
63
+ api_url = f"https://api.github.com/repos/{owner}/{repo}/contents/{file_path}"
64
+ headers = {
65
+ 'Authorization': f'token {self.api_key}',
66
+ 'Content-Type': 'application/json'
67
+ }
68
+
69
+ data = {
70
+ "message": commit_message,
71
+ "content": file_contents
72
+ }
73
+
74
+ response = requests.put(api_url, headers=headers, data=json.dumps(data))
75
+ response.raise_for_status()
76
+
77
+ return response.json()
@@ -0,0 +1,116 @@
1
+ import os
2
+
3
+ import requests
4
+ import json
5
+ import git
6
+ from pathlib import Path
7
+
8
+ from..base import BaseTool
9
+ from ..config import FunctionRegistry
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ @FunctionRegistry.register
15
+ class CommitFiles(BaseTool):
16
+ def __init__(self,
17
+ api_key=None,
18
+ name="commit_files",
19
+ git_user_name=None,
20
+ git_user_email=None):
21
+ super().__init__()
22
+ self.api_key = api_key
23
+ self.name = name
24
+ self.git_user_name = git_user_name
25
+ self.git_user_email = git_user_email
26
+ self.API_SERVICE = 'github'
27
+
28
+
29
+ @property
30
+ def definition(self):
31
+ return {
32
+ "type": "function",
33
+ "function": {
34
+ "name": self.name,
35
+ "description": "Commit multiple files to a GitHub repository",
36
+ "parameters": {
37
+ "type": "object",
38
+ "properties": {
39
+ "repo_url": {
40
+ "type": "string",
41
+ "description": "The URL of the repository, e.g. https://github.com/The-AI-Alliance//gofannon"
42
+ },
43
+ "branch": {
44
+ "type": "string",
45
+ "description": "The branch to commit to, e.g. 'main' or 'new-branch'"
46
+ },
47
+ "commit_msg": {
48
+ "type": "string",
49
+ "description": "The commit message, e.g. 'Added new files'"
50
+ },
51
+ "files_json": {
52
+ "type": "string",
53
+ "description": "A JSON string containing a list of files to commit, e.g. '{\"files\": [{\"path\": \"file1.py\", \"code\": \"import os\"}, {\"path\": \"file2.py\", \"code\": \"import sys\"}]}'"
54
+ },
55
+ "base_branch": {
56
+ "type": "string",
57
+ "description": "Optional. The base branch to create the new branch from. Default: 'main'"
58
+ }
59
+ },
60
+ "required": ["repo_url", "branch", "commit_msg", "files_json"]
61
+ }
62
+ }
63
+ }
64
+
65
+ def fn(self, repo_url, branch, commit_msg, files_json, base_branch='main'):
66
+ logger.debug(f"Committing files to {repo_url}")
67
+ # Extracting the owner and repo name from the URL
68
+ repo_parts = repo_url.rstrip('/').split('/')
69
+ owner = repo_parts[-2]
70
+ repo = repo_parts[-1]
71
+
72
+ if repo_url.startswith("https://"):
73
+ repo_url = repo_url.replace("https://", "https://"+ self.api_key+"@")
74
+ elif repo_url.startswith("github.com"):
75
+ repo_url = f"https://{self.api_key}@{repo_url}"
76
+ # Clone the repository
77
+ repo_dir = f"/tmp/{repo}"
78
+
79
+
80
+ if os.path.exists(repo_dir):
81
+ repo = git.Repo(repo_dir)
82
+ else:
83
+ repo = git.Repo.clone_from(repo_url, repo_dir)
84
+ repo.config_writer().set_value("user", "name", self.git_user_name).release()
85
+ repo.config_writer().set_value("user", "email", self.git_user_email).release()
86
+
87
+ # Check if the branch exists
88
+ if branch in repo.heads:
89
+ # If it does, checkout the branch and pull the latest changes
90
+ repo.git.checkout(branch)
91
+ repo.git.pull()
92
+ else:
93
+ # If it does not exist, checkout the base branch and create a new branch
94
+ try:
95
+ repo.git.checkout(base_branch)
96
+ except git.exc.GitCommandError:
97
+ # If the base branch does not exist, raise an error
98
+ raise ValueError(f"Base branch '{base_branch}' does not exist.")
99
+ repo.git.checkout('-b', branch)
100
+
101
+ # Load the JSON string
102
+ files = json.loads(files_json)['files']
103
+ # Update or create the files
104
+ for file in files:
105
+ file_path = file['path']
106
+ code = file['code']
107
+ with open(f"{repo_dir}/{file_path}", 'w') as f:
108
+ f.write(code)
109
+ repo.index.add(Path(file_path))
110
+
111
+ # Commit the files
112
+ repo.index.commit(commit_msg)
113
+
114
+ origin = repo.remotes.origin
115
+ origin.push(branch)
116
+ return "Files committed and pushed successfully"
@@ -0,0 +1,78 @@
1
+ from requests import post
2
+ from json import dumps
3
+ from..base import BaseTool
4
+ from ..config import FunctionRegistry
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ @FunctionRegistry.register
10
+ class CreateIssue(BaseTool):
11
+ def __init__(self,
12
+ api_key=None,
13
+ name="create_issue"):
14
+ super().__init__()
15
+ self.api_key = api_key
16
+ self.name = name
17
+ self.API_SERVICE = 'github'
18
+
19
+ @property
20
+ def definition(self):
21
+ return {
22
+ "type": "function",
23
+ "function": {
24
+ "name": self.name,
25
+ "description": "Create an issue in a GitHub repository",
26
+ "parameters": {
27
+ "type": "object",
28
+ "properties": {
29
+ "repo_url": {
30
+ "type": "string",
31
+ "description": "The URL of the repository, e.g. https://github.com/The-AI-Alliance//gofannon"
32
+ },
33
+ "title": {
34
+ "type": "string",
35
+ "description": "The title of the issue"
36
+ },
37
+ "body": {
38
+ "type": "string",
39
+ "description": "The body of the issue"
40
+ },
41
+ "labels": {
42
+ "type": "array",
43
+ "items": {
44
+ "type": "string"
45
+ },
46
+ "description": "An array of labels for the issue"
47
+ }
48
+ },
49
+ "required": ["repo_url", "title", "body"]
50
+ }
51
+ }
52
+ }
53
+
54
+ def fn(self, repo_url, title, body, labels=None):
55
+ logger.debug(f"Crating issue'{title}' in repo {repo_url}")
56
+ # Extracting the owner and repo name from the URL
57
+ repo_parts = repo_url.rstrip('/').split('/')
58
+ owner = repo_parts[-2]
59
+ repo = repo_parts[-1]
60
+
61
+ api_url = f"https://api.github.com/repos/{owner}/{repo}/issues"
62
+ headers = {
63
+ 'Authorization': f'token {self.api_key}',
64
+ 'Content-Type': 'application/json'
65
+ }
66
+
67
+ payload = {
68
+ "title": title,
69
+ "body": body
70
+ }
71
+
72
+ if labels:
73
+ payload["labels"] = labels
74
+
75
+ response = post(api_url, headers=headers, json=payload)
76
+ response.raise_for_status()
77
+
78
+ return dumps(response.json())
@@ -0,0 +1,98 @@
1
+ import requests
2
+ from ..base import BaseTool
3
+ from ..config import FunctionRegistry
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ @FunctionRegistry.register
9
+ class GetRepoContents(BaseTool):
10
+
11
+ def __init__(self,
12
+ api_key=None,
13
+ name="get_repo_contents",):
14
+ super().__init__()
15
+ self.api_key = api_key
16
+ self.name = name
17
+ self.API_SERVICE = 'github'
18
+ self.eoi = {'js' : 'javascript',
19
+ 'jsx' : 'javascript',
20
+ 'ts' : 'typescript',
21
+ 'tsx' : 'typescript',
22
+ 'py' : 'python',
23
+ 'html' : 'html',
24
+ 'css' : 'css',
25
+ 'scss' : 'scss',
26
+ 'sass' : 'sass',
27
+ 'md' : 'markdown',
28
+ 'json' : 'json'}
29
+
30
+ @property
31
+ def definition(self):
32
+ return {
33
+ "type": "function",
34
+ "function": {
35
+ "name": self.name,
36
+ "description": "Get contents of repo on github",
37
+ "parameters": {
38
+ "type": "object",
39
+ "properties": {
40
+ "repo_url": {
41
+ "type": "string",
42
+ "description":
43
+ "The URL of the Repo this is for reading an entire repo, not individual files., e.g. https://github.com/The-AI-Alliance//gofannon"
44
+ },
45
+ "directory_path": {
46
+ "type": "string",
47
+ "description": "Path from repository root of the directory of interest. Default '/'"
48
+ },
49
+ # "eoi": {
50
+ # "type": "dictionary",
51
+ # "description": "Extensions of interest. If not set, defaults to self.eoi (which contains common Python, Javascript, HTML, and Markdown extension). Example: `{'.js': 'javascript'}`",
52
+ # }
53
+ },
54
+ "required": ["repo_url"]
55
+ }
56
+ }
57
+ }
58
+
59
+ def fn(self, repo_url,
60
+ directory_path = "/",
61
+ eoi = None)-> str:
62
+ logger.debug(f"Getting contents of repo {repo_url}")
63
+ if eoi is None:
64
+ eoi = self.eoi
65
+ # Extracting the owner and repo name from the URL
66
+ repo_parts = repo_url.rstrip('/').split('/')
67
+ owner = repo_parts[-2]
68
+ repo = repo_parts[-1]
69
+
70
+ api_url = f"https://api.github.com/repos/{owner}/{repo}/contents/{directory_path}"
71
+ headers = {
72
+ 'Authorization': f'token {self.api_key}'
73
+ }
74
+
75
+ response = requests.get(api_url, headers=headers)
76
+ response.raise_for_status()
77
+
78
+ contents = response.json()
79
+
80
+ result = []
81
+
82
+ for item in contents:
83
+ if item['type'] == 'file':
84
+ file_response = requests.get(item['download_url'], headers=headers)
85
+ extension = item['name'].split('.')[-1]
86
+ if extension in eoi:
87
+ language = eoi[extension]
88
+ else:
89
+ continue
90
+ file_response.raise_for_status()
91
+ file_content = file_response.text
92
+ result.append(f"{item['path']}\n```{language}\n{file_content}\n```")
93
+ elif item['type'] == 'dir':
94
+ # Recursively go through subdirectories
95
+ subdirectory_contents = self.fn(repo_url, item['path'], eoi)
96
+ result.append(subdirectory_contents)
97
+
98
+ return "\n\n".join(result)
@@ -0,0 +1,70 @@
1
+
2
+ from..base import BaseTool
3
+ import requests
4
+ import json
5
+ from ..config import FunctionRegistry
6
+ import logging
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ @FunctionRegistry.register
11
+ class ReadIssue(BaseTool):
12
+ def __init__(self, api_key=None, name="read_issue"):
13
+ super().__init__()
14
+ self.api_key = api_key
15
+ self.name = name
16
+ self.API_SERVICE = 'github'
17
+
18
+ @property
19
+ def definition(self):
20
+ return {
21
+ "type": "function",
22
+ "function": {
23
+ "name": self.name,
24
+ "description": "Read an issue and its comments from a GitHub repository",
25
+ "parameters": {
26
+ "type": "object",
27
+ "properties": {
28
+ "repo_url": {
29
+ "type": "string",
30
+ "description": "The URL of the repository, e.g. https://github.com/The-AI-Alliance//gofannon"
31
+ },
32
+ "issue_number": {
33
+ "type": "integer",
34
+ "description": "The number of the issue to read"
35
+ }
36
+ },
37
+ "required": ["repo_url", "issue_number"]
38
+ }
39
+ }
40
+ }
41
+
42
+ def fn(self, repo_url, issue_number):
43
+ logger.debug(f"Reading issue number {issue_number} from repo {repo_url}")
44
+ # Extracting the owner and repo name from the URL
45
+ repo_parts = repo_url.rstrip('/').split('/')
46
+ owner = repo_parts[-2]
47
+ repo = repo_parts[-1]
48
+
49
+ issue_url = f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_number}"
50
+ comment_url = f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_number}/comments"
51
+
52
+ headers = {
53
+ 'Authorization': f'token {self.api_key}'
54
+ }
55
+
56
+ issue_response = requests.get(issue_url, headers=headers)
57
+ issue_response.raise_for_status()
58
+
59
+ comment_response = requests.get(comment_url, headers=headers)
60
+ comment_response.raise_for_status()
61
+
62
+ issue_data = issue_response.json()
63
+ comment_data = comment_response.json()
64
+
65
+ result = {
66
+ "issue": issue_data,
67
+ "comments": comment_data
68
+ }
69
+
70
+ return json.dumps(result, indent=4)
@@ -0,0 +1,67 @@
1
+ from..base import BaseTool
2
+ import requests
3
+ from ..config import FunctionRegistry
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ @FunctionRegistry.register
9
+ class SearchRepos(BaseTool):
10
+ def __init__(self,
11
+ api_key=None,
12
+ name="search_repos",):
13
+ super().__init__()
14
+ self.api_key = api_key
15
+ self.name = name
16
+ self.API_SERVICE = 'github'
17
+
18
+ @property
19
+ def definition(self):
20
+ return {
21
+ "type": "function",
22
+ "function": {
23
+ "name": self.name,
24
+ "description": "Search for GitHub repositories",
25
+ "parameters": {
26
+ "type": "object",
27
+ "properties": {
28
+ "query": {
29
+ "type": "string",
30
+ "description": "The search query, e.g. 'machine learning'"
31
+ },
32
+ "page": {
33
+ "type": "integer",
34
+ "description": "The page number to retrieve (default: 1)"
35
+ },
36
+ "per_page": {
37
+ "type": "integer",
38
+ "description": "The number of results per page (default: 10)"
39
+ }
40
+ },
41
+ "required": ["query"]
42
+ }
43
+ }
44
+ }
45
+
46
+ def fn(self, query, page=1, per_page=10) -> str:
47
+ logger.debug(f"Searching github.com for '{query}'")
48
+ api_url = f"https://api.github.com/search/repositories"
49
+ headers = {
50
+ 'Authorization': f'token {self.api_key}'
51
+ }
52
+ params = {
53
+ "q": query,
54
+ "page": page,
55
+ "per_page": per_page
56
+ }
57
+
58
+ response = requests.get(api_url, headers=headers, params=params)
59
+ response.raise_for_status()
60
+
61
+ results = response.json()
62
+
63
+ formatted_results = []
64
+ for result in results['items']:
65
+ formatted_results.append(f"**{result['name']}** by **{result['owner']['login']}** - {result['description']}")
66
+
67
+ return "\n\n".join(formatted_results)
@@ -0,0 +1,59 @@
1
+ from requests import get
2
+ from json import dumps
3
+
4
+ from ..base import BaseTool
5
+ from ..config import FunctionRegistry
6
+ import logging
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ @FunctionRegistry.register
11
+ class ComplaintsByVehicle(BaseTool):
12
+ def __init__(self,
13
+ api_key=None,
14
+ name="complaints_by_vehicle",):
15
+ super().__init__()
16
+ self.api_key = api_key
17
+ self.name = name
18
+ self.API_SERVICE = 'nhtsa'
19
+
20
+ @property
21
+ def definition(self):
22
+ return {
23
+ "type": "function",
24
+ "function": {
25
+ "name": self.name,
26
+ "description": "Get complaints by vehicle make, model, and year",
27
+ "parameters": {
28
+ "type": "object",
29
+ "properties": {
30
+ "make": {
31
+ "type": "string",
32
+ "description": "The make of the vehicle, e.g. Acura"
33
+ },
34
+ "model": {
35
+ "type": "string",
36
+ "description": "The model of the vehicle, e.g. ILX"
37
+ },
38
+ "modelYear": {
39
+ "type": "string",
40
+ "description": "The year of the vehicle, e.g. 2022"
41
+ }
42
+ },
43
+ "required": ["make", "model", "modelYear"]
44
+ }
45
+ }
46
+ }
47
+
48
+ def fn(self, make,
49
+ model,
50
+ modelYear)-> str:
51
+ logger.debug(f"Searching for complaints related to {modelYear} {make} {model}")
52
+ base_url = "https://api.nhtsa.gov/complaints/complaintsByVehicle"
53
+ payload = {
54
+ "make": make,
55
+ "model": model,
56
+ "modelYear": modelYear
57
+ }
58
+ r = get(base_url, params=payload)
59
+ return dumps(r.json())