Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sweep: Fix importerror: cannot import name 'self' from 'typing' #259

Closed
hamero opened this issue Jul 5, 2023 · 5 comments · May be fixed by #260 or #261
Closed

Sweep: Fix importerror: cannot import name 'self' from 'typing' #259

hamero opened this issue Jul 5, 2023 · 5 comments · May be fixed by #260 or #261
Labels
sweep Assigns Sweep to an issue or pull request.

Comments

@hamero
Copy link

hamero commented Jul 5, 2023

Description

when running python sweepai/app/cli.py

Relevant files

sweepai/app/cli.py
@hamero hamero added the sweep Assigns Sweep to an issue or pull request. label Jul 5, 2023
@sweep-ai
Copy link
Contributor

sweep-ai bot commented Jul 5, 2023

Hey @hamero,

I've started working on this issue. The plan is to add a workaround in sweepai/app/cli.py to handle the ImportError for 'Self' from 'typing'. This will ensure compatibility with Python versions lower than 3.11.

Give me a minute!

Some code snippets I looked at (click to expand). If some file is missing from here, you can mention the path in the ticket description.

"""
This file should be split into environment and config files
"""
PREFIX = "prod"
DB_NAME = PREFIX + "-db"
API_NAME = PREFIX + "-api"
UTILS_NAME = PREFIX + "-utils"
SLACK_NAME = PREFIX + "-slack"
BOT_TOKEN_NAME = PREFIX + "-bot-token"
if PREFIX == "prod":
BOT_TOKEN_NAME = "bot-token"
SWEEP_LOGIN = "sweep-ai[bot]"
if PREFIX == "prod":
APP_ID = 307814
ENV = PREFIX
elif PREFIX == "dev2":
APP_ID = 327588
ENV = PREFIX
SWEEP_LOGIN = "sweep-canary[bot]"
elif PREFIX == "dev":
APP_ID = 324098
ENV = PREFIX
SWEEP_LOGIN = "sweep-nightly[bot]"
LABEL_NAME = "sweep"
LABEL_COLOR = "#9400D3"
LABEL_DESCRIPTION = "Sweep your software chores"

import typer
from sweepai.app.config import SweepChatConfig
epilog = "Sweep is a AI junior developer. Docs at https://docs.sweep.dev, install at https://github.com/apps/sweep-ai and support at https://discord.gg/sweep-ai."
typer_app = typer.Typer(epilog=epilog)
# @app.callback()
@typer_app.command()
def start():
"""
Launch Sweep Chat in the browser
"""
SweepChatConfig.load()
from sweepai.app.ui import demo
demo.queue()
demo.launch(enable_queue=True, inbrowser=True)
@typer_app.command()
def auth():
"""
Reauthenticate with Github API for Sweep to work (for token expiry)
"""
SweepChatConfig.load(recreate=True)
print("Setup completed successfully!")
def app():
# hacky solution based on https://github.com/tiangolo/typer/issues/18#issuecomment-1577788949
import sys
commands = {'start', 'auth'}
sys.argv.append('start') if sys.argv[-1] not in commands else None
typer_app()
if __name__ == "__main__":

from copy import deepcopy
import json
import os
from typing import Iterator, Literal, Self
import modal
import openai
import anthropic
from loguru import logger
from pydantic import BaseModel
import backoff
from sweepai.core.entities import (
Function,
Message,
)
from sweepai.core.prompts import (
system_message_prompt,
system_message_issue_comment_prompt,
)
from sweepai.utils.constants import UTILS_NAME
from sweepai.utils.prompt_constructor import HumanMessagePrompt
from sweepai.core.entities import Message, Function
from sweepai.utils.chat_logger import ChatLogger
# TODO: combine anthropic and openai
AnthropicModel = (
Literal["claude-v1"]
| Literal["claude-v1.3-100k"]
| Literal["claude-instant-v1.1-100k"]
)
OpenAIModel = Literal["gpt-3.5-turbo"] | Literal["gpt-4"] | Literal["gpt-4-32k"] | Literal["gpt-4-0613"] | Literal["gpt-4-32k-0613"] | Literal["gpt-3.5-turbo-16k-0613"]
ChatModel = OpenAIModel | AnthropicModel
model_to_max_tokens = {
"gpt-3.5-turbo": 4096,
"gpt-4": 8192,
"gpt-4-0613": 8192,
"gpt-4-32k": 32000,
"gpt-4-32k-0613": 32000,
"claude-v1": 9000,
"claude-v1.3-100k": 100000,
"claude-instant-v1.3-100k": 100000,
"gpt-3.5-turbo-16k-0613": 16000,
}
temperature = 0.1
def format_for_anthropic(messages: list[Message]) -> str:
if len(messages) > 1:
new_messages: list[Message] = [Message(role="system", content=messages[0].content + "\n" + messages[1].content)]
messages = messages[2:] if len(messages) >= 3 else []
else:
new_messages: list[Message] = []
for message in messages:
new_messages.append(message)
return "\n".join(
f"{anthropic.HUMAN_PROMPT if message.role != 'assistant' else anthropic.AI_PROMPT} {message.content}"
for message in new_messages
) + (anthropic.AI_PROMPT if new_messages[-1].role != "assistant" else "")
class ChatGPT(BaseModel):
messages: list[Message] = [
Message(
role="system",
content=system_message_prompt,
)
]
prev_message_states: list[list[Message]] = []
model: ChatModel = "gpt-4-32k-0613"
human_message: HumanMessagePrompt | None = None
file_change_paths = []
chat_logger: ChatLogger | None = None
@classmethod
def from_system_message_content(
cls, human_message: HumanMessagePrompt, is_reply: bool = False, **kwargs
) -> Self:
if is_reply:
system_message_content = system_message_issue_comment_prompt
system_message_content = (
system_message_prompt + "\n\n" + human_message.construct_prompt()
)
return cls(
messages=[
Message(role="system", content=system_message_content, key="system")
],
human_message=human_message,
**kwargs,
)
@classmethod
def from_system_message_string(cls, prompt_string, **kwargs) -> Self:
return cls(
messages=[Message(role="system", content=prompt_string, key="system")],
**kwargs,
)

from typing import Any
import webbrowser
import httpx
from pydantic import BaseModel
import requests
import json
from loguru import logger
from sweepai.app.config import SweepChatConfig
from sweepai.core.entities import Function, PullRequest, Snippet
from sweepai.utils.constants import PREFIX
create_pr_function = Function(
name="create_pr",
description="Creates a PR.",
parameters={
"properties": {
"plan": {
"type": "array",
"items": {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "The file path to change."
},
"instructions": {
"type": "string",
"description": "Concise NATURAL LANGUAGE summary of what to change in each file. There should be absolutely NO code, only English.",
"example": [
"Refactor the algorithm by moving the main function to the top of the file.",
"Change the implementation to recursion"
]
},
},
"required": ["file_path", "instructions"]
},
"description": "A list of files to modify or create and corresponding instructions."
},
"title": {
"type": "string",
"description": "Title of PR",
},
"summary": {
"type": "string",
"description": "Detailed summary of PR",
},
"branch": {
"type": "string",
"description": "Name of branch to create PR in.",
},
},
"required": ["plan", "title", "summary", "branch"]
}
)
create_pr_function_call = {"name": "create_pr"}
def break_json(raw_json: str):
# turns something like {"function_call": {"arguments": " \""}}{"function_call": {"arguments": "summary"}} into two objects
try:
yield json.loads(raw_json)

import re
from typing import ClassVar, Literal, Type, TypeVar
from loguru import logger
from pydantic import BaseModel
try: # Python 3.11+
from typing import Self
except ImportError: # Python 3.10
Self = TypeVar("Self", bound="RegexMatchableBaseModel")
class Message(BaseModel):
role: Literal["system"] | Literal["user"] | Literal["assistant"] | Literal["function"]
content: str | None = None
name: str | None = None
function_call: dict | None = None
key: str | None = None
@classmethod
def from_tuple(cls, tup: tuple[str | None, str | None]) -> Self:
if tup[0] is None:
return cls(role="assistant", content=tup[1])
else:
return cls(role="user", content=tup[0])
def to_openai(self) -> str:
obj = {
"role": self.role,
"content": self.content,
}
if self.function_call:
obj["function_call"] = self.function_call
if self.role == "function":
obj["name"] = self.name
return obj
class Function(BaseModel):
class Parameters(BaseModel):
type: str = "object"
properties: dict
name: str
description: str
parameters: Parameters
class RegexMatchError(ValueError):
pass
class RegexMatchableBaseModel(BaseModel):
_regex: ClassVar[str]
@classmethod
def from_string(cls: Type[Self], string: str, **kwargs) -> Self:
# match = re.search(file_regex, string, re.DOTALL)
match = re.search(cls._regex, string, re.DOTALL)
if match is None:
logger.warning(f"Did not match {string} with pattern {cls._regex}")
raise RegexMatchError("Did not match")
return cls(
**{k: (v if v else "").strip() for k, v in match.groupdict().items()},
**kwargs,
)
class FilesToChange(RegexMatchableBaseModel):
files_to_modify: str
files_to_create: str
_regex = r"""<create>(?P<files_to_create>.*)</create>\s*<modify>(?P<files_to_modify>.*)</modify>"""
class FileChangeRequest(RegexMatchableBaseModel):
filename: str
instructions: str
change_type: Literal["modify"] | Literal["create"]
_regex = r"""^ *`?(?P<filename>\S*)`?:(?P<instructions>.*)"""
@classmethod
def from_string(cls: Type[Self], string: str, **kwargs) -> Self:
result = super().from_string(string, **kwargs)
result.filename = result.filename.strip('`')
return result
class FileChange(RegexMatchableBaseModel):
commit_message: str
code: str
_regex = r"""Commit Message:(?P<commit_message>.*)<new_file>(python|javascript|typescript|csharp|tsx|jsx)?(?P<code>.*)$"""
# _regex = r"""Commit Message:(?P<commit_message>.*)(<new_file>|```)(python|javascript|typescript|csharp|tsx|jsx)?(?P<code>.*)($|```)"""
@classmethod
def from_string(cls: Type[Self], string: str, **kwargs) -> Self:
result = super().from_string(string, **kwargs)
result.code = result.code.strip()
if result.code.endswith("</new_file>"):
result.code = result.code[: -len("</new_file>")]
if len(result.code) == 1:
result.code = result.code.replace("```", "")
return result.code + "\n"


I'm a bot that handles simple bugs and feature requests but I might make mistakes. Please be kind!

@sweep-nightly
Copy link
Contributor

sweep-nightly bot commented Jul 5, 2023

Hey @hamero,

I've started working on the issue you reported. The problem seems to be related to the Python version compatibility with the 'Self' attribute from the 'typing' module. I'm planning to modify the fallback logic in the 'entities.py' file to handle Python versions below 3.10.

Give me a minute!

Best,
Sweep bot

Some code snippets I looked at (click to expand). If some file is missing from here, you can mention the path in the ticket description.

"""
This file should be split into environment and config files
"""
PREFIX = "prod"
DB_NAME = PREFIX + "-db"
API_NAME = PREFIX + "-api"
UTILS_NAME = PREFIX + "-utils"
SLACK_NAME = PREFIX + "-slack"
BOT_TOKEN_NAME = PREFIX + "-bot-token"
if PREFIX == "prod":
BOT_TOKEN_NAME = "bot-token"
SWEEP_LOGIN = "sweep-ai[bot]"
if PREFIX == "prod":
APP_ID = 307814
ENV = PREFIX
elif PREFIX == "dev2":
APP_ID = 327588
ENV = PREFIX
SWEEP_LOGIN = "sweep-canary[bot]"
elif PREFIX == "dev":
APP_ID = 324098
ENV = PREFIX
SWEEP_LOGIN = "sweep-nightly[bot]"
LABEL_NAME = "sweep"
LABEL_COLOR = "#9400D3"
LABEL_DESCRIPTION = "Sweep your software chores"

import typer
from sweepai.app.config import SweepChatConfig
epilog = "Sweep is a AI junior developer. Docs at https://docs.sweep.dev, install at https://github.com/apps/sweep-ai and support at https://discord.gg/sweep-ai."
typer_app = typer.Typer(epilog=epilog)
# @app.callback()
@typer_app.command()
def start():
"""
Launch Sweep Chat in the browser
"""
SweepChatConfig.load()
from sweepai.app.ui import demo
demo.queue()
demo.launch(enable_queue=True, inbrowser=True)
@typer_app.command()
def auth():
"""
Reauthenticate with Github API for Sweep to work (for token expiry)
"""
SweepChatConfig.load(recreate=True)
print("Setup completed successfully!")
def app():
# hacky solution based on https://github.com/tiangolo/typer/issues/18#issuecomment-1577788949
import sys
commands = {'start', 'auth'}
sys.argv.append('start') if sys.argv[-1] not in commands else None
typer_app()
if __name__ == "__main__":

from copy import deepcopy
import json
import os
from typing import Iterator, Literal, Self
import modal
import openai
import anthropic
from loguru import logger
from pydantic import BaseModel
import backoff
from sweepai.core.entities import (
Function,
Message,
)
from sweepai.core.prompts import (
system_message_prompt,
system_message_issue_comment_prompt,
)
from sweepai.utils.constants import UTILS_NAME
from sweepai.utils.prompt_constructor import HumanMessagePrompt
from sweepai.core.entities import Message, Function
from sweepai.utils.chat_logger import ChatLogger
# TODO: combine anthropic and openai
AnthropicModel = (
Literal["claude-v1"]
| Literal["claude-v1.3-100k"]
| Literal["claude-instant-v1.1-100k"]
)
OpenAIModel = Literal["gpt-3.5-turbo"] | Literal["gpt-4"] | Literal["gpt-4-32k"] | Literal["gpt-4-0613"] | Literal["gpt-4-32k-0613"] | Literal["gpt-3.5-turbo-16k-0613"]
ChatModel = OpenAIModel | AnthropicModel
model_to_max_tokens = {
"gpt-3.5-turbo": 4096,
"gpt-4": 8192,
"gpt-4-0613": 8192,
"gpt-4-32k": 32000,
"gpt-4-32k-0613": 32000,
"claude-v1": 9000,
"claude-v1.3-100k": 100000,
"claude-instant-v1.3-100k": 100000,
"gpt-3.5-turbo-16k-0613": 16000,
}
temperature = 0.1
def format_for_anthropic(messages: list[Message]) -> str:
if len(messages) > 1:
new_messages: list[Message] = [Message(role="system", content=messages[0].content + "\n" + messages[1].content)]
messages = messages[2:] if len(messages) >= 3 else []
else:
new_messages: list[Message] = []
for message in messages:
new_messages.append(message)
return "\n".join(
f"{anthropic.HUMAN_PROMPT if message.role != 'assistant' else anthropic.AI_PROMPT} {message.content}"
for message in new_messages
) + (anthropic.AI_PROMPT if new_messages[-1].role != "assistant" else "")
class ChatGPT(BaseModel):
messages: list[Message] = [
Message(
role="system",
content=system_message_prompt,
)
]
prev_message_states: list[list[Message]] = []
model: ChatModel = "gpt-4-32k-0613"
human_message: HumanMessagePrompt | None = None
file_change_paths = []
chat_logger: ChatLogger | None = None
@classmethod
def from_system_message_content(
cls, human_message: HumanMessagePrompt, is_reply: bool = False, **kwargs
) -> Self:
if is_reply:
system_message_content = system_message_issue_comment_prompt
system_message_content = (
system_message_prompt + "\n\n" + human_message.construct_prompt()
)
return cls(
messages=[
Message(role="system", content=system_message_content, key="system")
],
human_message=human_message,
**kwargs,
)
@classmethod
def from_system_message_string(cls, prompt_string, **kwargs) -> Self:
return cls(
messages=[Message(role="system", content=prompt_string, key="system")],
**kwargs,
)

from typing import Any
import webbrowser
import httpx
from pydantic import BaseModel
import requests
import json
from loguru import logger
from sweepai.app.config import SweepChatConfig
from sweepai.core.entities import Function, PullRequest, Snippet
from sweepai.utils.constants import PREFIX
create_pr_function = Function(
name="create_pr",
description="Creates a PR.",
parameters={
"properties": {
"plan": {
"type": "array",
"items": {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "The file path to change."
},
"instructions": {
"type": "string",
"description": "Concise NATURAL LANGUAGE summary of what to change in each file. There should be absolutely NO code, only English.",
"example": [
"Refactor the algorithm by moving the main function to the top of the file.",
"Change the implementation to recursion"
]
},
},
"required": ["file_path", "instructions"]
},
"description": "A list of files to modify or create and corresponding instructions."
},
"title": {
"type": "string",
"description": "Title of PR",
},
"summary": {
"type": "string",
"description": "Detailed summary of PR",
},
"branch": {
"type": "string",
"description": "Name of branch to create PR in.",
},
},
"required": ["plan", "title", "summary", "branch"]
}
)
create_pr_function_call = {"name": "create_pr"}
def break_json(raw_json: str):
# turns something like {"function_call": {"arguments": " \""}}{"function_call": {"arguments": "summary"}} into two objects
try:
yield json.loads(raw_json)

import re
from typing import ClassVar, Literal, Type, TypeVar
from loguru import logger
from pydantic import BaseModel
try: # Python 3.11+
from typing import Self
except ImportError: # Python 3.10
Self = TypeVar("Self", bound="RegexMatchableBaseModel")
class Message(BaseModel):
role: Literal["system"] | Literal["user"] | Literal["assistant"] | Literal["function"]
content: str | None = None
name: str | None = None
function_call: dict | None = None
key: str | None = None
@classmethod
def from_tuple(cls, tup: tuple[str | None, str | None]) -> Self:
if tup[0] is None:
return cls(role="assistant", content=tup[1])
else:
return cls(role="user", content=tup[0])
def to_openai(self) -> str:
obj = {
"role": self.role,
"content": self.content,
}
if self.function_call:
obj["function_call"] = self.function_call
if self.role == "function":
obj["name"] = self.name
return obj
class Function(BaseModel):
class Parameters(BaseModel):
type: str = "object"
properties: dict
name: str
description: str
parameters: Parameters
class RegexMatchError(ValueError):
pass
class RegexMatchableBaseModel(BaseModel):
_regex: ClassVar[str]
@classmethod
def from_string(cls: Type[Self], string: str, **kwargs) -> Self:
# match = re.search(file_regex, string, re.DOTALL)
match = re.search(cls._regex, string, re.DOTALL)
if match is None:
logger.warning(f"Did not match {string} with pattern {cls._regex}")
raise RegexMatchError("Did not match")
return cls(
**{k: (v if v else "").strip() for k, v in match.groupdict().items()},
**kwargs,
)
class FilesToChange(RegexMatchableBaseModel):
files_to_modify: str
files_to_create: str
_regex = r"""<create>(?P<files_to_create>.*)</create>\s*<modify>(?P<files_to_modify>.*)</modify>"""
class FileChangeRequest(RegexMatchableBaseModel):
filename: str
instructions: str
change_type: Literal["modify"] | Literal["create"]
_regex = r"""^ *`?(?P<filename>\S*)`?:(?P<instructions>.*)"""
@classmethod
def from_string(cls: Type[Self], string: str, **kwargs) -> Self:
result = super().from_string(string, **kwargs)
result.filename = result.filename.strip('`')
return result
class FileChange(RegexMatchableBaseModel):
commit_message: str
code: str
_regex = r"""Commit Message:(?P<commit_message>.*)<new_file>(python|javascript|typescript|csharp|tsx|jsx)?(?P<code>.*)$"""
# _regex = r"""Commit Message:(?P<commit_message>.*)(<new_file>|```)(python|javascript|typescript|csharp|tsx|jsx)?(?P<code>.*)($|```)"""
@classmethod
def from_string(cls: Type[Self], string: str, **kwargs) -> Self:
result = super().from_string(string, **kwargs)
result.code = result.code.strip()
if result.code.endswith("</new_file>"):
result.code = result.code[: -len("</new_file>")]
if len(result.code) == 1:
result.code = result.code.replace("```", "")
return result.code + "\n"


I'm a bot that handles simple bugs and feature requests but I might make mistakes. Please be kind!

@wwzeng1
Copy link
Contributor

wwzeng1 commented Jul 7, 2023

This issue seems to come from when your python version is <3.10

@sweep-nightly
Copy link
Contributor

sweep-nightly bot commented Jul 7, 2023

0%


Error: 🚫 Unable to Complete PR

If you would like to report this bug, please join our Discord.


I'm a bot that handles simple bugs and feature requests but I might make mistakes. Please be kind!
Join Our Discord

@kevinlu1248
Copy link
Member

kevinlu1248 commented Jul 7, 2023

@hamero I fixed this bug a while back, just make sure you're using the latest sweep on pip. Thanks!

Also in python 3.10 it will just use a generic instead of self. Less than python 3.10 is not supported right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
sweep Assigns Sweep to an issue or pull request.
Projects
None yet
3 participants