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

feat: Implement lockfiles for composio toolset #1196

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

tushar-composio
Copy link
Contributor

@tushar-composio tushar-composio commented Jan 15, 2025

Important

This PR adds lockfile functionality to the Composio toolset for managing tool versions, including a new version attribute in ActionModel and methods for lockfile operations.

  • Behavior:
    • Adds version attribute to ActionModel in collections.py.
    • Introduces lockfile functionality in toolset.py to manage tool versions.
    • Adds LOCKFILE_PATH constant in constants.py.
  • Functions:
    • Implements load_tool_versions_from_lockfile() and store_tool_versions_to_lockfile() in toolset.py for handling lockfile operations.
  • Misc:
    • Adds placeholder version "0_1" in _generate_schema() in abs.py and get_action_schemas() in handler.py.

This description was created by Ellipsis for 33d6814. It will automatically update as commits are pushed.

Copy link

vercel bot commented Jan 15, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
composio ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 16, 2025 11:34am

Comment on lines 1681 to 1706
lockfile_path = Path("./.composio.lock")
if not lockfile_path.exists():
return {}

with open(lockfile_path, "r") as file:
tool_versions = yaml.safe_load(file)

# Validate lockfile
if not isinstance(tool_versions, dict):
raise ComposioSDKError(
f"Invalid lockfile format, expected dict, got {type(tool_versions)}"
)

for tool in tool_versions.values():
if not isinstance(tool, str):
raise ComposioSDKError(
f"Invalid lockfile format, expected version to be string, got {tool!r}"
)

return tool_versions

def store_tool_versions_to_lockfile(self) -> None:
"""Store tool versions to lockfile."""
lockfile_path = Path("./.composio.lock")
with open(lockfile_path, "w") as file:
yaml.safe_dump(self._tool_versions, file)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded lockfile path can cause write failures or security issues; use a configurable path and validate directory existence. Additionally, validate the lockfile path to prevent potential file path injection vulnerabilities.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
lockfile_path = Path("./.composio.lock")
if not lockfile_path.exists():
return {}
with open(lockfile_path, "r") as file:
tool_versions = yaml.safe_load(file)
# Validate lockfile
if not isinstance(tool_versions, dict):
raise ComposioSDKError(
f"Invalid lockfile format, expected dict, got {type(tool_versions)}"
)
for tool in tool_versions.values():
if not isinstance(tool, str):
raise ComposioSDKError(
f"Invalid lockfile format, expected version to be string, got {tool!r}"
)
return tool_versions
def store_tool_versions_to_lockfile(self) -> None:
"""Store tool versions to lockfile."""
lockfile_path = Path("./.composio.lock")
with open(lockfile_path, "w") as file:
yaml.safe_dump(self._tool_versions, file)
def get_tool_versions_from_lockfile(self, lockfile_path_str: str) -> dict:
"""Get tool versions from lockfile."""
try:
lockfile_path = Path(lockfile_path_str)
except TypeError:
raise ComposioSDKError("Invalid lockfile path provided.")
if not lockfile_path.is_absolute():
lockfile_path = Path.cwd() / lockfile_path
if not lockfile_path.exists():
return {}
if not lockfile_path.is_file():
raise ComposioSDKError("Lockfile path is not a file.")
with open(lockfile_path, "r") as file:
tool_versions = yaml.safe_load(file)
# Validate lockfile
if not isinstance(tool_versions, dict):
raise ComposioSDKError(
f"Invalid lockfile format, expected dict, got {type(tool_versions)}"
)
for tool in tool_versions.values():
if not isinstance(tool, str):
raise ComposioSDKError(
f"Invalid lockfile format, expected version to be string, got {tool!r}"
)
return tool_versions
def store_tool_versions_to_lockfile(self, lockfile_path_str: str) -> None:
"""Store tool versions to lockfile."""
try:
lockfile_path = Path(lockfile_path_str)
except TypeError:
raise ComposioSDKError("Invalid lockfile path provided.")
if not lockfile_path.is_absolute():
lockfile_path = Path.cwd() / lockfile_path
# Ensure the directory exists
lockfile_path.parent.mkdir(parents=True, exist_ok=True)
with open(lockfile_path, "w") as file:
yaml.safe_dump(self._tool_versions, file)

Comment on lines 1685 to 1686
with open(lockfile_path, "r") as file:
tool_versions = yaml.safe_load(file)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YAML.safe_load() without exception handling can crash on malformed files; add try-except for graceful error handling.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
with open(lockfile_path, "r") as file:
tool_versions = yaml.safe_load(file)
try:
with open(lockfile_path, "r") as file:
tool_versions = yaml.safe_load(file)
except yaml.YAMLError as e:
print(f"Error loading YAML file: {e}")
tool_versions = None

Comment on lines 1704 to 1706
lockfile_path = Path("./.composio.lock")
with open(lockfile_path, "w") as file:
yaml.safe_dump(self._tool_versions, file)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File operations lack proper closing in error cases; use context managers consistently for cleanup.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
lockfile_path = Path("./.composio.lock")
with open(lockfile_path, "w") as file:
yaml.safe_dump(self._tool_versions, file)
try:
with open(lockfile_path, "w") as file:
yaml.safe_dump(self._tool_versions, file)
except Exception as e:
print("Error writing to lockfile:", e)

Comment on lines 56 to 60
for schema in action_schemas:
schema["name"] = schema["enum"]

# TODO: Put version in local tool schemas
schema["version"] = "0_1"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded version string '0_1' should be dynamically fetched to ensure accuracy and consistency.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
for schema in action_schemas:
schema["name"] = schema["enum"]
# TODO: Put version in local tool schemas
schema["version"] = "0_1"
for schema in action_schemas:
schema["name"] = schema["enum"]
# TODO: Put version in local tool schemas
schema["version"] = get_version()

@staticmethod
def load_tool_versions_from_lockfile() -> t.Dict[str, str]:
"""Load tool versions from lockfile."""
lockfile_path = Path("./.composio.lock")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lockfile path is hardcoded as ./.composio.lock. Consider using a constant or configuration value for this path, similar to how other paths are handled in the codebase (e.g., LOCAL_CACHE_DIRECTORY). This would make it easier to change the location if needed and maintain consistency with other file paths in the codebase.

if not lockfile_path.exists():
return {}

with open(lockfile_path, "r") as file:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file operations here lack proper error handling. Consider wrapping the file operations in try-except blocks to handle potential IOError, PermissionError, or yaml.YAMLError exceptions. This would provide better error messages and prevent crashes in case of file access or parsing issues.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good to me! Reviewed everything up to a2b7d3f in 2 minutes and 11 seconds

More details
  • Looked at 174 lines of code in 5 files
  • Skipped 0 files when reviewing.
  • Skipped posting 3 drafted comments based on config settings.
1. python/composio/tools/base/abs.py:344
  • Draft comment:
    Setting a hardcoded version '0_1' is not ideal. Consider fetching the version dynamically from an API or configuration.
  • Reason this comment was not posted:
    Confidence changes required: 50%
    The code is setting a default version '0_1' for schemas, but it should ideally fetch the version from an API or a more dynamic source. This is a potential area for improvement.
2. python/composio/tools/local/handler.py:60
  • Draft comment:
    Setting a hardcoded version '0_1' is not ideal. Consider fetching the version dynamically from an API or configuration.
  • Reason this comment was not posted:
    Confidence changes required: 50%
    The code is setting a default version '0_1' for schemas, but it should ideally fetch the version from an API or a more dynamic source. This is a potential area for improvement.
3. python/composio/tools/toolset.py:1067
  • Draft comment:
    Setting a hardcoded version '0_1' is not ideal. Consider fetching the version dynamically from an API or configuration.
  • Reason this comment was not posted:
    Confidence changes required: 50%
    The code is setting a default version '0_1' for schemas, but it should ideally fetch the version from an API or a more dynamic source. This is a potential area for improvement.

Workflow ID: wflow_hjfafTpMeXcr6IXK


You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

@@ -56,6 +56,8 @@ def get_action_schemas(
for schema in action_schemas:
schema["name"] = schema["enum"]

# TODO: Put version in local tool schemas
schema["version"] = "0_1"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a potential bug in the get_action_schemas method in handler.py where schema["version"] is set after the loop. This should be inside the loop as it's modifying a single schema instead of all schemas in the list.

@shreysingla11
Copy link
Collaborator

Code Review Summary

The implementation of lockfiles for the composio toolset is a good addition that will help with version management. However, there are several areas that need attention:

Critical Issues:

  1. Bug in get_action_schemas where version is set outside the schema loop
  2. Missing error handling for file operations
  3. Required version field in ActionModel might cause backward compatibility issues

Improvements Needed:

  1. Use constants for file paths instead of hardcoding
  2. Consider using semantic versioning format (e.g., "0.1.0") instead of "0_1"
  3. Add proper error handling for file operations
  4. Make version field optional in ActionModel with a default value

Positive Aspects:

  1. Good validation of lockfile format
  2. Clean integration with existing toolset functionality
  3. Well-structured implementation of version tracking
  4. Proper separation of concerns between loading and storing versions

Overall Rating: 7/10 - Good implementation but needs addressing the identified issues for better reliability and maintainability.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good to me! Incremental review on 9d3823c in 45 seconds

More details
  • Looked at 37 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 1 drafted comments based on config settings.
1. python/plugins/autogen/composio_autogen/toolset.py:259
  • Draft comment:
    The removal of the self.locking_enabled check and the associated logic for updating _tool_versions might affect tool version management. If locking_enabled is intended to control this behavior, consider re-adding this logic.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable:
    The comment is speculative, saying "might affect" and using "if" conditions. There's no clear evidence in the file that removing this logic is problematic. The variables aren't used elsewhere in the file. The comment is asking the author to "consider" something rather than pointing out a definite issue.
    I could be missing important context about tool version management that exists in other files or in the broader codebase. The locking feature could be important for version control.
    Even if version locking is important, the comment is still speculative and asks for consideration rather than pointing out a definite issue. Per the rules, we should not keep speculative comments or comments that require broader context to understand.
    Delete the comment because it is speculative ("might affect") and asks the author to "consider" something rather than pointing out a definite issue.

Workflow ID: wflow_amo5qAfiSLOwFHjV


You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

def store_tool_versions_to_lockfile(self) -> None:
"""Store tool versions to lockfile."""
lockfile_path = Path("./.composio.lock")
with open(lockfile_path, "w", encoding="utf-8") as file:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'encoding' parameter should be explicitly set to 'utf-8' when opening files to prevent encoding issues.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good to me! Incremental review on 8ebb3c2 in 25 seconds

More details
  • Looked at 51 lines of code in 2 files
  • Skipped 0 files when reviewing.
  • Skipped posting 1 drafted comments based on config settings.
1. python/plugins/autogen/composio_autogen/toolset.py:245
  • Draft comment:
    The change in the get_tools function improves readability by assigning the list comprehension to a variable before returning it. This is a good practice for complex expressions.
  • Reason this comment was not posted:
    Confidence changes required: 0%
    The change in the get_tools function is a minor refactor that doesn't affect functionality. It improves readability by assigning the list comprehension to a variable before returning it.

Workflow ID: wflow_09P5KqISSBrbN5Gj


You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good to me! Incremental review on 64afacb in 42 seconds

More details
  • Looked at 16 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 1 drafted comments based on config settings.
1. python/composio/tools/local/handler.py:58
  • Draft comment:
    The TODO comment is outdated and should be removed since the version is now being set in the loop.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable:
    The comment is technically incorrect. While the version assignment was moved inside the loop, the TODO comment is still relevant - it's asking to "Put version in local tool schemas" which hasn't been fully addressed just by moving the assignment inside the loop. The TODO appears to be about a larger architectural issue of where versions should be defined for local tools.
    Maybe I'm misinterpreting the TODO's intent - perhaps it was just about the mechanical act of setting the version field?
    No, the TODO's wording specifically mentions "in local tool schemas" which suggests it's about where the version information should be architecturally stored, not just about setting the field.
    The comment should be deleted because it incorrectly assumes the TODO is no longer relevant just because the version assignment moved location.

Workflow ID: wflow_PZQAyz5MMDv54odv


You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

Comment on lines +1679 to +1706
@staticmethod
def load_tool_versions_from_lockfile() -> t.Dict[str, str]:
"""Load tool versions from lockfile."""
if not LOCKFILE_PATH.exists():
return {}

with open(LOCKFILE_PATH, encoding="utf-8") as file:
tool_versions = yaml.safe_load(file)

# Validate lockfile
if not isinstance(tool_versions, dict):
raise ComposioSDKError(
f"Invalid lockfile format, expected dict, got {type(tool_versions)}"
)

for tool in tool_versions.values():
if not isinstance(tool, str):
raise ComposioSDKError(
f"Invalid lockfile format, expected version to be string, got {tool!r}"
)

return tool_versions

def store_tool_versions_to_lockfile(self) -> None:
"""Store tool versions to lockfile."""
with open(LOCKFILE_PATH, "w", encoding="utf-8") as file:
yaml.safe_dump(self._tool_versions, file)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opening files with write mode without proper error handling could fail silently or leave files in an inconsistent state. Add try-except to handle IOError/OSError when reading/writing lockfile.

Comment on lines 52 to 55
LOCAL_CACHE_DIRECTORY,
LOCAL_OUTPUT_FILE_DIRECTORY_NAME,
USER_DATA_FILE_NAME,
LOCKFILE_PATH,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LOCKFILE_PATH variable is defined but not imported in the toolset.py file, which may cause a NameError during execution.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good to me! Incremental review on 01412ef in 16 seconds

More details
  • Looked at 49 lines of code in 2 files
  • Skipped 0 files when reviewing.
  • Skipped posting 1 drafted comments based on config settings.
1. python/composio/constants.py:102
  • Draft comment:
    Consider using an absolute path for LOCKFILE_PATH to avoid issues with changing working directories.
  • Reason this comment was not posted:
    Confidence changes required: 50%
    The PR introduces a constant for the lockfile path and uses it in multiple places. This is a good practice as it centralizes the path definition, making it easier to change in the future if needed. However, the constant is defined using a relative path, which can lead to issues if the working directory changes. It's better to use an absolute path or ensure the working directory is consistent.

Workflow ID: wflow_DhAFSay2AUriao6B


You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good to me! Incremental review on be4984c in 16 seconds

More details
  • Looked at 14 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 1 drafted comments based on config settings.
1. python/composio/tools/toolset.py:54
  • Draft comment:
    Consider keeping imports organized, typically with constants grouped together.
  • Reason this comment was not posted:
    Confidence changes required: 33%
    The import order was changed, but it doesn't affect functionality. However, it is a good practice to keep imports organized.

Workflow ID: wflow_608lSHgOym91Wz5M


You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

Comment on lines +1104 to +1106
self._tool_versions = {
tool.name: tool.version for tool in items
} | self._tool_versions

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change in merge order means new tool versions will take precedence over existing ones, potentially overwriting intended version locks. Consider if this new precedence is desired.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good to me! Incremental review on 33d6814 in 20 seconds

More details
  • Looked at 15 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 1 drafted comments based on config settings.
1. python/composio/tools/toolset.py:1105
  • Draft comment:
    The order of merging dictionaries has been changed. Previously, self._tool_versions was updated with new tool versions, but now the new tool versions will overwrite existing ones if there are conflicts. Ensure this is the intended behavior.
  • Reason this comment was not posted:
    Comment did not seem useful.

Workflow ID: wflow_079WKxlcHjcMiQPw


You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants