diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48392c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +__pycache__ +*.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..5105040 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +InstAL-Rest fiddling repository diff --git a/api.py b/api.py new file mode 100644 index 0000000..303fa2b --- /dev/null +++ b/api.py @@ -0,0 +1,18 @@ +import os +from instalrest.v1.api import app, models +import instalrest.instalcelery.instalcelery +def create_default(app): + print("CREATE DEFAULT") + models.setup_tables() + host = os.environ.get("INSTAL_FLASK_REST_HOST","0.0.0.0") + port = os.environ.get("INSTAL_FLASK_REST_PORT","5000") + debug = os.environ.get("INSTAL_FLASK_REST_DEBUG",False) + if debug == "False": + debug = False + threads = os.environ.get("INSTAL_FLASK_REST_THREADS",1) + return app + +app_modified = create_default(app) + +if __name__ == "__main__": + app.run() \ No newline at end of file diff --git a/instalrest/__init__.py b/instalrest/__init__.py new file mode 100644 index 0000000..d538f87 --- /dev/null +++ b/instalrest/__init__.py @@ -0,0 +1 @@ +__version__ = "1.0.0" \ No newline at end of file diff --git a/instalrest/docker-compose.yml b/instalrest/docker-compose.yml new file mode 100644 index 0000000..094cd07 --- /dev/null +++ b/instalrest/docker-compose.yml @@ -0,0 +1,23 @@ +version: '2' +services: + instal-db: + image: postgres + ports: + - "5432:5432" + environment: + - POSTGRES_PASSWORD=instal + - POSTGRES_USER=instal + - POSTGRES_DB=instal-rest-v1 + + instal-rabbit: + image: rabbitmq + ports: + - "5672:5672" + environment: + - RABBITMQ_DEFAULT_USER=instal + - RABBITMQ_DEFAULT_PASS=instal + + instal-rest: + image: instal-rest + ports: + - "5000:5000" diff --git a/instalrest/instalcelery/__init__.py b/instalrest/instalcelery/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/instalrest/instalcelery/instalcelery.py b/instalrest/instalcelery/instalcelery.py new file mode 100644 index 0000000..d76cb67 --- /dev/null +++ b/instalrest/instalcelery/instalcelery.py @@ -0,0 +1,26 @@ +from celery import Celery +from instalrest.v1 import models +from tempfile import NamedTemporaryFile +import instal +import simplejson as json +celery_app = Celery('instal-rest', broker='amqp://instal:instal@instal-rabbit:5672//') + +@celery_app.task +def execute_instal_async(instalQuery): + query = models.InstALQuery.get(id=instalQuery) + query.execute_instal() + +@celery_app.task +def get_instal_trace_text(as_json): + json_file = NamedTemporaryFile("w+t") + json_file.write(json.dumps(as_json)) + json_file.seek(0) + text_out = NamedTemporaryFile("w+t") + instal.instaltrace.instal_trace_keyword(json_file=json_file.name, text_file=text_out.name) + text_out.seek(0) + return text_out.read() + +@celery_app.task +def get_instal_inspect(instalModel): + model = models.InstALModel.get(id=instalModel) + return model.get_inspect() \ No newline at end of file diff --git a/instalrest/tests/TestBasic.py b/instalrest/tests/TestBasic.py new file mode 100644 index 0000000..e0127e6 --- /dev/null +++ b/instalrest/tests/TestBasic.py @@ -0,0 +1,124 @@ +from unittest import TestCase +import requests +import simplejson as json +import time + +INSTAL_URL = "http://127.0.0.1:5000" +class Basic(TestCase): + def test_query_execute(self): + model_response = requests.post(INSTAL_URL + "/model/",headers={'Content-Type': 'application/json'}, + data=json.dumps({"institutions": ["institution a; exogenous event e; initially pow(e);" ] })) + assert(model_response.status_code==201) + model_response_json = json.loads(model_response.content) + assert(model_response_json.get("id",0)) + + grounding_response = requests.post(INSTAL_URL + "/model/{}/grounding/".format(model_response_json["id"]),data=json.dumps({}),headers={'Content-Type': 'application/json'}) + assert(grounding_response.status_code==201) + grounding_response_json = json.loads(grounding_response.content) + assert(grounding_response_json.get("id",0)) + + query_response = requests.post(INSTAL_URL + "/model/{}/grounding/{}/query/".format(model_response_json["id"],grounding_response_json["id"]), + headers={'Content-Type': 'application/json'},data=json.dumps({"query" : ["observed(e)"]})) + assert(query_response.status_code==201) + query_response_json = json.loads(query_response.content) + assert(query_response_json.get("id",0)) + #assert(query_response_json.get("json_out",{})) + + time.sleep(3) + response = requests.get(INSTAL_URL + "/model/{}/grounding/{}/query/{}/output/1/".format( + query_response_json.get("grounding", {}).get("model", {}).get("id"), query_response_json.get("grounding", {}).get("id"), + query_response_json.get("id") + ) + , + data=json.dumps( + {"type": "json"} + ), + headers={'Content-Type': 'application/json'}) + + assert (response.status_code == 200) + + def test_query_more_complicated(self): + model_response = requests.post(INSTAL_URL + "/model/", + data=json.dumps({"institutions": ["institution a; type A; exogenous event ex_a(A); inst event in_a(A); initially pow(ex_a(A)), perm(ex_a(A)), perm(in_a(A));", + "institution c; type A; exogenous event ex_b(A); initially pow(ex_b(A));"], + "bridges" : ["bridge b; source a; sink c; in_a(A) xgenerates ex_b(A); initially gpow(a, ex_b(A), c);"]}), + headers={'Content-Type': 'application/json'}) + assert(model_response.status_code==201) + model_response_json = json.loads(model_response.content) + assert(model_response_json.get("id",0)) + + grounding_response = requests.post(INSTAL_URL + "/model/{}/grounding/".format(model_response_json["id"]), data=json.dumps({"types" : {"A" : ["alpha", "beta"]}}), + headers={'Content-Type': 'application/json'}) + assert(grounding_response.status_code==201) + grounding_response_json = json.loads(grounding_response.content) + assert(grounding_response_json.get("id",0)) + + query_response = requests.post(INSTAL_URL + "/model/{}/grounding/{}/query/".format(model_response_json["id"],grounding_response_json["id"]), + data=json.dumps({"query" : []}), + headers = {'Content-Type': 'application/json'}) + assert(query_response.status_code==201) + query_response_json = json.loads(query_response.content) + assert(query_response_json.get("id",0)) + assert(query_response_json.get("json_out",{})) + time.sleep(3) + response = requests.get(INSTAL_URL + "/model/{}/grounding/{}/query/{}/output/1/".format( + query_response_json.get("grounding", {}).get("model", {}).get("id"), query_response_json.get("grounding", {}).get("id"), + query_response_json.get("id") + ) + , + data=json.dumps( + {"type": "json"} + ), + headers={'Content-Type': 'application/json'}) + + assert (response.status_code == 200) + + def test_new_basic(self): + query_response = requests.post(INSTAL_URL + "/new/", + data = json.dumps({ + "institutions": [ + "institution a; type A; exogenous event ex_a(A); inst event in_a(A); initially pow(ex_a(A)), perm(ex_a(A)), perm(in_a(A));", + "institution c; type A; exogenous event ex_b(A); initially pow(ex_b(A));"], + "bridges": [ + "bridge b; source a; sink c; in_a(A) xgenerates ex_b(A); initially gpow(a, ex_b(A), c);"], + "types": {"A": ["alpha", "beta"]}, + "query": ["ex_a(alpha)", "ex_a(beta)"] + }), + headers= {'Content-Type' : 'application/json'}) + assert(query_response.status_code==201) + + json_resp = query_response.json() + time.sleep(3) + response = requests.get(INSTAL_URL + "/model/{}/grounding/{}/query/{}/output/1/".format( + json_resp.get("grounding", {}).get("model", {}).get("id"), json_resp.get("grounding", {}).get("id"), + json_resp.get("id") + ) + , + headers={'Accept' : 'application/json'}) + + assert (response.status_code == 200) + + def test_get_text(self): + query_response = requests.post(INSTAL_URL + "/new/", + data=json.dumps({ + "institutions": [ + "institution a; type A; exogenous event ex_a(A); inst event in_a(A); initially pow(ex_a(A)), perm(ex_a(A)), perm(in_a(A));", + "institution c; type A; exogenous event ex_b(A); initially pow(ex_b(A));"], + "bridges": [ + "bridge b; source a; sink c; in_a(A) xgenerates ex_b(A); initially gpow(a, ex_b(A), c);"], + "types": {"A": ["alpha", "beta"]}, + "query": ["ex_a(alpha)", "ex_a(beta)"] + }), + headers={'Content-Type': 'application/json'}) + assert (query_response.status_code == 201) + + json_resp = query_response.json() + time.sleep(3) + response = requests.get(INSTAL_URL + "/model/{}/grounding/{}/query/{}/output/1/".format( + json_resp.get("grounding", {}).get("model", {}).get("id"), json_resp.get("grounding", {}).get("id"), + json_resp.get("id") + ) + , + headers={'Accept': 'text/plain'}) + + assert (response.status_code == 200) \ No newline at end of file diff --git a/instalrest/tests/TestSmoke.py b/instalrest/tests/TestSmoke.py new file mode 100644 index 0000000..1cc9b97 --- /dev/null +++ b/instalrest/tests/TestSmoke.py @@ -0,0 +1,7 @@ +INSTAL_URL = "http://127.0.0.1:5000" +from unittest import TestCase +import requests +import simplejson as json + +class Smoke(TestCase): + pass \ No newline at end of file diff --git a/instalrest/tests/__init__.py b/instalrest/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/instalrest/v1/Dockerfile b/instalrest/v1/Dockerfile new file mode 100644 index 0000000..b0a7a90 --- /dev/null +++ b/instalrest/v1/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.5.3 +COPY . /code +WORKDIR /code +RUN apt-get update && apt-get install -y \ +libpq-dev \ +libstdc++6 \ +libgcc-4.9-dev \ +g++ \ +gcc +RUN pip install . +RUN pip install -i http://127.0.0.1:6789/ instal +RUN pip install -r instalrest/requirements.txt +CMD ["python", "instalrest/v1/api.py"] \ No newline at end of file diff --git a/instalrest/v1/__init__.py b/instalrest/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/instalrest/v1/api.py b/instalrest/v1/api.py new file mode 100644 index 0000000..ca804ff --- /dev/null +++ b/instalrest/v1/api.py @@ -0,0 +1,159 @@ +import os +from flask import Flask, request, make_response +from flask_restful import Resource, Api, abort +from playhouse.shortcuts import model_to_dict +from instalrest.v1 import models +import simplejson as json +import instalrest +from instalrest.instalcelery.instalcelery import execute_instal_async, get_instal_inspect, celery_app +from peewee import DoesNotExist +app = Flask(__name__) +api = Api(app) + +def make_response_json(data, code): + rq = make_response(data,code) + rq.headers["Content-Type"] = 'application/json' + return rq + +def make_response_text(data, code): + rq = make_response(data, code) + rq.headers["Content-Type"] = 'text/plain' + return rq + +class InstALModelList(Resource): + @api.representation('application/json') + def post(self): + model = models.InstALModel.new_from_form_data(request.get_json()) + return make_response_json(model.to_json(), 201) + + @api.representation('application/json') + def get(self): + model = list(map(model_to_dict, models.InstALModel.select())) + return make_response_json(json.dumps(model), 200) + +class InstALModelInspect(Resource): + @api.representation('application/json') + def get(self, model_id): + try: + model = models.InstALModel.get(id=model_id) + return make_response_json(json.dumps(get_instal_inspect(model_id)),200) + except DoesNotExist as e: + abort(404) + + +class InstALModel(Resource): + @api.representation('application/json') + def get(self, model_id): + try: + model = models.InstALModel.get(id=model_id) + return make_response_json(model.to_json(), 200) + except DoesNotExist as e: + abort(404) + +class InstALGroundingList(Resource): + @api.representation('application/json') + def get(self, model_id): + try: + grounding = list(map(model_to_dict, models.InstALGrounding.select().where( + models.InstALGrounding.model == model_id))) + return make_response_json(json.dumps(grounding),200) + except DoesNotExist as e: + abort(404) + + @api.representation('application/json') + def post(self, model_id): + try: + grounding = models.InstALGrounding.new_from_form_data(request.get_json(), model_id) + return make_response_json(grounding.to_json(),201) + except DoesNotExist as e: + abort(404) + + +class InstALGrounding(Resource): + @api.representation('application/json') + def get(self, model_id, grounding_id): + try: + grounding = models.InstALGrounding.get(id=grounding_id) + return make_response_json(grounding.to_json(),201) + except DoesNotExist as e: + abort(404) + +class InstALQueryList(Resource): + @api.representation('application/json') + def get(self, model_id, grounding_id): + query = list(map(model_to_dict, models.InstALQuery.select().where( + models.InstALQuery.grounding == grounding_id))) + return make_response_json(json.dumps(query),200) + + @api.representation('application/json') + def post(self, model_id, grounding_id): + new_query = models.InstALQuery.new_from_form_data(request.get_json(), grounding_id) + execute_instal_async.delay(new_query.id) + return make_response_json(new_query.to_json(),201) + + +class InstALQuery(Resource): + @api.representation('application/json') + def get(self, model_id, grounding_id, query_id): + try: + query = models.InstALQuery.get(id=query_id) + return make_response_json(query.to_json(),200) + except DoesNotExist as e: + abort(404) + +class InstALOutput(Resource): + @api.representation('application/json') + def get(self, model_id, grounding_id, query_id, answer_set_id): + query = models.InstALQuery.get(id=query_id) + try: + data, mimetype = query.output_from_form_data(request, answer_set_number=answer_set_id) + except query.AnswerSetNotFound as e: + abort(404) + return + except query.AnswerSetRepresentationNotFound as e: + abort(417) + return + if mimetype == 'application/json': + rq = make_response_json(data, 200) + elif mimetype == 'text/plain': + rq = make_response_text(data,200) + else: + abort(417) + return + return rq + +class InstALNew(Resource): + @api.representation('application/json') + def post(self): + new_model = models.InstALModel.new_from_form_data(request.get_json()) + new_grounding = models.InstALGrounding.new_from_form_data(request.get_json(),new_model.id) + new_query = models.InstALQuery.new_from_form_data(request.get_json(),new_grounding.id) + execute_instal_async.delay(new_query.id) + return make_response_json(json.dumps(model_to_dict(new_query)),201) + + +class Up(Resource): + @api.representation('application/json') + def get(self): + return make_response_json(json.dumps({"status" : "ok", + "instalrest_version" : instalrest.__version__, + "total_queries" : models.InstALQuery.select().count(), + "total_groundings" : models.InstALGrounding.select().count(), + "total_models" : models.InstALModel.select().count() + }), 200) + +def add_resources(api): + api.add_resource(Up, '/_up') + + api.add_resource(InstALModelList, '/model/') + api.add_resource(InstALModel, '/model//') + api.add_resource(InstALModelInspect, '/model//inspect/') + api.add_resource(InstALGroundingList, '/model//grounding/') + api.add_resource(InstALGrounding, '/model//grounding//') + api.add_resource(InstALQueryList, '/model//grounding//query/') + api.add_resource(InstALQuery, '/model//grounding//query//') + api.add_resource(InstALOutput, + '/model//grounding//query//output//') + + api.add_resource(InstALNew, '/new/') +api = add_resources(api) \ No newline at end of file diff --git a/instalrest/v1/db-docker-script.sh b/instalrest/v1/db-docker-script.sh new file mode 100755 index 0000000..0c95f1b --- /dev/null +++ b/instalrest/v1/db-docker-script.sh @@ -0,0 +1,2 @@ +docker run --name instal-rest -e POSTGRES_PASSWORD=instal -e POSTGRES_USER=instal -p 5432:5432 -e POSTGRES_DB=instal-rest-v1 postgres + diff --git a/instalrest/v1/models.py b/instalrest/v1/models.py new file mode 100644 index 0000000..3690c08 --- /dev/null +++ b/instalrest/v1/models.py @@ -0,0 +1,208 @@ +from peewee import Model, PostgresqlDatabase, ForeignKeyField, TextField, CharField, IntegerField +from marshmallow import fields, schema, post_load +from playhouse.postgres_ext import ArrayField, JSONField +from io import StringIO +import instal +from playhouse.shortcuts import model_to_dict +import simplejson as json +from tempfile import NamedTemporaryFile +import time +import random +import instalrest.instalcelery.instalcelery + +database = PostgresqlDatabase( + 'instal-rest-v1', + user='instal', + password='instal', + host='instal-db', + port=5432 +) +class BaseModel(Model): + class Meta: + database = database + +class InstALModel(BaseModel): + institutions = ArrayField(TextField,index=False) + bridges = ArrayField(TextField,index=False) + logic_programming = ArrayField(TextField,index=False) + facts = ArrayField(TextField,index=False) + @classmethod + def get_marshelled_dict(cls,data): + return cls.ModelSchema().load(data) + + @classmethod + def new_from_form_data(cls, data) -> "InstALModel": + marshelled = cls.get_marshelled_dict(data) + di = marshelled.data + new_model = cls(institutions=di.get("institutions",[]),bridges=di.get("bridges",[]),logic_programming=di.get("logic_programs",[]),facts=di.get("facts",[])) + new_model.save() + return new_model + + class ModelSchema(schema.Schema): + institutions = fields.List(fields.String(),required=True) + bridges = fields.List(fields.String()) + logic_programs = fields.List(fields.String()) + facts = fields.List(fields.String()) + + @post_load + def make_model(self, data): + pass + + def to_dict(self): + return model_to_dict(self,recurse=True) + + def to_json(self): + return json.dumps(self.to_dict()) + + def get_inspect(self): + ial_files = [StringIO(s) for s in self.institutions] + bridge_files = [StringIO(s) for s in self.bridges] + logic_programs = [StringIO(s) for s in self.logic_programming] + inspect = instal.instalinspect.instal_inspect_files(ial_files=ial_files, bridge_files=bridge_files, + lp_files=logic_programs, domain_files=[], + query_file=[], + fact_files=[]) + return inspect + + +class InstALGrounding(BaseModel): + model = ForeignKeyField(InstALModel, related_name="groundings") + types = JSONField() + facts = ArrayField(TextField,index=False) + #types + class GroundingSchema(schema.Schema): + types = fields.Dict(required=False) + facts = fields.List(fields.String()) + + @classmethod + def get_marshelled_dict(cls, data): + return cls.GroundingSchema().load(data) + + @classmethod + def new_from_form_data(cls, data, model_id) -> "InstALGrounding": + marshelled = cls.get_marshelled_dict(data) + di = marshelled.data + new_grounding = cls(model_id=model_id, types=di.get("types",{}),facts=di.get("facts",[])) + new_grounding.save() + return new_grounding + + def get_domain_text(self): + outstr = "" + for k, v in self.types.items(): + outstr += "{}: ".format(k.title()) + for val in v: + outstr += val + " " + outstr += "\n" + return outstr + + def to_dict(self): + return model_to_dict(self,recurse=True) + + def to_json(self): + return json.dumps(self.to_dict()) + +class InstALQuery(BaseModel): + grounding = ForeignKeyField(InstALGrounding, related_name="queries") + query = ArrayField(CharField,index=False) + facts = ArrayField(TextField,index=False) + length = IntegerField(default=0) + number = IntegerField(default=1) + json_out = JSONField(default=[]) + status = CharField(default="running") + errors = ArrayField(TextField,default=[],index=False) + + class AnswerSetNotFound(Exception): + pass + + class AnswerSetRepresentationNotFound(Exception): + pass + + def n_answer_sets(self): + return len(self.json_out) + + class QuerySchema(schema.Schema): + query = fields.List(fields.String()) + length = fields.Integer() + facts = fields.List(fields.String()) + number = fields.Integer() + + def output_from_form_data(self, rq, answer_set_number): + if answer_set_number > len(self.json_out): + raise AnswerSetNotFound + answer_set = self.json_out[answer_set_number-1] + accept = rq.headers.get("Accept",'application/json') + if accept == "application/json": + return (json.dumps(answer_set), 'application/json') + elif accept == "text/plain": + return (instalrest.instalcelery.instalcelery.get_instal_trace_text(answer_set), 'text/plain') + else: + raise AnswerSetRepresentationNotFound + + + def to_dict(self): + return model_to_dict(self,exclude=["json_out"],extra_attrs=["n_answer_sets"],recurse=True) + + def to_json(self): + return json.dumps(self.to_dict()) + + @classmethod + def get_marshelled_dict(cls, data): + return cls.QuerySchema().load(data) + + @classmethod + def new_from_form_data(cls, data, grounding_id) -> "InstALQuery": + marshelled = cls.get_marshelled_dict(data) + di = marshelled.data + new_query = cls(grounding_id=grounding_id,length=di.get("length",0),query=di.get("query",[]), + facts=di.get("facts",[]),number=di.get("number",1)) + new_query.save() + return new_query + + def execute_instal(self) -> None: + ial_files = [StringIO(s) for s in self.grounding.model.institutions] + bridge_files = [StringIO(s) for s in self.grounding.model.bridges] + logic_programs = [StringIO(s) for s in self.grounding.model.logic_programming] + domains = [StringIO(self.grounding.get_domain_text())] + query_file = StringIO(self.get_query_text()) + fact_files = [StringIO(s) for s in self.facts] + [StringIO(s) for s in self.grounding.facts] + [StringIO(s) for s in self.grounding.model.facts] + try: + out = instal.instalquery.instal_query_files(ial_files=ial_files,bridge_files=bridge_files, + lp_files=logic_programs,domain_files=domains,query_file=query_file, + fact_files=fact_files, + length=self.length, number=self.number) + self.json_out = [] + for o in out: self.json_out.append(o.to_json()) + self.status = "complete" + except Exception as e: + self.errors = [str(type(e).__name__)] + list(e.args) + self.status = "error" + finally: + self.save() + + def get_query_text(self): + outstr = "" + for s in self.query: + outstr += "observed({})\n".format(s) + return outstr + +def setup_tables(): + t = 10 + connected = False + while t > 0 and not connected: + try: + print("Running initial database setup") + database.connect() + connected = True + except: + t -= 1 + print("Database connect failure. Sleeping for 2 seconds. Trying again {} times...".format(t)) + time.sleep(2) + + try: + print("Attempting to create database tables...") + InstALModel.create_table() + InstALGrounding.create_table() + InstALQuery.create_table() + except: + print("...but presumably they're already created.") + database.rollback() diff --git a/instalrest/v1/test-api.py b/instalrest/v1/test-api.py new file mode 100644 index 0000000..6f2061d --- /dev/null +++ b/instalrest/v1/test-api.py @@ -0,0 +1,14 @@ +from flask import Flask +from flask_restful import Resource, Api + +app = Flask(__name__) +api = Api(app) + +class Up(Resource): + def get(self): + return {"status" : "ok"} + +api.add_resource(Up, '/_up') + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0ef55e3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,60 @@ +amqp==2.1.4 +aniso8601==1.2.1 +appdirs==1.4.3 +astroid==1.4.9 +autopep8==1.2.4 +billiard==3.5.0.2 +celery==4.0.2 +certifi==2017.4.17 +chardet==3.0.3 +click==6.7 +decorator==4.0.11 +Flask==0.12.2 +Flask-Admin==1.5.0 +flask-peewee==0.6.7 +Flask-RESTful==0.3.5 +idna==2.5 +inflect==0.2.5 +ipython==6.1.0 +ipython-genutils==0.2.0 +isort==4.2.5 +itsdangerous==0.24 +jedi==0.10.2 +Jinja2==2.9.6 +kombu==4.0.2 +lazy-object-proxy==1.2.2 +MarkupSafe==1.0 +marshmallow==2.13.5 +mccabe==0.5.3 +nose==1.3.7 +nose-exclude==0.5.0 +nose-htmloutput==0.6.0 +nose-parameterized==0.5.0 +packaging==16.8 +parsec==3.3 +peewee==2.10.1 +pep8==1.7.0 +pexpect==4.2.1 +pickleshare==0.7.4 +ply==3.8 +prompt-toolkit==1.0.14 +psycopg2==2.7.1 +ptyprocess==0.5.1 +Pygments==2.2.0 +pylint==1.6.4 +pyparsing==2.2.0 +python-dateutil==2.6.0 +pytz==2017.2 +requests==2.17.3 +simplegeneric==0.8.1 +simplejson==3.10.0 +six==1.10.0 +traitlets==4.3.2 +urllib3==1.21.1 +vine==1.1.3 +wcwidth==0.1.7 +Werkzeug==0.12.2 +wrapt==1.10.8 +wtf-peewee==0.2.6 +WTForms==2.1 +gunicorn==19.7.1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0638dfc --- /dev/null +++ b/setup.py @@ -0,0 +1,15 @@ +from distutils.core import setup, Extension +import instalrest +setup( + name='instalrest', + version=instalrest.__version__, + packages=['instalrest', + 'instalrest.instalcelery', + 'instalrest.tests', + 'instalrest.v1'], + url='http://instsuite.github.io/', + license='GPLv3', + author='InstAL team @ Univesity of Bath', + author_email='', + description='InstAL: Institutional Action Language Framework and Tools', +)