Skip to content

Latest commit

 

History

History
296 lines (238 loc) · 9.59 KB

STYLEGUIDE.md

File metadata and controls

296 lines (238 loc) · 9.59 KB

Particle Cloud Framework Style Guide

This is a Python Style Guide for the PCF Framework. The intent of this guide is to strive for cleaner and better code quality. Following this guide, our code will be approachable to anyone who needs to maintain it later, including ourselves!

Table of Contents

  1. Virtual Environments
  2. Imports
  3. Naming
    i. Variables
    ii. Class names
    iii. Functions/Methods
  4. Spacing
  5. Strings
    i. Quotes
    ii. String Formatting
    iii. Docstrings
  6. Dictionary Key-Value Retrieval
  7. Method Returns
  8. PCF Utility Functions
  9. PCF Exceptions
  10. Logging Standards

Conda is the preferred virtual environment setup for testing and development. However, Python v3.3+ include the native Python library venv (virtual environment). The steps for setting up a virtual environment with venv can be found here.

Conda

Conda is a package management system that can help you easily keep track of the package requirements for projects and install them automatically for you. You can set up and share collections of packages called environments.

For sake of clarity, all references will be intended for MacOS users.

Installation

Visit this link to find installation details for your appropriate OS.

MacOs

Click version 3.7

Create a new virtual env

Visit this link to find documentation on your appropriate OS.

All conda commands will be typed in the Terminal window.

conda create --name pcf-local python==3.6

PCF requires Python version 3.6+

Activate virutal env

source activate pcf-local

Deactivate virtual env

source deactivate

View list of exisiting virtual envs

conda info --envs

Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants.

Refrain from importing entire modules when only 1 object from the module is needed. For consistency, entire import modules should be imported first. (import module) Then, imported functions from modules should be imported. (from module import function)

Don't:

from time import sleep

import boto3, json, logging
import pcf.core.PCF_exceptions 

Do:

import boto3
import json
import logging

from pcf.core.pcf_exceptions import NoResourceException
from time import sleep

PCF typically follows the same naming conventions as PEP8 standards.

# Lowercase multi-word separated by an underscore
my_greeting = "hello"
# Non-public instance variables should begin with a single underscore
_client = ""
# UpperCaseCamelCase convention
class LambaFunction():
    # class

class AWSResource():
    # class

class EC2Instance():
    # class
# Lowercase multi-word separated by an underscore
def get_status(self):
    pass
# Non-public methods should begin with a single underscore
def _terminate(self):
    pass
  • 4 spaces = 1 indent
  • Leave a single blank line after function/method declarations to aid in visual clarity and organization

In Python single quotes and double quotes are used interchangablely. We will stick to double quotes for consistency.

Don't:

nameOfSchool = 'Marshall'
nameOfOffice = "Clarendon"

Do:

nameOfSchool = "Marshall"
nameOfOffice = "Clarendon"

As of Python 3.6, f-strings (formatted string literals) have optimized the former way of formatting strings: %-formatting and str.format(). Learn more about the f-strings here.

Don't:

def generate_pcf_id(flavor, pcf_name):
    return "{}:{}".format(flavor, pcf_name)

Do:

def generate_pcf_id(flavor, pcf_name):
    return f"{flavor}:{pcf_name}"  # f and F are to be used interchangeably

Docstrings act as documentation for method definitions of a class within PCF. Always use """triple double quotes""" around docstrings. For multi-line docstrings, place the closing qoutes on a line by itself.

The docstrings should give a brief description of what the method/class is doing, explain arguments and their type, if any, and list what the method/class returns.

If methods and classes are not documented this way they will not be merged in.

For more info see the docs.

Example:

def is_state_equivalent(self, state1, state2):
    """
    Determines if states are equivalent. Uses equivalent_states defined in the Glacier class.

    Args:
        state1 (State):
        state1 (State):

    Returns:
        bool
    """

When retrieving a value from a dictionary key, do not use square bracket notation. This method will return an error if the indicated key does not exist in the dictionary. Instead, use the get method which returns None by default if the indicated key does not exist in the dictionary.

Don't:

bucket_name = desired_state_definition["Bucket"]

Do:

# dict.get("Key", default=None)
bucket_name = desired_state_definition.get("Bucket")

Methods that returns dictionaries should always return an empty dictionary, {}, rather than None in the case that there is nothing to return. The reason being is so that typing is consistent and so that we can utilize the built-in boolean valuation of dictionaries.

Example:

def _get_alias(self):
    """
    Returns the alias record for the provided key_name in custom configuration.

    Returns:
         {} (dict) Containing nothing, or the keys below:
             - AliasName (str) -  The alias name (always starts with "alias/")
             - AliasArn (str) - The ARN for the key alias
             - TargetKeyId (str) - The unique identifier for the key the alias is\
                associated with
    """
    alias_list = self.client.list_aliases()
    for alias in alias_list.get('Aliases'):
        if alias.get('AliasName') == 'alias/' + self.key_name:
            return alias
    return {}

There are several functions in pcf_util.py that perform various tasks such as creating a dictionary passed on a key set and finding nested values in a dictionary. Refer to this module when performing such complex operations. If there is not an existing function that meets your needs, simply create one. The desired functions are then imported into the particular module you are implementing.

Example:

def param_filter(curr_dict, key_set, remove=False):
    """
    Filters param dictionary to only have keys in the key set
    Args:
        curr_dict (dict): param dictionary
        key_set (set): set of keys you want
        remove (bool): filters by what to remove instead of what to keep
    Returns:
        filtered param dictionary
    """
    if remove:
        return {key: curr_dict[key] for key in curr_dict.keys() if key not in key_set}
    else:
        return {key: curr_dict[key] for key in key_set if key in curr_dict.keys()}

There are several exceptions in pcf_exceptions.py that define various exceptions. Refer to this module when considering exception handling. If there is not an existing exception that meets your needs, simply create one. The desired exceptions are then imported into the particular module you are implementing.

Example:

class NoCodeException(Exception):
    def __init__(self):
        Exception.__init__(self, "Did not provide local zipfile or zipfile location in S3")

Logging is a means of tracking events that happen when some software runs. More information on logging in Python can be found here.

To enable logging in PCF, add the following code to the top of your pcf python file.

Example:

import logging

logger = logging.getLogger(__name__)

def get_state(self):
    """
    Calls sync state and afterward returns the current state. Uses cached state if available.

    Returns:
        state
    """
    if not self.use_cached_state():
        self.sync_state()
        self.state_last_refresh_time = time.time()
        logger.info(f"Refreshed state for {self.pcf_id}: {self.state}")
    else:
        logger.debug(f"Using cached state for {self.pcf_id}: {self.state}")
    return self.state