compair-core 0.3.10__py3-none-any.whl → 0.3.12__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.
- compair_core/api.py +178 -3
- compair_core/server/app.py +8 -2
- compair_core/server/routers/capabilities.py +5 -1
- compair_core/server/settings.py +4 -0
- {compair_core-0.3.10.dist-info → compair_core-0.3.12.dist-info}/METADATA +4 -1
- {compair_core-0.3.10.dist-info → compair_core-0.3.12.dist-info}/RECORD +9 -9
- {compair_core-0.3.10.dist-info → compair_core-0.3.12.dist-info}/WHEEL +0 -0
- {compair_core-0.3.10.dist-info → compair_core-0.3.12.dist-info}/licenses/LICENSE +0 -0
- {compair_core-0.3.10.dist-info → compair_core-0.3.12.dist-info}/top_level.txt +0 -0
compair_core/api.py
CHANGED
|
@@ -13,6 +13,7 @@ import psutil
|
|
|
13
13
|
from celery.result import AsyncResult
|
|
14
14
|
from fastapi import APIRouter, Body, Depends, File, Form, Header, HTTPException, Query, Request, UploadFile
|
|
15
15
|
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
|
|
16
|
+
from fastapi.routing import APIRoute
|
|
16
17
|
from sqlalchemy import distinct, func, select, or_
|
|
17
18
|
from sqlalchemy.orm import joinedload, Session
|
|
18
19
|
|
|
@@ -46,6 +47,7 @@ redis_client = redis.Redis.from_url(redis_url) if (redis and redis_url) else Non
|
|
|
46
47
|
#from compair.main import process_document
|
|
47
48
|
|
|
48
49
|
router = APIRouter()
|
|
50
|
+
core_router = APIRouter()
|
|
49
51
|
WEB_URL = os.environ.get("WEB_URL")
|
|
50
52
|
ADMIN_API_KEY = os.environ.get("ADMIN_API_KEY")
|
|
51
53
|
|
|
@@ -59,6 +61,112 @@ GA4_MEASUREMENT_ID = os.getenv("GA4_MEASUREMENT_ID")
|
|
|
59
61
|
GA4_API_SECRET = os.getenv("GA4_API_SECRET")
|
|
60
62
|
|
|
61
63
|
IS_CLOUD = os.getenv("COMPAIR_EDITION", "core").lower() == "cloud"
|
|
64
|
+
SINGLE_USER_SESSION_TTL = timedelta(days=365)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _ensure_single_user(session: Session, settings: Settings) -> models.User:
|
|
68
|
+
"""Create or fetch the singleton user used when authentication is disabled."""
|
|
69
|
+
changed = False
|
|
70
|
+
user = (
|
|
71
|
+
session.query(models.User)
|
|
72
|
+
.options(joinedload(models.User.groups))
|
|
73
|
+
.filter(models.User.username == settings.single_user_username)
|
|
74
|
+
.first()
|
|
75
|
+
)
|
|
76
|
+
if user is None:
|
|
77
|
+
now = datetime.now(timezone.utc)
|
|
78
|
+
user = models.User(
|
|
79
|
+
username=settings.single_user_username,
|
|
80
|
+
name=settings.single_user_name,
|
|
81
|
+
datetime_registered=now,
|
|
82
|
+
verification_token=None,
|
|
83
|
+
token_expiration=None,
|
|
84
|
+
)
|
|
85
|
+
user.set_password(secrets.token_urlsafe(16))
|
|
86
|
+
user.status = "active"
|
|
87
|
+
user.status_change_date = now
|
|
88
|
+
session.add(user)
|
|
89
|
+
session.flush()
|
|
90
|
+
admin = models.Administrator(user_id=user.user_id)
|
|
91
|
+
group = models.Group(
|
|
92
|
+
name=user.username,
|
|
93
|
+
datetime_created=now,
|
|
94
|
+
group_image=None,
|
|
95
|
+
category="Private",
|
|
96
|
+
description=f"Private workspace for {settings.single_user_name}",
|
|
97
|
+
visibility="private",
|
|
98
|
+
)
|
|
99
|
+
group.admins.append(admin)
|
|
100
|
+
user.groups = [group]
|
|
101
|
+
session.add_all([group, admin])
|
|
102
|
+
changed = True
|
|
103
|
+
else:
|
|
104
|
+
now = datetime.now(timezone.utc)
|
|
105
|
+
if user.status != "active":
|
|
106
|
+
user.status = "active"
|
|
107
|
+
user.status_change_date = now
|
|
108
|
+
changed = True
|
|
109
|
+
group = next((g for g in user.groups if g.name == user.username), None)
|
|
110
|
+
if group is None:
|
|
111
|
+
group = session.query(models.Group).filter(models.Group.name == user.username).first()
|
|
112
|
+
if group is None:
|
|
113
|
+
group = models.Group(
|
|
114
|
+
name=user.username,
|
|
115
|
+
datetime_created=now,
|
|
116
|
+
group_image=None,
|
|
117
|
+
category="Private",
|
|
118
|
+
description=f"Private workspace for {user.name}",
|
|
119
|
+
visibility="private",
|
|
120
|
+
)
|
|
121
|
+
session.add(group)
|
|
122
|
+
changed = True
|
|
123
|
+
if group not in user.groups:
|
|
124
|
+
user.groups.append(group)
|
|
125
|
+
changed = True
|
|
126
|
+
admin = session.query(models.Administrator).filter(models.Administrator.user_id == user.user_id).first()
|
|
127
|
+
if admin is None:
|
|
128
|
+
admin = models.Administrator(user_id=user.user_id)
|
|
129
|
+
session.add(admin)
|
|
130
|
+
changed = True
|
|
131
|
+
if admin not in group.admins:
|
|
132
|
+
group.admins.append(admin)
|
|
133
|
+
changed = True
|
|
134
|
+
|
|
135
|
+
if changed:
|
|
136
|
+
session.commit()
|
|
137
|
+
user = (
|
|
138
|
+
session.query(models.User)
|
|
139
|
+
.options(joinedload(models.User.groups))
|
|
140
|
+
.filter(models.User.username == settings.single_user_username)
|
|
141
|
+
.first()
|
|
142
|
+
)
|
|
143
|
+
if user is None:
|
|
144
|
+
raise RuntimeError("Failed to initialize the local Compair user.")
|
|
145
|
+
user.groups # ensure relationship is loaded before detaching
|
|
146
|
+
return user
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _ensure_single_user_session(session: Session, user: models.User) -> models.Session:
|
|
150
|
+
"""Return a long-lived session token for the singleton user."""
|
|
151
|
+
now = datetime.now(timezone.utc)
|
|
152
|
+
existing = (
|
|
153
|
+
session.query(models.Session)
|
|
154
|
+
.filter(models.Session.user_id == user.user_id, models.Session.datetime_valid_until >= now)
|
|
155
|
+
.order_by(models.Session.datetime_valid_until.desc())
|
|
156
|
+
.first()
|
|
157
|
+
)
|
|
158
|
+
if existing:
|
|
159
|
+
return existing
|
|
160
|
+
token = secrets.token_urlsafe()
|
|
161
|
+
user_session = models.Session(
|
|
162
|
+
id=token,
|
|
163
|
+
user_id=user.user_id,
|
|
164
|
+
datetime_created=now,
|
|
165
|
+
datetime_valid_until=now + SINGLE_USER_SESSION_TTL,
|
|
166
|
+
)
|
|
167
|
+
session.add(user_session)
|
|
168
|
+
session.commit()
|
|
169
|
+
return user_session
|
|
62
170
|
|
|
63
171
|
|
|
64
172
|
def require_cloud(feature: str) -> None:
|
|
@@ -90,7 +198,13 @@ def require_feature(flag: bool, feature: str) -> None:
|
|
|
90
198
|
if not flag:
|
|
91
199
|
raise HTTPException(status_code=501, detail=f"{feature} is only available in the Compair Cloud edition.")
|
|
92
200
|
|
|
93
|
-
def get_current_user(auth_token: str = Header(
|
|
201
|
+
def get_current_user(auth_token: str | None = Header(None)):
|
|
202
|
+
settings = get_settings_dependency()
|
|
203
|
+
if not settings.require_authentication:
|
|
204
|
+
with compair.Session() as session:
|
|
205
|
+
return _ensure_single_user(session, settings)
|
|
206
|
+
if not auth_token:
|
|
207
|
+
raise HTTPException(status_code=401, detail="Missing session token")
|
|
94
208
|
with compair.Session() as session:
|
|
95
209
|
user_session = session.query(models.Session).filter(models.Session.id == auth_token).first()
|
|
96
210
|
if not user_session:
|
|
@@ -154,9 +268,20 @@ log_service_resource_metrics(service_name="backend") # or "frontend"
|
|
|
154
268
|
|
|
155
269
|
@router.post("/login")
|
|
156
270
|
def login(request: schema.LoginRequest) -> dict:
|
|
271
|
+
settings = get_settings_dependency()
|
|
157
272
|
with compair.Session() as session:
|
|
273
|
+
if not settings.require_authentication:
|
|
274
|
+
user = _ensure_single_user(session, settings)
|
|
275
|
+
user_session = _ensure_single_user_session(session, user)
|
|
276
|
+
return {
|
|
277
|
+
"user_id": user.user_id,
|
|
278
|
+
"username": user.username,
|
|
279
|
+
"name": user.name,
|
|
280
|
+
"status": user.status,
|
|
281
|
+
"role": user.role,
|
|
282
|
+
"auth_token": user_session.id,
|
|
283
|
+
}
|
|
158
284
|
user = session.query(models.User).filter(models.User.username == request.username).first()
|
|
159
|
-
print("PW yo: {request.password}")
|
|
160
285
|
if not user or not user.check_password(request.password):
|
|
161
286
|
raise HTTPException(status_code=401, detail="Invalid credentials")
|
|
162
287
|
if user.status == 'inactive':
|
|
@@ -530,8 +655,15 @@ def create_user(
|
|
|
530
655
|
|
|
531
656
|
|
|
532
657
|
@router.get("/load_session")
|
|
533
|
-
def load_session(auth_token: str) -> schema.Session | None:
|
|
658
|
+
def load_session(auth_token: str | None = None) -> schema.Session | None:
|
|
659
|
+
settings = get_settings_dependency()
|
|
660
|
+
if not settings.require_authentication:
|
|
661
|
+
with compair.Session() as session:
|
|
662
|
+
user = _ensure_single_user(session, settings)
|
|
663
|
+
return _ensure_single_user_session(session, user)
|
|
534
664
|
with compair.Session() as session:
|
|
665
|
+
if not auth_token:
|
|
666
|
+
raise HTTPException(status_code=400, detail="auth_token is required when authentication is enabled.")
|
|
535
667
|
user_session = session.query(models.Session).filter(models.Session.id == auth_token).first()
|
|
536
668
|
if not user_session:
|
|
537
669
|
raise HTTPException(status_code=404, detail="Session not found")
|
|
@@ -596,6 +728,9 @@ def update_session_duration(
|
|
|
596
728
|
def delete_user(
|
|
597
729
|
current_user: models.User = Depends(get_current_user)
|
|
598
730
|
):
|
|
731
|
+
settings = get_settings_dependency()
|
|
732
|
+
if not settings.require_authentication:
|
|
733
|
+
raise HTTPException(status_code=403, detail="Deleting the local user is not supported when authentication is disabled.")
|
|
599
734
|
with compair.Session() as session:
|
|
600
735
|
current_user.delete()
|
|
601
736
|
session.commit()
|
|
@@ -1711,6 +1846,9 @@ def load_references(
|
|
|
1711
1846
|
|
|
1712
1847
|
@router.get("/verify-email")
|
|
1713
1848
|
def verify_email(token: str):
|
|
1849
|
+
settings = get_settings_dependency()
|
|
1850
|
+
if not settings.require_authentication:
|
|
1851
|
+
raise HTTPException(status_code=403, detail="Email verification is disabled when authentication is disabled.")
|
|
1714
1852
|
with compair.Session() as session:
|
|
1715
1853
|
print(token)
|
|
1716
1854
|
user = session.query(models.User).filter(models.User.verification_token == token).first()
|
|
@@ -1769,6 +1907,9 @@ def sign_up(
|
|
|
1769
1907
|
request: schema.SignUpRequest,
|
|
1770
1908
|
analytics: Analytics = Depends(get_analytics),
|
|
1771
1909
|
) -> dict:
|
|
1910
|
+
settings = get_settings_dependency()
|
|
1911
|
+
if not settings.require_authentication:
|
|
1912
|
+
raise HTTPException(status_code=403, detail="Sign-up is disabled when authentication is disabled.")
|
|
1772
1913
|
print('1')
|
|
1773
1914
|
if not is_valid_email(request.username):
|
|
1774
1915
|
raise HTTPException(status_code=400, detail="Invalid email address")
|
|
@@ -1802,6 +1943,9 @@ def sign_up(
|
|
|
1802
1943
|
|
|
1803
1944
|
@router.post("/forgot-password")
|
|
1804
1945
|
def forgot_password(request: schema.ForgotPasswordRequest) -> dict:
|
|
1946
|
+
settings = get_settings_dependency()
|
|
1947
|
+
if not settings.require_authentication:
|
|
1948
|
+
raise HTTPException(status_code=403, detail="Password resets are disabled when authentication is disabled.")
|
|
1805
1949
|
print('1')
|
|
1806
1950
|
with compair.Session() as session:
|
|
1807
1951
|
print('2')
|
|
@@ -1834,6 +1978,9 @@ def forgot_password(request: schema.ForgotPasswordRequest) -> dict:
|
|
|
1834
1978
|
|
|
1835
1979
|
@router.post("/reset-password")
|
|
1836
1980
|
def reset_password(request: schema.ResetPasswordRequest) -> dict:
|
|
1981
|
+
settings = get_settings_dependency()
|
|
1982
|
+
if not settings.require_authentication:
|
|
1983
|
+
raise HTTPException(status_code=403, detail="Password resets are disabled when authentication is disabled.")
|
|
1837
1984
|
with compair.Session() as session:
|
|
1838
1985
|
print('1')
|
|
1839
1986
|
print(request.token)
|
|
@@ -3352,6 +3499,34 @@ def submit_deactivate_request(
|
|
|
3352
3499
|
return {"message": f"We’ve received your request and will delete your account and data shortly. If you change your mind, reach out within 24 hours at {EMAIL_USER}."}
|
|
3353
3500
|
|
|
3354
3501
|
|
|
3502
|
+
CORE_PATHS: set[str] = {
|
|
3503
|
+
"/login",
|
|
3504
|
+
"/load_session",
|
|
3505
|
+
"/load_groups",
|
|
3506
|
+
"/load_group",
|
|
3507
|
+
"/create_group",
|
|
3508
|
+
"/join_group",
|
|
3509
|
+
"/load_group_users",
|
|
3510
|
+
"/delete_group",
|
|
3511
|
+
"/load_documents",
|
|
3512
|
+
"/load_document",
|
|
3513
|
+
"/load_document_by_id",
|
|
3514
|
+
"/create_doc",
|
|
3515
|
+
"/process_doc",
|
|
3516
|
+
"/status/{task_id}",
|
|
3517
|
+
"/upload/ocr-file",
|
|
3518
|
+
"/ocr-file-result/{task_id}",
|
|
3519
|
+
"/load_chunks",
|
|
3520
|
+
"/load_references",
|
|
3521
|
+
"/load_feedback",
|
|
3522
|
+
"/documents/{document_id}/feedback",
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
for route in router.routes:
|
|
3526
|
+
if isinstance(route, APIRoute) and route.path in CORE_PATHS:
|
|
3527
|
+
core_router.routes.append(route)
|
|
3528
|
+
|
|
3529
|
+
|
|
3355
3530
|
def create_fastapi_app():
|
|
3356
3531
|
"""Backwards-compatible app factory for running this module directly."""
|
|
3357
3532
|
from fastapi import FastAPI
|
compair_core/server/app.py
CHANGED
|
@@ -28,9 +28,15 @@ def create_app(settings: Settings | None = None) -> FastAPI:
|
|
|
28
28
|
|
|
29
29
|
app = FastAPI(title="Compair API", version=resolved_settings.version)
|
|
30
30
|
|
|
31
|
-
from ..api import router as legacy_router
|
|
31
|
+
from ..api import core_router, router as legacy_router
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
if edition == "cloud":
|
|
34
|
+
app.include_router(legacy_router)
|
|
35
|
+
else:
|
|
36
|
+
if resolved_settings.include_legacy_routes:
|
|
37
|
+
app.include_router(legacy_router)
|
|
38
|
+
else:
|
|
39
|
+
app.include_router(core_router)
|
|
34
40
|
app.include_router(capabilities_router)
|
|
35
41
|
|
|
36
42
|
# Share the resolved settings with request handlers
|
|
@@ -11,10 +11,13 @@ router = APIRouter(tags=["meta"])
|
|
|
11
11
|
@router.get("/capabilities")
|
|
12
12
|
def capabilities(settings: Settings = Depends(get_settings)) -> dict[str, object]:
|
|
13
13
|
edition = settings.edition.lower()
|
|
14
|
+
require_auth = settings.require_authentication
|
|
14
15
|
return {
|
|
15
16
|
"auth": {
|
|
16
17
|
"device_flow": edition == "cloud",
|
|
17
|
-
"password_login":
|
|
18
|
+
"password_login": require_auth,
|
|
19
|
+
"required": require_auth,
|
|
20
|
+
"single_user": not require_auth,
|
|
18
21
|
},
|
|
19
22
|
"inputs": {
|
|
20
23
|
"text": True,
|
|
@@ -35,4 +38,5 @@ def capabilities(settings: Settings = Depends(get_settings)) -> dict[str, object
|
|
|
35
38
|
},
|
|
36
39
|
"server": "Compair Cloud" if edition == "cloud" else "Compair Core",
|
|
37
40
|
"version": settings.version,
|
|
41
|
+
"legacy_routes": settings.include_legacy_routes,
|
|
38
42
|
}
|
compair_core/server/settings.py
CHANGED
|
@@ -16,6 +16,10 @@ class Settings(BaseSettings):
|
|
|
16
16
|
billing_enabled: bool = False
|
|
17
17
|
integrations_enabled: bool = False
|
|
18
18
|
premium_models: bool = False
|
|
19
|
+
require_authentication: bool = False
|
|
20
|
+
single_user_username: str = "compair-local@example.com"
|
|
21
|
+
single_user_name: str = "Compair Local User"
|
|
22
|
+
include_legacy_routes: bool = False
|
|
19
23
|
|
|
20
24
|
# Core/local storage defaults
|
|
21
25
|
local_upload_dir: str = "~/.compair-core/data/uploads"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: compair-core
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.12
|
|
4
4
|
Summary: Open-source foundation of the Compair collaboration platform.
|
|
5
5
|
Author: RocketResearch, Inc.
|
|
6
6
|
License: MIT
|
|
@@ -89,6 +89,9 @@ Key environment variables for the core edition:
|
|
|
89
89
|
- `COMPAIR_SQLITE_DIR` / `COMPAIR_SQLITE_NAME` – override the default local SQLite path (falls back to `./compair_data` if `/data` is not writable).
|
|
90
90
|
- `COMPAIR_LOCAL_MODEL_URL` – endpoint for your local embeddings/feedback service (defaults to `http://local-model:9000`).
|
|
91
91
|
- `COMPAIR_EMAIL_BACKEND` – the core mailer logs emails to stdout; cloud overrides this with transactional delivery.
|
|
92
|
+
- `COMPAIR_REQUIRE_AUTHENTICATION` (`true`) – set to `false` to run the API in single-user mode without login or account management. When disabled, Compair auto-provisions a local user, group, and long-lived session token so you can upload documents immediately.
|
|
93
|
+
- `COMPAIR_SINGLE_USER_USERNAME` / `COMPAIR_SINGLE_USER_NAME` – override the email-style username and display name that are used for the auto-provisioned local user in single-user mode.
|
|
94
|
+
- `COMPAIR_INCLUDE_LEGACY_ROUTES` (`false`) – opt-in to the full legacy API surface (used by the hosted product) when running the core edition. Leave unset to expose only the streamlined single-user endpoints in Swagger.
|
|
92
95
|
|
|
93
96
|
See `compair_core/server/settings.py` for the full settings surface.
|
|
94
97
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
compair_core/__init__.py,sha256=ktPgTk1QCd7PF-CUzfcd49JvkEut68SEefx2qcL5M5s,122
|
|
2
|
-
compair_core/api.py,sha256=
|
|
2
|
+
compair_core/api.py,sha256=XkTj7YrZMtTceNoe4G7hq9IMNETFXcjM1v-dF7HlynA,135954
|
|
3
3
|
compair_core/compair/__init__.py,sha256=V2mqe6UQEvY4U8XL8T-TtCRNDWVUMNeCLZ8nsYQLvr4,2494
|
|
4
4
|
compair_core/compair/celery_app.py,sha256=OM_Saza9yC9Q0kz_WXctfswrKkG7ruT52Zl5E4guiT0,640
|
|
5
5
|
compair_core/compair/default_groups.py,sha256=dbacrFkSjqEQZ_uoFU5gYhgIoP_3lmvz6LJNHCJvxlw,498
|
|
@@ -17,9 +17,9 @@ compair_core/compair_email/email_core.py,sha256=da7JxTo5ude55mB7UNLlpNp8xenYwoPa
|
|
|
17
17
|
compair_core/compair_email/templates.py,sha256=JVlLdJEcpu14mVKRAYRIPIw2JGy70kG70mfjXgby-To,206
|
|
18
18
|
compair_core/compair_email/templates_core.py,sha256=1XzBXGjmM6gApSXq382fxCRiVa5J-iskC3r9QQvmV1U,967
|
|
19
19
|
compair_core/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
compair_core/server/app.py,sha256=
|
|
20
|
+
compair_core/server/app.py,sha256=4FY4Pur1fJUkQAwBApLhPQsnF6BRNZjUQ-X7bJCv2mI,3625
|
|
21
21
|
compair_core/server/deps.py,sha256=0X-Z5JQGeXwbMooWIOC2kXVmsiJIvgUtqkK2PmDjKpI,1557
|
|
22
|
-
compair_core/server/settings.py,sha256=
|
|
22
|
+
compair_core/server/settings.py,sha256=mWE5vgIx3jxm6LzyeH6QL1tCwxQ6bsZIQffVowqtkFQ,1681
|
|
23
23
|
compair_core/server/local_model/__init__.py,sha256=YlzDgorgAjGou9d_W29Xp3TVu08e4t9x8csFxn8cgSE,50
|
|
24
24
|
compair_core/server/local_model/app.py,sha256=2bOLjgAyKnFcng2lmDVMB0G6WgnnVGjHyj2X8TeJjnU,1626
|
|
25
25
|
compair_core/server/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -30,9 +30,9 @@ compair_core/server/providers/noop_analytics.py,sha256=OKw23SObxBlQzFdB0xEBg5qD1
|
|
|
30
30
|
compair_core/server/providers/noop_billing.py,sha256=V18Cpl1D1reM3xhgw-lShGliVpYO8IsiAPWOAIR34jM,1358
|
|
31
31
|
compair_core/server/providers/noop_ocr.py,sha256=fMaJrivDef38-ECgIuTXUBCIm_avgvZf3nQ3UTdFPNI,341
|
|
32
32
|
compair_core/server/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
-
compair_core/server/routers/capabilities.py,sha256=
|
|
34
|
-
compair_core-0.3.
|
|
35
|
-
compair_core-0.3.
|
|
36
|
-
compair_core-0.3.
|
|
37
|
-
compair_core-0.3.
|
|
38
|
-
compair_core-0.3.
|
|
33
|
+
compair_core/server/routers/capabilities.py,sha256=2U9lEzyQRRYftprrvEeM5Lif_5rhiRGqZhIsvYZBaE4,1349
|
|
34
|
+
compair_core-0.3.12.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
35
|
+
compair_core-0.3.12.dist-info/METADATA,sha256=L__UeN0VYvCRMSPEZIDdBebUczdsDRP1CtRJ7XT_0bo,5315
|
|
36
|
+
compair_core-0.3.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
37
|
+
compair_core-0.3.12.dist-info/top_level.txt,sha256=1dpwoLSY2DWQUVGS05Tq0MuFXg8sabYzg4V2deLzzuo,13
|
|
38
|
+
compair_core-0.3.12.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|