Part 12: API

Usage

Populus is a versatile tool, designed to help you from the moment you start do develop a smart contract, until it’s working and integrated in any Python project. The core of Populus is a Pythonic interface and command line tools to the Ethereum platform.

The main areas you will use Populus are:

[1] Smart Contracts Development: manage and work with your blockchain assets, the Solidity source files, compilation data, deployments etc

[2] Testing: a testing framework with py.test, tester chains, and py.test fixtures

[3] Integration to any Python project and application: with the Pythonic API

So far we covered the contract development, deployments, and testing. We touched the API with a few simple scripts. In this part, we will cover the important classes of the API, and describe a few important members of each class.

Orientation

Typically your entry point to the API is a Project object. From the project object you get a chain object, and from a chain object you get a contract object. Then you can work with this contract.

The chain also has a web3 property with the full Web3.py API.

Why do we need a chain abstraction at all? Because it enables the core Populus idea, to work with contracts in every state: the source files, the compiled data, and the deployed contract instances on one or more chains.

True, Solidity source files and compiled data are not chain related, only the deployed instances on a given chain are. But when you call a contract from a chain, Populus will either find the instance on that chain, or compiles and deploys a new instance. Similar code is used regardless of the contract’s state. E.g., the same code is used when Populus needs to re-deploy on each test run with the tester chain, and when you interact with a presistent contract isnstance on a local chain or mainnet.

So with the chain object, you have one consistent interface, no matter what the underlying contract state is. See what is a contract

Project

class populus.project.Project

The Project object is the main entry point to the Populus API.

Existing Project:

from populus.project import Project
# the directory should have a project.json file
p = Project(project_dir='/home/mary/project/donations')

New Project:

from populus.project import Project
# will create a new project.json file
# will not create the default project structure you get with the command line populus init
p = Project(project_dir='/home/mary/project/donations')

PopulusContract

class populus.contracts.provider.PopulusContract

A subclass of web3.contract.Contract. It is a Python object, with Python methods, that lets you interact with a corresponding contract instance on a blockchain.

Usually you will not instanciate it directly, but will get it from a contract factory. Populus keeps track of deployments, addresses, compiled data, abi, etc, and uses this info to create the PopulusContract for you.

Chain

class populus.chain.base.BaseChain

The chain object is a Python object that corresponds to a running blockchain.

Get the chain from a project object in a context manager:

# for a chain name as it appears in the config
with p.get_chain('chainname') as chain:
    # chain object available here inside the context manager

chainname is any chain that is defined either (a) in the project config file, project.json, or (b) in the user-scope config file at ~/.populus/config.json.

In both files, the chain settings appears under the chains key.

Note

If the same chain name appears in both the project config and the user config, the project config name will overide the user-scope config

Chain Classes

class populus.chain.external.ExternalChain

A chain object over a running local instance of geth. The default chain when you don’t use a chain for tests

class populus.chain.tester.TesterChain

An ephemeral chain that saves data to memory and resets on every run, great for testing (similar to a blank slate DB for each test run)

class populus.chain.testrpc.TestRPCChain

Local chain with RPC client, for fast RPC response in testing

Web3

Full Web3 API to the running chain

w3 = chain.web3

Provider

class populus.contracts.provider.Provider

The Provider object is the handle to a contract factory. It is capable of handling all the possible states of a contract, and using a contract factory, returns a PopulusContract.

To get a provider:

prv = chain.provider
provider.get_contract(...)

Returns: PopulusContract

Tries to find a contract in the registrar, if exist, will verify the bytecode and return a PopulusContract

Note

Currently matching bytecode is only by the current installed solc version

provider.get_or_deploy_contract(...)

Returns: PopulusContract

Perhaps the most powerful line in the Populus API

[1] If the contract’s is already deployed, same as get_contract

[2] If the contract is not deployed, Populus will compile it, prepare a deployment transaction, calculte the gas estimate, send and wait for the deployment to a new address, verify the byte code, saves the deployment details to the reigstrar, and then create the Python contract object that corresponds to this address and return it.

def get_contract_data ("contract_identifier")

Returns a dictionary with the contract’s data: abi, bytecode, etc.

Registrar

class populus.contracts.registrar.Registrar

A handler of contracts instances and addresses on chains.

def set_contract_address(...)

set a contract address in the registrar

populus.contracts.registrar.get_contract_addresses(...)

Retrieve a contract address in the registrar

Config

class populus.config.base.Config

The Config class is a “magic” object. It behaves like a dictionary, but knows how to unpack nested keys:

>>> from populus.project import Project
>>> p = Project('/home/mary/projects/donations')
>>> p.config
{'chains': {'web3http': {'web3': {'foo': 'baz'}, 'chain': {'class': ....
>>> p.config.keys()
('chains', 'web3', 'compilation', 'contracts', 'version')
>>> type(p.config)
<class 'populus.config.base.Config'>
>>> p.config.get('chains')
{'web3http': {'web3': {}, 'chain': {cts.backends.testing: 50}, 'ProjectContracts'....
>>> p.config.get('chains.web3http')
{'web3': {}, 'chain': {'class': 'populus.chain.web3provider.Web3HTTPProviderChain'}.....
>>> p.config.get('chains.web3http.web3')
{}
>>> p.config['chains.web3http.web3'] = {'foo':'baz'}
>>> p.config.get('chains.web3http.web3')
{'foo': 'baz'}
>>> p.config.get('chains.web3http.web3.foo')
'baz'

Usually you don’t initiate a Config object yourself, but use an existing object that Populus built from the configuration files. Then use common dictionary methods, which are implemented in the Config class.

populus.config.base.items

Retrieves the top level keys, so the value can be another nested config

populus.config.base.items(flatten=True)

Retrieves the full path.

>>> p.config.items()
(('chains', {'web3http': {'web3': {'foo': 'baz'}, 'chain': {'class': 'populus.chain.web3provider ....
>>> p.config.items(flatten=True)
(('chains.horton.chain.class', 'populus.chain.ExternalChain'), ('chains ...

Populus Configs Usage

proj_obj.project_config

The configuration loaded from the project local config file, project.json

proj_obj.user_config

The configuration loaded from the user config file, ~/.populus/config.json

proj_obj.config

The merged project_config and user_config: when project_config and user_config has the same key, the project_config overides user_config, and the key value in the merged project.config will be that of project_config

proj_obj.get_chain_config(...)

The chain configuration

chain_obj.get_web3_config

The chain’s Web3 configuration

proj_obj.reload_config

Reloads configuration from project.json and ~/.populus/config.json. You should instanciate the chain objects after reload.

Assignment & Persistency

Populus initial configuration is loaded from the JSON files.

You can customise the config keys in runtime, but these changes are not persisten and will not be saved. The next time Populus run, the configs will reset to project.json and ~/.populus/config.json.

Assignment of simple values works like any dictionary:

project_obj.config["chains.my_tester.chain.class"] = "populus.chain.tester.TesterChain"

However, since config is nested, you can assign a dictionary, or another config object, to a key:

project_obj.config["chains.my_tester.chain"] = {"class":"populus.chain.tester.TesterChain"}

You can even keep a another separate configuration file, and replace the entire project config in runtime, e.g. for testing, running in different environments, etc:

from populus.config import load_config
proj_object.config = load_config("path/to/another/config.json")

Reset all changes back to the default:

proj_obj.reload_config()

You will have to re-instanciate chains after the reload.

Note

JSON files may seem odd if you are used to Python settings files (like django), but we think that for blockchain development, the external, static files are safer than a programmble Python module.

JSON References

There is a caveat: config_obj['foo.baz'] may not return the same value is config_obj.get('foo.baz'). The reasone is that the configuration files are loaded as JSON schema, which allows $ref$. So if the config is:

{'foo':{'baz':{'$ref':'fawltyTowers'}}}

And in another place on the file you have:

'fawltyTowers':['Basil','Sybil','Polly','Manual']

Then:

>>> config_obj['foo.baz'] # doesn't solve $ref
{'$ref':'fawltyTowers'}
>>> config_obj.get('foo.baz') # solves $ref
['Basil','Sybil','Polly','Manual']

To avoid this, if you assign your own config_obj, use config_obj.unref(), which will solve all of the refernces.

Backends

Populus is plugable, using backend. The interface is defined in a base class, and a backend can overide or implement part or all this functionality.

E.g., the default backend for the Registrar is the JSONFileBackend, which saves the deployments details to a JSON file. But if you would need to save these details to RDBMS, you can write your own backend, and as long as it implements the Registrar functions (set_contract_address, get_contract_addresses) it will work.

Contracts backends:

class populus.contracts.filesystem.JSONFileBackend

is_provider: False, is_registrar: True

Saves registrar details to a JSON file

class populus.contracts.filesystem.MemoryBackend

is_provider: False, is_registrar: True

Saves registrar details to memory, in a simple dict variable

class populus.contracts.project.ProjectContractsBackend

is_provider: True, is_registrar: False

Gets the contracts data from the project source dirs

class populus.contracts.testing.TestContractsBackend

is_provider: True, is_registrar: False

Gets the contracts data from the project tests dir