diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index a8003eb..07f89b4 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -57,3 +57,4 @@ jobs: python 02-data-types/lists.py python 04-functions/basics.py echo "All scripts executed successfully!" + diff --git a/.gitignore b/.gitignore index 504e5fd..7dc72a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,40 +1,2 @@ ``` -# Dependencies -__pycache__/ -*.pyc -*.pyo -*.pyd -.Python -env/ -venv/ -.venv/ -.venv-local/ -.ENV/ -.ENV.local/ -.env -.env.local -.env.* -.envrc -pip-log.txt -pip-delete-this-directory.txt -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.log -.git -.mypy_cache/ -.pytest_cache/ -.hypothesis/ -.ropeproject/ -.vscode/ -.idea/ -*.swp -*.swo -*~ -.DS_Store -Thumbs.db -``` + diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..102e8c1 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,157 @@ +# Deployment Guide + +This repository is configured for seamless deployment on multiple platforms. + +## šŸš€ Vercel Deployment (Recommended) + +### Quick Deploy + +1. **Install Vercel CLI** (optional, for local testing): + ```bash + npm install -g vercel + ``` + +2. **Deploy to Vercel**: + ```bash + # Login to Vercel + vercel login + + # Deploy + vercel + ``` + +3. **Or use the Vercel Dashboard**: + - Go to [vercel.com](https://vercel.com) + - Import your GitHub repository + - Vercel will automatically detect the `vercel.json` configuration + - Click "Deploy" + +### What Gets Deployed + +- **Frontend**: `index.html` - Interactive dashboard +- **API Endpoints**: + - `GET /api/health` - Health check + - `GET /api/status` - Repository status + - `GET /api/lessons` - List all lessons + - `GET /api/run?script=` - Run Python scripts + +### Local Testing + +```bash +# Install Vercel CLI +npm install -g vercel + +# Run locally +vercel dev +``` + +Visit `http://localhost:3000` to test. + +--- + +## šŸ™ GitHub Actions Workflow + +The repository includes a CI/CD workflow that: + +1. **Tests** on Python 3.9, 3.10, 3.11, 3.12 +2. **Runs linting** with flake8 +3. **Checks formatting** with black +4. **Executes tests** with pytest +5. **Verifies Vercel deployment** readiness + +### Workflow Triggers + +- Push to `main` or `master` branch +- Pull requests +- Manual trigger via GitHub Actions UI + +--- + +## šŸ”§ Alternative Deployment Options + +### Docker + +Create a `Dockerfile`: + +```dockerfile +FROM python:3.11-slim + +WORKDIR /app +COPY . . + +RUN pip install --no-cache-dir -r requirements.txt + +EXPOSE 8000 +CMD ["python", "-m", "http.server", "8000"] +``` + +### Netlify + +1. Connect your GitHub repository +2. Set build command: `echo "No build needed"` +3. Set publish directory: `/` +4. Add serverless functions in `api/` directory + +### Render + +1. Create new Web Service +2. Connect GitHub repository +3. Build Command: `pip install -r requirements.txt` +4. Start Command: `python -m http.server $PORT` + +--- + +## šŸ“ Project Structure + +``` +ā”œā”€ā”€ api/ # Vercel serverless functions +│ └── __init__.py # API endpoints +ā”œā”€ā”€ .github/workflows/ # GitHub Actions CI/CD +│ └── python-package.yml +ā”œā”€ā”€ 01-basics/ # Lesson modules +ā”œā”€ā”€ 02-data-types/ +ā”œā”€ā”€ ... +ā”œā”€ā”€ projects/ # Example projects +ā”œā”€ā”€ index.html # Frontend dashboard +ā”œā”€ā”€ vercel.json # Vercel configuration +ā”œā”€ā”€ requirements.txt # Python dependencies +└── README.md # Documentation +``` + +--- + +## āœ… Pre-Deployment Checklist + +- [ ] All Python scripts run without errors +- [ ] Tests pass (`pytest`) +- [ ] Code is formatted (`black .`) +- [ ] `vercel.json` is properly configured +- [ ] API endpoints are tested locally +- [ ] Environment variables are set (if needed) + +--- + +## šŸŽÆ Post-Deployment + +After deploying to Vercel: + +1. Visit your deployed URL +2. Test all API endpoints via the dashboard +3. Share your deployment link! + +### Environment Variables (Optional) + +Set these in Vercel dashboard if needed: + +- `SECRET_KEY` - For API security +- `DATABASE_URI` - For database connections +- `JWT_SECRET_KEY` - For JWT authentication + +--- + +## šŸ“ž Support + +For issues or questions: +- Check the [README.md](./README.md) +- Review [CONTRIBUTING.md](./CONTRIBUTING.md) +- Open an issue on GitHub diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..04f5b7a --- /dev/null +++ b/api/__init__.py @@ -0,0 +1,324 @@ +""" +Vercel Serverless API for Python Learning Repository +===================================================== + +This module provides serverless API endpoints compatible with Vercel. +It demonstrates how to deploy Python applications on Vercel. + +Endpoints: +- GET /api/health - Health check endpoint +- GET /api/status - System status +- GET /api/lessons - List of available lessons +- GET /api/run?script= - Run a Python script remotely +""" + +import json +import os +import sys +from datetime import datetime, timezone +from typing import Dict, Any, Optional + + +def get_utc_now() -> str: + """Get current UTC time in ISO format""" + return datetime.now(timezone.utc).isoformat() + + +def health_check(event: Dict[str, Any], context: Any = None) -> Dict[str, Any]: + """ + Health check endpoint for Vercel + + Returns: + JSON response with health status + """ + return { + 'statusCode': 200, + 'headers': { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + 'body': json.dumps({ + 'status': 'healthy', + 'timestamp': get_utc_now(), + 'environment': os.environ.get('VERCEL_ENV', 'development'), + 'python_version': f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" + }) + } + + +def get_status(event: Dict[str, Any], context: Any = None) -> Dict[str, Any]: + """ + Get system status and repository information + + Returns: + JSON response with system status + """ + # Count available lessons and projects + base_dir = os.path.dirname(os.path.dirname(__file__)) + + lesson_count = 0 + project_count = 0 + + # Count lesson directories + for item in os.listdir(base_dir): + if item.startswith(('01-', '02-', '03-', '04-', '05-', '06-', '07-', '08-', '09-', '10-')): + lesson_count += 1 + + # Count projects + projects_dir = os.path.join(base_dir, 'projects') + if os.path.exists(projects_dir): + for category in os.listdir(projects_dir): + category_path = os.path.join(projects_dir, category) + if os.path.isdir(category_path): + project_count += len([f for f in os.listdir(category_path) if f.endswith('.py')]) + + return { + 'statusCode': 200, + 'headers': { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + 'body': json.dumps({ + 'repository': 'Python Learning Repository', + 'version': '1.0.0', + 'lessons_available': lesson_count, + 'projects_available': project_count, + 'features': [ + 'Interactive Python tutorials', + 'Beginner to Advanced projects', + 'Code exercises with solutions', + 'Best practices and patterns' + ], + 'timestamp': get_utc_now() + }) + } + + +def get_lessons(event: Dict[str, Any], context: Any = None) -> Dict[str, Any]: + """ + Get list of available lessons + + Returns: + JSON response with lessons list + """ + base_dir = os.path.dirname(os.path.dirname(__file__)) + + lessons = [] + for item in sorted(os.listdir(base_dir)): + if item.startswith(('01-', '02-', '03-', '04-', '05-', '06-', '07-', '08-', '09-', '10-')): + item_path = os.path.join(base_dir, item) + if os.path.isdir(item_path): + # Count Python files in the lesson + py_files = [f for f in os.listdir(item_path) if f.endswith('.py')] + + # Extract lesson number and name + parts = item.split('-', 1) + lesson_num = parts[0] if len(parts) > 0 else '' + lesson_name = parts[1].replace('-', ' ').title() if len(parts) > 1 else item + + lessons.append({ + 'number': lesson_num, + 'name': lesson_name, + 'directory': item, + 'files_count': len(py_files), + 'files': py_files + }) + + return { + 'statusCode': 200, + 'headers': { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + 'body': json.dumps({ + 'total_lessons': len(lessons), + 'lessons': lessons + }) + } + + +def run_script(event: Dict[str, Any], context: Any = None) -> Dict[str, Any]: + """ + Run a Python script from the repository (demo purpose) + + Query Parameters: + script: Name of the script to run (e.g., 'hello_world') + + Returns: + JSON response with script output or error + """ + # Get query parameters + query_string = event.get('queryStringParameters', {}) or {} + script_name = query_string.get('script', 'hello_world') + + # Security: Only allow specific safe scripts + allowed_scripts = ['hello_world', 'variables', 'operators', 'calculator'] + + if script_name not in allowed_scripts: + return { + 'statusCode': 400, + 'headers': { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + 'body': json.dumps({ + 'error': f'Script "{script_name}" is not in the allowed list', + 'allowed_scripts': allowed_scripts + }) + } + + try: + # Find and execute the script + base_dir = os.path.dirname(os.path.dirname(__file__)) + + # Search for the script + script_path = None + for root, dirs, files in os.walk(base_dir): + if f'{script_name}.py' in files: + script_path = os.path.join(root, f'{script_name}.py') + break + + if not script_path: + return { + 'statusCode': 404, + 'headers': { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + 'body': json.dumps({ + 'error': f'Script "{script_name}.py" not found' + }) + } + + # Execute the script safely + import io + from contextlib import redirect_stdout + + f = io.StringIO() + with redirect_stdout(f): + # Read and execute the script content + with open(script_path, 'r') as file: + code = file.read() + exec(code, {'__name__': '__main__'}) + + output = f.getvalue() + + return { + 'statusCode': 200, + 'headers': { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + 'body': json.dumps({ + 'script': script_name, + 'output': output.strip(), + 'success': True + }) + } + + except Exception as e: + return { + 'statusCode': 500, + 'headers': { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + 'body': json.dumps({ + 'error': str(e), + 'script': script_name + }) + } + + +# Vercel serverless function handlers +def handler(event: Dict[str, Any], context: Any = None) -> Dict[str, Any]: + """ + Main handler for Vercel serverless functions + + Routes requests to appropriate handlers based on path + """ + # Get the request path + path = event.get('path', '/') + http_method = event.get('httpMethod', 'GET') + + # Route mapping + routes = { + ('GET', '/api/health'): health_check, + ('GET', '/api/status'): get_status, + ('GET', '/api/lessons'): get_lessons, + ('GET', '/api/run'): run_script, + } + + # Find matching route + handler_func = routes.get((http_method, path)) + + if handler_func: + return handler_func(event, context) + else: + return { + 'statusCode': 404, + 'headers': { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + 'body': json.dumps({ + 'error': 'Endpoint not found', + 'available_endpoints': [ + 'GET /api/health', + 'GET /api/status', + 'GET /api/lessons', + 'GET /api/run?script=' + ] + }) + } + + +# Individual function exports for Vercel +def health(event, context=None): + """Vercel entry point for /api/health""" + return health_check(event, context) + + +def status(event, context=None): + """Vercel entry point for /api/status""" + return get_status(event, context) + + +def lessons(event, context=None): + """Vercel entry point for /api/lessons""" + return get_lessons(event, context) + + +def run(event, context=None): + """Vercel entry point for /api/run""" + return run_script(event, context) + + +# For local testing +if __name__ == '__main__': + print("Testing Vercel API endpoints locally...") + print("=" * 60) + + # Test health endpoint + print("\nšŸ„ Testing /api/health") + result = health_check({}) + print(f"Status: {result['statusCode']}") + print(f"Response: {result['body']}") + + # Test status endpoint + print("\nšŸ“Š Testing /api/status") + result = get_status({}) + print(f"Status: {result['statusCode']}") + print(f"Response: {result['body']}") + + # Test lessons endpoint + print("\nšŸ“š Testing /api/lessons") + result = get_lessons({}) + print(f"Status: {result['statusCode']}") + response_data = json.loads(result['body']) + print(f"Total lessons: {response_data['total_lessons']}") + for lesson in response_data['lessons'][:3]: # Show first 3 + print(f" - {lesson['number']}: {lesson['name']} ({lesson['files_count']} files)") + + print("\n" + "=" * 60) + print("āœ… All tests completed!") diff --git a/index.html b/index.html new file mode 100644 index 0000000..cecd527 --- /dev/null +++ b/index.html @@ -0,0 +1,230 @@ + + + + + + Python Learning Repository + + + +
+

šŸ Python Learning Repository

+

Interactive Python tutorials and projects - Now deployed on Vercel!

+ +
ā— API Online
+ +
+
+
šŸ“š
+ 10 Lessons +

From basics to advanced

+
+
+
šŸ’»
+ Projects +

Hands-on coding

+
+
+
āœ…
+ Exercises +

Practice & solutions

+
+
+
šŸš€
+ Deployed +

On Vercel platform

+
+
+ +
+

šŸ”Œ Available API Endpoints

+ +
+ Health Check +
GET /api/health
+ +
+ +
+ System Status +
GET /api/status
+ +
+ +
+ List Lessons +
GET /api/lessons
+ +
+ +
+ Run Script +
GET /api/run?script=hello_world
+ +
+
+ +
+ Click any "Test Endpoint" button to see the API response... +
+
+ + + + diff --git a/requirements.txt b/requirements.txt index 92f5495..0cd2f01 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,6 +14,7 @@ pytest>=7.0.0 # Testing framework pytest-cov>=4.0.0 # Coverage reporting pytest-xdist>=3.0.0 # Parallel test execution + # Documentation Tools (optional) # sphinx>=6.0.0 # Documentation generator # sphinx-rtd-theme>=1.2.0 # ReadTheDocs theme diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..860121a --- /dev/null +++ b/vercel.json @@ -0,0 +1,34 @@ +{ + "version": 2, + "builds": [ + { + "src": "api/__init__.py", + "use": "@vercel/python" + } + ], + "routes": [ + { + "src": "/api/health", + "dest": "api/__init__.py:health" + }, + { + "src": "/api/status", + "dest": "api/__init__.py:status" + }, + { + "src": "/api/lessons", + "dest": "api/__init__.py:lessons" + }, + { + "src": "/api/run", + "dest": "api/__init__.py:run" + }, + { + "src": "/(.*)", + "dest": "/index.html" + } + ], + "env": { + "PYTHON_VERSION": "3.11" + } +}