def main(argv=sys.argv, processor=None): logger = logging.getLogger(__name__) if len(argv) != 2: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) settings = setup_database({}, **settings) session = settings['session'] subscription_model = SubscriptionModel(session) tx_model = TransactionModel(session) maximum_retry = int(settings.get( 'billy.transaction.maximum_retry', TransactionModel.DEFAULT_MAXIMUM_RETRY, )) resolver = DottedNameResolver() if processor is None: processor_factory = settings['billy.processor_factory'] processor_factory = resolver.maybe_resolve(processor_factory) processor = processor_factory() # yield all transactions and commit before we process them, so that # we won't double process them. with db_transaction.manager: logger.info('Yielding transaction ...') subscription_model.yield_transactions() with db_transaction.manager: logger.info('Processing transaction ...') tx_model.process_transactions(processor, maximum_retry=maximum_retry) logger.info('Done')
def setUp(self): from webtest import TestApp from billy import main from billy.models import setup_database from billy.models.tables import DeclarativeBase if hasattr(self, 'settings'): settings = self.settings else: settings = {} # init database db_url = os.environ.get('BILLY_FUNC_TEST_DB', 'sqlite://') settings['sqlalchemy.url'] = db_url if hasattr(ViewTestCase, '_engine'): settings['engine'] = ViewTestCase._engine settings = setup_database({}, **settings) # we want to save and reuse the SQL connection if it is not SQLite, # otherwise, somehow, it will create too many connections to server # and make the testing results in failure if settings['engine'].name != 'sqlite': ViewTestCase._engine = settings['engine'] DeclarativeBase.metadata.bind = settings['engine'] DeclarativeBase.metadata.create_all() app = main({}, **settings) self.testapp = TestApp(app) self.testapp.session = settings['session']
def main(argv=sys.argv): if len(argv) != 2: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) settings = setup_database({}, **settings) engine = settings['engine'] DeclarativeBase.metadata.create_all(engine)
def setUp(self): self.dummy_processor = DummyProcessor() def model_factory_func(): return self.model_factory if not hasattr(self, 'settings'): self.settings = { 'billy.processor_factory': lambda: self.dummy_processor, 'model_factory_func': model_factory_func, # do not remove when a request is processed, so that we don't # have to use session.add every time 'db_session_cleanup': False, } # init database db_url = os.environ.get('BILLY_FUNC_TEST_DB', 'sqlite://') self.settings['sqlalchemy.url'] = db_url self.settings = setup_database({}, **self.settings) DeclarativeBase.metadata.bind = self.settings['engine'] DeclarativeBase.metadata.create_all() app = main({}, **self.settings) self.testapp = TestApp(app) self.testapp.session = self.settings['session'] self.dummy_request = DummyRequest() # create model factory self.model_factory = ModelFactory( session=self.testapp.session, processor_factory=lambda: self.dummy_processor, settings=self.settings, ) # create all models self.company_model = self.model_factory.create_company_model() self.customer_model = self.model_factory.create_customer_model() self.plan_model = self.model_factory.create_plan_model() self.subscription_model = self.model_factory.create_subscription_model( ) self.invoice_model = self.model_factory.create_invoice_model() self.transaction_model = self.model_factory.create_transaction_model() self.transaction_failure_model = self.model_factory.create_transaction_failure_model( )
def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ # setup database settings = setup_database(global_config, **settings) config = Configurator( settings=settings, request_factory=APIRequest, ) # add basic authentication parsing config.add_tween('billy.api.auth.basic_auth_tween_factory') # provides table entity to json renderers config.include('.renderers') # provides api views config.include('.api') config.scan(ignore=b'billy.tests') return config.make_wsgi_app()
def main(argv=sys.argv): if len(argv) < 2 or len(argv) > 3: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) settings = setup_database({}, **settings) engine = settings['engine'] DeclarativeBase.metadata.create_all(engine) if len(argv) != 3: return alembic_uri = argv[2] # load the Alembic configuration and generate the # version table, "stamping" it with the most recent rev: from alembic.config import Config from alembic import command alembic_cfg = Config(alembic_uri) command.stamp(alembic_cfg, 'head')
def setUp(self): self.dummy_processor = DummyProcessor() def model_factory_func(): return self.model_factory if not hasattr(self, "settings"): self.settings = { "billy.processor_factory": lambda: self.dummy_processor, "model_factory_func": model_factory_func, # do not remove when a request is processed, so that we don't # have to use session.add every time "db_session_cleanup": False, } # init database db_url = os.environ.get("BILLY_FUNC_TEST_DB", "sqlite://") self.settings["sqlalchemy.url"] = db_url self.settings = setup_database({}, **self.settings) DeclarativeBase.metadata.bind = self.settings["engine"] DeclarativeBase.metadata.create_all() app = main({}, **self.settings) self.testapp = TestApp(app) self.testapp.session = self.settings["session"] self.dummy_request = DummyRequest() # create model factory self.model_factory = ModelFactory( session=self.testapp.session, processor_factory=lambda: self.dummy_processor, settings=self.settings ) # create all models self.company_model = self.model_factory.create_company_model() self.customer_model = self.model_factory.create_customer_model() self.plan_model = self.model_factory.create_plan_model() self.subscription_model = self.model_factory.create_subscription_model() self.invoice_model = self.model_factory.create_invoice_model() self.transaction_model = self.model_factory.create_transaction_model() self.transaction_failure_model = self.model_factory.create_transaction_failure_model()
def setUp(self): self.dummy_processor = DummyProcessor() def model_factory_func(): return self.model_factory if not hasattr(self, 'settings'): self.settings = { 'billy.processor_factory': lambda: self.dummy_processor, 'model_factory_func': model_factory_func, } # init database db_url = os.environ.get('BILLY_FUNC_TEST_DB', 'sqlite://') self.settings['sqlalchemy.url'] = db_url self.settings = setup_database({}, **self.settings) DeclarativeBase.metadata.bind = self.settings['engine'] DeclarativeBase.metadata.create_all() app = main({}, **self.settings) self.testapp = TestApp(app) self.testapp.session = self.settings['session'] self.dummy_request = DummyRequest() # create model factory self.model_factory = ModelFactory( session=self.testapp.session, processor_factory=lambda: self.dummy_processor, settings=self.settings, ) # create all models self.company_model = self.model_factory.create_company_model() self.customer_model = self.model_factory.create_customer_model() self.plan_model = self.model_factory.create_plan_model() self.subscription_model = self.model_factory.create_subscription_model() self.invoice_model = self.model_factory.create_invoice_model() self.transaction_model = self.model_factory.create_transaction_model() self.transaction_failure_model = self.model_factory.create_transaction_failure_model()
def main(argv=sys.argv, processor=None): logger = logging.getLogger(__name__) if len(argv) != 2: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) settings = setup_database({}, **settings) session = settings['session'] subscription_model = SubscriptionModel(session) tx_model = TransactionModel(session) maximum_retry = int( settings.get( 'billy.transaction.maximum_retry', TransactionModel.DEFAULT_MAXIMUM_RETRY, )) resolver = DottedNameResolver() if processor is None: processor_factory = settings['billy.processor_factory'] processor_factory = resolver.maybe_resolve(processor_factory) processor = processor_factory() # yield all transactions and commit before we process them, so that # we won't double process them. with db_transaction.manager: logger.info('Yielding transaction ...') subscription_model.yield_transactions() with db_transaction.manager: logger.info('Processing transaction ...') tx_model.process_transactions(processor, maximum_retry=maximum_retry) logger.info('Done')
def main(argv=sys.argv, processor=None): logger = logging.getLogger(__name__) if len(argv) != 2: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) settings = setup_database({}, **settings) session = settings['session'] try: if processor is None: processor_factory = get_processor_factory(settings) else: processor_factory = lambda: processor factory = ModelFactory( session=session, processor_factory=processor_factory, settings=settings, ) subscription_model = factory.create_subscription_model() tx_model = factory.create_transaction_model() # yield all transactions and commit before we process them, so that # we won't double process them. with db_transaction.manager: logger.info('Yielding transaction ...') subscription_model.yield_invoices() with db_transaction.manager: logger.info('Processing transaction ...') tx_model.process_transactions() logger.info('Done') finally: session.close()
def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ # setup database settings = setup_database(global_config, **settings) config = Configurator( settings=settings, request_factory=APIRequest, authentication_policy=AuthenticationPolicy(), authorization_policy=ACLAuthorizationPolicy(), default_permission='view', ) # add basic authentication parsing config.add_tween('.api.auth.basic_auth_tween_factory') # add access-control-allow-origin header setting config.add_tween('.api.allow_origin.allow_origin_tween_factory') # provides table entity to json renderers config.include('.renderers') # provides api views config.include('.api') config.scan(ignore=b'billy.tests') return config.make_wsgi_app()
def test_main_with_crash(self): dummy_processor = DummyProcessor() dummy_processor.debit = mock.Mock() tx_guids = set() debits = [] def mock_charge(transaction): if dummy_processor.debit.call_count == 2: raise KeyboardInterrupt uri = 'MOCK_DEBIT_URI_FOR_{}'.format(transaction.guid) if transaction.guid in tx_guids: return dict( processor_uri=uri, status=TransactionModel.statuses.SUCCEEDED, ) tx_guids.add(transaction.guid) debits.append(uri) return dict( processor_uri=uri, status=TransactionModel.statuses.SUCCEEDED, ) dummy_processor.debit.side_effect = mock_charge cfg_path = os.path.join(self.temp_dir, 'config.ini') with open(cfg_path, 'wt') as f: f.write( textwrap.dedent("""\ [app:main] use = egg:billy sqlalchemy.url = sqlite:///%(here)s/billy.sqlite """)) initializedb.main([initializedb.__file__, cfg_path]) settings = get_appsettings(cfg_path) settings = setup_database({}, **settings) session = settings['session'] factory = ModelFactory( session=session, processor_factory=lambda: dummy_processor, settings=settings, ) company_model = factory.create_company_model() customer_model = factory.create_customer_model() plan_model = factory.create_plan_model() subscription_model = factory.create_subscription_model() with db_transaction.manager: company = company_model.create('my_secret_key') plan = plan_model.create( company=company, plan_type=plan_model.types.DEBIT, amount=10, frequency=plan_model.frequencies.MONTHLY, ) customer = customer_model.create(company=company, ) subscription_model.create( customer=customer, plan=plan, funding_instrument_uri='/v1/cards/tester', ) subscription_model.create( customer=customer, plan=plan, funding_instrument_uri='/v1/cards/tester', ) with self.assertRaises(KeyboardInterrupt): process_transactions.main( [process_transactions.__file__, cfg_path], processor=dummy_processor) process_transactions.main([process_transactions.__file__, cfg_path], processor=dummy_processor) # here is the story, we have two subscriptions here # # Subscription1 # Subscription2 # # And the time is not advanced, so we should only have two transactions # to be yielded and processed. However, we assume bad thing happens # durring the process. We let the second call to charge of processor # raises a KeyboardInterrupt error. So, it would look like this # # charge for transaction from Subscription1 # charge for transaction from Subscription2 (Crash) # # Then, we perform the process_transactions again, if it works # correctly, the first transaction is already yield and processed. # # charge for transaction from Subscription2 # # So, there would only be two charges in processor. This is mainly # for making sure we won't duplicate charges/payouts self.assertEqual(len(debits), 2)
def test_main_with_crash(self): dummy_processor = DummyProcessor() dummy_processor.debit = mock.Mock() tx_guids = set() debits = [] def mock_charge(transaction): if dummy_processor.debit.call_count == 2: raise KeyboardInterrupt uri = 'MOCK_DEBIT_URI_FOR_{}'.format(transaction.guid) if transaction.guid in tx_guids: return dict( processor_uri=uri, status=TransactionModel.statuses.SUCCEEDED, ) tx_guids.add(transaction.guid) debits.append(uri) return dict( processor_uri=uri, status=TransactionModel.statuses.SUCCEEDED, ) dummy_processor.debit.side_effect = mock_charge cfg_path = os.path.join(self.temp_dir, 'config.ini') with open(cfg_path, 'wt') as f: f.write(textwrap.dedent("""\ [app:main] use = egg:billy sqlalchemy.url = sqlite:///%(here)s/billy.sqlite """)) initializedb.main([initializedb.__file__, cfg_path]) settings = get_appsettings(cfg_path) settings = setup_database({}, **settings) session = settings['session'] factory = ModelFactory( session=session, processor_factory=lambda: dummy_processor, settings=settings, ) company_model = factory.create_company_model() customer_model = factory.create_customer_model() plan_model = factory.create_plan_model() subscription_model = factory.create_subscription_model() with db_transaction.manager: company = company_model.create('my_secret_key') plan = plan_model.create( company=company, plan_type=plan_model.types.DEBIT, amount=10, frequency=plan_model.frequencies.MONTHLY, ) customer = customer_model.create( company=company, ) subscription_model.create( customer=customer, plan=plan, funding_instrument_uri='/v1/cards/tester', ) subscription_model.create( customer=customer, plan=plan, funding_instrument_uri='/v1/cards/tester', ) with self.assertRaises(KeyboardInterrupt): process_transactions.main([process_transactions.__file__, cfg_path], processor=dummy_processor) process_transactions.main([process_transactions.__file__, cfg_path], processor=dummy_processor) # here is the story, we have two subscriptions here # # Subscription1 # Subscription2 # # And the time is not advanced, so we should only have two transactions # to be yielded and processed. However, we assume bad thing happens # durring the process. We let the second call to charge of processor # raises a KeyboardInterrupt error. So, it would look like this # # charge for transaction from Subscription1 # charge for transaction from Subscription2 (Crash) # # Then, we perform the process_transactions again, if it works # correctly, the first transaction is already yield and processed. # # charge for transaction from Subscription2 # # So, there would only be two charges in processor. This is mainly # for making sure we won't duplicate charges/payouts self.assertEqual(len(debits), 2)
def test_main_with_crash(self): from pyramid.paster import get_appsettings from billy.models import setup_database from billy.models.company import CompanyModel from billy.models.customer import CustomerModel from billy.models.plan import PlanModel from billy.models.subscription import SubscriptionModel from billy.scripts import initializedb from billy.scripts import process_transactions class MockProcessor(object): def __init__(self): self.charges = {} self.tx_sn = 0 self.called_times = 0 def create_customer(self, customer): return 'MOCK_PROCESSOR_CUSTOMER_ID' def prepare_customer(self, customer, payment_uri=None): pass def charge(self, transaction): self.called_times += 1 if self.called_times == 2: raise KeyboardInterrupt guid = transaction.guid if guid in self.charges: return self.charges[guid] self.charges[guid] = self.tx_sn self.tx_sn += 1 mock_processor = MockProcessor() cfg_path = os.path.join(self.temp_dir, 'config.ini') with open(cfg_path, 'wt') as f: f.write( textwrap.dedent("""\ [app:main] use = egg:billy sqlalchemy.url = sqlite:///%(here)s/billy.sqlite """)) initializedb.main([initializedb.__file__, cfg_path]) settings = get_appsettings(cfg_path) settings = setup_database({}, **settings) session = settings['session'] company_model = CompanyModel(session) customer_model = CustomerModel(session) plan_model = PlanModel(session) subscription_model = SubscriptionModel(session) with db_transaction.manager: company_guid = company_model.create('my_secret_key') plan_guid = plan_model.create( company_guid=company_guid, plan_type=plan_model.TYPE_CHARGE, amount=10, frequency=plan_model.FREQ_MONTHLY, ) customer_guid = customer_model.create(company_guid=company_guid, ) subscription_model.create( customer_guid=customer_guid, plan_guid=plan_guid, payment_uri='/v1/cards/tester', ) subscription_model.create( customer_guid=customer_guid, plan_guid=plan_guid, payment_uri='/v1/cards/tester', ) with self.assertRaises(KeyboardInterrupt): process_transactions.main( [process_transactions.__file__, cfg_path], processor=mock_processor) process_transactions.main([process_transactions.__file__, cfg_path], processor=mock_processor) # here is the story, we have two subscriptions here # # Subscription1 # Subscription2 # # And the time is not advanced, so we should only have two transactions # to be yielded and processed. However, we assume bad thing happens # durring the process. We let the second call to charge of processor # raises a KeyboardInterrupt error. So, it would look like this # # charge for transaction from Subscription1 # charge for transaction from Subscription2 (Crash) # # Then, we perform the process_transactions again, if it works # correctly, the first transaction is already yield and processed. # # charge for transaction from Subscription2 # # So, there would only be two charges in processor. This is mainly # for making sure we won't duplicate charges/payouts self.assertEqual(len(mock_processor.charges), 2)
def test_main_with_crash(self): from pyramid.paster import get_appsettings from billy.models import setup_database from billy.models.company import CompanyModel from billy.models.customer import CustomerModel from billy.models.plan import PlanModel from billy.models.subscription import SubscriptionModel from billy.scripts import initializedb from billy.scripts import process_transactions class MockProcessor(object): def __init__(self): self.charges = {} self.tx_sn = 0 self.called_times = 0 def create_customer(self, customer): return 'MOCK_PROCESSOR_CUSTOMER_ID' def prepare_customer(self, customer, payment_uri=None): pass def charge(self, transaction): self.called_times += 1 if self.called_times == 2: raise KeyboardInterrupt guid = transaction.guid if guid in self.charges: return self.charges[guid] self.charges[guid] = self.tx_sn self.tx_sn += 1 mock_processor = MockProcessor() cfg_path = os.path.join(self.temp_dir, 'config.ini') with open(cfg_path, 'wt') as f: f.write(textwrap.dedent("""\ [app:main] use = egg:billy sqlalchemy.url = sqlite:///%(here)s/billy.sqlite """)) initializedb.main([initializedb.__file__, cfg_path]) settings = get_appsettings(cfg_path) settings = setup_database({}, **settings) session = settings['session'] company_model = CompanyModel(session) customer_model = CustomerModel(session) plan_model = PlanModel(session) subscription_model = SubscriptionModel(session) with db_transaction.manager: company_guid = company_model.create('my_secret_key') plan_guid = plan_model.create( company_guid=company_guid, plan_type=plan_model.TYPE_CHARGE, amount=10, frequency=plan_model.FREQ_MONTHLY, ) customer_guid = customer_model.create( company_guid=company_guid, ) subscription_model.create( customer_guid=customer_guid, plan_guid=plan_guid, payment_uri='/v1/cards/tester', ) subscription_model.create( customer_guid=customer_guid, plan_guid=plan_guid, payment_uri='/v1/cards/tester', ) with self.assertRaises(KeyboardInterrupt): process_transactions.main([process_transactions.__file__, cfg_path], processor=mock_processor) process_transactions.main([process_transactions.__file__, cfg_path], processor=mock_processor) # here is the story, we have two subscriptions here # # Subscription1 # Subscription2 # # And the time is not advanced, so we should only have two transactions # to be yielded and processed. However, we assume bad thing happens # durring the process. We let the second call to charge of processor # raises a KeyboardInterrupt error. So, it would look like this # # charge for transaction from Subscription1 # charge for transaction from Subscription2 (Crash) # # Then, we perform the process_transactions again, if it works # correctly, the first transaction is already yield and processed. # # charge for transaction from Subscription2 # # So, there would only be two charges in processor. This is mainly # for making sure we won't duplicate charges/payouts self.assertEqual(len(mock_processor.charges), 2)