Cleanup .pyc and .DS_Store, add new modules, remove legacy services
This commit is contained in:
9
common/services/entitlements/__init__.py
Normal file
9
common/services/entitlements/__init__.py
Normal 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'
|
||||
]
|
||||
223
common/services/entitlements/license_period_services.py
Normal file
223
common/services/entitlements/license_period_services.py
Normal 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
|
||||
@@ -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"""
|
||||
143
common/services/entitlements/license_usage_services.py
Normal file
143
common/services/entitlements/license_usage_services.py
Normal 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
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
5
common/services/user/__init__.py
Normal file
5
common/services/user/__init__.py
Normal 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']
|
||||
Reference in New Issue
Block a user