Rename KYC models (Application/Profile) and add public schema with MongoDB
All checks were successful
Build Docker Image / build (push) Successful in 3m7s
All checks were successful
Build Docker Image / build (push) Successful in 3m7s
This commit is contained in:
@@ -145,3 +145,7 @@ ODOO_INTERNAL_URL = os.getenv('ODOO_INTERNAL_URL', 'odoo:8069')
|
||||
TEMPORAL_HOST = os.getenv('TEMPORAL_HOST', 'temporal:7233')
|
||||
TEMPORAL_NAMESPACE = os.getenv('TEMPORAL_NAMESPACE', 'default')
|
||||
TEMPORAL_TASK_QUEUE = os.getenv('TEMPORAL_TASK_QUEUE', 'platform-worker')
|
||||
|
||||
# MongoDB connection (for company monitoring data)
|
||||
MONGODB_URI = os.getenv('MONGODB_URI', '')
|
||||
MONGODB_DB = os.getenv('MONGODB_DB', 'optovia')
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from kyc_app.views import UserGraphQLView
|
||||
from kyc_app.views import UserGraphQLView, M2MGraphQLView, PublicGraphQLView
|
||||
from kyc_app.schemas.user_schema import user_schema
|
||||
from kyc_app.schemas.m2m_schema import m2m_schema
|
||||
from kyc_app.schemas.public_schema import public_schema
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('graphql/user/', csrf_exempt(UserGraphQLView.as_view(graphiql=True, schema=user_schema))),
|
||||
path('graphql/m2m/', csrf_exempt(M2MGraphQLView.as_view(graphiql=True, schema=m2m_schema))),
|
||||
path('graphql/public/', csrf_exempt(PublicGraphQLView.as_view(graphiql=True, schema=public_schema))),
|
||||
]
|
||||
@@ -1,21 +1,21 @@
|
||||
from django.contrib import admin, messages
|
||||
from .models import KYCRequest, KYCMonitoring, KYCRequestRussia
|
||||
from .models import KYCApplication, KYCProfile, KYCDetailsRussia
|
||||
from .temporal import KycWorkflowClient
|
||||
|
||||
|
||||
@admin.action(description="Start KYC workflow")
|
||||
def start_kyc_workflow(modeladmin, request, queryset):
|
||||
"""Start KYC workflow for selected requests."""
|
||||
for kyc_request in queryset:
|
||||
workflow_id = KycWorkflowClient.start(kyc_request)
|
||||
"""Start KYC workflow for selected applications."""
|
||||
for kyc_app in queryset:
|
||||
workflow_id = KycWorkflowClient.start(kyc_app)
|
||||
if workflow_id:
|
||||
messages.success(request, f"Workflow started for {kyc_request.uuid}")
|
||||
messages.success(request, f"Workflow started for {kyc_app.uuid}")
|
||||
else:
|
||||
messages.error(request, f"Failed to start workflow for {kyc_request.uuid}")
|
||||
messages.error(request, f"Failed to start workflow for {kyc_app.uuid}")
|
||||
|
||||
|
||||
@admin.register(KYCRequest)
|
||||
class KYCRequestAdmin(admin.ModelAdmin):
|
||||
@admin.register(KYCApplication)
|
||||
class KYCApplicationAdmin(admin.ModelAdmin):
|
||||
list_display = ('uuid', 'user_id', 'team_name', 'country_code', 'workflow_status', 'contact_person', 'created_at')
|
||||
list_filter = ('workflow_status', 'country_code', 'created_at')
|
||||
search_fields = ('uuid', 'user_id', 'team_name', 'contact_email', 'contact_person')
|
||||
@@ -40,8 +40,8 @@ class KYCRequestAdmin(admin.ModelAdmin):
|
||||
)
|
||||
|
||||
|
||||
@admin.register(KYCMonitoring)
|
||||
class KYCMonitoringAdmin(admin.ModelAdmin):
|
||||
@admin.register(KYCProfile)
|
||||
class KYCProfileAdmin(admin.ModelAdmin):
|
||||
list_display = ('uuid', 'user_id', 'team_name', 'country_code', 'workflow_status', 'contact_person', 'created_at')
|
||||
list_filter = ('workflow_status', 'country_code', 'created_at')
|
||||
search_fields = ('uuid', 'user_id', 'team_name', 'contact_email', 'contact_person')
|
||||
@@ -65,8 +65,8 @@ class KYCMonitoringAdmin(admin.ModelAdmin):
|
||||
)
|
||||
|
||||
|
||||
@admin.register(KYCRequestRussia)
|
||||
class KYCRequestRussiaAdmin(admin.ModelAdmin):
|
||||
@admin.register(KYCDetailsRussia)
|
||||
class KYCDetailsRussiaAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'company_name', 'inn', 'ogrn')
|
||||
search_fields = ('company_name', 'inn', 'ogrn')
|
||||
ordering = ('-id',)
|
||||
|
||||
@@ -35,7 +35,7 @@ class AbstractKycBase(models.Model):
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='kyc_requests'
|
||||
related_name='%(class)s_set'
|
||||
)
|
||||
object_id = models.PositiveIntegerField(null=True, blank=True)
|
||||
country_details = GenericForeignKey('content_type', 'object_id')
|
||||
@@ -49,24 +49,16 @@ class AbstractKycBase(models.Model):
|
||||
abstract = True
|
||||
|
||||
|
||||
class KYCRequest(AbstractKycBase):
|
||||
"""Главная модель KYC заявки. Ссылается на детали страны через ContentType."""
|
||||
|
||||
content_type = models.ForeignKey(
|
||||
ContentType,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='kyc_requests'
|
||||
)
|
||||
object_id = models.PositiveIntegerField(null=True, blank=True)
|
||||
country_details = GenericForeignKey('content_type', 'object_id')
|
||||
class KYCApplication(AbstractKycBase):
|
||||
"""KYC заявка на верификацию. Одноразовый процесс."""
|
||||
|
||||
class Meta:
|
||||
db_table = 'kyc_requests'
|
||||
db_table = 'kyc_requests' # Сохраняем для совместимости
|
||||
verbose_name = 'KYC Application'
|
||||
verbose_name_plural = 'KYC Applications'
|
||||
|
||||
def __str__(self):
|
||||
return f"KYC {self.user_id} - {self.workflow_status}"
|
||||
return f"KYC Application {self.user_id} - {self.workflow_status}"
|
||||
|
||||
def get_country_data(self) -> dict:
|
||||
"""Получить данные страны как словарь для Temporal workflow."""
|
||||
@@ -75,27 +67,28 @@ class KYCRequest(AbstractKycBase):
|
||||
return {}
|
||||
|
||||
|
||||
class KYCMonitoring(AbstractKycBase):
|
||||
"""KYC мониторинг (копия заявки для долгосрочного наблюдения)."""
|
||||
class KYCProfile(AbstractKycBase):
|
||||
"""KYC профиль компании для долгосрочного мониторинга.
|
||||
|
||||
content_type = models.ForeignKey(
|
||||
ContentType,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='kyc_monitoring'
|
||||
)
|
||||
object_id = models.PositiveIntegerField(null=True, blank=True)
|
||||
country_details = GenericForeignKey('content_type', 'object_id')
|
||||
Создается после успешной верификации (approval) заявки.
|
||||
Используется Dagster для сбора данных о компании.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
db_table = 'kyc_monitoring'
|
||||
db_table = 'kyc_monitoring' # Сохраняем для совместимости
|
||||
verbose_name = 'KYC Profile'
|
||||
verbose_name_plural = 'KYC Profiles'
|
||||
|
||||
def __str__(self):
|
||||
return f"KYC Monitoring {self.user_id} - {self.workflow_status}"
|
||||
return f"KYC Profile {self.user_id} - {self.workflow_status}"
|
||||
|
||||
|
||||
class KYCRequestRussia(models.Model):
|
||||
# Aliases for backwards compatibility
|
||||
KYCRequest = KYCApplication
|
||||
KYCMonitoring = KYCProfile
|
||||
|
||||
|
||||
class KYCDetailsRussia(models.Model):
|
||||
"""Детали KYC для России. Отдельная модель без наследования."""
|
||||
|
||||
# Данные компании от DaData
|
||||
@@ -113,6 +106,8 @@ class KYCRequestRussia(models.Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'kyc_details_russia'
|
||||
verbose_name = 'KYC Details Russia'
|
||||
verbose_name_plural = 'KYC Details Russia'
|
||||
|
||||
def __str__(self):
|
||||
return f"KYC Russia: {self.company_name} (ИНН: {self.inn})"
|
||||
@@ -130,3 +125,7 @@ class KYCRequestRussia(models.Model):
|
||||
"bik": self.bik,
|
||||
"correspondent_account": self.correspondent_account,
|
||||
}
|
||||
|
||||
|
||||
# Alias for backwards compatibility
|
||||
KYCRequestRussia = KYCDetailsRussia
|
||||
|
||||
98
kyc_app/schemas/m2m_schema.py
Normal file
98
kyc_app/schemas/m2m_schema.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
M2M GraphQL Schema for internal service-to-service calls.
|
||||
|
||||
This endpoint is called by Temporal worker to create KYCProfile records.
|
||||
No authentication required (internal network only).
|
||||
"""
|
||||
|
||||
import graphene
|
||||
|
||||
from ..models import KYCApplication, KYCProfile
|
||||
|
||||
|
||||
class CreateKycProfileMutation(graphene.Mutation):
|
||||
"""Create KYCProfile record from existing KYCApplication.
|
||||
|
||||
Called after KYC approval to create long-term monitoring profile.
|
||||
"""
|
||||
|
||||
class Arguments:
|
||||
kyc_application_id = graphene.String(required=True)
|
||||
|
||||
success = graphene.Boolean()
|
||||
profile_uuid = graphene.String()
|
||||
message = graphene.String()
|
||||
|
||||
def mutate(self, info, kyc_application_id: str):
|
||||
try:
|
||||
# Find the KYCApplication
|
||||
kyc_application = KYCApplication.objects.get(uuid=kyc_application_id)
|
||||
|
||||
# Check if profile already exists for this user/team
|
||||
existing = KYCProfile.objects.filter(
|
||||
user_id=kyc_application.user_id,
|
||||
team_name=kyc_application.team_name,
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
return CreateKycProfileMutation(
|
||||
success=True,
|
||||
profile_uuid=str(existing.uuid),
|
||||
message="Profile already exists",
|
||||
)
|
||||
|
||||
# Create KYCProfile by copying fields from KYCApplication
|
||||
profile = KYCProfile.objects.create(
|
||||
user_id=kyc_application.user_id,
|
||||
team_name=kyc_application.team_name,
|
||||
country_code=kyc_application.country_code,
|
||||
workflow_status='active', # Approved = active for profile
|
||||
score=kyc_application.score,
|
||||
contact_person=kyc_application.contact_person,
|
||||
contact_email=kyc_application.contact_email,
|
||||
contact_phone=kyc_application.contact_phone,
|
||||
content_type=kyc_application.content_type,
|
||||
object_id=kyc_application.object_id,
|
||||
approved_by=kyc_application.approved_by,
|
||||
approved_at=kyc_application.approved_at,
|
||||
)
|
||||
|
||||
return CreateKycProfileMutation(
|
||||
success=True,
|
||||
profile_uuid=str(profile.uuid),
|
||||
message="Profile created",
|
||||
)
|
||||
|
||||
except KYCApplication.DoesNotExist:
|
||||
return CreateKycProfileMutation(
|
||||
success=False,
|
||||
profile_uuid="",
|
||||
message=f"KYCApplication not found: {kyc_application_id}",
|
||||
)
|
||||
except Exception as e:
|
||||
return CreateKycProfileMutation(
|
||||
success=False,
|
||||
profile_uuid="",
|
||||
message=str(e),
|
||||
)
|
||||
|
||||
|
||||
class M2MQuery(graphene.ObjectType):
|
||||
"""M2M Query - health check only."""
|
||||
|
||||
health = graphene.String()
|
||||
|
||||
def resolve_health(self, info):
|
||||
return "ok"
|
||||
|
||||
|
||||
class M2MMutation(graphene.ObjectType):
|
||||
"""M2M Mutations for internal service calls."""
|
||||
|
||||
# New name
|
||||
create_kyc_profile = CreateKycProfileMutation.Field()
|
||||
# Old name for backwards compatibility
|
||||
create_kyc_monitoring = CreateKycProfileMutation.Field()
|
||||
|
||||
|
||||
m2m_schema = graphene.Schema(query=M2MQuery, mutation=M2MMutation)
|
||||
264
kyc_app/schemas/public_schema.py
Normal file
264
kyc_app/schemas/public_schema.py
Normal file
@@ -0,0 +1,264 @@
|
||||
"""
|
||||
Public GraphQL Schema for company data.
|
||||
|
||||
This endpoint provides:
|
||||
- companyTeaser: public data (no auth required)
|
||||
- companyFull: full data (requires authentication)
|
||||
|
||||
Data is read from MongoDB company_documents collection.
|
||||
Queries can be by INN (direct) or by KYC Profile UUID.
|
||||
"""
|
||||
|
||||
import graphene
|
||||
from django.conf import settings
|
||||
from pymongo import MongoClient
|
||||
|
||||
from ..models import KYCProfile, KYCDetailsRussia
|
||||
|
||||
|
||||
def get_mongo_client():
|
||||
"""Get MongoDB client."""
|
||||
if not settings.MONGODB_URI:
|
||||
return None
|
||||
return MongoClient(settings.MONGODB_URI)
|
||||
|
||||
|
||||
def get_company_documents(inn: str) -> list:
|
||||
"""Get all documents for a company by INN."""
|
||||
client = get_mongo_client()
|
||||
if not client:
|
||||
return []
|
||||
|
||||
try:
|
||||
db = client[settings.MONGODB_DB]
|
||||
collection = db["company_documents"]
|
||||
return list(collection.find({"inn": inn}))
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def get_inn_by_profile_uuid(profile_uuid: str) -> str | None:
|
||||
"""Get INN from KYCProfile by its UUID."""
|
||||
try:
|
||||
profile = KYCProfile.objects.get(uuid=profile_uuid)
|
||||
# Get country details (KYCDetailsRussia) via GenericForeignKey
|
||||
if profile.country_details and isinstance(profile.country_details, KYCDetailsRussia):
|
||||
return profile.country_details.inn
|
||||
return None
|
||||
except KYCProfile.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def aggregate_company_data(documents: list) -> dict:
|
||||
"""Aggregate data from multiple source documents into summary."""
|
||||
if not documents:
|
||||
return {}
|
||||
|
||||
summary = {
|
||||
"inn": None,
|
||||
"ogrn": None,
|
||||
"name": None,
|
||||
"company_type": None,
|
||||
"registration_year": None,
|
||||
"is_active": True,
|
||||
"address": None,
|
||||
"director": None,
|
||||
"capital": None,
|
||||
"activities": [],
|
||||
"sources": [],
|
||||
"last_updated": None,
|
||||
}
|
||||
|
||||
for doc in documents:
|
||||
source = doc.get("source", "unknown")
|
||||
summary["sources"].append(source)
|
||||
|
||||
data = doc.get("data", {})
|
||||
|
||||
# Extract common fields
|
||||
if not summary["inn"]:
|
||||
summary["inn"] = doc.get("inn")
|
||||
|
||||
if not summary["ogrn"] and data.get("ogrn"):
|
||||
summary["ogrn"] = data["ogrn"]
|
||||
|
||||
if not summary["name"] and data.get("name"):
|
||||
summary["name"] = data["name"]
|
||||
|
||||
# Parse company type from name
|
||||
if not summary["company_type"] and summary["name"]:
|
||||
name = summary["name"].upper()
|
||||
if "ООО" in name or "ОБЩЕСТВО С ОГРАНИЧЕННОЙ" in name:
|
||||
summary["company_type"] = "ООО"
|
||||
elif "АО" in name or "АКЦИОНЕРНОЕ ОБЩЕСТВО" in name:
|
||||
summary["company_type"] = "АО"
|
||||
elif "ИП" in name or "ИНДИВИДУАЛЬНЫЙ ПРЕДПРИНИМАТЕЛЬ" in name:
|
||||
summary["company_type"] = "ИП"
|
||||
elif "ПАО" in name:
|
||||
summary["company_type"] = "ПАО"
|
||||
|
||||
# Track last update
|
||||
collected_at = doc.get("collected_at")
|
||||
if collected_at:
|
||||
if not summary["last_updated"] or collected_at > summary["last_updated"]:
|
||||
summary["last_updated"] = collected_at
|
||||
|
||||
return summary
|
||||
|
||||
|
||||
class CompanyTeaserType(graphene.ObjectType):
|
||||
"""Public company data (teaser)."""
|
||||
|
||||
company_type = graphene.String(description="Company type: ООО, АО, ИП, etc.")
|
||||
registration_year = graphene.Int(description="Year of registration")
|
||||
is_active = graphene.Boolean(description="Is company active")
|
||||
sources_count = graphene.Int(description="Number of data sources")
|
||||
|
||||
|
||||
class CompanyFullType(graphene.ObjectType):
|
||||
"""Full company data (requires auth)."""
|
||||
|
||||
inn = graphene.String()
|
||||
ogrn = graphene.String()
|
||||
name = graphene.String()
|
||||
company_type = graphene.String()
|
||||
registration_year = graphene.Int()
|
||||
is_active = graphene.Boolean()
|
||||
address = graphene.String()
|
||||
director = graphene.String()
|
||||
capital = graphene.String()
|
||||
activities = graphene.List(graphene.String)
|
||||
sources = graphene.List(graphene.String)
|
||||
last_updated = graphene.DateTime()
|
||||
|
||||
|
||||
class PublicQuery(graphene.ObjectType):
|
||||
"""Public queries - no authentication required."""
|
||||
|
||||
# Query by KYC Profile UUID (preferred - used by frontend)
|
||||
company_teaser_by_profile = graphene.Field(
|
||||
CompanyTeaserType,
|
||||
profile_uuid=graphene.String(required=True),
|
||||
description="Get public company teaser data by KYC Profile UUID",
|
||||
)
|
||||
|
||||
company_full_by_profile = graphene.Field(
|
||||
CompanyFullType,
|
||||
profile_uuid=graphene.String(required=True),
|
||||
description="Get full company data by KYC Profile UUID (requires auth)",
|
||||
)
|
||||
|
||||
# Query by INN (legacy/direct)
|
||||
company_teaser = graphene.Field(
|
||||
CompanyTeaserType,
|
||||
inn=graphene.String(required=True),
|
||||
description="Get public company teaser data by INN",
|
||||
)
|
||||
|
||||
company_full = graphene.Field(
|
||||
CompanyFullType,
|
||||
inn=graphene.String(required=True),
|
||||
description="Get full company data by INN (requires auth)",
|
||||
)
|
||||
|
||||
health = graphene.String()
|
||||
|
||||
def resolve_health(self, info):
|
||||
return "ok"
|
||||
|
||||
def resolve_company_teaser_by_profile(self, info, profile_uuid: str):
|
||||
"""Return public teaser data by KYC Profile UUID."""
|
||||
inn = get_inn_by_profile_uuid(profile_uuid)
|
||||
if not inn:
|
||||
return None
|
||||
|
||||
documents = get_company_documents(inn)
|
||||
if not documents:
|
||||
return None
|
||||
|
||||
summary = aggregate_company_data(documents)
|
||||
|
||||
return CompanyTeaserType(
|
||||
company_type=summary.get("company_type"),
|
||||
registration_year=summary.get("registration_year"),
|
||||
is_active=summary.get("is_active", True),
|
||||
sources_count=len(summary.get("sources", [])),
|
||||
)
|
||||
|
||||
def resolve_company_full_by_profile(self, info, profile_uuid: str):
|
||||
"""Return full company data by KYC Profile UUID (requires auth)."""
|
||||
# Check authentication
|
||||
user_id = getattr(info.context, 'user_id', None)
|
||||
if not user_id:
|
||||
return None # Not authenticated
|
||||
|
||||
inn = get_inn_by_profile_uuid(profile_uuid)
|
||||
if not inn:
|
||||
return None
|
||||
|
||||
documents = get_company_documents(inn)
|
||||
if not documents:
|
||||
return None
|
||||
|
||||
summary = aggregate_company_data(documents)
|
||||
|
||||
return CompanyFullType(
|
||||
inn=summary.get("inn"),
|
||||
ogrn=summary.get("ogrn"),
|
||||
name=summary.get("name"),
|
||||
company_type=summary.get("company_type"),
|
||||
registration_year=summary.get("registration_year"),
|
||||
is_active=summary.get("is_active", True),
|
||||
address=summary.get("address"),
|
||||
director=summary.get("director"),
|
||||
capital=summary.get("capital"),
|
||||
activities=summary.get("activities", []),
|
||||
sources=summary.get("sources", []),
|
||||
last_updated=summary.get("last_updated"),
|
||||
)
|
||||
|
||||
def resolve_company_teaser(self, info, inn: str):
|
||||
"""Return public teaser data by INN (no auth required)."""
|
||||
documents = get_company_documents(inn)
|
||||
if not documents:
|
||||
return None
|
||||
|
||||
summary = aggregate_company_data(documents)
|
||||
|
||||
return CompanyTeaserType(
|
||||
company_type=summary.get("company_type"),
|
||||
registration_year=summary.get("registration_year"),
|
||||
is_active=summary.get("is_active", True),
|
||||
sources_count=len(summary.get("sources", [])),
|
||||
)
|
||||
|
||||
def resolve_company_full(self, info, inn: str):
|
||||
"""Return full company data by INN (requires auth)."""
|
||||
# Check authentication
|
||||
user_id = getattr(info.context, 'user_id', None)
|
||||
if not user_id:
|
||||
return None # Not authenticated
|
||||
|
||||
documents = get_company_documents(inn)
|
||||
if not documents:
|
||||
return None
|
||||
|
||||
summary = aggregate_company_data(documents)
|
||||
|
||||
return CompanyFullType(
|
||||
inn=summary.get("inn"),
|
||||
ogrn=summary.get("ogrn"),
|
||||
name=summary.get("name"),
|
||||
company_type=summary.get("company_type"),
|
||||
registration_year=summary.get("registration_year"),
|
||||
is_active=summary.get("is_active", True),
|
||||
address=summary.get("address"),
|
||||
director=summary.get("director"),
|
||||
capital=summary.get("capital"),
|
||||
activities=summary.get("activities", []),
|
||||
sources=summary.get("sources", []),
|
||||
last_updated=summary.get("last_updated"),
|
||||
)
|
||||
|
||||
|
||||
public_schema = graphene.Schema(query=PublicQuery)
|
||||
@@ -1,13 +1,14 @@
|
||||
import graphene
|
||||
from graphene_django import DjangoObjectType
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from ..models import KYCRequest, KYCRequestRussia
|
||||
from ..models import KYCApplication, KYCDetailsRussia
|
||||
from ..temporal import KycWorkflowClient
|
||||
|
||||
|
||||
class KYCRequestType(DjangoObjectType):
|
||||
class KYCApplicationType(DjangoObjectType):
|
||||
"""GraphQL type for KYC Application (заявка)."""
|
||||
class Meta:
|
||||
model = KYCRequest
|
||||
model = KYCApplication
|
||||
fields = '__all__'
|
||||
|
||||
country_data = graphene.JSONString()
|
||||
@@ -17,13 +18,15 @@ class KYCRequestType(DjangoObjectType):
|
||||
return self.get_country_data()
|
||||
|
||||
|
||||
class KYCRequestRussiaType(DjangoObjectType):
|
||||
class KYCDetailsRussiaType(DjangoObjectType):
|
||||
"""GraphQL type for Russia-specific KYC details."""
|
||||
class Meta:
|
||||
model = KYCRequestRussia
|
||||
model = KYCDetailsRussia
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class KYCRequestRussiaInput(graphene.InputObjectType):
|
||||
class KYCApplicationRussiaInput(graphene.InputObjectType):
|
||||
"""Input for creating KYC Application for Russia."""
|
||||
companyName = graphene.String(required=True)
|
||||
companyFullName = graphene.String(required=True)
|
||||
inn = graphene.String(required=True)
|
||||
@@ -38,11 +41,12 @@ class KYCRequestRussiaInput(graphene.InputObjectType):
|
||||
contactPhone = graphene.String(required=True)
|
||||
|
||||
|
||||
class CreateKYCRequestRussia(graphene.Mutation):
|
||||
class CreateKYCApplicationRussia(graphene.Mutation):
|
||||
"""Create KYC Application for Russian company."""
|
||||
class Arguments:
|
||||
input = KYCRequestRussiaInput(required=True)
|
||||
input = KYCApplicationRussiaInput(required=True)
|
||||
|
||||
kyc_request = graphene.Field(KYCRequestType)
|
||||
kyc_application = graphene.Field(KYCApplicationType)
|
||||
success = graphene.Boolean()
|
||||
|
||||
def mutate(self, info, input):
|
||||
@@ -52,7 +56,7 @@ class CreateKYCRequestRussia(graphene.Mutation):
|
||||
raise Exception("Not authenticated")
|
||||
|
||||
# 1. Create Russia details
|
||||
russia_details = KYCRequestRussia.objects.create(
|
||||
russia_details = KYCDetailsRussia.objects.create(
|
||||
company_name=input.companyName,
|
||||
company_full_name=input.companyFullName,
|
||||
inn=input.inn,
|
||||
@@ -64,49 +68,67 @@ class CreateKYCRequestRussia(graphene.Mutation):
|
||||
correspondent_account=input.correspondentAccount or '',
|
||||
)
|
||||
|
||||
# 2. Create main KYCRequest with reference to details
|
||||
kyc_request = KYCRequest.objects.create(
|
||||
# 2. Create main KYC Application with reference to details
|
||||
kyc_application = KYCApplication.objects.create(
|
||||
user_id=user_id,
|
||||
team_name=input.companyName,
|
||||
country_code='RU',
|
||||
contact_person=input.contactPerson,
|
||||
contact_email=input.contactEmail,
|
||||
contact_phone=input.contactPhone,
|
||||
content_type=ContentType.objects.get_for_model(KYCRequestRussia),
|
||||
content_type=ContentType.objects.get_for_model(KYCDetailsRussia),
|
||||
object_id=russia_details.id,
|
||||
)
|
||||
|
||||
# 3. Start Temporal workflow
|
||||
KycWorkflowClient.start(kyc_request)
|
||||
KycWorkflowClient.start(kyc_application)
|
||||
|
||||
return CreateKYCRequestRussia(kyc_request=kyc_request, success=True)
|
||||
return CreateKYCApplicationRussia(kyc_application=kyc_application, success=True)
|
||||
|
||||
|
||||
class UserQuery(graphene.ObjectType):
|
||||
"""User schema - ID token authentication"""
|
||||
kyc_requests = graphene.List(KYCRequestType)
|
||||
kyc_request = graphene.Field(KYCRequestType, uuid=graphene.String(required=True))
|
||||
# Keep old names for backwards compatibility
|
||||
kyc_requests = graphene.List(KYCApplicationType, description="Get user's KYC applications")
|
||||
kyc_request = graphene.Field(KYCApplicationType, uuid=graphene.String(required=True))
|
||||
# New names
|
||||
kyc_applications = graphene.List(KYCApplicationType, description="Get user's KYC applications")
|
||||
kyc_application = graphene.Field(KYCApplicationType, uuid=graphene.String(required=True))
|
||||
|
||||
def resolve_kyc_requests(self, info):
|
||||
# Filter by user_id from JWT token
|
||||
return self._get_applications(info)
|
||||
|
||||
def resolve_kyc_applications(self, info):
|
||||
return self._get_applications(info)
|
||||
|
||||
def _get_applications(self, info):
|
||||
user_id = getattr(info.context, 'user_id', None)
|
||||
if not user_id:
|
||||
return []
|
||||
return KYCRequest.objects.filter(user_id=user_id)
|
||||
return KYCApplication.objects.filter(user_id=user_id)
|
||||
|
||||
def resolve_kyc_request(self, info, uuid):
|
||||
return self._get_application(info, uuid)
|
||||
|
||||
def resolve_kyc_application(self, info, uuid):
|
||||
return self._get_application(info, uuid)
|
||||
|
||||
def _get_application(self, info, uuid):
|
||||
user_id = getattr(info.context, 'user_id', None)
|
||||
if not user_id:
|
||||
return None
|
||||
try:
|
||||
return KYCRequest.objects.get(uuid=uuid, user_id=user_id)
|
||||
except KYCRequest.DoesNotExist:
|
||||
return KYCApplication.objects.get(uuid=uuid, user_id=user_id)
|
||||
except KYCApplication.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
class UserMutation(graphene.ObjectType):
|
||||
"""User mutations - ID token authentication"""
|
||||
create_kyc_request_russia = CreateKYCRequestRussia.Field()
|
||||
# Keep old name for backwards compatibility
|
||||
create_kyc_request_russia = CreateKYCApplicationRussia.Field()
|
||||
# New name
|
||||
create_kyc_application_russia = CreateKYCApplicationRussia.Field()
|
||||
|
||||
|
||||
user_schema = graphene.Schema(query=UserQuery, mutation=UserMutation)
|
||||
|
||||
@@ -13,3 +13,37 @@ class UserGraphQLView(GraphQLView):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['middleware'] = [UserJWTMiddleware()]
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class M2MGraphQLView(GraphQLView):
|
||||
"""M2M endpoint - no authentication (internal network only)."""
|
||||
pass
|
||||
|
||||
|
||||
class OptionalUserJWTMiddleware:
|
||||
"""Middleware that optionally extracts user_id but doesn't fail if missing."""
|
||||
|
||||
def resolve(self, next, root, info, **args):
|
||||
request = info.context
|
||||
|
||||
# Try to extract user_id from Authorization header if present
|
||||
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
|
||||
if auth_header.startswith('Bearer '):
|
||||
try:
|
||||
from .auth import validate_jwt_token
|
||||
token = auth_header.split(' ', 1)[1]
|
||||
payload = validate_jwt_token(token)
|
||||
if payload:
|
||||
request.user_id = payload.get('sub')
|
||||
except Exception:
|
||||
pass # Ignore auth errors - user just won't get full data
|
||||
|
||||
return next(root, info, **args)
|
||||
|
||||
|
||||
class PublicGraphQLView(GraphQLView):
|
||||
"""Public endpoint - optional auth for full data, no auth for teaser."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Use optional auth middleware that doesn't fail on missing token
|
||||
kwargs['middleware'] = [OptionalUserJWTMiddleware()]
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
117
poetry.lock
generated
117
poetry.lock
generated
@@ -414,6 +414,27 @@ files = [
|
||||
asgiref = ">=3.6"
|
||||
django = ">=4.2"
|
||||
|
||||
[[package]]
|
||||
name = "dnspython"
|
||||
version = "2.8.0"
|
||||
description = "DNS toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af"},
|
||||
{file = "dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["black (>=25.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.17.0)", "mypy (>=1.17)", "pylint (>=3)", "pytest (>=8.4)", "pytest-cov (>=6.2.0)", "quart-trio (>=0.12.0)", "sphinx (>=8.2.0)", "sphinx-rtd-theme (>=3.0.0)", "twine (>=6.1.0)", "wheel (>=0.45.0)"]
|
||||
dnssec = ["cryptography (>=45)"]
|
||||
doh = ["h2 (>=4.2.0)", "httpcore (>=1.0.0)", "httpx (>=0.28.0)"]
|
||||
doq = ["aioquic (>=1.2.0)"]
|
||||
idna = ["idna (>=3.10)"]
|
||||
trio = ["trio (>=0.30)"]
|
||||
wmi = ["wmi (>=1.5.1) ; platform_system == \"Windows\""]
|
||||
|
||||
[[package]]
|
||||
name = "graphene"
|
||||
version = "3.4.3"
|
||||
@@ -728,6 +749,100 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte
|
||||
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
|
||||
tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pymongo"
|
||||
version = "4.16.0"
|
||||
description = "PyMongo - the Official MongoDB Python driver"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pymongo-4.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ed162b2227f98d5b270ecbe1d53be56c8c81db08a1a8f5f02d89c7bb4d19591d"},
|
||||
{file = "pymongo-4.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a9390dce61d705a88218f0d7b54d7e1fa1b421da8129fc7c009e029a9a6b81e"},
|
||||
{file = "pymongo-4.16.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:92a232af9927710de08a6c16a9710cc1b175fb9179c0d946cd4e213b92b2a69a"},
|
||||
{file = "pymongo-4.16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d79aa147ce86aef03079096d83239580006ffb684eead593917186aee407767"},
|
||||
{file = "pymongo-4.16.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:19a1c96e7f39c7a59a9cfd4d17920cf9382f6f684faeff4649bf587dc59f8edc"},
|
||||
{file = "pymongo-4.16.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efe020c46ce3c3a89af6baec6569635812129df6fb6cf76d4943af3ba6ee2069"},
|
||||
{file = "pymongo-4.16.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dc2c00bed568732b89e211b6adca389053d5e6d2d5a8979e80b813c3ec4d1f9"},
|
||||
{file = "pymongo-4.16.0-cp310-cp310-win32.whl", hash = "sha256:5b9c6d689bbe5beb156374508133218610e14f8c81e35bc17d7a14e30ab593e6"},
|
||||
{file = "pymongo-4.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:2290909275c9b8f637b0a92eb9b89281e18a72922749ebb903403ab6cc7da914"},
|
||||
{file = "pymongo-4.16.0-cp310-cp310-win_arm64.whl", hash = "sha256:6af1aaa26f0835175d2200e62205b78e7ec3ffa430682e322cc91aaa1a0dbf28"},
|
||||
{file = "pymongo-4.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6f2077ec24e2f1248f9cac7b9a2dfb894e50cc7939fcebfb1759f99304caabef"},
|
||||
{file = "pymongo-4.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d4f7ba040f72a9f43a44059872af5a8c8c660aa5d7f90d5344f2ed1c3c02721"},
|
||||
{file = "pymongo-4.16.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8a0f73af1ea56c422b2dcfc0437459148a799ef4231c6aee189d2d4c59d6728f"},
|
||||
{file = "pymongo-4.16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa30cd16ddd2f216d07ba01d9635c873e97ddb041c61cf0847254edc37d1c60e"},
|
||||
{file = "pymongo-4.16.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d638b0b1b294d95d0fdc73688a3b61e05cc4188872818cd240d51460ccabcb5"},
|
||||
{file = "pymongo-4.16.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:21d02cc10a158daa20cb040985e280e7e439832fc6b7857bff3d53ef6914ad50"},
|
||||
{file = "pymongo-4.16.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fbb8d3552c2ad99d9e236003c0b5f96d5f05e29386ba7abae73949bfebc13dd"},
|
||||
{file = "pymongo-4.16.0-cp311-cp311-win32.whl", hash = "sha256:be1099a8295b1a722d03fb7b48be895d30f4301419a583dcf50e9045968a041c"},
|
||||
{file = "pymongo-4.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:61567f712bda04c7545a037e3284b4367cad8d29b3dec84b4bf3b2147020a75b"},
|
||||
{file = "pymongo-4.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:c53338613043038005bf2e41a2fafa08d29cdbc0ce80891b5366c819456c1ae9"},
|
||||
{file = "pymongo-4.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bd4911c40a43a821dfd93038ac824b756b6e703e26e951718522d29f6eb166a8"},
|
||||
{file = "pymongo-4.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25a6b03a68f9907ea6ec8bc7cf4c58a1b51a18e23394f962a6402f8e46d41211"},
|
||||
{file = "pymongo-4.16.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:91ac0cb0fe2bf17616c2039dac88d7c9a5088f5cb5829b27c9d250e053664d31"},
|
||||
{file = "pymongo-4.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf0ec79e8ca7077f455d14d915d629385153b6a11abc0b93283ed73a8013e376"},
|
||||
{file = "pymongo-4.16.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2d0082631a7510318befc2b4fdab140481eb4b9dd62d9245e042157085da2a70"},
|
||||
{file = "pymongo-4.16.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85dc2f3444c346ea019a371e321ac868a4fab513b7a55fe368f0cc78de8177cc"},
|
||||
{file = "pymongo-4.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dabbf3c14de75a20cc3c30bf0c6527157224a93dfb605838eabb1a2ee3be008d"},
|
||||
{file = "pymongo-4.16.0-cp312-cp312-win32.whl", hash = "sha256:60307bb91e0ab44e560fe3a211087748b2b5f3e31f403baf41f5b7b0a70bd104"},
|
||||
{file = "pymongo-4.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:f513b2c6c0d5c491f478422f6b5b5c27ac1af06a54c93ef8631806f7231bd92e"},
|
||||
{file = "pymongo-4.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:dfc320f08ea9a7ec5b2403dc4e8150636f0d6150f4b9792faaae539c88e7db3b"},
|
||||
{file = "pymongo-4.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d15f060bc6d0964a8bb70aba8f0cb6d11ae99715438f640cff11bbcf172eb0e8"},
|
||||
{file = "pymongo-4.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a19ea46a0fe71248965305a020bc076a163311aefbaa1d83e47d06fa30ac747"},
|
||||
{file = "pymongo-4.16.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:311d4549d6bf1f8c61d025965aebb5ba29d1481dc6471693ab91610aaffbc0eb"},
|
||||
{file = "pymongo-4.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46ffb728d92dd5b09fc034ed91acf5595657c7ca17d4cf3751322cd554153c17"},
|
||||
{file = "pymongo-4.16.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:acda193f440dd88c2023cb00aa8bd7b93a9df59978306d14d87a8b12fe426b05"},
|
||||
{file = "pymongo-4.16.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d9fdb386cf958e6ef6ff537d6149be7edb76c3268cd6833e6c36aa447e4443f"},
|
||||
{file = "pymongo-4.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91899dd7fb9a8c50f09c3c1cf0cb73bfbe2737f511f641f19b9650deb61c00ca"},
|
||||
{file = "pymongo-4.16.0-cp313-cp313-win32.whl", hash = "sha256:2cd60cd1e05de7f01927f8e25ca26b3ea2c09de8723241e5d3bcfdc70eaff76b"},
|
||||
{file = "pymongo-4.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3ead8a0050c53eaa55935895d6919d393d0328ec24b2b9115bdbe881aa222673"},
|
||||
{file = "pymongo-4.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:dbbc5b254c36c37d10abb50e899bc3939bbb7ab1e7c659614409af99bd3e7675"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:8a254d49a9ffe9d7f888e3c677eed3729b14ce85abb08cd74732cead6ccc3c66"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a1bf44e13cf2d44d2ea2e928a8140d5d667304abe1a61c4d55b4906f389fbe64"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f1c5f1f818b669875d191323a48912d3fcd2e4906410e8297bb09ac50c4d5ccc"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77cfd37a43a53b02b7bd930457c7994c924ad8bbe8dff91817904bcbf291b371"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:36ef2fee50eee669587d742fb456e349634b4fcf8926208766078b089054b24b"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55f8d5a6fe2fa0b823674db2293f92d74cd5f970bc0360f409a1fc21003862d3"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9caacac0dd105e2555521002e2d17afc08665187017b466b5753e84c016628e6"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314-win32.whl", hash = "sha256:c789236366525c3ee3cd6e4e450a9ff629a7d1f4d88b8e18a0aea0615fd7ecf8"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b0714d7764efb29bf9d3c51c964aed7c4c7237b341f9346f15ceaf8321fdb35"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:12762e7cc0f8374a8cae3b9f9ed8dabb5d438c7b33329232dd9b7de783454033"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1c01e8a7cd0ea66baf64a118005535ab5bf9f9eb63a1b50ac3935dccf9a54abe"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4c4872299ebe315a79f7f922051061634a64fda95b6b17677ba57ef00b2ba2a4"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:78037d02389745e247fe5ab0bcad5d1ab30726eaac3ad79219c7d6bbb07eec53"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c126fb72be2518395cc0465d4bae03125119136462e1945aea19840e45d89cfc"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3867dc225d9423c245a51eaac2cfcd53dde8e0a8d8090bb6aed6e31bd6c2d4f"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f25001a955073b80510c0c3db0e043dbbc36904fd69e511c74e3d8640b8a5111"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d9885aad05f82fd7ea0c9ca505d60939746b39263fa273d0125170da8f59098"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314t-win32.whl", hash = "sha256:948152b30eddeae8355495f9943a3bf66b708295c0b9b6f467de1c620f215487"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f6e42c1bc985d9beee884780ae6048790eb4cd565c46251932906bdb1630034a"},
|
||||
{file = "pymongo-4.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6b2a20edb5452ac8daa395890eeb076c570790dfce6b7a44d788af74c2f8cf96"},
|
||||
{file = "pymongo-4.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e2d509786344aa844ae243f68f833ca1ac92ac3e35a92ae038e2ceb44aa355ef"},
|
||||
{file = "pymongo-4.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:15bb062c0d6d4b0be650410032152de656a2a9a2aa4e1a7443a22695afacb103"},
|
||||
{file = "pymongo-4.16.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4cd047ba6cc83cc24193b9208c93e134a985ead556183077678c59af7aacc725"},
|
||||
{file = "pymongo-4.16.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96aa7ab896889bf330209d26459e493d00f8855772a9453bfb4520bb1f495baf"},
|
||||
{file = "pymongo-4.16.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:66af44ed23686dd5422307619a6db4b56733c5e36fe8c4adf91326dcf993a043"},
|
||||
{file = "pymongo-4.16.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:03f42396c1b2c6f46f5401c5b185adc25f6113716e16d9503977ee5386fca0fb"},
|
||||
{file = "pymongo-4.16.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d284bf68daffc57516535f752e290609b3b643f4bd54b28fc13cb16a89a8bda6"},
|
||||
{file = "pymongo-4.16.0-cp39-cp39-win32.whl", hash = "sha256:7902882ed0efb7f0e991458ab3b8cf0eb052957264949ece2f09b63c58b04f78"},
|
||||
{file = "pymongo-4.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:e37469602473f41221cea93fd3736708f561f0fa08ab6b2873dd962014390d52"},
|
||||
{file = "pymongo-4.16.0-cp39-cp39-win_arm64.whl", hash = "sha256:2a3ba6be3d8acf64b77cdcd4e36f0e4a8e87965f14a8b09b90ca86f10a1dd2f2"},
|
||||
{file = "pymongo-4.16.0.tar.gz", hash = "sha256:8ba8405065f6e258a6f872fe62d797a28f383a12178c7153c01ed04e845c600c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
dnspython = ">=2.6.1,<3.0.0"
|
||||
|
||||
[package.extras]
|
||||
aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"]
|
||||
docs = ["furo (==2025.12.19)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<9)", "sphinx-autobuild (>=2020.9.1)", "sphinx-rtd-theme (>=2,<4)", "sphinxcontrib-shellcheck (>=1,<2)"]
|
||||
encryption = ["certifi (>=2023.7.22) ; os_name == \"nt\" or sys_platform == \"darwin\"", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.13.0,<2.0.0)"]
|
||||
gssapi = ["pykerberos (>=1.2.4) ; os_name != \"nt\"", "winkerberos (>=0.5.0) ; os_name == \"nt\""]
|
||||
ocsp = ["certifi (>=2023.7.22) ; os_name == \"nt\" or sys_platform == \"darwin\"", "cryptography (>=42.0.0)", "pyopenssl (>=23.2.0)", "requests (>=2.23.0,<3.0)", "service-identity (>=23.1.0)"]
|
||||
snappy = ["python-snappy (>=0.6.0)"]
|
||||
test = ["importlib-metadata (>=7.0) ; python_version < \"3.13\"", "pytest (>=8.2)", "pytest-asyncio (>=0.24.0)"]
|
||||
zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
@@ -1002,4 +1117,4 @@ brotli = ["brotli"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "489568163820e27b9ef91c3484ac57aeff3284d0909f216fe6af37f59dfbc00e"
|
||||
content-hash = "b6022569e46f079bf6775c23e68fd07035e5fa611f0bda14dddbfbbdee5b490f"
|
||||
|
||||
@@ -19,7 +19,8 @@ dependencies = [
|
||||
"sentry-sdk (>=2.47.0,<3.0.0)",
|
||||
"pyjwt (>=2.10.1,<3.0.0)",
|
||||
"cryptography (>=41.0.0)",
|
||||
"temporalio (>=1.20.0,<2.0.0)"
|
||||
"temporalio (>=1.20.0,<2.0.0)",
|
||||
"pymongo (>=4.16.0,<5.0.0)"
|
||||
]
|
||||
|
||||
[build-system]
|
||||
|
||||
Reference in New Issue
Block a user