├── LICENSE ├── Makefile ├── README.md ├── dna ├── HoloWorld │ ├── holoWorldEntry.json │ └── holoWorldZome.py ├── dna.json └── properties_schema.json ├── package-lock.json ├── package.json ├── pavement.py ├── test └── holoWorld.json ├── ui ├── HoloWorld.py └── index.html └── yarn.lock /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | HoloWorld Copyright (c) 2018 Holochain 4 | PyHoloWorld Copyright (c) 2018 Michael Goldman 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | paver -q build 3 | 4 | clean: 5 | paver clean 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyHoloWorld 2 | Basic Hello World app for Holochain ported to Python 3 | 4 | Please see original code at https://github.com/holochain/HoloWorld 5 | 6 | To compile this Python code for Holochain, you will first need to install 7 | [Transcrypt](https://www.transcrypt.org) and [Paver](https://pythonhosted.org/Paver). 8 | 9 | pip install transcrypt 10 | pip install paver 11 | 12 | After that just run 'make' and it should build. 13 | 14 | Then 'hcdev test' and 'hcdev web' should work without any problem. 15 | 16 | If you wish to modify the app DNA in a way that requires inclusion of the 17 | Python runtime, modify Makefile accordingly to build with 'paver -q build -r' 18 | and run 'npm install' or 'yarn' to obtain needed libraries. 19 | -------------------------------------------------------------------------------- /dna/HoloWorld/holoWorldEntry.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "holoWorldEntry Schema", 3 | "type": "object", 4 | "properties": { 5 | "content": { 6 | "type": "string" 7 | }, 8 | "timestamp": { 9 | "type": "integer" 10 | } 11 | }, 12 | "required": ["content", "timestamp"] 13 | } 14 | -------------------------------------------------------------------------------- /dna/HoloWorld/holoWorldZome.py: -------------------------------------------------------------------------------- 1 | # EXPOSED METHODS 2 | 3 | # creates a holoWorldEntry entry 4 | def holoWorldEntryCreate(entry): 5 | return hc_commit('holoWorldEntry', entry) 6 | 7 | # retrieves a holoWorldEntry entry 8 | def holoWorldEntryRead(hash): 9 | return hc_get(hash) 10 | 11 | # Called only when your source chain is generated 12 | # return {boolean} success 13 | def genesis(): 14 | return True 15 | 16 | #----------------------------------------------------------------- 17 | # validation functions for every DHT entry change 18 | #----------------------------------------------------------------- 19 | 20 | def validateCommit(entryName, entry, header, pkg, sources): 21 | if entryName == 'holoWorldEntry': 22 | # in order for the 'commit' action to work, validateCommit (given a holoWorldEntry) must return True 23 | # there is no special validation that we have to perform for our simple app 24 | return True 25 | # default: invalid entry name 26 | return False 27 | 28 | def validatePut(entryName, entry, header, pkg, sources): 29 | if entryName == 'holoWorldEntry': 30 | return True 31 | # default: invalid entry name 32 | return False 33 | 34 | def validatePutPkg(entryName): 35 | return None 36 | -------------------------------------------------------------------------------- /dna/dna.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 1, 3 | "UUID": "a211fc6c-3dbf-11e8-b744-4a00057935b0", 4 | "Name": "HoloWorld", 5 | "Properties": { 6 | "description": "provides an application template", 7 | "language": "en" 8 | }, 9 | "PropertiesSchemaFile": "properties_schema.json", 10 | "BasedOn": { 11 | "H": null 12 | }, 13 | "RequiresVersion": 23, 14 | "DHTConfig": { 15 | "HashType": "sha2-256", 16 | "NeighborhoodSize": 0 17 | }, 18 | "Progenitor": { 19 | "Identity": "", 20 | "PubKey": null 21 | }, 22 | "Zomes": [ 23 | { 24 | "Name": "HoloWorld", 25 | "Description": "provide a sample zome", 26 | "CodeFile": "__javascript__/holoWorldZome.js", 27 | "RibosomeType": "js", 28 | "BridgeFuncs": null, 29 | "Config": null, 30 | "Entries": [ 31 | { 32 | "Name": "holoWorldEntry", 33 | "DataFormat": "json", 34 | "Schema": "", 35 | "SchemaFile": "holoWorldEntry.json", 36 | "Sharing": "public" 37 | } 38 | ], 39 | "Functions": [ 40 | { 41 | "Name": "holoWorldEntryCreate", 42 | "CallingType": "json", 43 | "Exposure": "public" 44 | }, 45 | { 46 | "Name": "holoWorldEntryRead", 47 | "CallingType": "json", 48 | "Exposure": "public" 49 | } 50 | ] 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /dna/properties_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Properties Schema", 3 | "type": "object", 4 | "properties": { 5 | "description": { 6 | "type": "string" 7 | }, 8 | "language": { 9 | "type": "string" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PyHoloWorld", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "babel-polyfill": { 8 | "version": "6.26.0", 9 | "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", 10 | "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", 11 | "requires": { 12 | "babel-runtime": "^6.26.0", 13 | "core-js": "^2.5.0", 14 | "regenerator-runtime": "^0.10.5" 15 | } 16 | }, 17 | "babel-runtime": { 18 | "version": "6.26.0", 19 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", 20 | "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", 21 | "requires": { 22 | "core-js": "^2.4.0", 23 | "regenerator-runtime": "^0.11.0" 24 | }, 25 | "dependencies": { 26 | "regenerator-runtime": { 27 | "version": "0.11.1", 28 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", 29 | "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" 30 | } 31 | } 32 | }, 33 | "commander": { 34 | "version": "2.15.1", 35 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 36 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" 37 | }, 38 | "core-js": { 39 | "version": "2.5.5", 40 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz", 41 | "integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs=" 42 | }, 43 | "regenerator-runtime": { 44 | "version": "0.10.5", 45 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", 46 | "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" 47 | }, 48 | "source-map": { 49 | "version": "0.6.1", 50 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 51 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 52 | }, 53 | "uglify-js": { 54 | "version": "3.3.23", 55 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.23.tgz", 56 | "integrity": "sha512-Ks+KqLGDsYn4z+pU7JsKCzC0T3mPYl+rU+VcPZiQOazjE4Uqi4UCRY3qPMDbJi7ze37n1lDXj3biz1ik93vqvw==", 57 | "requires": { 58 | "commander": "~2.15.0", 59 | "source-map": "~0.6.1" 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PyHoloWorld", 3 | "version": "0.1.0", 4 | "description": "Basic Hello World app for Holochain ported to Python", 5 | "main": "", 6 | "directories": {}, 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/pythagorean/PyHoloWorld.git" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/pythagorean/PyHoloWorld/issues" 19 | }, 20 | "homepage": "https://github.com/pythagorean/PyHoloWorld#readme", 21 | "dependencies": { 22 | "babel-polyfill": "^6.26.0", 23 | "uglify-js": "^3.3.23" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pavement.py: -------------------------------------------------------------------------------- 1 | from paver.easy import * 2 | 3 | @task 4 | @cmdopts([ 5 | ('runtime', 'r', 'Include Transcrypt runtime in app DNA'), 6 | ]) 7 | def build(options): 8 | hc_api = ['// Holochain API functions for Transcrypted Python module'] 9 | hc_functions = [ 10 | 'property', 'makeHash', 'debug', 'call', 'bridge', 'getBridges', 'sign', 11 | 'verifySignature', 'commit', 'get', 'getLinks', 'update', 'updateAgent', 12 | 'remove', 'query', 'send', 'bundleStart', 'bundleClose'] 13 | for function in hc_functions: 14 | hc_api.append('var hc_' + function + ' = ' + function + ';') 15 | hc_api += [' = '.join(hc_functions) + ' = undefined;', ''] 16 | runtime = polyfill = [] 17 | if hasattr(options, 'runtime') and options.runtime: 18 | polyfill = ['// Using babel polyfill libraries for otto'] 19 | polyfill += path('node_modules/babel-polyfill/dist/polyfill.js').lines() 20 | 21 | for dir in path('dna').dirs(): 22 | for pyfile in dir.files('*.py'): 23 | sh('transcrypt -n -p .none ' + pyfile.relpath()) 24 | jsfile = dir + '/__javascript__/' + pyfile.namebase + '.js' 25 | modfile = path(jsfile.relpath()[:-3] + '.mod.js') 26 | if hasattr(options, 'runtime') and options.runtime and not runtime: 27 | # Construct javascript runtime for otto 28 | runtime = polyfill[:] 29 | runtime.append('\n// Transcrypt runtime code for otto') 30 | for line in jsfile.lines()[3:]: 31 | if line[1:2] == '(': break 32 | runtime.append(line) 33 | runtime.append('\n// Transcrypted Python module for otto') 34 | 35 | modlines = [] 36 | for line in modfile.lines()[2:]: 37 | if line[3:4] == '_': break 38 | modlines.append(line[2:]) 39 | 40 | jsfile.write_lines(hc_api + runtime + modlines) 41 | if not path('node_modules/uglify-js').exists(): continue 42 | minfile = path(jsfile.relpath()[:-3] + '.min.js') 43 | print('Minifying target code in: ' + minfile.relpath()) 44 | sh('node_modules/uglify-js/bin/uglifyjs --compress --mangle -- ' + 45 | jsfile.relpath() + ' > ' + minfile.relpath()) 46 | for pyfile in path('ui').files('*.py'): 47 | sh('transcrypt ' + pyfile.relpath()) 48 | 49 | @task 50 | def clean(): 51 | sh('for x in `find . -name "__javascript__"`; do rm -rf $x; done') 52 | -------------------------------------------------------------------------------- /test/holoWorld.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tests": [ 3 | { 4 | "Convey": "We can create a new HoloWorldEntry", 5 | "Zome": "HoloWorld", 6 | "FnName": "holoWorldEntryCreate", 7 | "Input": { 8 | "content": "this is the entry body", 9 | "timestamp": 12345 10 | }, 11 | "Output": "%h%", 12 | "Err": null, 13 | "ErrMsg": "", 14 | "Regexp": "", 15 | "Time": 0, 16 | "Wait": 0, 17 | "Exposure": "public", 18 | "Raw": false, 19 | "Repeat": 0, 20 | "Benchmark": false 21 | }, 22 | { 23 | "Convey": "We can read a holoWorldEntry", 24 | "Zome": "HoloWorld", 25 | "FnName": "holoWorldEntryRead", 26 | "Input": "%h%", 27 | "Output": { 28 | "content": "this is the entry body", 29 | "timestamp": 12345 30 | }, 31 | "Exposure": "public", 32 | "Raw": false, 33 | "Repeat": 0 34 | } 35 | ], 36 | "Identity": "", 37 | "Fixtures": { 38 | "Agents": null 39 | }, 40 | "Benchmark": false 41 | } 42 | -------------------------------------------------------------------------------- /ui/HoloWorld.py: -------------------------------------------------------------------------------- 1 | def xmlHttpRequest(): return __new__ (XMLHttpRequest()) 2 | 3 | def holoWorldEntryCreate(task, callback): 4 | xhr = xmlHttpRequest() 5 | url = '/fn/HoloWorld/holoWorldEntryCreate' 6 | xhr.open('POST', url, True) 7 | xhr.setRequestHeader('Content-type', 'application/json') 8 | def onreadystatechange(): 9 | if xhr.readyState == 4 and xhr.status == 200: 10 | callback(JSON.parse(xhr.responseText)) 11 | xhr.onreadystatechange = onreadystatechange 12 | data = JSON.stringify({'content': task, 'timestamp': 101010}) 13 | xhr.send(data) 14 | 15 | def holoWorldEntryRead(hash, callback): 16 | xhr = xmlHttpRequest() 17 | url = '/fn/HoloWorld/holoWorldEntryRead' 18 | xhr.open('POST', url, True) 19 | xhr.setRequestHeader('Content-type', 'application/json') 20 | def onreadystatechange(): 21 | if xhr.readyState == 4 and xhr.status == 200: 22 | callback(JSON.parse(xhr.responseText)) 23 | xhr.onreadystatechange = onreadystatechange 24 | data = JSON.stringify(hash) 25 | xhr.send(data) 26 | 27 | def submitEntry(): 28 | event.preventDefault() 29 | holoWorldEntry = document.getElementById('holoWorldEntry').value 30 | def callback(hash): document.getElementById('hash').value = hash 31 | holoWorldEntryCreate(holoWorldEntry, callback) 32 | 33 | def readEntry(): 34 | event.preventDefault() 35 | hash = document.getElementById('hash').value 36 | def callback(task): document.getElementById('entryContent').value = task.content 37 | holoWorldEntryRead(hash, callback) 38 | -------------------------------------------------------------------------------- /ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |

Enter the text of the entry.

8 |

This will be stored in Holochain and the hash key for the new entry will be shown 9 | in the Hash text box below.

10 | 11 | 12 | 13 |
14 | 15 |
16 |

Press the Read button and the hash key will be used to retrieve the entry.

17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | babel-polyfill@^6.26.0: 6 | version "6.26.0" 7 | resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" 8 | dependencies: 9 | babel-runtime "^6.26.0" 10 | core-js "^2.5.0" 11 | regenerator-runtime "^0.10.5" 12 | 13 | babel-runtime@^6.26.0: 14 | version "6.26.0" 15 | resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" 16 | dependencies: 17 | core-js "^2.4.0" 18 | regenerator-runtime "^0.11.0" 19 | 20 | commander@~2.15.0: 21 | version "2.15.1" 22 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" 23 | 24 | core-js@^2.4.0, core-js@^2.5.0: 25 | version "2.5.5" 26 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.5.tgz#b14dde936c640c0579a6b50cabcc132dd6127e3b" 27 | 28 | regenerator-runtime@^0.10.5: 29 | version "0.10.5" 30 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" 31 | 32 | regenerator-runtime@^0.11.0: 33 | version "0.11.1" 34 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" 35 | 36 | source-map@~0.6.1: 37 | version "0.6.1" 38 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 39 | 40 | uglify-js@^3.3.23: 41 | version "3.3.23" 42 | resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.23.tgz#48ea43e638364d18be292a6fdc2b5b7c35f239ab" 43 | dependencies: 44 | commander "~2.15.0" 45 | source-map "~0.6.1" 46 | --------------------------------------------------------------------------------