Skip to content

Commit

Permalink
Merge pull request #33 from victorlin/rewrite
Browse files Browse the repository at this point in the history
Rewrite
  • Loading branch information
mahmoudimus committed Sep 5, 2013
2 parents aaf5ee7 + 8a9237e commit 95e52e4
Show file tree
Hide file tree
Showing 150 changed files with 7,538 additions and 5,622 deletions.
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[run]
omit=
billy/tests/*
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ develop-eggs
.installed.cfg
lib
lib64
cover

# Installer logs
pip-log.txt
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "chef"]
path = chef
url = https://github.com/victorlin/billy-chef.git
2 changes: 1 addition & 1 deletion MAINFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
include distribute_setup.py
recursive-include billy
prune env
prune env
95 changes: 75 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,87 @@ Billy - The Open Source Recurring Billing System, powered by Balanced

## Running It

There are three major parts to billy: the models, the api, and the web layer.
This library currently has the API and the models.
To run billy (development mode), you need to install the package first.
As we don't want to mess the global Python environment, you should
create a virtual environmnet first and switch to it

1. Create a pgsql DB called 'billy' with 'test' user and no password
2. Install requirements ```python setup.py develop```
3. Create the tables: ```python manage.py create_tables```
4. To run the api server run: ```python manage.py run_api```
5. Cron job this hourly: ```python manage.py billy_tasks```
```
virtualenv --no-site-packages env
source env/bin/activate
```

Congrats. You've got recurring billing.
If above works correctly, you should see

## Models
```
(env) $
```

The models should have all the methods necessary to bill recuringly. Generally,
modifactions to the underlying data should be done via the model methods not,
directly. This is to ensure that all the accounting is handled correctly.
in you command line tool. The `(env)` indicates that you are currently
in the virtual Python environment. Then you need to install the billy project.
Here you run

To see how they work check out the interface tests
(tests/models/test_interface.py)
```
python setup.py develop
```

## Api
This should install all required dependencies. Then you need to create
tables in database, here you type

Check out the spec at api/spec.json, which is generated using api/spec.py
```
initialize_billy_db development.ini
```

This should create all necessary tables for you in a default SQLite database.

#### Major Todos:
- Redo import strucutre
- Redo commit/flush/rollback handling.
- Better transient exception handling.
Then, to run the API web server, here you type

```
pserve development.ini --reload
```

To process recurring transactions, here you can type

```
process_billy_tx development.ini
```

You can setup a crontab job to run the process_billy_tx periodically.

## Running Unit and Functional Tests

To run tests, after installing billy project and all dependencies, you need
to install dependencies for testing, here you type:

```
pip install -r test_requirements.txt
```

And to run the tests, here you type

```
python setup.py nosetests
```

or, if you prefer run specific tests, you can run

```
nosetests billy/tests/functional
```

## Running Integration Tests

To run integration tests, here you type

```
nosetests billy/tests/integration
```

The default testing target URL is `http://127.0.0.1:6543`, to modify it, you can
set environment variable `BILLY_TEST_URL`. To change balanced API key, you can set
`BILLY_TEST_PROCESSOR_KEY` variable. For example

```
export BILLY_TEST_URL=http://example-billy-api.com
export BILLY_TEST_PROCESSOR_KEY=MY_SECRET_KEY_HERE
nosetests billy/tests/integration
```
121 changes: 121 additions & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# All Vagrant configuration is done here. The most common configuration
# options are documented and commented below. For a complete reference,
# please see the online documentation at vagrantup.com.

# Every Vagrant virtual environment requires a box to build off of.
config.vm.box = "precise64"

# The url from where the 'config.vm.box' box will be fetched if it
# doesn't already exist on the user's system.
config.vm.box_url = "http://files.vagrantup.com/precise64.box"

# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
# config.vm.network :forwarded_port, guest: 80, host: 8080

# Create a private network, which allows host-only access to the machine
# using a specific IP.
# config.vm.network :private_network, ip: "192.168.33.10"

# Create a public network, which generally matched to bridged network.
# Bridged networks make the machine appear as another physical device on
# your network.
# config.vm.network :public_network

# If true, then any SSH connections made will enable agent forwarding.
# Default value: false
# config.ssh.forward_agent = true

# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
# config.vm.synced_folder "../data", "/vagrant_data"

# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
# config.vm.provider :virtualbox do |vb|
# # Don't boot with headless mode
# vb.gui = true
#
# # Use VBoxManage to customize the VM. For example to change memory:
# vb.customize ["modifyvm", :id, "--memory", "1024"]
# end
#
# View the documentation for the provider you're using for more
# information on available options.

# Enable provisioning with Puppet stand alone. Puppet manifests
# are contained in a directory path relative to this Vagrantfile.
# You will need to create the manifests directory and a manifest in
# the file precise64.pp in the manifests_path directory.
#
# An example Puppet manifest to provision the message of the day:
#
# # group { "puppet":
# # ensure => "present",
# # }
# #
# # File { owner => 0, group => 0, mode => 0644 }
# #
# # file { '/etc/motd':
# # content => "Welcome to your Vagrant-built virtual machine!
# # Managed by Puppet.\n"
# # }
#
# config.vm.provision :puppet do |puppet|
# puppet.manifests_path = "manifests"
# puppet.manifest_file = "init.pp"
# end

# Enable provisioning with chef solo, specifying a cookbooks path, roles
# path, and data_bags path (all relative to this Vagrantfile), and adding
# some recipes and/or roles.
#
config.vm.provision :shell, :inline => "gem install chef --version 11.6.0 --no-rdoc --no-ri --conservative"
config.vm.provision :chef_solo do |chef|
chef.cookbooks_path = "chef/cookbooks"
chef.add_recipe "billy"

chef.json = {
:postgresql => {
:password => {
:postgres => "billie jean"
}
}
}
end

# Enable provisioning with chef server, specifying the chef server URL,
# and the path to the validation key (relative to this Vagrantfile).
#
# The Opscode Platform uses HTTPS. Substitute your organization for
# ORGNAME in the URL and validation key.
#
# If you have your own Chef Server, use the appropriate URL, which may be
# HTTP instead of HTTPS depending on your configuration. Also change the
# validation key to validation.pem.
#
# config.vm.provision :chef_client do |chef|
# chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
# chef.validation_key_path = "ORGNAME-validator.pem"
# end
#
# If you're using the Opscode platform, your validator client is
# ORGNAME-validator, replacing ORGNAME with your organization name.
#
# If you have your own Chef Server, the default validation client name is
# chef-validator, unless you changed the configuration.
#
# chef.validation_client_name = "ORGNAME-validator"
end
27 changes: 27 additions & 0 deletions billy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from __future__ import unicode_literals

from pyramid.config import Configurator

from billy.models import setup_database
from billy.request import APIRequest


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()
60 changes: 6 additions & 54 deletions billy/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,9 @@
from __future__ import unicode_literals
import re

import difflib
from flask import request
from flask.ext.restful import Api
from flask.ext.restful.utils import unauthorized, error_data
from flask.signals import got_request_exception
from werkzeug.http import HTTP_STATUS_CODES
from werkzeug.exceptions import HTTPException


class ApiFixed(Api):

def handle_error(self, e):
"""Error handler for the API transforms a raised exception into a Flask
response, with the appropriate HTTP status code and body.
:param e: the raised Exception object
:type e: Exception
"""
got_request_exception.send(self.app, exception=e)
if isinstance(e, HTTPException):
return e
code = getattr(e, 'code', 500)
data = getattr(e, 'data', error_data(code))

if code >= 500:
self.app.logger.exception("Internal Error")

if code == 404 and ('message' not in data or
data['message'] == HTTP_STATUS_CODES[404]):
rules = dict([(re.sub('(<.*>)', '', rule.rule), rule.rule)
for rule in self.app.url_map.iter_rules()])
close_matches = difflib.get_close_matches(
request.path, rules.keys())
if close_matches:
# If we already have a message, add punctuation and
# continue it.
if "message" in data:
data["message"] += ". "
else:
data["message"] = ""

data['message'] += 'You have requested this URI [' + request.path + \
'] but did you mean ' + \
' or '.join((rules[match]
for match in close_matches)) + ' ?'

resp = self.make_response(data, code)

if code == 401:
resp = unauthorized(resp,
self.app.config.get("HTTP_BASIC_AUTH_REALM", "flask-restful"))

return resp
def includeme(config):
config.include('.company', route_prefix='/v1')
config.include('.customer', route_prefix='/v1')
config.include('.plan', route_prefix='/v1')
config.include('.subscription', route_prefix='/v1')
config.include('.transaction', route_prefix='/v1')
15 changes: 0 additions & 15 deletions billy/api/app.py

This file was deleted.

Loading

0 comments on commit 95e52e4

Please sign in to comment.