From 12de833f6651bca2482d853cac0d0fa84be68247 Mon Sep 17 00:00:00 2001
From: PurvaG1700 <purvag1700@gmail.com>
Date: Mon, 17 Feb 2025 11:03:02 -0800
Subject: [PATCH] [ADD] - Added social media user profile get and upsert
 microservice with nosql database integration

---
 .gitignore                                    | 26 ++++++++-
 README.md                                     | 56 +++++++++++++++++++
 app/cache/__init__.py                         |  0
 cache.py => app/cache/cache.py                |  0
 .../cache/prefetch_cache.py                   |  2 +-
 tiered_cache.py => app/cache/tiered_cache.py  |  2 +-
 app/config.py                                 | 15 +++++
 app/config.yaml                               |  3 +
 app/database.py                               | 29 ++++++++++
 app/main.py                                   | 39 +++++++++++++
 app/run.py                                    | 12 ++++
 requirements.txt                              |  5 ++
 12 files changed, 186 insertions(+), 3 deletions(-)
 create mode 100644 app/cache/__init__.py
 rename cache.py => app/cache/cache.py (100%)
 rename prefetch_cache.py => app/cache/prefetch_cache.py (93%)
 rename tiered_cache.py => app/cache/tiered_cache.py (98%)
 create mode 100644 app/config.py
 create mode 100644 app/config.yaml
 create mode 100644 app/database.py
 create mode 100644 app/main.py
 create mode 100644 app/run.py
 create mode 100644 requirements.txt

diff --git a/.gitignore b/.gitignore
index ed8ebf5..a1cc728 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,25 @@
-__pycache__
\ No newline at end of file
+# Ignore the virtual environment
+venv/
+
+# Ignore Python cache files
+__pycache__/
+**/__pycache__/
+
+# Ignore database files (TinyDB JSON)
+database.json
+
+# Ignore environment variables file (if used)
+.env
+
+# Ignore logs and temporary files
+*.log
+*.tmp
+
+# Ignore VSCode & PyCharm project files
+.vscode/
+.idea/
+
+# Ignore MacOS system files
+.DS_Store
+
+
diff --git a/README.md b/README.md
index 29467bf..6a0ef16 100644
--- a/README.md
+++ b/README.md
@@ -8,3 +8,59 @@ Thus, we want to follow up on MuCache and see how much impact different caching
 ## Methodology
 To test caching strategies on microservices, we decided to build a micro service system mimicking interactions between microservices to easily implement and test different caching strategies. The idea is that the caching between two services will be the same for any other pair of services. Thus, we can scale down our targeted environment.
 We designed our own cache and microservice system and will use different metrics to test our caching strategies.
+
+
+## How to run
+
+### Set Up Virtual Environment
+macOS/Linux
+
+```python3 -m venv venv && source venv/bin/activate```
+
+Windows (CMD)
+
+```python -m venv venv && venv\Scripts\activate```
+
+Windows (PowerShell)
+
+```python -m venv venv && venv\Scripts\Activate.ps1```
+
+### Install Dependencies
+
+```pip install -r requirements.txt```
+
+### Edit config.yaml to set the caching strategy:
+
+``` 
+    cache_strategy: "Baseline"  # Change to "Prefetch" or "Tiered"
+    cache_limit: 10
+    l2_cache_limit: 100
+```
+
+### Run Microservice
+
+```python run.py```
+
+### Test API Endpoints
+
+Fetch a User Profile
+
+```curl -X GET "http://127.0.0.1:8000/user/1"```
+
+Update a User Profile
+
+```
+curl -X POST "http://127.0.0.1:8000/update_user/?user_id=2&name=Bob&followers=200&bio=TechEnthusiast&posts=AIIsAwesome"
+
+
+```
+
+### Stop the server and deactive virtual env
+
+macOS/Linux
+
+```deactivate```
+
+Windows
+
+```venv\Scripts\deactivate.bat```
\ No newline at end of file
diff --git a/app/cache/__init__.py b/app/cache/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/cache.py b/app/cache/cache.py
similarity index 100%
rename from cache.py
rename to app/cache/cache.py
diff --git a/prefetch_cache.py b/app/cache/prefetch_cache.py
similarity index 93%
rename from prefetch_cache.py
rename to app/cache/prefetch_cache.py
index f5bd3f8..63a0799 100644
--- a/prefetch_cache.py
+++ b/app/cache/prefetch_cache.py
@@ -1,4 +1,4 @@
-from cache import BaselineCache
+from .cache import BaselineCache
 
 class PrefetchCache(BaselineCache):
     key_relations = None
diff --git a/tiered_cache.py b/app/cache/tiered_cache.py
similarity index 98%
rename from tiered_cache.py
rename to app/cache/tiered_cache.py
index 3954b20..e0a0627 100644
--- a/tiered_cache.py
+++ b/app/cache/tiered_cache.py
@@ -1,4 +1,4 @@
-from cache import BaselineCache
+from .cache import BaselineCache
 from collections import OrderedDict
 import os
 
diff --git a/app/config.py b/app/config.py
new file mode 100644
index 0000000..fcdc507
--- /dev/null
+++ b/app/config.py
@@ -0,0 +1,15 @@
+import os
+import yaml
+
+CONFIG_FILE = "config.yaml"
+
+def load_config():
+    with open(CONFIG_FILE, "r") as f:
+        return yaml.safe_load(f)
+
+config = load_config()
+
+# Read from environment variable or fallback to YAML value
+CACHE_STRATEGY = os.getenv("CACHE_STRATEGY", config.get("cache_strategy", "Baseline"))
+CACHE_LIMIT = config.get("cache_limit", 10)
+L2_CACHE_LIMIT = config.get("l2_cache_limit", 100)
diff --git a/app/config.yaml b/app/config.yaml
new file mode 100644
index 0000000..ab73f0a
--- /dev/null
+++ b/app/config.yaml
@@ -0,0 +1,3 @@
+cache_strategy: "Baseline"  # Change this to "Prefetch" or "Tiered"
+cache_limit: 10
+l2_cache_limit: 100
diff --git a/app/database.py b/app/database.py
new file mode 100644
index 0000000..b2935da
--- /dev/null
+++ b/app/database.py
@@ -0,0 +1,29 @@
+
+from tinydb import TinyDB, Query
+
+# Initialize TinyDB as a NoSQL key-value store
+DB_FILE = "database.json"
+db = TinyDB(DB_FILE)
+User = Query()
+
+def get_user_profile(user_id):
+    """Fetch user profile from TinyDB"""
+    result = db.search(User.user_id == user_id)
+    return result[0] if result else None
+
+def update_user_profile(user_id, name, followers, bio, posts):
+    """Update user profile in TinyDB"""
+    db.upsert({"user_id": user_id, "name": name, "followers": followers, "bio": bio, "posts": posts}, User.user_id == user_id)
+
+def init_db():
+    """Ensure TinyDB is initialized before FastAPI starts and prepopulate some data"""
+    global db
+    db = TinyDB(DB_FILE)  # Reload TinyDB if needed
+
+    # Prepopulate database with some sample users if empty
+    if len(db) == 0:
+        db.insert_multiple([
+            {"user_id": "1", "name": "Alice", "followers": 100, "bio": "Love coding!", "posts": "Hello, world!"},
+            {"user_id": "2", "name": "Bob", "followers": 200, "bio": "Tech enthusiast", "posts": "AI is amazing!"},
+            {"user_id": "3", "name": "Charlie", "followers": 50, "bio": "Blogger", "posts": "Check out my latest post!"}
+        ])
diff --git a/app/main.py b/app/main.py
new file mode 100644
index 0000000..109bbba
--- /dev/null
+++ b/app/main.py
@@ -0,0 +1,39 @@
+from fastapi import FastAPI, HTTPException
+from database import get_user_profile, update_user_profile
+from cache.cache import BaselineCache
+from cache.prefetch_cache import PrefetchCache
+from cache.tiered_cache import TieredCache
+from config import CACHE_STRATEGY, CACHE_LIMIT, L2_CACHE_LIMIT
+
+app = FastAPI()
+
+# Initialize cache based on strategy from config.yaml or environment variable
+if CACHE_STRATEGY == "Baseline":
+    cache = BaselineCache(limit=CACHE_LIMIT)
+elif CACHE_STRATEGY == "Prefetch":
+    cache = PrefetchCache()
+elif CACHE_STRATEGY == "Tiered":
+    cache = TieredCache(limit=CACHE_LIMIT, l2_limit=L2_CACHE_LIMIT)
+else:
+    raise ValueError(f"Invalid CACHE_STRATEGY: {CACHE_STRATEGY}")
+
+@app.get("/user/{user_id}")
+def fetch_user_profile(user_id: str):
+    """Fetch user profile with caching"""
+    cached_profile = cache.get(user_id)
+    if cached_profile:
+        return {"user_id": user_id, "profile": cached_profile, "source": "cache"}
+
+    profile = get_user_profile(user_id)
+    if profile is None:
+        raise HTTPException(status_code=404, detail="User not found")
+
+    cache.put(user_id, profile)  # Store in cache
+    return {"user_id": user_id, "profile": profile, "source": "database"}
+
+@app.post("/update_user/")
+def modify_user_profile(user_id: str, name: str, followers: int, bio: str, posts: str):
+    """Update user profile and refresh cache"""
+    update_user_profile(user_id, name, followers, bio, posts)
+    cache.invalidate(user_id)  # Invalidate old cache
+    return {"message": "User profile updated successfully"}
diff --git a/app/run.py b/app/run.py
new file mode 100644
index 0000000..1e087a7
--- /dev/null
+++ b/app/run.py
@@ -0,0 +1,12 @@
+import os
+import uvicorn
+from database import init_db  # Ensure database initializes before starting FastAPI
+
+os.environ["PYTHONDONTWRITEBYTECODE"] = "1"
+
+if __name__ == "__main__":
+    # Initialize TinyDB (NoSQL) before FastAPI starts
+    init_db()
+    
+    # Start the FastAPI server with custom options
+    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True, workers=2)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..0d94123
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,5 @@
+fastapi
+uvicorn
+tinydb
+pyyaml
+