-
Couldn't load subscription status.
- Fork 121
feat: Add Python URL shortener template #341
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: main
Are you sure you want to change the base?
feat: Add Python URL shortener template #341
Conversation
WalkthroughAdds a Python URL Shortener for Appwrite: a README documenting env vars and deploy/usage; requirements.txt pinning appwrite==3.1.0 and nanoid==2.0.0; src/utils.py providing Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Actionable comments posted: 4
🧹 Nitpick comments (4)
python/url-shortener/src/utils.py (1)
7-15: Consider adding validation for environment variables.The function directly accesses environment variables without validation. If any required variable is missing, a
KeyErrorwill be raised with a generic message. While deployment should validate these variables, adding explicit checks would provide clearer error messages during development and debugging.Apply this diff to add validation with helpful error messages:
def get_database(): + required_vars = ["APPWRITE_ENDPOINT", "APPWRITE_PROJECT", "APPWRITE_API_KEY"] + missing = [var for var in required_vars if var not in os.environ] + if missing: + raise ValueError(f"Missing required environment variables: {', '.join(missing)}") + # Initialize the Appwrite client client = Client() client.set_endpoint(os.environ["APPWRITE_ENDPOINT"]) client.set_project(os.environ["APPWRITE_PROJECT"]) client.set_key(os.environ["APPWRITE_API_KEY"]) # Initialize the database service return Databases(client)python/url-shortener/src/main.py (3)
26-36: Consider more specific exception handling.The broad
except Exceptioncatch on line 35 could mask unexpected errors and make debugging difficult. Consider catching specific exceptions likeAppwriteExceptionto handle known error cases while allowing unexpected errors to surface.Apply this diff to catch specific exceptions:
try: database.create_document( database_id, collection_id, short_id, {'original_url': original_url} ) short_url = f"{context.req.scheme}://{context.req.host}/v1/databases/{database_id}/collections/{collection_id}/documents/{short_id}" return context.res.json({'short_url': short_url}) - except Exception as e: + except Exception as e: # Consider catching specific exceptions like AppwriteException return context.res.json({'error': str(e)}, status_code=500)Based on static analysis hints.
42-57: Consider more specific exception handling.Similar to the create operation, the broad
except Exceptioncatch on line 56 could mask unexpected errors. Consider catching specific exceptions from the Appwrite SDK.Apply this diff:
try: # Find the document with the given short_id result = database.list_documents( database_id, collection_id, [Query.equal("$id", short_id)] ) if result['total'] > 0: original_url = result['documents'][0]['original_url'] return context.res.redirect(original_url, 301) else: return context.res.json({'error': 'URL not found'}, status_code=404) - except Exception as e: + except Exception as e: # Consider catching specific exceptions like AppwriteException return context.res.json({'error': str(e)}, status_code=500)Based on static analysis hints.
22-24: Add validation for the URL input.The URL from the payload is used directly without validation. Consider validating:
- The URL format (valid scheme, structure)
- The URL length (the collection attribute is documented as size 2048)
Apply this diff to add basic validation:
# Action: Create a short URL if 'url' in payload: original_url = payload['url'] + + # Validate URL + if not original_url or not isinstance(original_url, str): + return context.res.json({'error': 'Invalid URL format'}, status_code=400) + + if len(original_url) > 2048: + return context.res.json({'error': 'URL too long (max 2048 characters)'}, status_code=400) + + if not original_url.startswith(('http://', 'https://')): + return context.res.json({'error': 'URL must start with http:// or https://'}, status_code=400) + short_id = nanoid.generate(size=7)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
python/url-shortener/README.md(1 hunks)python/url-shortener/requirements.txt(1 hunks)python/url-shortener/src/main.py(1 hunks)python/url-shortener/src/utils.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
python/url-shortener/src/main.py (1)
python/url-shortener/src/utils.py (1)
get_database(7-15)
🪛 LanguageTool
python/url-shortener/README.md
[grammar] ~5-~5: There might be a mistake here.
Context: ...short URLs. ## 📝 Environment Variables - APPWRITE_ENDPOINT: Your Appwrite endpoint. - `APPWRITE_AP...
(QB_NEW_EN)
[grammar] ~13-~13: There might be a mistake here.
Context: ...ore URLs. ## 🚀 Building and Deployment 1. **Create an Appwrite Database and Collecti...
(QB_NEW_EN)
[grammar] ~20-~20: There might be a mistake here.
Context: ...g, required) 2. Deploy the Function: * Package the function: `tar -czvf code.ta...
(QB_NEW_EN)
[grammar] ~24-~24: There might be a mistake here.
Context: ... to your Appwrite project. ## 🛠️ Usage ### Create a Short URL Execute the function...
(QB_NEW_EN)
🪛 Ruff (0.14.0)
python/url-shortener/src/main.py
35-35: Do not catch blind exception: Exception
(BLE001)
56-56: Do not catch blind exception: Exception
(BLE001)
🔇 Additional comments (1)
python/url-shortener/requirements.txt (1)
1-2: Verify package versions and security advisories.Since the knowledge of the latest versions and security advisories may be outdated, ensure that the specified versions of
appwrite(3.1.0) andnanoid(2.0.0) are valid, up-to-date, and free from known vulnerabilities.Run the following script to verify the package versions:
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.
Actionable comments posted: 3
🧹 Nitpick comments (6)
python/url-shortener/README.md (2)
15-23: Add entrypoint/runtime step to deployment.Readers need to set the function entrypoint and runtime explicitly.
2. **Deploy the Function:** - * Package the function: `tar -czvf code.tar.gz .` - * Upload and deploy the packaged function to your Appwrite project. + * Package the function: `tar -czvf code.tar.gz .` + * In Appwrite Console, create a Function (Python runtime), upload `code.tar.gz`. + * Set Entrypoint to `src/main.py:main` and trigger to HTTP.
24-33: Document redirect usage to match the code.Show how to resolve a short ID via HTTP GET (and optional JSON fallback).
## 🛠️ Usage ### Create a Short URL @@ ```json { "url": "https://www.google.com" }
+### Redirect using the short ID
+
+- From a browser:
+
- GET https://<YOUR_FUNCTION_URL>/<short_id>
+- From code (fallback API style):
+
+```json
+{
- "short_id": "abcdefg"
+}
+```</blockquote></details> <details> <summary>python/url-shortener/src/utils.py (1)</summary><blockquote> `7-21`: **Cache the Databases client to avoid reconnect overhead.** Cold-start is fine, but per-invocation client creation adds latency. Memoize once per container. ```diff +from functools import lru_cache @@ -def get_database(): +@lru_cache(maxsize=1) +def get_database(): # Validate environment variablespython/url-shortener/src/main.py (3)
56-63: Trim and validate the URL more strictly.Strip whitespace before checks to avoid accidental acceptance of blank/space-only strings.
- original_url = payload['url'] + original_url = payload['url'].strip() if isinstance(payload.get('url'), str) else payload.get('url')
64-80: Retry on short ID collision instead of failing.
create_documentwith a custom ID can 409 on collision. Retry a few times before returning 500.- short_id = nanoid.generate(size=7) - - try: - database.create_document( - database_id, - collection_id, - short_id, - {'original_url': original_url} - ) + # Generate a unique short ID with limited retries + short_id = None + for _ in range(5): + candidate = nanoid.generate(size=7) + try: + database.create_document( + database_id, + collection_id, + candidate, + {'original_url': original_url} + ) + short_id = candidate + break + except AppwriteException as e: + if getattr(e, "code", None) == 409: + continue # collision, try again + raise + if not short_id: + return context.res.json({'error': 'Failed to generate unique short ID'}, status_code=500) @@ - short_url = f"{context.req.scheme}://{context.req.host}{execution_path}/{short_id}" + short_url = f"{context.req.scheme}://{context.req.host}{execution_path}/{short_id}" return context.res.json({'short_url': short_url})
4-6: Remove unused imports after refactor.If you switch to
get_document,DatabasesandQueryare unused here.-from appwrite.services.databases import Databases -from appwrite.query import Query +# (unused imports removed)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
python/url-shortener/README.md(1 hunks)python/url-shortener/src/main.py(1 hunks)python/url-shortener/src/utils.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
python/url-shortener/src/main.py (1)
python/url-shortener/src/utils.py (1)
get_database(7-21)
🪛 LanguageTool
python/url-shortener/README.md
[grammar] ~5-~5: There might be a mistake here.
Context: ...short URLs. ## 📝 Environment Variables - APPWRITE_ENDPOINT: Your Appwrite endpoint. - `APPWRITE_AP...
(QB_NEW_EN)
[grammar] ~13-~13: There might be a mistake here.
Context: ...ore URLs. ## 🚀 Building and Deployment 1. **Create an Appwrite Database and Collecti...
(QB_NEW_EN)
[grammar] ~20-~20: There might be a mistake here.
Context: ...g, required) 2. Deploy the Function: * Package the function: `tar -czvf code.ta...
(QB_NEW_EN)
[grammar] ~24-~24: There might be a mistake here.
Context: ... to your Appwrite project. ## 🛠️ Usage ### Create a Short URL Execute the function...
(QB_NEW_EN)
🪛 Ruff (0.14.0)
python/url-shortener/src/utils.py
12-12: Avoid specifying long messages outside the exception class
(TRY003)
🔇 Additional comments (1)
python/url-shortener/src/main.py (1)
15-23: Env var check for DATABASE_ID/COLLECTION_ID looks good.
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.
Actionable comments posted: 1
♻️ Duplicate comments (1)
python/url-shortener/README.md (1)
37-37: Fix the JSON example format.The URL value still contains markdown link syntax, which is invalid JSON. This issue was previously reported but remains unfixed.
Apply this diff to use a plain URL string:
- "url": "[https://www.google.com](https://www.google.com)" + "url": "https://www.google.com"
🧹 Nitpick comments (1)
python/url-shortener/src/main.py (1)
76-76: Consider collision probability with 7-character IDs.The default nanoid length is 21 characters for collision safety. Using 7 characters significantly reduces the ID space and increases collision probability under high load. While the retry logic (5 attempts) mitigates this for low-to-moderate traffic, consider documenting this trade-off or offering a configurable ID length for production deployments.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
python/url-shortener/README.md(1 hunks)python/url-shortener/src/main.py(1 hunks)python/url-shortener/src/utils.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
python/url-shortener/src/main.py (1)
python/url-shortener/src/utils.py (1)
get_database(9-23)
🪛 LanguageTool
python/url-shortener/README.md
[grammar] ~5-~5: There might be a mistake here.
Context: ...short URLs. ## 📝 Environment Variables - APPWRITE_ENDPOINT: Your Appwrite endpoint. - `APPWRITE_AP...
(QB_NEW_EN)
[grammar] ~13-~13: There might be a mistake here.
Context: ...ore URLs. ## 🚀 Building and Deployment 1. **Create an Appwrite Database and Collecti...
(QB_NEW_EN)
[grammar] ~29-~29: There might be a mistake here.
Context: ... * Activate the function. ## 🛠️ Usage ### Create a Short URL Execute the function...
(QB_NEW_EN)
🪛 Ruff (0.14.0)
python/url-shortener/src/utils.py
14-14: Avoid specifying long messages outside the exception class
(TRY003)
🔇 Additional comments (4)
python/url-shortener/src/utils.py (1)
1-23: LGTM!The implementation correctly validates environment variables, uses caching to maintain a singleton database client, and provides clear error messages for missing configuration.
python/url-shortener/src/main.py (3)
23-42: LGTM!The redirect routing correctly handles both path-based and query-based short IDs, with proper fallback logic and error handling for missing URLs.
94-96: LGTM!The short URL construction correctly uses the request context to build a URL that will trigger the redirect logic when accessed.
76-76: No action required—API usage is correct.The
generate(size=INT)API for Python nanoid 2.0.0 is valid, and the code correctly usesnanoid.generate(size=7)to generate a 7-character ID.
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.
Actionable comments posted: 0
🧹 Nitpick comments (3)
python/url-shortener/src/main.py (3)
22-30: Add validation for short_id from path and query parameters.The short_id extracted from the path (lines 24-25) and query (line 29) lacks the validation applied to JSON payloads (lines 47-48). This inconsistency could lead to unnecessary database queries with invalid identifiers.
Apply this diff to add consistent validation:
short_id = None if context.req.method in ('GET', 'HEAD'): path_parts = context.req.path.split('/') if path_parts and len(path_parts[-1]) == 7: - short_id = path_parts[-1] + candidate = path_parts[-1] + if isinstance(candidate, str) and candidate.isalnum(): + short_id = candidate query = getattr(context.req, 'query', {}) if not short_id and query and 'id' in query: - short_id = query['id'] + candidate = query['id'] + if isinstance(candidate, str) and len(candidate) == 7 and candidate.isalnum(): + short_id = candidate
60-63: Consider using urllib.parse for more robust URL validation.The current validation checks for http/https prefix and length, which covers basic cases. For production use, consider validating the URL structure more thoroughly.
Apply this diff to use urllib.parse:
+ from urllib.parse import urlparse + if 'url' in payload: original_url = payload['url'].strip() if isinstance(payload.get('url'), str) else None - if not original_url or not original_url.startswith(('http://', 'https://')): - return context.res.json({'error': 'Invalid URL format. Must start with http:// or https://'}, status_code=400) + if not original_url: + return context.res.json({'error': 'URL is required'}, status_code=400) + + try: + parsed = urlparse(original_url) + if not parsed.scheme in ('http', 'https') or not parsed.netloc: + return context.res.json({'error': 'Invalid URL format. Must be a valid http:// or https:// URL'}, status_code=400) + except ValueError: + return context.res.json({'error': 'Invalid URL format'}, status_code=400) + if len(original_url) > 2048: return context.res.json({'error': 'URL too long (max 2048 characters)'}, status_code=400)
84-84: Use explicit conversion flag in f-string.Per static analysis, the f-string can use an explicit conversion flag for clarity.
Apply this diff:
- return context.res.json({'error': f'Database error: {str(e)}'}, status_code=500) + return context.res.json({'error': f'Database error: {e!s}'}, status_code=500)Based on static analysis.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
python/url-shortener/src/main.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
python/url-shortener/src/main.py (1)
python/url-shortener/src/utils.py (1)
get_database(9-23)
🪛 Ruff (0.14.0)
python/url-shortener/src/main.py
84-84: Use explicit conversion flag
Replace with conversion flag
(RUF010)
🔇 Additional comments (5)
python/url-shortener/src/main.py (5)
1-10: LGTM! Clean imports and setup.The imports are well-organized, the previous relative import issue has been resolved, and the database initialization properly delegates to the utils module.
12-19: LGTM! Proper environment variable validation.The validation for required environment variables is well-implemented with clear error messaging.
40-55: LGTM! Robust JSON handling and redirect validation.The JSON parsing includes proper error handling, and the JSON-based redirect path validates the short_id format consistently.
65-84: LGTM! Excellent collision-resistant ID generation.The retry logic with proper 409 handling and outer exception handling is well-implemented and addresses previous review feedback.
86-93: LGTM! Proper failure handling and response construction.The fallback for retry exhaustion, short URL construction using the request context, and final catch-all error handling are all well-implemented.
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.
I've implemented all the requested changes as per the CodeRabbit review and ensured everything aligns with the project requirements.
Please review the updates and approve the pull request if everything looks good.
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.
@loks0n sir can you please review my pull request
What does this PR do?
This PR adds a new serverless function template for a URL shortener using the Python runtime. This function allows users to:
The implementation uses the
nanoidlibrary for generating short IDs and the officialappwrite-server-sdkfor database interactions, making it a robust and practical example for the templates collection.Test Plan
The function has been tested locally by following these steps:
1. Appwrite Setup:
original_url, a size of2048, and marked it as required.2. Function Deployment:
python/url-shortener/directory.code.tar.gzfile of the source code.code.tar.gzfile.APPWRITE_ENDPOINTAPPWRITE_API_KEYAPPWRITE_PROJECTDATABASE_ID(from step 1)COLLECTION_ID(from step 1)3. Execution and Verification:
Test Case 1: Creating a Short URL
{ "url": "[https://appwrite.io/](https://appwrite.io/)" }short_url, confirming the document was created in the database.Test Case 2: Redirecting to the Original URL
{ "short_id": "[COPIED_SHORT_ID_HERE]" }301 redirectto the original URL (https://appwrite.io/), verifying the lookup and redirect logic.Related PRs and Issues
None.
Have you read the Contributing Guidelines on issues?
Yes.
Summary by CodeRabbit
New Features
Documentation