diff --git a/.gitignore b/.gitignore index ad82db58..1a22ad18 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,7 @@ benchmarking-tests/ssh_key.py # Ansible-specific files ntools/one-m/ansible/hosts ntools/one-m/ansible/ansible.cfg + +# Just in time documentation +docs/server/source/schema +docs/server/source/drivers-clients/samples diff --git a/docs/generate/__init__.py b/docs/generate/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/docs/server/generate_http_server_api_documentation.py b/docs/server/generate_http_server_api_documentation.py new file mode 100644 index 00000000..d9d86647 --- /dev/null +++ b/docs/server/generate_http_server_api_documentation.py @@ -0,0 +1,87 @@ +""" Script to build http examples for http server api docs """ + +import json +import os +import os.path + +from bigchaindb.common.transaction import Asset, Transaction + + +TPLS = {} + +TPLS['post-tx-request'] = """\ +POST /transactions/ HTTP/1.1 +Host: example.com +Content-Type: application/json + +%(tx)s +""" + + +TPLS['post-tx-response'] = """\ +HTTP/1.1 201 Created +Content-Type: application/json + +%(tx)s +""" + + +TPLS['get-tx-status-request'] = """\ +GET /transactions/%(txid)s/status HTTP/1.1 +Host: example.com + +""" + + +TPLS['get-tx-status-response'] = """\ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "status": "valid" +} +""" + + +TPLS['get-tx-request'] = """\ +GET /transactions/%(txid)s HTTP/1.1 +Host: example.com + +""" + + +TPLS['get-tx-response'] = """\ +HTTP/1.1 200 OK +Content-Type: application/json + +%(tx)s +""" + + +def main(): + """ Main function """ + pubkey = '9RaWxppkP9UyYWA7NJb5FcgkzfJNPfvPX3FCNw2T5Pwb' + asset = Asset(None, 'e6969f87-4fc9-4467-b62a-f0dfa1c85002') + tx = Transaction.create([pubkey], [([pubkey], 1)], asset=asset) + tx_json = json.dumps(tx.to_dict(), indent=2, sort_keys=True) + + base_path = os.path.join(os.path.dirname(__file__), + 'source/drivers-clients/samples') + + if not os.path.exists(base_path): + os.makedirs(base_path) + + for name, tpl in TPLS.items(): + path = os.path.join(base_path, name + '.http') + code = tpl % {'tx': tx_json, 'txid': tx.id} + with open(path, 'w') as handle: + handle.write(code) + + +def setup(*_): + """ Fool sphinx into think it's an extension muahaha """ + main() + + +if __name__ == '__main__': + main() diff --git a/docs/server/generate_schema_documentation.py b/docs/server/generate_schema_documentation.py index 0e1a626a..8767cabf 100644 --- a/docs/server/generate_schema_documentation.py +++ b/docs/server/generate_schema_documentation.py @@ -168,12 +168,20 @@ def main(): 'file': os.path.basename(__file__), } - path = os.path.join(os.path.dirname(__file__), - 'source/schema/transaction.rst') + base_path = os.path.join(os.path.dirname(__file__), 'source/schema') + path = os.path.join(base_path, 'transaction.rst') + + if not os.path.exists(base_path): + os.makedirs(base_path) with open(path, 'w') as handle: handle.write(doc) +def setup(*_): + """ Fool sphinx into think it's an extension muahaha """ + main() + + if __name__ == '__main__': main() diff --git a/docs/server/source/conf.py b/docs/server/source/conf.py index c0de76d7..93402f75 100644 --- a/docs/server/source/conf.py +++ b/docs/server/source/conf.py @@ -35,6 +35,10 @@ _version = {} with open('../../../bigchaindb/version.py') as fp: exec(fp.read(), _version) +import os.path +import sys + +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/..')) extensions = [ 'sphinx.ext.autodoc', @@ -44,6 +48,10 @@ extensions = [ 'sphinx.ext.napoleon', 'sphinxcontrib.httpdomain', 'sphinx.ext.autosectionlabel', + # Below are actually build steps made to look like sphinx extensions. + # It was the easiest way to get it running with ReadTheDocs. + 'generate_schema_documentation', + 'generate_http_server_api_documentation', ] # autodoc settings diff --git a/docs/server/source/drivers-clients/http-client-server-api.rst b/docs/server/source/drivers-clients/http-client-server-api.rst index 12f30a79..bb037d15 100644 --- a/docs/server/source/drivers-clients/http-client-server-api.rst +++ b/docs/server/source/drivers-clients/http-client-server-api.rst @@ -64,108 +64,13 @@ POST /transactions/ **Example request**: - .. sourcecode:: http - - POST /transactions/ HTTP/1.1 - Host: example.com - Content-Type: application/json - - { - "transaction": { - "conditions": [ - { - "cid": 0, - "condition": { - "uri": "cc:4:20:fSlVCKNSzSl0meiwwuUk5JpJ0KLlECTqbd25KyQefFY:96", - "details": { - "signature": null, - "type": "fulfillment", - "type_id": 4, - "bitmask": 32, - "public_key": "9RaWxppkP9UyYWA7NJb5FcgkzfJNPfvPX3FCNw2T5Pwb" - } - }, - "amount": 1, - "owners_after": [ - "9RaWxppkP9UyYWA7NJb5FcgkzfJNPfvPX3FCNw2T5Pwb" - ] - } - ], - "operation": "CREATE", - "asset": { - "divisible": false, - "updatable": false, - "data": null, - "id": "b57801f8-b865-4360-9d1a-3e3009f5ce01", - "refillable": false - }, - "metadata": null, - "fulfillments": [ - { - "fid": 0, - "input": null, - "fulfillment": "cf:4:fSlVCKNSzSl0meiwwuUk5JpJ0KLlECTqbd25KyQefFaf8bQVH1gesZGEGZepCE8_kgo-UfBrCHPlvBsnAsfq56GWjrLTyZ9NXISwcyJ3zmygnVhCMG8xzE6c9fj1-6wK", - "owners_before": [ - "9RaWxppkP9UyYWA7NJb5FcgkzfJNPfvPX3FCNw2T5Pwb" - ] - } - ] - }, - "id": "65f1f69b6ebf995a7b2c5ae8a6fb480ce20f0e8f1eb1d77d75f37ab00ccdeec3", - "version": 1 - } + .. literalinclude:: samples/post-tx-request.http + :language: http **Example response**: - .. sourcecode:: http - - HTTP/1.1 201 Created - Content-Type: application/json - - { - "id": "65f1f69b6ebf995a7b2c5ae8a6fb480ce20f0e8f1eb1d77d75f37ab00ccdeec3", - "version": 1, - "transaction": { - "conditions": [ - { - "amount": 1, - "condition": { - "uri": "cc:4:20:fSlVCKNSzSl0meiwwuUk5JpJ0KLlECTqbd25KyQefFY:96", - "details": { - "signature": null, - "type_id": 4, - "type": "fulfillment", - "bitmask": 32, - "public_key": "9RaWxppkP9UyYWA7NJb5FcgkzfJNPfvPX3FCNw2T5Pwb" - } - }, - "owners_after": [ - "9RaWxppkP9UyYWA7NJb5FcgkzfJNPfvPX3FCNw2T5Pwb" - ], - "cid": 0 - } - ], - "fulfillments": [ - { - "input": null, - "fulfillment": "cf:4:fSlVCKNSzSl0meiwwuUk5JpJ0KLlECTqbd25KyQefFaf8bQVH1gesZGEGZepCE8_kgo-UfBrCHPlvBsnAsfq56GWjrLTyZ9NXISwcyJ3zmygnVhCMG8xzE6c9fj1-6wK", - "fid": 0, - "owners_before": [ - "9RaWxppkP9UyYWA7NJb5FcgkzfJNPfvPX3FCNw2T5Pwb" - ] - } - ], - "operation": "CREATE", - "asset": { - "updatable": false, - "refillable": false, - "divisible": false, - "data": null, - "id": "b57801f8-b865-4360-9d1a-3e3009f5ce01" - }, - "metadata": null - } - } + .. literalinclude:: samples/post-tx-response.http + :language: http :statuscode 201: A new transaction was created. :statuscode 400: The transaction was invalid and not created. @@ -187,21 +92,13 @@ GET /transactions/{tx_id}/status **Example request**: - .. sourcecode:: http - - GET /transactions/65f1f69b6ebf995a7b2c5ae8a6fb480ce20f0e8f1eb1d77d75f37ab00ccdeec3/status HTTP/1.1 - Host: example.com + .. literalinclude:: samples/get-tx-status-request.http + :language: http **Example response**: - .. sourcecode:: http - - HTTP/1.1 200 OK - Content-Type: application/json - - { - "status": "valid" - } + .. literalinclude:: samples/get-tx-status-response.http + :language: http :statuscode 200: A transaction with that ID was found and the status is returned. :statuscode 404: A transaction with that ID was not found. @@ -222,62 +119,13 @@ GET /transactions/{tx_id} **Example request**: - .. sourcecode:: http - - GET /transactions/65f1f69b6ebf995a7b2c5ae8a6fb480ce20f0e8f1eb1d77d75f37ab00ccdeec3 HTTP/1.1 - Host: example.com + .. literalinclude:: samples/get-tx-request.http + :language: http **Example response**: - .. sourcecode:: http - - HTTP/1.1 200 OK - Content-Type: application/json - - { - "transaction": { - "conditions": [ - { - "cid": 0, - "condition": { - "uri": "cc:4:20:fSlVCKNSzSl0meiwwuUk5JpJ0KLlECTqbd25KyQefFY:96", - "details": { - "signature": null, - "type": "fulfillment", - "type_id": 4, - "bitmask": 32, - "public_key": "9RaWxppkP9UyYWA7NJb5FcgkzfJNPfvPX3FCNw2T5Pwb" - } - }, - "amount": 1, - "owners_after": [ - "9RaWxppkP9UyYWA7NJb5FcgkzfJNPfvPX3FCNw2T5Pwb" - ] - } - ], - "operation": "CREATE", - "asset": { - "divisible": false, - "updatable": false, - "data": null, - "id": "b57801f8-b865-4360-9d1a-3e3009f5ce01", - "refillable": false - }, - "metadata": null, - "fulfillments": [ - { - "fid": 0, - "input": null, - "fulfillment": "cf:4:fSlVCKNSzSl0meiwwuUk5JpJ0KLlECTqbd25KyQefFaf8bQVH1gesZGEGZepCE8_kgo-UfBrCHPlvBsnAsfq56GWjrLTyZ9NXISwcyJ3zmygnVhCMG8xzE6c9fj1-6wK", - "owners_before": [ - "9RaWxppkP9UyYWA7NJb5FcgkzfJNPfvPX3FCNw2T5Pwb" - ] - } - ] - }, - "id": "65f1f69b6ebf995a7b2c5ae8a6fb480ce20f0e8f1eb1d77d75f37ab00ccdeec3", - "version": 1 - } + .. literalinclude:: samples/get-tx-response.http + :language: http :statuscode 200: A transaction with that ID was found. :statuscode 404: A transaction with that ID was not found. diff --git a/docs/server/source/schema/transaction.rst b/docs/server/source/schema/transaction.rst deleted file mode 100644 index 1d2b8f7d..00000000 --- a/docs/server/source/schema/transaction.rst +++ /dev/null @@ -1,307 +0,0 @@ -.. This file was auto generated by generate_schema_documentation.py - -================== -Transaction Schema -================== - -* `Transaction`_ - -* `Transaction Body`_ - -* Condition_ - -* Fulfillment_ - -* Asset_ - -* Metadata_ - -.. raw:: html - - - -Transaction ------------ - -This is the outer transaction wrapper. It contains the ID, version and the body of the transaction, which is also called ``transaction``. - - -Transaction.id -^^^^^^^^^^^^^^ - -**type:** string - -A sha3 digest of the transaction. The ID is calculated by removing all -derived hashes and signatures from the transaction, serializing it to -JSON with keys in sorted order and then hashing the resulting string -with sha3. - - - -Transaction.transaction -^^^^^^^^^^^^^^^^^^^^^^^ - -**type:** object - -See: `Transaction Body`_. - - - -Transaction.version -^^^^^^^^^^^^^^^^^^^ - -**type:** integer - -BigchainDB transaction schema version. - - - - - -Transaction Body ----------------- - -See: `Transaction Body`_. - - -Transaction.operation -^^^^^^^^^^^^^^^^^^^^^ - -**type:** string - -Type of the transaction: - -A ``CREATE`` transaction creates an asset in BigchainDB. This -transaction has outputs (conditions) but no inputs (fulfillments), -so a dummy fulfillment is used. - -A ``TRANSFER`` transaction transfers ownership of an asset, by providing -fulfillments to conditions of earlier transactions. - -A ``GENESIS`` transaction is a special case transaction used as the -sole member of the first block in a BigchainDB ledger. - - - -Transaction.asset -^^^^^^^^^^^^^^^^^ - -**type:** object - -Description of the asset being transacted. - -See: `Asset`_. - - - -Transaction.fulfillments -^^^^^^^^^^^^^^^^^^^^^^^^ - -**type:** array (object) - -Array of the fulfillments (inputs) of a transaction. - -See: Fulfillment_. - - - -Transaction.conditions -^^^^^^^^^^^^^^^^^^^^^^ - -**type:** array (object) - -Array of conditions (outputs) provided by this transaction. - -See: Condition_. - - - -Transaction.metadata -^^^^^^^^^^^^^^^^^^^^ - -**type:** object or null - -User provided transaction metadata. This field may be ``null`` or may -contain an object with freeform metadata. - -See: `Metadata`_. - - - - - -Condition ----------- - -An output of a transaction. A condition describes a quantity of an asset -and what conditions must be met in order for it to be fulfilled. See also: -fulfillment_. - - -Condition.cid -^^^^^^^^^^^^^ - -**type:** integer - -Index of this condition's appearance in the `Transaction.conditions`_ -array. In a transaction with 2 conditions, the ``cid``s will be 0 and 1. - - - -Condition.condition -^^^^^^^^^^^^^^^^^^^ - -**type:** object - -Body of the condition. Has the properties: - -- **details**: Details of the condition. -- **uri**: Condition encoded as an ASCII string. - - - -Condition.owners_after -^^^^^^^^^^^^^^^^^^^^^^ - -**type:** array (string) or null - -List of public keys associated with asset ownership at the time -of the transaction. - - - -Condition.amount -^^^^^^^^^^^^^^^^ - -**type:** integer - -Integral amount of the asset represented by this condition. -In the case of a non divisible asset, this will always be 1. - - - - - -Fulfillment ------------ - -A fulfillment is an input to a transaction, named as such because it fulfills a condition of a previous transaction. In the case of a ``CREATE`` transaction, a fulfillment may provide no ``input``. - -Fulfillment.fid -^^^^^^^^^^^^^^^ - -**type:** integer - -The offset of the fulfillment within the fulfillents array. - - - -Fulfillment.owners_before -^^^^^^^^^^^^^^^^^^^^^^^^^ - -**type:** array (string) or null - -List of public keys of the previous owners of the asset. - - - -Fulfillment.fulfillment -^^^^^^^^^^^^^^^^^^^^^^^ - -**type:** object or string - -Fulfillment of a condition_, or put a different way, this is a -payload that satisfies a condition in order to spend the associated -asset. - - - -Fulfillment.input -^^^^^^^^^^^^^^^^^ - -**type:** object or null - -Reference to a condition of a previous transaction - - - - - -Asset ------ - -Description of the asset being transacted. In the case of a ``TRANSFER`` -transaction, this field contains only the ID of asset. In the case -of a ``CREATE`` transaction, this field may contain properties: - - -Asset.id -^^^^^^^^ - -**type:** string - -A `UUID `_ -of type 4 (random). - - - -Asset.divisible -^^^^^^^^^^^^^^^ - -**type:** boolean - -Whether or not the asset has a quantity that may be partially spent. - - - -Asset.updatable -^^^^^^^^^^^^^^^ - -**type:** boolean - -Whether or not the description of the asset may be updated. Defaults to false. - - - -Asset.refillable -^^^^^^^^^^^^^^^^ - -**type:** boolean - -Whether the amount of the asset can change after its creation. Defaults to false. - - - -Asset.data -^^^^^^^^^^ - -**type:** object or null - -User provided metadata associated with the asset. May also be ``null``. - - - - - -Metadata --------- - -User provided transaction metadata. This field may be ``null`` or may -contain an non empty object with freeform metadata. - - - diff --git a/setup.py b/setup.py index 4043c0be..ecb801a3 100644 --- a/setup.py +++ b/setup.py @@ -27,18 +27,6 @@ def check_setuptools_features(): check_setuptools_features() - -tests_require = [ - 'coverage', - 'pep8', - 'flake8', - 'pylint', - 'pytest', - 'pytest-cov>=2.2.1', - 'pytest-xdist', - 'pytest-flask', -] - dev_require = [ 'ipdb', 'ipython', @@ -52,6 +40,17 @@ docs_require = [ 'sphinxcontrib-napoleon>=0.4.4', ] +tests_require = [ + 'coverage', + 'pep8', + 'flake8', + 'pylint', + 'pytest', + 'pytest-cov>=2.2.1', + 'pytest-xdist', + 'pytest-flask', +] + docs_require + benchmarks_require = [ 'line-profiler==1.0', ] diff --git a/tests/test_docs.py b/tests/test_docs.py new file mode 100644 index 00000000..037bf87c --- /dev/null +++ b/tests/test_docs.py @@ -0,0 +1,16 @@ + +import subprocess + + +def test_build_server_docs(): + proc = subprocess.Popen(['bash'], stdin=subprocess.PIPE) + proc.stdin.write('cd docs/server; make html'.encode()) + proc.stdin.close() + assert proc.wait() == 0 + + +def test_build_root_docs(): + proc = subprocess.Popen(['bash'], stdin=subprocess.PIPE) + proc.stdin.write('cd docs/root; make html'.encode()) + proc.stdin.close() + assert proc.wait() == 0