diff --git a/artifice/api/web.py b/artifice/api/web.py index ca5bf2f..c334f67 100644 --- a/artifice/api/web.py +++ b/artifice/api/web.py @@ -1,6 +1,7 @@ import flask from flask import Flask, Blueprint from artifice import interface, database +from artifice.sales_order import RatesFile from artifice.models import UsageEntry, SalesOrder, Tenant, billing import sqlalchemy from sqlalchemy import create_engine, func @@ -170,7 +171,7 @@ def run_usage_collection(): return json.dumps(resp) -def generate_sales_order(tenant, session, end): +def generate_sales_order(tenant, session, end, rates): db = database.Database(session) session.begin() @@ -196,7 +197,7 @@ def generate_sales_order(tenant, session, end): # and will probably result in the CSV exporter being changed. billable = billing.build_billable(usage, session) session.close() - exporter = invoicer(start, end, config["export_config"]) + exporter = invoicer(start, end, config["export_config"], rates) exporter.bill(billable) exporter.close() return {"id": tenant.id, @@ -262,9 +263,10 @@ def run_sales_order_generation(): # Handled like this for a later move to Celery distributed workers resp = {"tenants": []} + rates = RatesFile(config['export_config']) for tenant in tenant_query: - resp['tenants'].append(generate_sales_order(tenant, session, end)) + resp['tenants'].append(generate_sales_order(tenant, session, end, rates)) return 200, resp diff --git a/artifice/clerk_mixins.py b/artifice/clerk_mixins.py index d630d45..989b413 100644 --- a/artifice/clerk_mixins.py +++ b/artifice/clerk_mixins.py @@ -2,8 +2,7 @@ import requests from decimal import Decimal -class ClerkRatesMixin(object): - +class ClerkRatesSource(object): def rate(self, name, loc_name): url = "http://10.5.36.32/" url = (url + "regions/" + loc_name + diff --git a/artifice/plugins/csv_exporter.py b/artifice/plugins/csv_exporter.py index fcac4f0..49a7114 100644 --- a/artifice/plugins/csv_exporter.py +++ b/artifice/plugins/csv_exporter.py @@ -5,13 +5,14 @@ from artifice import sales_order from decimal import * -class Csv(sales_order.RatesFileMixin, sales_order.SalesOrder): +class Csv(sales_order.SalesOrder): - def __init__(self, start, end, config): - super(Csv, self).__init__(start, end, config) + def __init__(self, start, end, config, rates): + super(Csv, self).__init__(start, end, config, rates) self.lines = {} self.total = Decimal(0.0) self.tenant = None + self.rates = rates def _bill(self, tenant): """Generates the lines for a sales order for the tenant.""" @@ -49,7 +50,7 @@ class Csv(sales_order.RatesFileMixin, sales_order.SalesOrder): # GET REGION FROM CONFIG: region = 'wellington' - rate = self.rate(service.name, region) + rate = self.rates.rate(service.name, region) cost = usage * rate total_cost += cost appendee.append(service.name) diff --git a/artifice/sales_order.py b/artifice/sales_order.py index f55a3cf..b9b6bc5 100644 --- a/artifice/sales_order.py +++ b/artifice/sales_order.py @@ -18,7 +18,7 @@ class SalesOrder(object): # __metaclass__ = requirements - def __init__(self, start, end, config): + def __init__(self, start, end, config, rates): self.start = start self.end = end self.config = config @@ -33,10 +33,12 @@ class SalesOrder(object): raise NotImplementedError("Not implemented in base class") -class RatesFileMixin(object): +class RatesFile(object): # Mixin # Adds a rates file loader, expecting various things from the # configuration + def __init__(self, config): + self.config = config def rate(self, name, region=None): try: diff --git a/tests/mock_exporter.py b/tests/mock_exporter.py index 88647cd..e26e59e 100644 --- a/tests/mock_exporter.py +++ b/tests/mock_exporter.py @@ -2,7 +2,6 @@ from artifice import sales_order class MockExporter(sales_order.SalesOrder): - def _bill(self, tenant): pass diff --git a/tests/test_csv_exporter.py b/tests/test_csv_exporter.py index 78295b2..e94a0f8 100644 --- a/tests/test_csv_exporter.py +++ b/tests/test_csv_exporter.py @@ -4,6 +4,7 @@ from artifice.plugins import csv_exporter from decimal import Decimal import csv import os +import mock config = { @@ -27,44 +28,18 @@ class TestCSVExporter(test_interface.TestInterface): tenant = helpers.build_billable(numb_resources, volume) self.tenant = tenant + # mock rates provider that just yields 1.0 for everything. + rates = mock.Mock() + rates.rate.return_value = Decimal(1.0) + sales_order = csv_exporter.Csv(self.start, self.end, - csv_config) + csv_config, rates) sales_order.bill(tenant) self.filename = sales_order.filename sales_order.close() def get_rate(self, service): - try: - self.__rates - except AttributeError: - self.__rates = {} - if not self.__rates: - self.__rates = {} - try: - fh = open(config["rates"]["file"]) - # Makes no opinions on the file structure - reader = csv.reader(fh, delimiter="|") - for row in reader: - # The default layout is expected to be: - # location | rate name | rate measurement | rate value - self.__rates[row[1].strip()] = { - "cost": Decimal(row[3].strip()), - "region": row[0].strip(), - "measures": row[2].strip() - } - if not self.__rates: - raise IndexError("malformed rates CSV!") - fh.close() - except KeyError: - # couldn't actually find the useful info for rateS? - print "Couldn't find rates info configuration option!" - raise - except IndexError: - raise IndexError("Malformed rates CSV!") - except IOError: - print "Couldn't open the file!" - raise - return self.__rates[service]["cost"] # ignore the regions-ness for now + return Decimal(1.0); def test_generate_csv(self): """Generates a CSV, checks that: