Cleanup .pyc and .DS_Store, add new modules, remove legacy services

This commit is contained in:
Josako
2025-05-17 18:46:17 +02:00
parent 5c982fcc2c
commit d2a9092f46
93 changed files with 1620 additions and 80 deletions

View File

@@ -0,0 +1,9 @@
from common.services.entitlements.license_period_services import LicensePeriodServices
from common.services.entitlements.license_usage_services import LicenseUsageServices
from common.services.entitlements.license_tier_services import LicenseTierServices
__all__ = [
'LicensePeriodServices',
'LicenseUsageServices',
'LicenseTierServices'
]

View File

@@ -0,0 +1,223 @@
from dateutil.relativedelta import relativedelta
from datetime import datetime as dt, timezone as tz, timedelta
from flask import current_app
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.sql.expression import and_
from common.extensions import db
from common.models.entitlements import LicensePeriod, License, PeriodStatus, LicenseUsage
from common.utils.eveai_exceptions import EveAILicensePeriodsExceeded, EveAIPendingLicensePeriod, EveAINoActiveLicense
from common.utils.model_logging_utils import set_logging_information, update_logging_information
class LicensePeriodServices:
@staticmethod
def find_current_license_period_for_usage(tenant_id: int) -> LicensePeriod:
"""
Find the current license period for a tenant. It ensures the status of the different license periods are adapted
when required, and a LicenseUsage object is created if required.
Args:
tenant_id: The ID of the tenant to find the license period for
Raises:
EveAIException: and derived classes
"""
current_date = dt.now(tz.utc).date()
license_period = (db.session.query(LicensePeriod)
.filter_by(tenant_id=tenant_id)
.filter(and_(LicensePeriod.period_start_date <= current_date,
LicensePeriod.period_end_date >= current_date))
.first())
if not license_period:
license_period = LicensePeriodServices._create_next_license_period_for_usage(tenant_id)
if license_period:
match license_period.status:
case PeriodStatus.UPCOMING:
LicensePeriodServices._complete_last_license_period()
LicensePeriodServices._activate_license_period(license_period)
if not license_period.license_usage:
new_license_usage = LicenseUsage()
new_license_usage.license_period = license_period
try:
db.session.add(new_license_usage)
db.session.commit()
except SQLAlchemyError as e:
db.session.rollback()
current_app.logger.error(
f"Error creating new license usage for license period {license_period.id}: {str(e)}")
raise e
if license_period.status == PeriodStatus.ACTIVE:
return license_period
else:
# Status is PENDING, so no prepaid payment received. There is no license period we can use.
# We allow for a delay of 5 days before raising an exception.
current_date = dt.now(tz.utc).date()
delta = abs(current_date - license_period.period_start_date)
if delta > timedelta(days=current_app.config.get('ENTITLEMENTS_MAX_PENDING_DAYS', 5)):
raise EveAIPendingLicensePeriod()
case PeriodStatus.ACTIVE:
return license_period
case PeriodStatus.PENDING:
return license_period
else:
raise EveAILicensePeriodsExceeded(license_id=None)
@staticmethod
def _create_next_license_period_for_usage(tenant_id) -> LicensePeriod:
"""
Create a new period for this license using the current license configuration
Args:
tenant_id: The ID of the tenant to create the period for
Returns:
LicensePeriod: The newly created license period
"""
current_date = dt.now(tz.utc).date()
# Zoek de actieve licentie voor deze tenant op de huidige datum
the_license = (db.session.query(License)
.filter_by(tenant_id=tenant_id)
.filter(License.start_date <= current_date)
.filter(License.end_date >= current_date)
.first())
if not the_license:
current_app.logger.error(f"No active license found for tenant {tenant_id} on date {current_date}")
raise EveAINoActiveLicense(tenant_id=tenant_id)
next_period_number = 1
if the_license.periods:
# If there are existing periods, get the next sequential number
next_period_number = max(p.period_number for p in the_license.periods) + 1
if next_period_number > the_license.max_periods:
raise EveAILicensePeriodsExceeded(license_id=the_license.id)
new_license_period = LicensePeriod(
license_id=the_license.id,
tenant_id=tenant_id,
period_number=next_period_number,
period_start=the_license.start_date + relativedelta(months=next_period_number-1),
period_end=the_license.end_date + relativedelta(months=next_period_number, days=-1),
status=PeriodStatus.UPCOMING,
)
set_logging_information(new_license_period, dt.now(tz.utc))
new_license_usage = LicenseUsage(
license_period=new_license_period,
tenant_id=tenant_id,
)
set_logging_information(new_license_usage, dt.now(tz.utc))
try:
db.session.add(new_license_period)
db.session.add(new_license_usage)
db.session.commit()
return new_license_period
except SQLAlchemyError as e:
db.session.rollback()
current_app.logger.error(f"Error creating next license period for tenant {tenant_id}: {str(e)}")
raise e
@staticmethod
def _activate_license_period(license_period_id: int = None, license_period: LicensePeriod = None) -> LicensePeriod:
"""
Activate a license period
Args:
license_period_id: The ID of the license period to activate (optional if license_period is provided)
license_period: The LicensePeriod object to activate (optional if license_period_id is provided)
Returns:
LicensePeriod: The activated license period object
Raises:
ValueError: If neither license_period_id nor license_period is provided
"""
if license_period is None and license_period_id is None:
raise ValueError("Either license_period_id or license_period must be provided")
# Get a license period object if only ID was provided
if license_period is None:
license_period = LicensePeriod.query.get_or_404(license_period_id)
if license_period.upcoming_at is not None:
license_period.pending_at.upcoming_at = dt.now(tz.utc)
license_period.status = PeriodStatus.PENDING
if license_period.prepaid_payment:
# There is a payment received for the given period
license_period.active_at = dt.now(tz.utc)
license_period.status = PeriodStatus.ACTIVE
# Copy snapshot fields from the license to the period
the_license = License.query.get_or_404(license_period.license_id)
license_period.currency = the_license.currency
license_period.basic_fee = the_license.basic_fee
license_period.max_storage_mb = the_license.max_storage_mb
license_period.additional_storage_price = the_license.additional_storage_price
license_period.additional_storage_bucket = the_license.additional_storage_bucket
license_period.included_embedding_mb = the_license.included_embedding_mb
license_period.additional_embedding_price = the_license.additional_embedding_price
license_period.additional_embedding_bucket = the_license.additional_embedding_bucket
license_period.included_interaction_tokens = the_license.included_interaction_tokens
license_period.additional_interaction_token_price = the_license.additional_interaction_token_price
license_period.additional_interaction_bucket = the_license.additional_interaction_bucket
license_period.additional_storage_allowed = the_license.additional_storage_allowed
license_period.additional_embedding_allowed = the_license.additional_embedding_allowed
license_period.additional_interaction_allowed = the_license.additional_interaction_allowed
update_logging_information(license_period, dt.now(tz.utc))
if not license_period.license_usage:
license_period.license_usage = LicenseUsage(
tenant_id=license_period.tenant_id,
license_period=license_period,
)
license_period.license_usage.recalculate_storage()
try:
db.session.add(license_period)
db.session.add(license_period.license_usage)
db.session.commit()
except SQLAlchemyError as e:
db.session.rollback()
current_app.logger.error(f"Error activating license period {license_period_id}: {str(e)}")
raise e
return license_period
@staticmethod
def _complete_last_license_period(tenant_id) -> None:
"""
Complete the active or pending license period for a tenant. This is done by setting the status to COMPLETED.
Args:
tenant_id: De ID van de tenant
"""
# Zoek de licenseperiode voor deze tenant met status ACTIVE of PENDING
active_period = (
db.session.query(LicensePeriod)
.filter_by(tenant_id=tenant_id)
.filter(LicensePeriod.status.in_([PeriodStatus.ACTIVE, PeriodStatus.PENDING]))
.first()
)
# Als er geen actieve periode gevonden is, hoeven we niets te doen
if not active_period:
return
# Zet de gevonden periode op COMPLETED
active_period.status = PeriodStatus.COMPLETED
active_period.completed_at = dt.now(tz.utc)
update_logging_information(active_period, dt.now(tz.utc))
try:
db.session.add(active_period)
db.session.commit()
except SQLAlchemyError as e:
db.session.rollback()
current_app.logger.error(f"Error completing period {active_period.id} for {tenant_id}: {str(e)}")
raise e

View File

@@ -1,17 +1,16 @@
from flask import session, current_app, flash
from flask import session, flash, current_app
from datetime import datetime as dt, timezone as tz
from sqlalchemy.exc import SQLAlchemyError
from common.extensions import db
from common.models.entitlements import PartnerServiceLicenseTier
from common.models.user import Partner, PartnerTenant
from common.models.user import Partner
from common.utils.eveai_exceptions import EveAINoManagementPartnerService
from common.utils.model_logging_utils import set_logging_information
from datetime import datetime as dt, timezone as tz
from common.utils.security_utils import current_user_has_role
class EntitlementServices:
class LicenseTierServices:
@staticmethod
def associate_license_tier_with_partner(license_tier_id):
"""Associate a license tier with a partner"""

View File

@@ -0,0 +1,143 @@
from dateutil.relativedelta import relativedelta
from flask import session, current_app, flash
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.sql.expression import text
from common.extensions import db, cache_manager
from common.models.entitlements import PartnerServiceLicenseTier, License, LicenseUsage, LicensePeriod, PeriodStatus
from common.models.user import Partner, PartnerTenant
from common.services.entitlements import LicensePeriodServices
from common.utils.database import Database
from common.utils.eveai_exceptions import EveAINoManagementPartnerService, EveAINoActiveLicense, \
EveAIStorageQuotaExceeded, EveAIEmbeddingQuotaExceeded, EveAIInteractionQuotaExceeded, EveAILicensePeriodsExceeded, \
EveAIException
from common.utils.model_logging_utils import set_logging_information, update_logging_information
from datetime import datetime as dt, timezone as tz
from common.utils.security_utils import current_user_has_role
class LicenseUsageServices:
@staticmethod
def check_storage_and_embedding_quota(tenant_id: int, file_size_mb: float) -> None:
"""
Check if a tenant can add a new document without exceeding storage and embedding quotas
Args:
tenant_id: ID of the tenant
file_size_mb: Size of the file in MB
Raises:
EveAIStorageQuotaExceeded: If storage quota would be exceeded
EveAIEmbeddingQuotaExceeded: If embedding quota would be exceeded
EveAINoActiveLicense: If no active license is found
EveAIException: For other errors
"""
# Get active license period
license_period = LicensePeriodServices.find_current_license_period_for_usage(tenant_id)
# Early return if both overruns are allowed - no need to check usage at all
if license_period.additional_storage_allowed and license_period.additional_embedding_allowed:
return
# Check storage quota only if overruns are not allowed
if not license_period.additional_storage_allowed:
LicenseUsageServices._validate_storage_quota(license_period, file_size_mb)
# Check embedding quota only if overruns are not allowed
if not license_period.additional_embedding_allowed:
LicenseUsageServices._validate_embedding_quota(license_period, file_size_mb)
@staticmethod
def check_embedding_quota(tenant_id: int, file_size_mb: float) -> None:
"""
Check if a tenant can re-embed a document without exceeding embedding quota
Args:
tenant_id: ID of the tenant
file_size_mb: Size of the file in MB
Raises:
EveAIEmbeddingQuotaExceeded: If embedding quota would be exceeded
EveAINoActiveLicense: If no active license is found
EveAIException: For other errors
"""
# Get active license period
license_period = LicensePeriodServices.find_current_license_period_for_usage(tenant_id)
# Early return if both overruns are allowed - no need to check usage at all
if license_period.additional_embedding_allowed:
return
# Check embedding quota
LicenseUsageServices._validate_embedding_quota(license_period, file_size_mb)
@staticmethod
def check_interaction_quota(tenant_id: int) -> None:
"""
Check if a tenant can execute a specialist without exceeding interaction quota. As it is impossible to estimate
the number of interaction tokens, we only check if the interaction quota are exceeded. So we might have a
limited overrun.
Args:
tenant_id: ID of the tenant
Raises:
EveAIInteractionQuotaExceeded: If interaction quota would be exceeded
EveAINoActiveLicense: If no active license is found
EveAIException: For other errors
"""
# Get active license period
license_period = LicensePeriodServices.find_current_license_period_for_usage(tenant_id)
# Early return if both overruns are allowed - no need to check usage at all
if license_period.additional_interaction_allowed:
return
# Convert tokens to M tokens and check interaction quota
LicenseUsageServices._validate_interaction_quota(license_period)
@staticmethod
def _validate_storage_quota(license_period: LicensePeriod, additional_mb: float) -> None:
"""Check storage quota and raise exception if exceeded"""
current_storage = license_period.license_usage.storage_mb_used or 0
projected_storage = current_storage + additional_mb
max_storage = license_period.max_storage_mb
# Hard limit check (we only get here if overruns are NOT allowed)
if projected_storage > max_storage:
raise EveAIStorageQuotaExceeded(
current_usage=current_storage,
limit=max_storage,
additional=additional_mb
)
@staticmethod
def _validate_embedding_quota(license_period: LicensePeriod, additional_mb: float) -> None:
"""Check embedding quota and raise exception if exceeded"""
current_embedding = license_period.license_usage.embedding_mb_used or 0
projected_embedding = current_embedding + additional_mb
max_embedding = license_period.included_embedding_mb
# Hard limit check (we only get here if overruns are NOT allowed)
if projected_embedding > max_embedding:
raise EveAIEmbeddingQuotaExceeded(
current_usage=current_embedding,
limit=max_embedding,
additional=additional_mb
)
@staticmethod
def _validate_interaction_quota(license_period) -> None:
"""Check interaction quota and raise exception if exceeded (tokens in millions). We might have an overrun!"""
current_tokens = license_period.license_usage.interaction_total_tokens_used / 1_000_000 or 0
max_tokens = license_period.included_interaction_tokens
# Hard limit check (we only get here if overruns are NOT allowed)
if current_tokens > max_tokens:
raise EveAIInteractionQuotaExceeded(
current_usage=current_tokens,
limit=max_tokens
)

View File

@@ -0,0 +1,5 @@
from common.services.user.user_services import UserServices
from common.services.user.partner_services import PartnerServices
from common.services.user.tenant_services import TenantServices
__all__ = ['UserServices', 'PartnerServices', 'TenantServices']