From 9077a5cb85bae311762c501d2995105ac0fd65d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 01:53:19 +0000 Subject: [PATCH 1/3] fix: address Ruff and Vercel dependency issues Agent-Logs-Url: https://github.com/quantumdynamics927-dotcom/QPyth/sessions/ed476ffc-1ea5-43ae-9624-0d3544d77b41 Co-authored-by: quantumdynamics927-dotcom <247722560+quantumdynamics927-dotcom@users.noreply.github.com> --- migrate.py | 2 +- pyproject.toml | 8 +++++ quantumpytho/modules/database.py | 50 ++++++++++++++++---------------- test_neon_integration.py | 33 ++++++++++----------- vercel.json | 2 +- 5 files changed, 50 insertions(+), 45 deletions(-) diff --git a/migrate.py b/migrate.py index 2c7cd5f..9dcf639 100644 --- a/migrate.py +++ b/migrate.py @@ -130,7 +130,7 @@ def main(): config = DatabaseConfig(database_url=database_url) db = NeonDatabase(config) - print(f"✅ Connected to database") + print("✅ Connected to database") # Run appropriate command if args.rollback: diff --git a/pyproject.toml b/pyproject.toml index 2ac2489..e295641 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,11 @@ web = [ "uvicorn[standard]>=0.32.0,<1.0.0", "slowapi>=0.1.9", ] +docs = [ + "mkdocs>=1.5.0,<2.0.0", + "mkdocs-material>=9.0.0,<10.0.0", + "pymdown-extensions>=10.0.0", +] database = [ "psycopg2-binary>=2.9.9", ] @@ -72,6 +77,9 @@ all = [ "fastapi>=0.115.0,<1.0.0", "uvicorn[standard]>=0.32.0,<1.0.0", "slowapi>=0.1.9", + "mkdocs>=1.5.0,<2.0.0", + "mkdocs-material>=9.0.0,<10.0.0", + "pymdown-extensions>=10.0.0", "psycopg2-binary>=2.9.9", ] diff --git a/quantumpytho/modules/database.py b/quantumpytho/modules/database.py index 2c962f3..370cb75 100644 --- a/quantumpytho/modules/database.py +++ b/quantumpytho/modules/database.py @@ -14,10 +14,10 @@ """ import os +from collections.abc import Generator from contextlib import contextmanager from dataclasses import dataclass, field -from datetime import datetime -from typing import Any, Generator, Optional +from typing import Any # Optional import - database support is optional try: @@ -71,7 +71,7 @@ class NeonDatabase: results = db.fetch_all("SELECT * FROM jobs WHERE user_id = %s", (user_id,)) """ - def __init__(self, config: Optional[DatabaseConfig] = None): + def __init__(self, config: DatabaseConfig | None = None): """ Initialize database connection. @@ -85,7 +85,7 @@ def __init__(self, config: Optional[DatabaseConfig] = None): ) self.config = config or DatabaseConfig() - self._connection_pool: Optional[pool.SimpleConnectionPool] = None + self._connection_pool: pool.SimpleConnectionPool | None = None if self.config.is_configured: self._initialize_pool() @@ -159,7 +159,7 @@ def get_cursor(self, commit: bool = False) -> Generator[Any, None, None]: finally: cur.close() - def execute(self, query: str, params: Optional[tuple] = None) -> None: + def execute(self, query: str, params: tuple | None = None) -> None: """ Execute a database query (no return value). @@ -176,7 +176,7 @@ def execute(self, query: str, params: Optional[tuple] = None) -> None: with self.get_cursor(commit=True) as cur: cur.execute(query, params or ()) - def fetch_one(self, query: str, params: Optional[tuple] = None) -> Optional[dict]: + def fetch_one(self, query: str, params: tuple | None = None) -> dict | None: """ Fetch a single row from the database. @@ -192,7 +192,7 @@ def fetch_one(self, query: str, params: Optional[tuple] = None) -> Optional[dict result = cur.fetchone() return dict(result) if result else None - def fetch_all(self, query: str, params: Optional[tuple] = None) -> list[dict]: + def fetch_all(self, query: str, params: tuple | None = None) -> list[dict]: """ Fetch all rows from the database. @@ -230,7 +230,7 @@ def is_healthy(self) -> bool: # Global database instance (lazy initialization) -_db_instance: Optional[NeonDatabase] = None +_db_instance: NeonDatabase | None = None def get_database() -> NeonDatabase: @@ -383,12 +383,12 @@ def init_schema(db: NeonDatabase) -> None: # Convenience functions for common operations def log_quantum_job( job_type: str, - circuit_json: Optional[dict] = None, + circuit_json: dict | None = None, shots: int = 1024, backend: str = "simulator", - user_id: Optional[str] = None, - session_id: Optional[str] = None, -) -> Optional[int]: + user_id: str | None = None, + session_id: str | None = None, +) -> int | None: """ Log a quantum job execution. @@ -435,16 +435,16 @@ def log_quantum_job( def save_vqe_result( molecule: str, final_energy: float, - geometry: Optional[str] = None, + geometry: str | None = None, basis: str = "STO-3G", optimizer: str = "COBYLA", - initial_energy: Optional[float] = None, - convergence_iterations: Optional[int] = None, - parameters: Optional[dict] = None, + initial_energy: float | None = None, + convergence_iterations: int | None = None, + parameters: dict | None = None, backend_type: str = "simulator", - noise_profile: Optional[str] = None, - user_id: Optional[str] = None, -) -> Optional[int]: + noise_profile: str | None = None, + user_id: str | None = None, +) -> int | None: """ Save VQE computation result. @@ -505,12 +505,12 @@ def log_api_request( method: str, status_code: int, response_time_ms: int, - user_id: Optional[str] = None, - session_id: Optional[str] = None, - ip_address: Optional[str] = None, - request_data: Optional[dict] = None, - response_data: Optional[dict] = None, - error_message: Optional[str] = None, + user_id: str | None = None, + session_id: str | None = None, + ip_address: str | None = None, + request_data: dict | None = None, + response_data: dict | None = None, + error_message: str | None = None, ) -> None: """ Log an API request. diff --git a/test_neon_integration.py b/test_neon_integration.py index 20cedb6..69995d2 100644 --- a/test_neon_integration.py +++ b/test_neon_integration.py @@ -14,7 +14,6 @@ import os import sys -from datetime import datetime # Add project root to path sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -37,13 +36,13 @@ def test_neon_integration(): print(" ℹ️ Set environment variable: export DATABASE_URL='postgresql://...'") return False else: - print(f" ✅ DATABASE_URL is set") + print(" ✅ DATABASE_URL is set") print(f" Host: {database_url.split('@')[1].split('/')[0] if '@' in database_url else 'N/A'}") if database_url_unpooled: - print(f" ✅ DATABASE_URL_UNPOOLED is set") + print(" ✅ DATABASE_URL_UNPOOLED is set") else: - print(f" ⚠️ DATABASE_URL_UNPOOLED not set (optional)") + print(" ⚠️ DATABASE_URL_UNPOOLED not set (optional)") print() @@ -53,11 +52,9 @@ def test_neon_integration(): from quantumpytho.modules.database import ( DatabaseConfig, NeonDatabase, - get_database, init_schema, log_quantum_job, save_vqe_result, - get_database, ) print(" ✅ Database module imported successfully") except ImportError as e: @@ -71,7 +68,7 @@ def test_neon_integration(): print("3. Testing database connection...") try: config = DatabaseConfig() - print(f" ✅ Database configuration loaded") + print(" ✅ Database configuration loaded") print(f" Configured: {config.is_configured}") if not config.is_configured: @@ -79,13 +76,13 @@ def test_neon_integration(): return True db = NeonDatabase(config) - print(f" ✅ Database instance created") + print(" ✅ Database instance created") if db._connection_pool: - print(f" ✅ Connection pool initialized") + print(" ✅ Connection pool initialized") print(f" Pool size: {config.pool_min}-{config.pool_max}") else: - print(f" ❌ Connection pool failed to initialize") + print(" ❌ Connection pool failed to initialize") return False except Exception as e: @@ -99,9 +96,9 @@ def test_neon_integration(): try: is_healthy = db.is_healthy() if is_healthy: - print(f" ✅ Database connection is healthy") + print(" ✅ Database connection is healthy") else: - print(f" ❌ Database connection is unhealthy") + print(" ❌ Database connection is unhealthy") return False except Exception as e: print(f" ❌ Health check failed: {e}") @@ -113,7 +110,7 @@ def test_neon_integration(): print("5. Testing schema initialization...") try: init_schema(db) - print(f" ✅ Schema initialized successfully") + print(" ✅ Schema initialized successfully") except Exception as e: print(f" ❌ Schema initialization failed: {e}") return False @@ -133,7 +130,7 @@ def test_neon_integration(): if job_id: print(f" ✅ Quantum job logged (ID: {job_id})") else: - print(f" ❌ Failed to log quantum job") + print(" ❌ Failed to log quantum job") return False # Test VQE result saving @@ -148,7 +145,7 @@ def test_neon_integration(): if result_id: print(f" ✅ VQE result saved (ID: {result_id})") else: - print(f" ❌ Failed to save VQE result") + print(" ❌ Failed to save VQE result") return False except Exception as e: @@ -174,7 +171,7 @@ def test_neon_integration(): # Display sample data if jobs: - print(f" Sample job data:") + print(" Sample job data:") print(f" - Job ID: {jobs[0]['id']}") print(f" - Type: {jobs[0]['job_type']}") print(f" - Backend: {jobs[0]['backend']}") @@ -191,7 +188,7 @@ def test_neon_integration(): try: db.execute("DELETE FROM quantum_jobs WHERE job_type = 'test'") db.execute("DELETE FROM vqe_results WHERE molecule = 'H2_test'") - print(f" ✅ Test data cleaned up") + print(" ✅ Test data cleaned up") except Exception as e: print(f" ⚠️ Cleanup failed (non-critical): {e}") @@ -201,7 +198,7 @@ def test_neon_integration(): print("9. Checking connection pool status...") try: if db._connection_pool: - print(f" ✅ Connection pool active") + print(" ✅ Connection pool active") print(f" Min connections: {config.pool_min}") print(f" Max connections: {config.pool_max}") except Exception as e: diff --git a/vercel.json b/vercel.json index f33d238..63ac0f7 100644 --- a/vercel.json +++ b/vercel.json @@ -1,6 +1,6 @@ { "framework": null, - "installCommand": "python3 -m venv .vercel-venv && ./.vercel-venv/bin/python -m pip install --upgrade pip setuptools wheel && ./.vercel-venv/bin/python -m pip install -e .[web,database]", + "installCommand": "python3 -m venv .vercel-venv && ./.vercel-venv/bin/python -m pip install --upgrade pip setuptools wheel && ./.vercel-venv/bin/python -m pip install -e .[web,database,docs]", "buildCommand": "./.vercel-venv/bin/python -m mkdocs build --clean", "outputDirectory": "site" } From be10db7f528dcf16d9d05077385d6fd94ebcc34a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 01:54:21 +0000 Subject: [PATCH 2/3] style: apply Ruff formatting for CI Agent-Logs-Url: https://github.com/quantumdynamics927-dotcom/QPyth/sessions/ed476ffc-1ea5-43ae-9624-0d3544d77b41 Co-authored-by: quantumdynamics927-dotcom <247722560+quantumdynamics927-dotcom@users.noreply.github.com> --- coverage.xml | 3780 +++++++++++++++++++++++------- migrate.py | 56 +- quantumpytho/modules/database.py | 130 +- server.py | 12 +- test_neon_integration.py | 68 +- 5 files changed, 3044 insertions(+), 1002 deletions(-) diff --git a/coverage.xml b/coverage.xml index 7c95a97..593f175 100644 --- a/coverage.xml +++ b/coverage.xml @@ -1,9 +1,9 @@ - + - D:\QPyth\quantumpytho + /home/runner/work/QPyth/QPyth/quantumpytho @@ -11,8 +11,8 @@ - + @@ -298,7 +298,7 @@ - + @@ -306,99 +306,18 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - @@ -406,167 +325,287 @@ - + + + + + - + + - - - - - - - - + + + + + - + + - - - - + - - - - - - - - - - - + + + - - - - - + - - - - + + - - - - - + - - - - + - - - - - - - - - - - - + + + + + + - + - - - - - - - - - - + + + + + + - - - - - - + + + - - - - - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - + + + - - + + + + + + + @@ -574,914 +613,2869 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + - - - - - + + - - + + + + + - - - - - - - - - + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - + + + + + + - - - + + - - - - - - - - - - - + + - + + + + - + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - + + + + - + + + + + + + + - - - - + + - - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - - + - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + - - - - - - + + - - - - - - - - - - - + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + - - - - + + - - - - + + - - - - - - - - - - - + + + + + + + - - + - - - - + + - + + + + + + + - - - + + + + - + - - + - - + + - - + + + + + - + + + + + - - - - - - - - - + + + + + - - + + + + - - - - - - - - - - - + + + + + + + + + + + + + + - + - - + + - - + - + + - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - + - - + + + + + + - + + - - - - + + + + + + - - - - - - + + + + - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - + + + - - + - - - - + + + + + + + + + + - - - - - - - - - + + + + + + + + - - + + + - - + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + - - - + - + + - + + + + + + + + + + + + + - - - - + + + - + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + - + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + - - - - - + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - + - + + + + + - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - + - - + + + + + + + + + + + + + - - - - + + - - - + + - + + - - - - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + - + + + + + + + + + + + + + + + + + + + + @@ -1492,111 +3486,145 @@ + + + - - - - - - - - - - - + - + + + + + + - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - + + + + + - + + + - - + + + + + + + + + + + + + + + + + + + + + + - + - + + + - - + + + + + + + + + + + + + + + + + + + - + + + + + + diff --git a/migrate.py b/migrate.py index 9dcf639..610129a 100644 --- a/migrate.py +++ b/migrate.py @@ -35,41 +35,36 @@ def get_database_url() -> str: def run_migration(db) -> None: """ Run database migrations. - + This creates all required tables if they don't exist. """ print(f"[{datetime.now().isoformat()}] Starting database migration...") - + # Import database module from quantumpytho.modules.database import init_schema - + # Run schema initialization init_schema(db) - + print(f"[{datetime.now().isoformat()}] Migration completed successfully!") def check_migration_status(db) -> None: """Check current migration status.""" print(f"[{datetime.now().isoformat()}] Checking migration status...") - + # Check if tables exist - tables = [ - 'quantum_jobs', - 'vqe_results', - 'user_sessions', - 'api_logs' - ] - + tables = ["quantum_jobs", "vqe_results", "user_sessions", "api_logs"] + for table in tables: result = db.fetch_one( "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = %s)", - (table,) + (table,), ) - exists = result.get('exists', False) if result else False + exists = result.get("exists", False) if result else False status = "✅" if exists else "❌" print(f" {status} {table}") - + print(f"[{datetime.now().isoformat()}] Status check complete!") @@ -77,22 +72,22 @@ def rollback_migration(db) -> None: """Rollback last migration (use with caution!).""" print(f"[{datetime.now().isoformat()}] WARNING: Rolling back migrations...") print(" This will DROP all tables!") - + confirm = input(" Are you sure? Type 'YES' to confirm: ") if confirm != "YES": print(" Rollback cancelled.") return - + # Drop all tables - tables = ['api_logs', 'user_sessions', 'vqe_results', 'quantum_jobs'] - + tables = ["api_logs", "user_sessions", "vqe_results", "quantum_jobs"] + for table in tables: try: db.execute(f"DROP TABLE IF EXISTS {table} CASCADE") print(f" ✅ Dropped table: {table}") except Exception as e: print(f" ❌ Failed to drop {table}: {e}") - + print(f"[{datetime.now().isoformat()}] Rollback complete!") @@ -100,17 +95,15 @@ def main(): """Main entry point.""" parser = argparse.ArgumentParser(description="QPyth Database Migrations") parser.add_argument( - "--check", - action="store_true", - help="Check current migration status" + "--check", action="store_true", help="Check current migration status" ) parser.add_argument( "--rollback", action="store_true", - help="Rollback last migration (DROPS all tables)" + help="Rollback last migration (DROPS all tables)", ) args = parser.parse_args() - + # Get database URL try: database_url = get_database_url() @@ -122,16 +115,16 @@ def main(): print("\nOr create a .env file with:") print(" DATABASE_URL=postgresql://...") sys.exit(1) - + # Import and connect try: from quantumpytho.modules.database import DatabaseConfig, NeonDatabase - + config = DatabaseConfig(database_url=database_url) db = NeonDatabase(config) - + print("✅ Connected to database") - + # Run appropriate command if args.rollback: rollback_migration(db) @@ -139,13 +132,14 @@ def main(): check_migration_status(db) else: run_migration(db) - + # Close connection db.close() - + except Exception as e: print(f"❌ Migration failed: {e}") import traceback + traceback.print_exc() sys.exit(1) diff --git a/quantumpytho/modules/database.py b/quantumpytho/modules/database.py index 370cb75..7cb5894 100644 --- a/quantumpytho/modules/database.py +++ b/quantumpytho/modules/database.py @@ -24,6 +24,7 @@ import psycopg2 from psycopg2 import pool from psycopg2.extras import RealDictCursor + PSYCOPG2_AVAILABLE = True except ImportError: PSYCOPG2_AVAILABLE = False @@ -35,12 +36,14 @@ @dataclass class DatabaseConfig: """Database configuration from environment variables.""" - + database_url: str = field(default_factory=lambda: os.getenv("DATABASE_URL", "")) - database_url_unpooled: str = field(default_factory=lambda: os.getenv("DATABASE_URL_UNPOOLED", "")) + database_url_unpooled: str = field( + default_factory=lambda: os.getenv("DATABASE_URL_UNPOOLED", "") + ) pool_min: int = 1 pool_max: int = 10 - + @property def is_configured(self) -> bool: """Check if database is configured.""" @@ -50,31 +53,31 @@ def is_configured(self) -> bool: class NeonDatabase: """ Neon PostgreSQL database connection manager. - + Features: - Connection pooling for serverless efficiency - Automatic reconnection on connection loss - SSL mode enforcement (required by Neon) - Context managers for safe connection handling - + Usage: db = NeonDatabase() - + # Using context manager (recommended) with db.get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT version()") print(cur.fetchone()) - + # Or use query helper methods db.execute("CREATE TABLE IF NOT EXISTS jobs (...)") results = db.fetch_all("SELECT * FROM jobs WHERE user_id = %s", (user_id,)) """ - + def __init__(self, config: DatabaseConfig | None = None): """ Initialize database connection. - + Args: config: Database configuration. If None, loads from environment. """ @@ -83,47 +86,51 @@ def __init__(self, config: DatabaseConfig | None = None): "psycopg2 is required for database support. " "Install with: pip install psycopg2-binary" ) - + self.config = config or DatabaseConfig() self._connection_pool: pool.SimpleConnectionPool | None = None - + if self.config.is_configured: self._initialize_pool() - + def _initialize_pool(self) -> None: """Initialize connection pool.""" if not PSYCOPG2_AVAILABLE: print("[NeonDB] psycopg2 not available, skipping pool initialization") return - + try: self._connection_pool = pool.SimpleConnectionPool( minconn=self.config.pool_min, maxconn=self.config.pool_max, dsn=self.config.database_url, - cursor_factory=RealDictCursor + cursor_factory=RealDictCursor, + ) + print( + f"[NeonDB] Connection pool initialized ({self.config.pool_min}-{self.config.pool_max} connections)" ) - print(f"[NeonDB] Connection pool initialized ({self.config.pool_min}-{self.config.pool_max} connections)") except Exception as e: print(f"[NeonDB] Failed to initialize connection pool: {e}") self._connection_pool = None - + @contextmanager def get_connection(self) -> Generator[Any, None, None]: """ Get a database connection from the pool. - + Yields: psycopg2 connection object - + Usage: with db.get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT 1") """ if not self._connection_pool: - raise RuntimeError("Database not initialized. Check DATABASE_URL environment variable.") - + raise RuntimeError( + "Database not initialized. Check DATABASE_URL environment variable." + ) + conn = None try: conn = self._connection_pool.getconn() @@ -131,18 +138,18 @@ def get_connection(self) -> Generator[Any, None, None]: finally: if conn: self._connection_pool.putconn(conn) - + @contextmanager def get_cursor(self, commit: bool = False) -> Generator[Any, None, None]: """ Get a database cursor with optional auto-commit. - + Args: commit: If True, commit transaction on success - + Yields: psycopg2 cursor object - + Usage: with db.get_cursor(commit=True) as cur: cur.execute("INSERT INTO jobs (...) VALUES (...)") @@ -158,15 +165,15 @@ def get_cursor(self, commit: bool = False) -> Generator[Any, None, None]: raise finally: cur.close() - + def execute(self, query: str, params: tuple | None = None) -> None: """ Execute a database query (no return value). - + Args: query: SQL query string params: Query parameters - + Usage: db.execute( "INSERT INTO jobs (user_id, circuit) VALUES (%s, %s)", @@ -175,15 +182,15 @@ def execute(self, query: str, params: tuple | None = None) -> None: """ with self.get_cursor(commit=True) as cur: cur.execute(query, params or ()) - + def fetch_one(self, query: str, params: tuple | None = None) -> dict | None: """ Fetch a single row from the database. - + Args: query: SQL query string params: Query parameters - + Returns: Dictionary with column names as keys, or None if no results """ @@ -191,15 +198,15 @@ def fetch_one(self, query: str, params: tuple | None = None) -> dict | None: cur.execute(query, params or ()) result = cur.fetchone() return dict(result) if result else None - + def fetch_all(self, query: str, params: tuple | None = None) -> list[dict]: """ Fetch all rows from the database. - + Args: query: SQL query string params: Query parameters - + Returns: List of dictionaries with column names as keys """ @@ -207,17 +214,17 @@ def fetch_all(self, query: str, params: tuple | None = None) -> list[dict]: cur.execute(query, params or ()) results = cur.fetchall() return [dict(row) for row in results] - + def close(self) -> None: """Close all database connections.""" if self._connection_pool: self._connection_pool.closeall() print("[NeonDB] Connection pool closed") - + def is_healthy(self) -> bool: """ Check database connection health. - + Returns: True if database is accessible, False otherwise """ @@ -236,10 +243,10 @@ def is_healthy(self) -> bool: def get_database() -> NeonDatabase: """ Get or create the global database instance. - + Returns: NeonDatabase instance - + Usage: db = get_database() if db.config.is_configured: @@ -249,13 +256,15 @@ def get_database() -> NeonDatabase: global _db_instance if _db_instance is None: if not PSYCOPG2_AVAILABLE: - print("[NeonDB] psycopg2 not available (install with: pip install psycopg2-binary)") + print( + "[NeonDB] psycopg2 not available (install with: pip install psycopg2-binary)" + ) # Create a disabled instance _db_instance = NeonDatabase.__new__(NeonDatabase) _db_instance.config = DatabaseConfig() _db_instance._connection_pool = None return _db_instance - + config = DatabaseConfig() if config.is_configured: _db_instance = NeonDatabase(config) @@ -272,18 +281,18 @@ def get_database() -> NeonDatabase: def init_schema(db: NeonDatabase) -> None: """ Initialize database schema for QPyth. - + Creates tables for: - quantum_jobs: Job execution history - vqe_results: VQE computation results - user_sessions: User session tracking - api_logs: API request logging - + Args: db: NeonDatabase instance """ print("[NeonDB] Initializing schema...") - + # Quantum jobs table db.execute(""" CREATE TABLE IF NOT EXISTS quantum_jobs ( @@ -302,7 +311,7 @@ def init_schema(db: NeonDatabase) -> None: execution_time_ms INTEGER ) """) - + # VQE results table db.execute(""" CREATE TABLE IF NOT EXISTS vqe_results ( @@ -321,7 +330,7 @@ def init_schema(db: NeonDatabase) -> None: user_id VARCHAR(255) ) """) - + # User sessions table db.execute(""" CREATE TABLE IF NOT EXISTS user_sessions ( @@ -335,7 +344,7 @@ def init_schema(db: NeonDatabase) -> None: is_active BOOLEAN DEFAULT TRUE ) """) - + # API logs table db.execute(""" CREATE TABLE IF NOT EXISTS api_logs ( @@ -353,7 +362,7 @@ def init_schema(db: NeonDatabase) -> None: created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ) """) - + # Create indexes for performance db.execute(""" CREATE INDEX IF NOT EXISTS idx_quantum_jobs_user_id ON quantum_jobs(user_id) @@ -376,7 +385,7 @@ def init_schema(db: NeonDatabase) -> None: db.execute(""" CREATE INDEX IF NOT EXISTS idx_api_logs_created_at ON api_logs(created_at) """) - + print("[NeonDB] Schema initialized successfully") @@ -391,7 +400,7 @@ def log_quantum_job( ) -> int | None: """ Log a quantum job execution. - + Args: job_type: Type of job (e.g., 'bloch', 'bell', 'vqe', 'qrng') circuit_json: Circuit representation as JSON @@ -399,16 +408,17 @@ def log_quantum_job( backend: Backend used for execution user_id: Optional user identifier session_id: Optional session identifier - + Returns: Job ID if successful, None otherwise """ db = get_database() if not db.config.is_configured: return None - + try: import json + db.execute( """ INSERT INTO quantum_jobs @@ -423,7 +433,7 @@ def log_quantum_job( json.dumps(circuit_json) if circuit_json else None, shots, backend, - ) + ), ) result = db.fetch_one("SELECT LASTVAL() as id") return result["id"] if result else None @@ -447,7 +457,7 @@ def save_vqe_result( ) -> int | None: """ Save VQE computation result. - + Args: molecule: Molecule name (e.g., 'H2', 'LiH') final_energy: Final ground state energy @@ -460,16 +470,17 @@ def save_vqe_result( backend_type: Type of backend used noise_profile: Noise profile if applicable user_id: Optional user identifier - + Returns: Result ID if successful, None otherwise """ db = get_database() if not db.config.is_configured: return None - + try: import json + db.execute( """ INSERT INTO vqe_results @@ -491,7 +502,7 @@ def save_vqe_result( backend_type, noise_profile, user_id, - ) + ), ) result = db.fetch_one("SELECT LASTVAL() as id") return result["id"] if result else None @@ -514,7 +525,7 @@ def log_api_request( ) -> None: """ Log an API request. - + Args: endpoint: API endpoint path method: HTTP method @@ -530,9 +541,10 @@ def log_api_request( db = get_database() if not db.config.is_configured: return - + try: import json + db.execute( """ INSERT INTO api_logs @@ -551,7 +563,7 @@ def log_api_request( json.dumps(request_data) if request_data else None, json.dumps(response_data) if response_data else None, error_message, - ) + ), ) except Exception as e: print(f"[NeonDB] Failed to log API request: {e}") diff --git a/server.py b/server.py index 6cc973f..3182921 100644 --- a/server.py +++ b/server.py @@ -52,6 +52,7 @@ log_quantum_job, save_vqe_result, ) + DATABASE_AVAILABLE = True except ImportError: DATABASE_AVAILABLE = False @@ -89,11 +90,11 @@ async def log_requests(request: Request, call_next): """Log API requests to database if configured.""" import time - + start_time = time.time() response = await call_next(request) process_time = int((time.time() - start_time) * 1000) - + # Log to database if available if DATABASE_AVAILABLE: try: @@ -107,9 +108,10 @@ async def log_requests(request: Request, call_next): except Exception as e: # Don't fail the request if logging fails print(f"[Server] Failed to log request: {e}") - + return response + # Initialize quantum engine engine = QuantumEngine(QuantumConfig()) @@ -125,7 +127,9 @@ async def log_requests(request: Request, call_next): except Exception as e: print(f"[Server] Database initialization failed: {e}") else: - print("[Server] Database module not available (install with: pip install QPyth[database])") + print( + "[Server] Database module not available (install with: pip install QPyth[database])" + ) class BlochRequest(BaseModel): diff --git a/test_neon_integration.py b/test_neon_integration.py index 69995d2..c5474e3 100644 --- a/test_neon_integration.py +++ b/test_neon_integration.py @@ -18,34 +18,37 @@ # Add project root to path sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + def test_neon_integration(): """Test Neon database integration.""" - + print("=" * 60) print("Neon Database Integration Test") print("=" * 60) print() - + # Test 1: Check environment variables print("1. Checking environment variables...") database_url = os.getenv("DATABASE_URL") database_url_unpooled = os.getenv("DATABASE_URL_UNPOOLED") - + if not database_url: print(" ❌ DATABASE_URL not set") print(" ℹ️ Set environment variable: export DATABASE_URL='postgresql://...'") return False else: print(" ✅ DATABASE_URL is set") - print(f" Host: {database_url.split('@')[1].split('/')[0] if '@' in database_url else 'N/A'}") - + print( + f" Host: {database_url.split('@')[1].split('/')[0] if '@' in database_url else 'N/A'}" + ) + if database_url_unpooled: print(" ✅ DATABASE_URL_UNPOOLED is set") else: print(" ⚠️ DATABASE_URL_UNPOOLED not set (optional)") - + print() - + # Test 2: Import database module print("2. Testing database module import...") try: @@ -56,41 +59,42 @@ def test_neon_integration(): log_quantum_job, save_vqe_result, ) + print(" ✅ Database module imported successfully") except ImportError as e: print(f" ❌ Failed to import database module: {e}") print(" ℹ️ Install with: pip install QPyth[database]") return False - + print() - + # Test 3: Initialize database connection print("3. Testing database connection...") try: config = DatabaseConfig() print(" ✅ Database configuration loaded") print(f" Configured: {config.is_configured}") - + if not config.is_configured: print(" ⚠️ Database not configured, skipping connection tests") return True - + db = NeonDatabase(config) print(" ✅ Database instance created") - + if db._connection_pool: print(" ✅ Connection pool initialized") print(f" Pool size: {config.pool_min}-{config.pool_max}") else: print(" ❌ Connection pool failed to initialize") return False - + except Exception as e: print(f" ❌ Database connection failed: {e}") return False - + print() - + # Test 4: Health check print("4. Testing database health...") try: @@ -103,9 +107,9 @@ def test_neon_integration(): except Exception as e: print(f" ❌ Health check failed: {e}") return False - + print() - + # Test 5: Schema initialization print("5. Testing schema initialization...") try: @@ -114,9 +118,9 @@ def test_neon_integration(): except Exception as e: print(f" ❌ Schema initialization failed: {e}") return False - + print() - + # Test 6: Insert test data print("6. Testing data insertion...") try: @@ -132,7 +136,7 @@ def test_neon_integration(): else: print(" ❌ Failed to log quantum job") return False - + # Test VQE result saving result_id = save_vqe_result( molecule="H2_test", @@ -147,13 +151,13 @@ def test_neon_integration(): else: print(" ❌ Failed to save VQE result") return False - + except Exception as e: print(f" ❌ Data insertion failed: {e}") return False - + print() - + # Test 7: Query test data print("7. Testing data retrieval...") try: @@ -162,13 +166,13 @@ def test_neon_integration(): "SELECT * FROM quantum_jobs WHERE job_type = 'test' ORDER BY created_at DESC LIMIT 5" ) print(f" ✅ Retrieved {len(jobs)} test job(s)") - + # Query VQE results vqe_results = db.fetch_all( "SELECT * FROM vqe_results WHERE molecule = 'H2_test' ORDER BY created_at DESC LIMIT 5" ) print(f" ✅ Retrieved {len(vqe_results)} test VQE result(s)") - + # Display sample data if jobs: print(" Sample job data:") @@ -176,13 +180,13 @@ def test_neon_integration(): print(f" - Type: {jobs[0]['job_type']}") print(f" - Backend: {jobs[0]['backend']}") print(f" - Created: {jobs[0]['created_at']}") - + except Exception as e: print(f" ❌ Data retrieval failed: {e}") return False - + print() - + # Test 8: Cleanup test data print("8. Cleaning up test data...") try: @@ -191,9 +195,9 @@ def test_neon_integration(): print(" ✅ Test data cleaned up") except Exception as e: print(f" ⚠️ Cleanup failed (non-critical): {e}") - + print() - + # Test 9: Connection pool statistics print("9. Checking connection pool status...") try: @@ -203,7 +207,7 @@ def test_neon_integration(): print(f" Max connections: {config.pool_max}") except Exception as e: print(f" ⚠️ Pool status check failed: {e}") - + print() print("=" * 60) print("✅ All tests passed!") @@ -218,7 +222,7 @@ def test_neon_integration(): print() print("Neon database integration is working correctly!") print() - + return True From 79f9afb6034f59b69fe631c340e05bc992627371 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 01:56:00 +0000 Subject: [PATCH 3/3] chore: restore tracked coverage artifact Agent-Logs-Url: https://github.com/quantumdynamics927-dotcom/QPyth/sessions/ed476ffc-1ea5-43ae-9624-0d3544d77b41 Co-authored-by: quantumdynamics927-dotcom <247722560+quantumdynamics927-dotcom@users.noreply.github.com> --- coverage.xml | 3762 ++++++++++++-------------------------------------- 1 file changed, 867 insertions(+), 2895 deletions(-) diff --git a/coverage.xml b/coverage.xml index 593f175..7c95a97 100644 --- a/coverage.xml +++ b/coverage.xml @@ -1,9 +1,9 @@ - + - /home/runner/work/QPyth/QPyth/quantumpytho + D:\QPyth\quantumpytho @@ -11,8 +11,8 @@ + - @@ -298,7 +298,7 @@ - + @@ -306,18 +306,99 @@ - + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -325,287 +406,167 @@ - + - - - - - - + + - - - - - + + + + + + + - - + + - + + + + + + + + - - + + + + + + - - + + + + + - - + + + + + + + + - + - + + + + - - + + + + + + + + + + + + - - - - - + - - - - + + + + + + + + + + - - + + + + + - + - - + - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + - + + + + + + - - + + - - - - - - - @@ -613,2869 +574,914 @@ - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - + + + + + + - - + + + + + - + + - - - - - - + + + + + + + + + - - - - - - + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + - - - - - - + + + + + + + + - - + + + - - + + + + + + + + + + + - + - - - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - - + - - - + + + + - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + - - + + + + + + - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - + + + + + + + + + + + + + + + - - - - - - - - + + + + + - + - - - - - - - - + - - - - + + + - + - + + + + - - + - - - - - + - - - - - + + + + + + + + + + - - - - - + - - - - + - - - - - - - - - - - - + + + + + + + + + + + - - + - - - + + - + + - - + - - - - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - + + + - - - - - - + + + + - - - - - - + + + + + + + + + + + + + + - - - - - - - - + - - - + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + - - - - - - - + + + + - + - - + - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - - - - - - - - - - - - - - - - - - - - - - - + - + + + + - - - - + - + + + + + - + + - - + + - - - - - - - - - - - - - + + - + + - - - - - - - - - - - - - - - - - + - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - + + + + - + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - + - - - - - - - - - - - - + - - - + + + - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - + + - + + + + + + + + - - - - - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - + + - - + + + + + - - + + - - + - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - + - - - - + - - - - - - - - - - - - - - - - - - - @@ -3486,145 +1492,111 @@ - - - + + + + + + + + + + + - - + - - - - - + + - - - - - - - - - - - + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - + - + - - - - - - - - - - - - - - - + - - - - - - - + - - - - + - - - - - - + + - - - - - - - - - - - - - - - - - - - +