- Development
- Contributing
- Getting started with contributing to Libcloud
- General contribution guidelines
- Code style guide
- Git pre-commit hook
- Code conventions
- Docstring conventions
- Contribution workflow
- 1. Start a discussion on our Github repository or on the mailing list
- 2. Open a new issue on our issue tracker
- 3. Fork our Github repository
- 4. Create a new branch for your changes
- 5. Make your changes
- 6. Write tests for your changes and make sure all the tests pass
- 7. Commit your changes
- 8. Open a pull request with your changes
- 9. Wait for the review
- Contributing Bigger Changes
- Supporting Multiple Python Versions
Development
This page describes Libcloud development process and contains generalguidelines and information on how to contribute to the project.
Contributing
We welcome contributions of any kind (ideas, code, tests, documentation,examples, …).
If you need help or get stuck at any point during this process, stop by on ourIRC channel (#libcloud on freenode) and we will do our best toassist you.
Getting started with contributing to Libcloud
General contribution guidelines
- Any non-trivial change must contain tests. For more information, refer to theTesting page.
- All the functions and methods must contain Sphinx docstrings which are usedto generate the API documentation. For more information, refer to theDocstring conventions section below.
- If you are adding a new feature, make sure to add a correspondingdocumentation.
Code style guide
- We follow PEP8 Python Style Guide
- Use 4 spaces for a tab
- Use 79 characters in a line
- Make sure edited file doesn’t contain any trailing whitespace
- You can verify that your modifications don’t break any rules by running the
flake8
script - e.g.flake8 libcloud/edited_file.py
ortox -e lint
.Second command will run flake8 on all the files in the repository.
And most importantly, follow the existing style in the file you are editing andbe consistent.
Git pre-commit hook
To make complying with our style guide easier, we provide a git pre-commit hookwhich automatically checks modified Python files for violations of our styleguide.
You can install it by running following command in the root of the repositorycheckout:
- ln -s contrib/pre-commit.sh .git/hooks/pre-commit
After you have installed this hook it will automatically check modified Pythonfiles for violations before a commit. If a violation is found, commit will beaborted.
Code conventions
This section describes some general code conventions you should follow whenwriting a Libcloud code.
1. Import ordering
Organize the imports in the following order:
- Standard library imports
- Third-party library imports
- Local library (Libcloud) importsEach section should be separated with a blank line. For example:
- import sys
- import base64
- import paramiko
- from libcloud.compute.base import Node, NodeDriver
- from libcloud.compute.providers import Provider
2. Function and method ordering
Functions in a module and methods on a class should be organized in thefollowing order:
- “Public” functions / methods
- “Private” functions / methods (methods prefixed with an underscore)
- “Internal” methods (methods prefixed and suffixed with a double underscore)For example:
- class Unicorn(object):
- def __init__(self, name='fluffy'):
- self._name = name
- def make_a_rainbow(self):
- pass
- def _get_rainbow_colors(self):
- pass
- def __eq__(self, other):
- return self.name == other.name
Methods on a driver class should be organized in the following order:
- Methods which are part of the standard API
- Extension methods
- “Private” methods (methods prefixed with an underscore)
- “Internal” methods (methods prefixed and suffixed with a double underscore)Methods which perform a similar functionality should be grouped together anddefined one after another.
For example:
- class MyDriver(object):
- def __init__(self):
- pass
- def list_nodes(self):
- pass
- def list_images(self):
- pass
- def create_node(self):
- pass
- def reboot_node(self):
- pass
- def ex_create_image(self):
- pass
- def _to_nodes(self):
- pass
- def _to_node(self):
- pass
- def _to_images(self):
- pass
- def _to_image(self):
- pass
Methods should be ordered this way for the consistency reasons and to makereading and following the generated API documentation easier.
3. Prefer keyword over regular arguments
For better readability and understanding of the code, prefer keyword overregular arguments.
Good:
- some_method(public_ips=public_ips, private_ips=private_ips)
Bad:
- some_method(public_ips, private_ips)
4. Don’t abuse **kwargs
You should always explicitly declare arguments in a function or a methodsignature and only use *kwargs
and args
respectively when there is avalid use case for it.
Using **kwargs
in many contexts is against Python’s “explicit is betterthan implicit” mantra and makes it for a bad and a confusing API. On top ofthat, it makes many useful things such as programmatic API introspection hardor impossible.
A use case when it might be valid to use **kwargs
is a decorator.
Good:
- def my_method(self, name, description=None, public_ips=None):
- pass
Bad (please avoid):
- def my_method(self, name, **kwargs):
- description = kwargs.get('description', None)
- public_ips = kwargs.get('public_ips', None)
5. When returning a dictionary, document its structure
Dynamic nature of Python can be very nice and useful, but if (ab)use it in awrong way it can also make it hard for the API consumer to understand what isgoing on and what kind of values are being returned.
If you have a function or a method which returns a dictionary, make sure toexplicitly document in the docstring which keys the returned dictionarycontains.
6. Prefer to use “is not None” when checking if a variable is provided or defined
When checking if a variable is provided or defined, prefer to useif foo is not None
instead of if foo
.
If you use if foo
approach, it’s easy to make a mistake when a valid valuecan also be falsy (e.g. a number 0
).
For example:
- class SomeClass(object):
- def some_method(self, domain=None):
- params = {}
- if domain is not None:
- params['Domain'] = domain
Docstring conventions
For documenting the API we we use Sphinx and reStructuredText syntax. Docstringconventions to which you should adhere to are described below.
- Docstrings should always be used to describe the purpose of methods,functions, classes, and modules.
- Method docstring should describe all the normal and keyword arguments. Youshould describe all the available arguments even if you use
args
and*
kwargs
. - All parameters must be documented using
:param p:
or:keyword p:
and:type p:
annotation. :param p: …
- A description of the parameterp
for a functionor method.:keyword p: …
- A description of the keyword parameterp
.:type p: …
The expected type of the parameterp
.- Return values must be documented using
:return:
and:rtype
annotation. :return: …
A description of return value for a function or method.:rtype: …
The type of the return value for a function or method.- Required keyword arguments must contain
(required)
notation indescription. For example::keyword image: OS Image to boot on node. (required)
- Multiple types are separated with
or
For example::type auth: :class:
.NodeAuthSSHKey
or :class:.NodeAuthPassword
- For a description of the container types use the following notation:
<container_type> of <objects_type>
. For example::rtype:
list
of :class:Node
For more information and examples, please refer to the following links:
- Sphinx Documentation - http://sphinx-doc.org/markup/desc.html#info-field-lists
- Example Libcloud module with documentation - https://github.com/apache/libcloud/blob/trunk/libcloud/compute/base.py
Contribution workflow
1. Start a discussion on our Github repository or on the mailing list
If you are implementing a big feature or a change, start a discussion on theissue tracker or themailing list first.
2. Open a new issue on our issue tracker
Go to our issue tracker and open a new issue for your changes there. Thisissue will be used as an umbrella place for your changes. As such, it will beused to track progress and discuss implementation details.
3. Fork our Github repository
Fork our Github git repository. Your fork will be used to hold your changes.
4. Create a new branch for your changes
For example:
- git checkout -b <change_name>
5. Make your changes
6. Write tests for your changes and make sure all the tests pass
Make sure that all the code you have added or modified has appropriate testcoverage. Also make sure all the tests including the existing ones still pass.
Use libcloud.test.unittest
as the unit testing package to ensure thatyour tests work with older versions of Python.
For more information on how to write and run tests, please seeTesting page.
7. Commit your changes
Commit your changes.
For example:
- git commit -m "Add a new compute driver for CloudStack based providers."
8. Open a pull request with your changes
Go to https://github.com/apache/libcloud/ and open a new pull request with yourchanges. Your pull request will appear at https://github.com/apache/libcloud/pulls.
9. Wait for the review
Wait for your changes to be reviewed and address any outstanding comments.
Contributing Bigger Changes
If you are contributing a bigger change (e.g. large new feature or a newprovider driver) you need to have signed Apache Individual ContributorLicense Agreement (ICLA) in order to have your patch accepted.
You can find more information on how to sign and file an ICLA on theApache website.
When filling the form, leave field preferred Apache id(s)
empty and inthe notify project
field, enter Libcloud
.
Supporting Multiple Python Versions
Libcloud supports a variety of Python versions so your code also needs to workwith all the supported versions. This means that in some cases you will need toinclude extra code to make sure it works in all the supported versions.
Some examples which show how to handle those cases are described below.
Context Managers
Context managers aren’t available in Python 2.5 by default. If you want to usethem make sure to put from future import with_statement
on top of thefile where you use them.
Utility functions for cross-version compatibility
You can find a lot of utility functions which make code easier to work withPython 2.x and 3.x in libcloud.utils.py3
module.
You can find some more information on changes which are involved in making thecode work with multiple versions on the following link -Lessons learned while porting Libcloud to Python 3