compair-core 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of compair-core might be problematic. Click here for more details.

@@ -0,0 +1,36 @@
1
+ compair/__init__.py,sha256=OUXUSHK8JTlWMgCpzodE1B6Xc7glkCcRMHZQi02sRCM,2594
2
+ compair/celery_app.py,sha256=OM_Saza9yC9Q0kz_WXctfswrKkG7ruT52Zl5E4guiT0,640
3
+ compair/default_groups.py,sha256=dbacrFkSjqEQZ_uoFU5gYhgIoP_3lmvz6LJNHCJvxlw,498
4
+ compair/embeddings.py,sha256=ELpMYWBw3nCjxTx9vLrDiHYptzlzj_7JW6jZAf2Iql4,2341
5
+ compair/feedback.py,sha256=jgDxYKo5PzW2p-uLf5ETmlL-mDAfqzJazO1NZDK-Z-g,2755
6
+ compair/logger.py,sha256=mB9gV3FfC0qE_G9NBErpEAJhyGxDxEVKqWKu3n8hOkc,802
7
+ compair/main.py,sha256=uc3q5TY3TZdC9UsqnbHX741rplNoveK0BwYOoCi0cCA,8073
8
+ compair/models.py,sha256=J24eVQSPdiz13VdrKe8QsZh-nte8OZySf4LQwp4wPuU,13891
9
+ compair/schema.py,sha256=TxQpDQ96J_tIj-Y_1C_x2eUYn9n_LOG6XiNLCX1-GYY,2902
10
+ compair/tasks.py,sha256=_wvwjeHMyH7dD9zbMyGDfwMp9KqRxR1901LX5rSsLbo,3590
11
+ compair/utils.py,sha256=5Cw3FrOsCc5jfO4R5Ghg_TXvh3DwHICl2PdwLZDm3XY,1581
12
+ compair_core-0.3.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
13
+ compair_email/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ compair_email/email.py,sha256=bedTrcfmi4K2kB0B2bSbryhq5k_4C-QNtb8wZjvzKAs,238
15
+ compair_email/email_core.py,sha256=da7JxTo5ude55mB7UNLlpNp8xenYwoPaqyTunxjU7to,316
16
+ compair_email/templates.py,sha256=JVlLdJEcpu14mVKRAYRIPIw2JGy70kG70mfjXgby-To,206
17
+ compair_email/templates_core.py,sha256=1jPbXo36TjzNDCgYwL_4FyT5SsAZe5r6ufJe6W94eeE,424
18
+ server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ server/app.py,sha256=XjdNPBX7978BWjWYS_66ekfrDuDsTWAEfndemaMFQF4,3413
20
+ server/deps.py,sha256=0X-Z5JQGeXwbMooWIOC2kXVmsiJIvgUtqkK2PmDjKpI,1557
21
+ server/settings.py,sha256=eqHrrv62h-l2VTDtiKPVfB5BV_ysA4W2qfX7Nxn7SNo,1476
22
+ server/local_model/__init__.py,sha256=YlzDgorgAjGou9d_W29Xp3TVu08e4t9x8csFxn8cgSE,50
23
+ server/local_model/app.py,sha256=2bOLjgAyKnFcng2lmDVMB0G6WgnnVGjHyj2X8TeJjnU,1626
24
+ server/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ server/providers/console_mailer.py,sha256=7ady954yOPlT8t3spkDvdMdO3BTwosJUj1cpVNOwj8U,308
26
+ server/providers/contracts.py,sha256=pYA_2AaPHw089O_UP2JtWRHbIiVkoNGhLuesuNATowU,1858
27
+ server/providers/local_storage.py,sha256=UNCDHejeWlDlPVDRdwsN13toNRi_qHiaX-kbmzxf0OA,1054
28
+ server/providers/noop_analytics.py,sha256=OKw23SObxBlQzFdB0xEBg5qD1Fcq_bcx6OOb4S3Mbd0,194
29
+ server/providers/noop_billing.py,sha256=V18Cpl1D1reM3xhgw-lShGliVpYO8IsiAPWOAIR34jM,1358
30
+ server/providers/noop_ocr.py,sha256=fMaJrivDef38-ECgIuTXUBCIm_avgvZf3nQ3UTdFPNI,341
31
+ server/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ server/routers/capabilities.py,sha256=FZBLgmlIw6tWZiuihpzRmZ9AvbrY1jolVs7zqnhNPRU,1150
33
+ compair_core-0.3.2.dist-info/METADATA,sha256=AQeN3eKdTcJGKJZKF4UAaKL7Mje41ONRutMhXW6d58s,4533
34
+ compair_core-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
+ compair_core-0.3.2.dist-info/top_level.txt,sha256=-UjF8_GOz0G56FNiZDZnMSkEpnzBX__7qNrKpKLOqNA,29
36
+ compair_core-0.3.2.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ compair
2
+ compair_email
3
+ server
File without changes
compair_email/email.py ADDED
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ try: # Cloud build ships the premium mailer
4
+ from compair_cloud.compair_email.email import * # type: ignore
5
+ except (ImportError, ModuleNotFoundError):
6
+ from .email_core import * # type: ignore
@@ -0,0 +1,15 @@
1
+ import os
2
+
3
+ from redmail import EmailSender
4
+
5
+ EMAIL_HOST = f"{os.environ.get('EMAIL_HOST')}"
6
+ EMAIL_USER = f"{os.environ.get('EMAIL_USER')}"
7
+ EMAIL_PW = f"{os.environ.get('EMAIL_PW')}"
8
+
9
+ emailer = EmailSender(
10
+ host=EMAIL_HOST,
11
+ port=587,
12
+ username=EMAIL_USER,
13
+ password=EMAIL_PW,
14
+ use_starttls=True
15
+ )
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ try:
4
+ from compair_cloud.compair_email.templates import * # type: ignore
5
+ except (ImportError, ModuleNotFoundError):
6
+ from .templates_core import * # type: ignore
@@ -0,0 +1,13 @@
1
+ """Minimal email templates for the core edition."""
2
+
3
+ ACCOUNT_VERIFY_TEMPLATE = """
4
+ <p>Hi {{user_name}},</p>
5
+ <p>Please verify your Compair account by clicking the link below:</p>
6
+ <p><a href="{{verify_link}}">Verify my account</a></p>
7
+ <p>Thanks!</p>
8
+ """.strip()
9
+
10
+ PASSWORD_RESET_TEMPLATE = """
11
+ <p>We received a request to reset your password.</p>
12
+ <p>Your password reset code is: <strong>{{reset_code}}</strong></p>
13
+ """.strip()
server/__init__.py ADDED
File without changes
server/app.py ADDED
@@ -0,0 +1,90 @@
1
+ """FastAPI app factory supporting Core and Cloud editions."""
2
+ from __future__ import annotations
3
+
4
+ from fastapi import FastAPI
5
+
6
+ from .deps import (
7
+ get_analytics,
8
+ get_billing,
9
+ get_mailer,
10
+ get_ocr,
11
+ get_settings_dependency,
12
+ get_storage,
13
+ )
14
+ from .providers.local_storage import LocalStorage
15
+ from .routers.capabilities import router as capabilities_router
16
+ from .settings import Settings, get_settings
17
+
18
+
19
+ def _normalize_edition(value: str) -> str:
20
+ return (value or "core").lower()
21
+
22
+
23
+ def create_app(settings: Settings | None = None) -> FastAPI:
24
+ """Instantiate the FastAPI application with edition-specific wiring."""
25
+
26
+ resolved_settings = settings or get_settings()
27
+ edition = _normalize_edition(resolved_settings.edition)
28
+
29
+ app = FastAPI(title="Compair API", version=resolved_settings.version)
30
+
31
+ from api import router as legacy_router
32
+
33
+ app.include_router(legacy_router)
34
+ app.include_router(capabilities_router)
35
+
36
+ # Share the resolved settings with request handlers
37
+ app.dependency_overrides[get_settings_dependency] = lambda: resolved_settings
38
+
39
+ if edition == "cloud":
40
+ try:
41
+ from compair_cloud.analytics.ga4 import GA4Analytics
42
+ from compair_cloud.billing.stripe_provider import StripeBilling
43
+ from compair_cloud.mailer.transactional import TransactionalMailer
44
+ from compair_cloud.ocr.claude_ocr import ClaudeOCR
45
+ from compair_cloud.storage.r2_storage import R2Storage
46
+ except ImportError as exc: # pragma: no cover - only triggered in misconfigured builds
47
+ raise RuntimeError(
48
+ "Cloud edition requires the private 'compair_cloud' package to be installed."
49
+ ) from exc
50
+
51
+ storage_provider = R2Storage(
52
+ bucket=resolved_settings.r2_bucket,
53
+ cdn_base=resolved_settings.r2_cdn_base,
54
+ access_key=resolved_settings.r2_access_key,
55
+ secret_key=resolved_settings.r2_secret_key,
56
+ endpoint_url=resolved_settings.r2_endpoint_url,
57
+ )
58
+ billing_provider = StripeBilling(
59
+ stripe_key=resolved_settings.stripe_key,
60
+ endpoint_secret=resolved_settings.stripe_endpoint_secret,
61
+ )
62
+ ocr_provider = ClaudeOCR()
63
+ mailer_provider = TransactionalMailer()
64
+
65
+ analytics_provider = None
66
+ if resolved_settings.ga4_measurement_id and resolved_settings.ga4_api_secret:
67
+ analytics_provider = GA4Analytics(
68
+ measurement_id=resolved_settings.ga4_measurement_id,
69
+ api_secret=resolved_settings.ga4_api_secret,
70
+ )
71
+
72
+ app.dependency_overrides[get_storage] = lambda sp=storage_provider: sp
73
+ app.dependency_overrides[get_billing] = lambda bp=billing_provider: bp
74
+ app.dependency_overrides[get_ocr] = lambda op=ocr_provider: op
75
+ if analytics_provider is not None:
76
+ app.dependency_overrides[get_analytics] = lambda ap=analytics_provider: ap
77
+ app.dependency_overrides[get_mailer] = lambda mp=mailer_provider: mp
78
+
79
+ else:
80
+ storage_provider = LocalStorage(
81
+ base_dir=resolved_settings.local_upload_dir,
82
+ base_url=resolved_settings.local_upload_base_url,
83
+ )
84
+ app.dependency_overrides[get_storage] = lambda sp=storage_provider: sp
85
+
86
+ return app
87
+
88
+
89
+ # Uvicorn compatibility: allow ``uvicorn server.app:app``
90
+ app = create_app()
server/deps.py ADDED
@@ -0,0 +1,67 @@
1
+ """Dependency entry points for features that differ by edition."""
2
+ from __future__ import annotations
3
+
4
+ from functools import lru_cache
5
+
6
+ from fastapi import Depends
7
+
8
+ from .providers.console_mailer import ConsoleMailer
9
+ from .providers.contracts import Analytics, BillingProvider, Mailer, OCRProvider, StorageProvider
10
+ from .providers.local_storage import LocalStorage
11
+ from .providers.noop_analytics import NoopAnalytics
12
+ from .providers.noop_billing import NoopBilling
13
+ from .providers.noop_ocr import NoopOCR
14
+ from .settings import Settings, get_settings
15
+
16
+
17
+ @lru_cache
18
+ def _local_storage_factory(base_dir: str, base_url: str) -> LocalStorage:
19
+ return LocalStorage(base_dir=base_dir, base_url=base_url)
20
+
21
+
22
+ @lru_cache
23
+ def _noop_billing() -> NoopBilling:
24
+ return NoopBilling()
25
+
26
+
27
+ @lru_cache
28
+ def _noop_ocr() -> NoopOCR:
29
+ return NoopOCR()
30
+
31
+
32
+ @lru_cache
33
+ def _console_mailer() -> ConsoleMailer:
34
+ return ConsoleMailer()
35
+
36
+
37
+ @lru_cache
38
+ def _noop_analytics() -> NoopAnalytics:
39
+ return NoopAnalytics()
40
+
41
+
42
+ def get_settings_dependency() -> Settings:
43
+ """Expose settings as a FastAPI dependency."""
44
+ return get_settings()
45
+
46
+
47
+
48
+ def get_storage(
49
+ settings: Settings = Depends(get_settings_dependency),
50
+ ) -> StorageProvider:
51
+ return _local_storage_factory(settings.local_upload_dir, settings.local_upload_base_url)
52
+
53
+
54
+ def get_billing() -> BillingProvider:
55
+ return _noop_billing()
56
+
57
+
58
+ def get_ocr() -> OCRProvider:
59
+ return _noop_ocr()
60
+
61
+
62
+ def get_mailer() -> Mailer:
63
+ return _console_mailer()
64
+
65
+
66
+ def get_analytics() -> Analytics:
67
+ return _noop_analytics()
@@ -0,0 +1 @@
1
+ """Local model endpoints for the Core edition."""
@@ -0,0 +1,62 @@
1
+ """Minimal FastAPI application serving local embedding and generation endpoints."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ from typing import List
6
+
7
+ from fastapi import FastAPI
8
+ from pydantic import BaseModel
9
+
10
+ app = FastAPI(title="Compair Local Model", version="0.1.0")
11
+
12
+ EMBED_DIMENSION = 384
13
+
14
+
15
+ def _hash_embedding(text: str, dimension: int = EMBED_DIMENSION) -> List[float]:
16
+ if not text:
17
+ text = " "
18
+ digest = hashlib.sha256(text.encode("utf-8", "ignore")).digest()
19
+ vector: List[float] = []
20
+ while len(vector) < dimension:
21
+ for byte in digest:
22
+ vector.append((byte / 255.0) * 2 - 1)
23
+ if len(vector) == dimension:
24
+ break
25
+ digest = hashlib.sha256(digest).digest()
26
+ return vector
27
+
28
+
29
+ class EmbedRequest(BaseModel):
30
+ text: str
31
+
32
+
33
+ class EmbedResponse(BaseModel):
34
+ embedding: List[float]
35
+
36
+
37
+ class GenerateRequest(BaseModel):
38
+ system: str | None = None
39
+ prompt: str
40
+ verbosity: str | None = None
41
+
42
+
43
+ class GenerateResponse(BaseModel):
44
+ text: str
45
+
46
+
47
+ @app.post("/embed", response_model=EmbedResponse)
48
+ def embed(request: EmbedRequest) -> EmbedResponse:
49
+ return EmbedResponse(embedding=_hash_embedding(request.text))
50
+
51
+
52
+ @app.post("/generate", response_model=GenerateResponse)
53
+ def generate(request: GenerateRequest) -> GenerateResponse:
54
+ prompt = request.prompt.strip()
55
+ if not prompt:
56
+ return GenerateResponse(text="NONE")
57
+
58
+ first_sentence = prompt.split("\n", 1)[0][:200]
59
+ verbosity = request.verbosity or "default"
60
+ return GenerateResponse(
61
+ text=f"[local-{verbosity}] Key takeaway: {first_sentence}"
62
+ )
File without changes
@@ -0,0 +1,9 @@
1
+ """Console mailer used in Core builds to avoid delivering real email."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Iterable
5
+
6
+
7
+ class ConsoleMailer:
8
+ def send(self, subject: str, sender: str, receivers: Iterable[str], html: str) -> None:
9
+ print(f"[MAIL] {subject} -> {list(receivers)}")
@@ -0,0 +1,66 @@
1
+ """Provider protocol definitions shared across editions."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from typing import BinaryIO, Iterable, Mapping, Protocol
6
+
7
+
8
+ @dataclass(slots=True)
9
+ class BillingSession:
10
+ """Represents the result of creating a checkout session."""
11
+
12
+ id: str
13
+ url: str
14
+
15
+
16
+ class StorageProvider(Protocol):
17
+ def put_file(self, key: str, fileobj: BinaryIO, content_type: str) -> str: ...
18
+
19
+ def get_file(self, key: str) -> tuple[BinaryIO, str]: ...
20
+
21
+ def build_url(self, key: str) -> str: ...
22
+
23
+
24
+ class BillingProvider(Protocol):
25
+ def ensure_customer(self, *, user_email: str, user_id: str) -> str: ...
26
+
27
+ def create_checkout_session(
28
+ self,
29
+ *,
30
+ customer_id: str,
31
+ price_id: str,
32
+ qty: int,
33
+ success_url: str,
34
+ cancel_url: str,
35
+ metadata: Mapping[str, str] | None = None,
36
+ ) -> BillingSession: ...
37
+
38
+ def retrieve_session(self, session_id: str) -> BillingSession: ...
39
+
40
+ def get_checkout_url(self, session_id: str) -> str: ...
41
+
42
+ def create_customer_portal(self, *, customer_id: str, return_url: str) -> str: ...
43
+
44
+ def create_coupon(self, amount: int) -> str: ...
45
+
46
+ def apply_coupon(self, *, customer_id: str, coupon_id: str) -> None: ...
47
+
48
+ def construct_event(self, payload: bytes, signature: str | None) -> Mapping[str, object]: ...
49
+
50
+
51
+ class OCRProvider(Protocol):
52
+ def submit(
53
+ self, *, user_id: str, filename: str, data: bytes, document_id: str | None
54
+ ) -> str: ...
55
+
56
+ def status(self, task_id: str) -> Mapping[str, object]: ...
57
+
58
+
59
+ class Mailer(Protocol):
60
+ def send(self, subject: str, sender: str, receivers: Iterable[str], html: str) -> None: ...
61
+
62
+
63
+ class Analytics(Protocol):
64
+ def track(
65
+ self, event_name: str, user_id: str, params: Mapping[str, object] | None = None
66
+ ) -> None: ...
@@ -0,0 +1,28 @@
1
+ """Local filesystem storage provider used in the Core edition."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+ from typing import BinaryIO
6
+
7
+
8
+ class LocalStorage:
9
+ def __init__(self, base_dir: str = "/data/uploads", base_url: str = "/uploads") -> None:
10
+ self.base_dir = Path(base_dir)
11
+ self.base_url = base_url.rstrip("/") or "/uploads"
12
+ self.base_dir.mkdir(parents=True, exist_ok=True)
13
+
14
+ def put_file(self, key: str, fileobj: BinaryIO, content_type: str) -> str:
15
+ destination = self.base_dir / key
16
+ destination.parent.mkdir(parents=True, exist_ok=True)
17
+ with destination.open("wb") as dest:
18
+ dest.write(fileobj.read())
19
+ return self.build_url(key)
20
+
21
+ def get_file(self, key: str) -> tuple[BinaryIO, str]:
22
+ path = self.base_dir / key
23
+ if not path.exists():
24
+ raise FileNotFoundError(key)
25
+ return path.open("rb"), "application/octet-stream"
26
+
27
+ def build_url(self, key: str) -> str:
28
+ return f"{self.base_url}/{key}".replace("//", "/")
@@ -0,0 +1,7 @@
1
+ """Analytics provider placeholder that records no events."""
2
+ from __future__ import annotations
3
+
4
+
5
+ class NoopAnalytics:
6
+ def track(self, *_: object, **__: object) -> None:
7
+ return None
@@ -0,0 +1,30 @@
1
+ """Billing provider placeholder for the Core edition."""
2
+ from __future__ import annotations
3
+
4
+ from .contracts import BillingSession
5
+
6
+
7
+ class NoopBilling:
8
+ def ensure_customer(self, **_: object) -> str:
9
+ raise NotImplementedError("Billing is not available in the Core edition.")
10
+
11
+ def create_checkout_session(self, **_: object) -> BillingSession:
12
+ raise NotImplementedError("Billing is not available in the Core edition.")
13
+
14
+ def retrieve_session(self, *_: object, **__: object) -> BillingSession:
15
+ raise NotImplementedError("Billing is not available in the Core edition.")
16
+
17
+ def get_checkout_url(self, *_: object, **__: object) -> str:
18
+ raise NotImplementedError("Billing is not available in the Core edition.")
19
+
20
+ def create_customer_portal(self, *_: object, **__: object) -> str:
21
+ raise NotImplementedError("Billing is not available in the Core edition.")
22
+
23
+ def create_coupon(self, *_: object, **__: object) -> str:
24
+ raise NotImplementedError("Billing is not available in the Core edition.")
25
+
26
+ def apply_coupon(self, *_: object, **__: object) -> None:
27
+ raise NotImplementedError("Billing is not available in the Core edition.")
28
+
29
+ def construct_event(self, *_: object, **__: object) -> dict[str, object]:
30
+ raise NotImplementedError("Billing is not available in the Core edition.")
@@ -0,0 +1,10 @@
1
+ """OCR provider placeholder for the Core edition."""
2
+ from __future__ import annotations
3
+
4
+
5
+ class NoopOCR:
6
+ def submit(self, **_: object) -> str:
7
+ raise NotImplementedError("OCR is not available in the Core edition.")
8
+
9
+ def status(self, task_id: str) -> dict[str, object]:
10
+ return {"status": "unknown", "task_id": task_id}
File without changes
@@ -0,0 +1,38 @@
1
+ """Meta endpoints that describe edition capabilities for the CLI."""
2
+ from __future__ import annotations
3
+
4
+ from fastapi import APIRouter, Depends
5
+
6
+ from ..settings import Settings, get_settings
7
+
8
+ router = APIRouter(tags=["meta"])
9
+
10
+
11
+ @router.get("/capabilities")
12
+ def capabilities(settings: Settings = Depends(get_settings)) -> dict[str, object]:
13
+ edition = settings.edition.lower()
14
+ return {
15
+ "auth": {
16
+ "device_flow": edition == "cloud",
17
+ "password_login": True,
18
+ },
19
+ "inputs": {
20
+ "text": True,
21
+ "ocr": settings.ocr_enabled,
22
+ "repos": True,
23
+ },
24
+ "models": {
25
+ "premium": settings.premium_models,
26
+ "open": True,
27
+ },
28
+ "integrations": {
29
+ "slack": settings.integrations_enabled,
30
+ "github": settings.integrations_enabled,
31
+ },
32
+ "limits": {
33
+ "docs": None if edition == "core" else 100,
34
+ "feedback_per_day": None if edition == "core" else 50,
35
+ },
36
+ "server": "Compair Cloud" if edition == "cloud" else "Compair Core",
37
+ "version": settings.version,
38
+ }
server/settings.py ADDED
@@ -0,0 +1,51 @@
1
+ """Application settings and feature flag definitions."""
2
+ from functools import lru_cache
3
+
4
+ from pydantic_settings import BaseSettings
5
+
6
+
7
+ class Settings(BaseSettings):
8
+ """Configuration injected via COMPAIR_ environment variables."""
9
+
10
+ # Edition metadata
11
+ edition: str = "core" # core | cloud
12
+ version: str = "dev"
13
+
14
+ # Feature gates
15
+ ocr_enabled: bool = False
16
+ billing_enabled: bool = False
17
+ integrations_enabled: bool = False
18
+ premium_models: bool = False
19
+
20
+ # Core/local storage defaults
21
+ local_upload_dir: str = "/data/uploads"
22
+ local_upload_base_url: str = "/uploads"
23
+
24
+ # Cloud storage (R2/S3-compatible)
25
+ r2_bucket: str | None = None
26
+ r2_cdn_base: str | None = None
27
+ r2_access_key: str | None = None
28
+ r2_secret_key: str | None = None
29
+ r2_endpoint_url: str | None = None
30
+
31
+ # Optional cloud secrets
32
+ stripe_key: str | None = None
33
+ stripe_endpoint_secret: str | None = None
34
+ stripe_success_url: str = "https://compair.sh/home"
35
+ stripe_cancel_url: str = "https://compair.sh/home"
36
+ ga4_measurement_id: str | None = None
37
+ ga4_api_secret: str | None = None
38
+
39
+ # Local model endpoints
40
+ local_model_url: str = "http://local-model:9000"
41
+ local_embedding_route: str = "/embed"
42
+ local_generation_route: str = "/generate"
43
+
44
+ class Config:
45
+ env_prefix = "COMPAIR_"
46
+
47
+
48
+ @lru_cache
49
+ def get_settings() -> Settings:
50
+ """Cached settings instance for dependency injection."""
51
+ return Settings()
@@ -1,5 +0,0 @@
1
- compair_core-0.3.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
2
- compair_core-0.3.1.dist-info/METADATA,sha256=I7eh0b6GwaAqI3XASxC-qYVQDVVLCpdTXWadD3CZ7hI,4466
3
- compair_core-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
4
- compair_core-0.3.1.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
- compair_core-0.3.1.dist-info/RECORD,,
@@ -1 +0,0 @@
1
-