@@ -294,10 +294,10 @@ CELERY_BEAT_SCHEDULE = { 'task': 'payments.tasks.send_low_balance_message', 'schedule': crontab(0, 8), }, - 'execute_recurring_payments': { - 'task': 'payments.tasks.execute_recurring_payments', - 'schedule': crontab(*env.list('RECURRING_PAYMENT_CRONTAB_SCHEDULE', [])), - }, + # 'execute_recurring_payments': { + # 'task': 'payments.tasks.execute_recurring_payments', + # 'schedule': crontab(*env.list('RECURRING_PAYMENT_CRONTAB_SCHEDULE', [])), + # }, } CACHES = { @@ -53,16 +53,27 @@ def healthz_status(request): urlpatterns = [] -urlpatterns += [ - path('api/v1/schema/', SpectacularAPIView.as_view(), name='schema'), - path( - 'api/v1/schema/swagger-ui/', - SpectacularSwaggerView.as_view(url_name='schema'), - ), -] +if settings.DEBUG: + urlpatterns += [ + path('api/v1/schema/', SpectacularAPIView.as_view(), name='schema'), + path( + 'api/v1/schema/swagger-ui/', + SpectacularSwaggerView.as_view(url_name='schema'), + ), + path( + 'api/v1/schema-public/', + SpectacularAPIView.as_view(urlconf=['backend.public']), + name='schema-public', + ), + path( + 'api/v1/public-view/', + SpectacularSwaggerView.as_view(url_name='schema-public'), + ), + ] + api.docs_url = '/docs' + compatibility_api.docs_url = '/docs' + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) -api.docs_url = '/docs' -compatibility_api.docs_url = '/docs' urlpatterns += [ path('api/v1/healthz/', healthz_status), @@ -73,16 +84,7 @@ urlpatterns += [ path('api/v1/reports/', include('reports.urls')), path('api/v1/chats/', include('tools.chats.urls')), path('api/v1/media/', include('tools.media.urls')), - path( - 'api/v1/schema-public/', - SpectacularAPIView.as_view(urlconf=['backend.public']), - name='schema-public', - ), path('api/v1/public/', include('tools.public_api.urls')), - path( - 'api/v1/public-view/', - SpectacularSwaggerView.as_view(url_name='schema-public'), - ), path('api/v1/api/', api.urls), path('api/v1/', compatibility_api.urls), ] @@ -1,18 +1,17 @@ import time -import httpx - -from backend import settings -from decimal import Decimal from datetime import timedelta +from decimal import Decimal from io import BytesIO from typing import Any +import httpx from django.core.files import File +from backend import settings from messages.models import Message - -from ml_model.exceptions import ModelTimeoutError, GenerationException +from ml_model.exceptions import GenerationException, ModelTimeoutError from ml_model.services.base import SimpleService +from poller.models import Proxy class Fluxlorafast(SimpleService): @@ -43,12 +42,27 @@ class Fluxlorafast(SimpleService): ) -> list[Message]: messages: list[Message] = [] for image in images: + for proxy in Proxy.objects.all(): + client = httpx.Client( + base_url='https://queue.fal.run', + headers={'Authorization': f'Key {settings.FAL_API_KEY}'}, + timeout=600, + proxy=f'{proxy.protocol}://{proxy.address}', + ) + + try: + file = File(BytesIO(client.get(image).content), '.png') + except Exception: + continue + + break + messages.append( Message( content_object=self.store, elapsed_time=time, content=prompt, - file=File(BytesIO(httpx.get(image).content), '.png'), + file=file, ) ) if save: @@ -64,20 +78,19 @@ class Fluxlorafast(SimpleService): '4:3': 'landscape_4_3', '16:9': 'landscape_16_9', } - requests_number = 0 start_time = time.time() version = 'flux-lora' translated_prompt = self.translate_prompt(input_message.content) callback_data = dict( { 'prompt': f'in style of raif3_corporate Isometric illustration, ' - f'contemporary vector art style, 3/4 perspective view: {translated_prompt}', + f'contemporary vector art style, 3/4 perspective view: {translated_prompt}', 'model_version': 'fb90c17a-d410-41e7-9961-dc7c687bc627', 'image_size': sizes.get(input_message.info.get('image_size', '1:1')), 'loras': [ { 'path': 'https://v3.fal.media/files/elephant/JthCZoCdAr7' - 'LqnOiNNVCC_pytorch_lora_weights.safetensors' + 'LqnOiNNVCC_pytorch_lora_weights.safetensors' } ], 'guidance_scale': 5, @@ -85,29 +98,52 @@ class Fluxlorafast(SimpleService): 'num_images': input_message.info.get('num_images', 4), } ) - client = httpx.Client( - base_url="https://queue.fal.run", - headers={"Authorization": f"Key {settings.FAL_API_KEY}"}, - timeout=600, - ) - result = client.post( - f'fal-ai/{version}', - json={'prompt': input_message.content, **callback_data}, - ).json() - try: - while True: - status = client.get(result['status_url']).json() - if status.get('status') == 'COMPLETED': - break - requests_number += 1 - if requests_number == 271: - raise ModelTimeoutError - time.sleep(1/3) - except Exception as exc: - raise GenerationException from exc + + final_result = self._request(version, input_message, callback_data) process_time = timedelta(seconds=(time.time() - start_time)) - final_result = client.get(result['response_url']).json() + images = [img['url'] for img in final_result['images']] self.handle_invoice(input_message.content_object.model, input_message=input_message, version=version) msgs = self.save_results(input_message.content, images, process_time, save) return msgs + + def _request(self, version: str, input_message: Message, callback_data: dict) -> dict: + for proxy in Proxy.objects.all(): + requests_number = 0 + client = httpx.Client( + base_url='https://queue.fal.run', + headers={'Authorization': f'Key {settings.FAL_API_KEY}'}, + timeout=600, + proxy=f'{proxy.protocol}://{proxy.address}', + ) + result = client.post( + f'fal-ai/{version}', + json={'prompt': input_message.content, **callback_data}, + ).json() + + is_success = False + + while True: + if requests_number == 271 / len(Proxy.objects.all()): + break + + try: + status = client.get(result['status_url']).json() + except Exception: + requests_number += 1 + continue + if status.get('status') == 'COMPLETED': + is_success = True + break + + time.sleep(1 / 3) + + if is_success: + break + + if not is_success: + raise GenerationException from ModelTimeoutError + + final_result = client.get(result['response_url']).json() + + return final_result @@ -211,7 +211,10 @@ async def create_payment_link(request, body: NewSubscriptionSchema): try: if request.auth.account_type not in {'regular', 'business_host'}: raise HttpError(401, 'Unauthorized') - payment_plan = await PaymentPlan.objects.aget(uid=body.uid) + is_corporate = request.auth.account_type == 'business_host' + payment_plan = await PaymentPlan.objects.aget( + uid=body.uid, price__gt=0, is_corporate=is_corporate, is_visible=True, individual=False + ) payment_url = await PaymentService(request.auth).create_payment_link(payment_plan) logger.info( 'Payment link endpoint completed: email=%s plan_uid=%s', @@ -99,6 +99,13 @@ class PlansAPITest(BaseAuthorizedAPITest): cls.hidden_plan = PaymentPlan.objects.create( price=500, tokens_per_plan=50, is_corporate=False, is_visible=False ) + cls.individual_plan = PaymentPlan.objects.create( + price=300, + tokens_per_plan=30, + is_corporate=False, + is_visible=True, + individual=True, + ) def test_unauthorized_status_code(self) -> None: response = self.client.get(self.ENDPOINT) @@ -147,6 +154,14 @@ class PlansAPITest(BaseAuthorizedAPITest): plan_uids = [str(plan['uid']) for plan in plans] self.assertNotIn(str(self.hidden_plan.uid), plan_uids) + def test_create_payment_link_rejects_hidden_plan(self) -> None: + response = self.post(data={'uid': str(self.hidden_plan.uid)}) + self.assertEqual(response.status_code, 400) + + def test_create_payment_link_rejects_individual_plan(self) -> None: + response = self.post(data={'uid': str(self.individual_plan.uid)}) + self.assertEqual(response.status_code, 400) + def test_zero_price_plans(self) -> None: plans = self.get().json() plan_uids = [str(plan['uid']) for plan in plans]