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
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
anduser_config
: whenproject_config
anduser_config
has the same key, theproject_config
overidesuser_config
, and the key value in the mergedproject.config
will be that ofproject_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