Skip to content

Conversation

@trivedikavya
Copy link

@trivedikavya trivedikavya commented Oct 16, 2025

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:

  1. Create a unique, short ID for any given long URL.
  2. Handle HTTP redirections from the short URL to the original long URL.

The implementation uses the nanoid library for generating short IDs and the official appwrite-server-sdk for 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:

  • Created a new database in an Appwrite project.
  • Created a new collection within the database.
  • Added a single String Attribute to the collection with the key original_url, a size of 2048, and marked it as required.
  • Saved the Database ID and Collection ID for the environment variables.

2. Function Deployment:

  • Navigated to the python/url-shortener/ directory.
  • Created a compressed code.tar.gz file of the source code.
  • Created a new function in the Appwrite console and uploaded the code.tar.gz file.
  • Added the following environment variables in the function's settings:
    • APPWRITE_ENDPOINT
    • APPWRITE_API_KEY
    • APPWRITE_PROJECT
    • DATABASE_ID (from step 1)
    • COLLECTION_ID (from step 1)
  • Deployed the function.

3. Execution and Verification:

  • Test Case 1: Creating a Short URL

    • Executed the function with the following JSON payload:
      {
        "url": "[https://appwrite.io/](https://appwrite.io/)"
      }
    • Result: The function executed successfully and returned a JSON response containing the short_url, confirming the document was created in the database.
  • Test Case 2: Redirecting to the Original URL

    • Copied the document ID (the short ID) from the result of the previous test.
    • Executed the function again with the following JSON payload:
      {
        "short_id": "[COPIED_SHORT_ID_HERE]"
      }
    • Result: The function correctly issued a 301 redirect to 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

    • Introduced a URL Shortener service to create short links and resolve them to original destinations (create short URLs, fetch and redirect).
  • Documentation

    • Added setup and deployment guide with required environment variables and build/deploy steps.
    • Included usage examples showing JSON requests to create and retrieve shortened URLs.

@coderabbitai
Copy link

coderabbitai bot commented Oct 16, 2025

Walkthrough

Adds 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 get_database() which validates APPWRITE_ENDPOINT, APPWRITE_PROJECT, and APPWRITE_API_KEY and returns a cached Appwrite Databases client; src/main.py adding main(context) that handles GET/HEAD redirects (path or query id), JSON lookups by short_id, and URL creation via url in JSON (validates URL, generates a 7-character nanoid with up to 5 collision retries, stores documents in Appwrite, and returns the constructed short URL). Error handling covers missing env vars, invalid JSON, validation errors, Appwrite 404/409, and other Appwrite errors.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: Add Python URL shortener template" directly and accurately captures the primary change of this pull request. The changeset adds a complete Python URL shortener template consisting of documentation, dependencies, and implementation code (main.py with URL shortening and redirect logic, plus utils.py for database utilities). The title is concise, clear, and specific—it uses the conventional commit format (feat:) appropriately and conveys exactly what a teammate scanning the repository history would expect: the addition of a new URL shortener template in Python.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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 KeyError will 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 Exception catch on line 35 could mask unexpected errors and make debugging difficult. Consider catching specific exceptions like AppwriteException to 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 Exception catch 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:

  1. The URL format (valid scheme, structure)
  2. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 79a7bcc and e29a0be.

📒 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) and nanoid (2.0.0) are valid, up-to-date, and free from known vulnerabilities.

Run the following script to verify the package versions:

Copy link

@coderabbitai coderabbitai bot left a 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 variables
python/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_document with 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, Databases and Query are 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

📥 Commits

Reviewing files that changed from the base of the PR and between e29a0be and 8ebb5c1.

📒 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.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8ebb5c1 and 547490f.

📒 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 uses nanoid.generate(size=7) to generate a 7-character ID.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 547490f and 3727d00.

📒 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.

Copy link
Author

@trivedikavya trivedikavya left a 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.

Copy link
Author

@trivedikavya trivedikavya left a 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

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.

1 participant