-
Notifications
You must be signed in to change notification settings - Fork 827
Load predefined users from a JSON file through command line. #9229 #9652
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -163,6 +163,157 @@ def get_role(role: str): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class ManageUsers: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @app.command() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @update_sqlite_path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def load_users(input_file: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sqlite_path: Optional[str] = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json: Optional[bool] = False): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Load users from a JSON file. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Expected JSON format: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "users": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "username": "user@example.com", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "email": "user@example.com", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "password": "password123", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "User", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "active": true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "auth_source": "internal" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "username": "ldap_user", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "email": "ldap@example.com", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "Administrator", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "active": true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "auth_source": "ldap" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from urllib.parse import unquote | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print('----------') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print('Loading users from:', input_file) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print('SQLite pgAdmin config:', config.SQLITE_PATH) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print('----------') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Parse the input file path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| file_path = unquote(input_file) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(str(e)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _handle_error(str(e), True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Read and parse JSON file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with open(file_path) as f: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data = jsonlib.load(f) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except jsonlib.decoder.JSONDecodeError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _handle_error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gettext("Error parsing input file %s: %s" % (file_path, e)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _handle_error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gettext("Error reading input file %s: [%d] %s" % | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (file_path, e.errno, e.strerror)), True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+202
to
+219
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Double error output on When the Proposed fix try:
file_path = unquote(input_file)
except Exception as e:
- print(str(e))
return _handle_error(str(e), True)📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.1)[warning] 204-204: Do not catch blind exception: (BLE001) [warning] 216-216: Do not catch blind exception: (BLE001) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Validate JSON structure | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if 'users' not in data: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _handle_error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gettext("Invalid JSON format: 'users' key not found"), True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| users_data = data['users'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not isinstance(users_data, list): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _handle_error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gettext("Invalid JSON format: 'users' must be a list"), True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| created_count = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| skipped_count = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error_count = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app = create_app(config.APP_NAME + '-cli') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with (app.test_request_context()): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for user_entry in users_data: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Validate required fields | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if 'username' not in user_entry and\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'email' not in user_entry: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Skipping user: missing 'username' or 'email'") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error_count += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+240
to
+244
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. f-string on line 242 has no placeholders — include the entry context for debuggability. Static analysis (Ruff F541) flags this. More importantly, when a user entry lacks both Proposed fix- if 'username' not in user_entry and\
- 'email' not in user_entry:
- print(f"Skipping user: missing 'username' or 'email'")
+ if 'username' not in user_entry and\
+ 'email' not in user_entry:
+ print("Skipping user entry: missing 'username' "
+ "or 'email'")🧰 Tools🪛 Ruff (0.15.1)[error] 242-242: f-string without any placeholders Remove extraneous (F541) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Determine auth_source (default to internal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auth_source = user_entry.get('auth_source', INTERNAL) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Build user data dict | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_data = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'username': user_entry.get('username', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_entry.get('email')), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'email': user_entry.get('email'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'role': user_entry.get('role', 'User'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'active': user_entry.get('active', True), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'auth_source': auth_source | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # For internal auth, password is required | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if auth_source == INTERNAL: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if 'password' not in user_entry: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Skipping user '{user_data['username']}': " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"password required for internal auth") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error_count += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_data['newPassword'] = user_entry['password'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_data['confirmPassword'] = user_entry['password'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check if user already exists | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uid = ManageUsers.get_user( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| username=user_data['username'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auth_source=auth_source) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if uid: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Skipping user '{user_data['username']}': " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"already exists") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| skipped_count += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Get role ID | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rid = ManageRoles.get_role(user_data['role']) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if rid is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Skipping user '{user_data['username']}': " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"role '{user_data['role']}' does not exist") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error_count += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+270
to
+285
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -type f -name "*.py" | xargs grep -l "def get_user" | head -5Repository: pgadmin-org/pgadmin4 Length of output: 452 🏁 Script executed: find . -type f -name "*.py" | xargs grep -l "def get_role" | head -5Repository: pgadmin-org/pgadmin4 Length of output: 143 🏁 Script executed: cat -n web/setup.py | sed -n '200,315p'Repository: pgadmin-org/pgadmin4 Length of output: 5715 🏁 Script executed: cat -n web/setup.py | grep -A 20 "def get_user"Repository: pgadmin-org/pgadmin4 Length of output: 3337 🏁 Script executed: cat -n web/setup.py | grep -A 20 "def get_role"Repository: pgadmin-org/pgadmin4 Length of output: 763 🏁 Script executed: # Check the json parameter usage
cat -n web/setup.py | sed -n '166,200p'Repository: pgadmin-org/pgadmin4 Length of output: 1395 🏁 Script executed: cat -n web/setup.py | sed -n '166,320p' | grep -E "(json|_handle_error)"Repository: pgadmin-org/pgadmin4 Length of output: 444 🏁 Script executed: # Find _handle_error definition
cat -n web/setup.py | grep -A 10 "def _handle_error"Repository: pgadmin-org/pgadmin4 Length of output: 46 🏁 Script executed: # Search for _handle_error definition more broadly
rg "def _handle_error" web/setup.py -A 5Repository: pgadmin-org/pgadmin4 Length of output: 46 🏁 Script executed: # Check if json parameter is actually used anywhere in load_users
rg "json" web/setup.py | grep -i "load_users\|def load_users" -A 30Repository: pgadmin-org/pgadmin4 Length of output: 46 🏁 Script executed: # Search for _handle_error in the entire file
grep -n "_handle_error" web/setup.py | head -10Repository: pgadmin-org/pgadmin4 Length of output: 490 🏁 Script executed: # Look for the definition of _handle_error
grep -B 2 -A 8 "_handle_error" web/setup.py | head -40Repository: pgadmin-org/pgadmin4 Length of output: 1620 🏁 Script executed: # Check how json parameter is used in the entire ManageUsers class
sed -n '166,320p' web/setup.py | cat -nRepository: pgadmin-org/pgadmin4 Length of output: 7212 🏁 Script executed: # Find the INTERNAL, LDAP etc. constants
rg "INTERNAL\s*=" web/pgadmin/utils/constants.pyRepository: pgadmin-org/pgadmin4 Length of output: 85 🏁 Script executed: # Also check what _handle_error does
rg "def _handle_error" web/pgadmin -A 8Repository: pgadmin-org/pgadmin4 Length of output: 551 Creating a new Flask app per user inside the loop is very expensive.
Query the Additionally:
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_data['role'] = rid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Validate password length for internal users | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if auth_source == INTERNAL: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(user_data['newPassword']) < 6: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Skipping user '{user_data['username']}': " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"password must be at least 6 characters") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error_count += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Create the user | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status, msg = create_user(user_data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if status: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Created user: {user_data['username']}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| created_count += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Error creating user '{user_data['username']}'" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f": {msg}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error_count += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Error processing user entry: {str(e)}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error_count += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print('----------') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Users created: {created_count}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Users skipped (already exist): {skipped_count}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Errors: {error_count}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print('----------') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @app.command() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @update_sqlite_path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def add_user(email: str, password: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jsonparameter is declared but never used — output format is unaffected.The
jsonparameter (line 170) is accepted but never referenced in the method body, so passing--jsonhas no effect. Either implement JSON output (consistent with other commands likeget-users) or remove the parameter. Thesqlite_pathparameter is consumed by the@update_sqlite_pathdecorator, so that one is fine.🧰 Tools
🪛 Ruff (0.15.1)
[warning] 169-169: Unused method argument:
sqlite_path(ARG002)
[warning] 170-170: Unused method argument:
json(ARG002)
🤖 Prompt for AI Agents