diff --git a/README.md b/README.md index edfd644..e425587 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This repository contains a *Python SDK* and a command line interface *CLI* (base ## Installing ### Via Python's pip -Cross-platform installation is available via pypi. Requires *Python 2.7* - this is not currently compatible with Python 3. +Cross-platform installation is available via pypi. If you have pip already installed the following command will get you running: ``` > pip install clc-sdk diff --git a/src/.pylintrc b/src/.pylintrc new file mode 100644 index 0000000..2f609b9 --- /dev/null +++ b/src/.pylintrc @@ -0,0 +1,241 @@ +[MASTER] + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore= + + +# Pickle collected data for later comparisons. +persistent=no + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). +# FIXME: I had to silence many more things than necessary. Everything after +# 'too-many-arguments' should be removed, and code re-checked. +disable=missing-docstring, no-name-in-module, locally-disabled, no-self-use, too-many-ancestors, too-many-public-methods, protected-access, arguments-differ, no-init, global-statement, logging-format-interpolation, bare-except, broad-except, no-member, too-many-nested-blocks, redefined-variable-type, len-as-condition, bad-continuation, bad-whitespace, invalid-name, too-many-arguments, mixed-indentation,multiple-statements,trailing-newlines,fixme,dangerous-default-value,redefined-builtin,attribute-defined-outside-init, superfluous-parens, + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Include message's id in output +include-ids=yes + +# Include symbolic ids of messages in output +symbols=yes + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + + +[BASIC] +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Regular expression which should only match correct module names +module-rgx=(([0-9\-]+)|([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([a-z_][a-z0-9_]*)|([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,80}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,80}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=(([A-Z_][A-Z0-9_]{2,40})|([a-z_][a-z0-9_]{2,40}))$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,40}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,40}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=a,b,c,e,f,i,j,k,m,n,p,q,v,x,y,_,cn,db,dc,dn,es,ex,fd,fn,ns,os,rc,id,ip,iv,vm,tz,hi,lo,cc,ws,pg + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=210 + +# Maximum number of lines in a module +max-module-lines=1800 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=1000 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=yes + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes= + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members= + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=7 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=1 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/src/clc/APIv2/__init__.py b/src/clc/APIv2/__init__.py index d1d74d8..b2e3e1d 100644 --- a/src/clc/APIv2/__init__.py +++ b/src/clc/APIv2/__init__.py @@ -15,6 +15,7 @@ API Documentaton v2: https://t3n.zendesk.com/categories/20067994-API-v2-0-Beta- """ +from __future__ import print_function, absolute_import, unicode_literals import requests import clc.defaults diff --git a/src/clc/APIv2/account.py b/src/clc/APIv2/account.py index e7aa95c..eea5870 100644 --- a/src/clc/APIv2/account.py +++ b/src/clc/APIv2/account.py @@ -21,6 +21,7 @@ account.is_managed """ +from __future__ import print_function, absolute_import, unicode_literals # TODO - change account # TODO - delete account @@ -29,7 +30,7 @@ import re import clc -class Account: +class Account(object): @staticmethod def GetAlias(session=None): diff --git a/src/clc/APIv2/alert.py b/src/clc/APIv2/alert.py index f818b82..34a8ad6 100644 --- a/src/clc/APIv2/alert.py +++ b/src/clc/APIv2/alert.py @@ -1,5 +1,5 @@ """ -Alert related functions. +Alert related functions. Alerts object variables: @@ -10,15 +10,13 @@ alert.server (optional server name already mapped to policy) """ +from __future__ import print_function, absolute_import, unicode_literals # TODO - Alerts filter by server, type (RAM, Disk, etc.) # TODO - Alert map/unmap # TODO - Alert delete # TODO - Alert create - missing API spec -import clc - - class Alerts(object): def __init__(self,alerts_lst,server=None): diff --git a/src/clc/APIv2/anti_affinity.py b/src/clc/APIv2/anti_affinity.py index 8d95577..60b544d 100644 --- a/src/clc/APIv2/anti_affinity.py +++ b/src/clc/APIv2/anti_affinity.py @@ -13,14 +13,15 @@ antiaffinity.servers """ +from __future__ import print_function, absolute_import, unicode_literals # TODO - Update Anti-Affinity Policy - returning 500 error # TODO - Create Anti-Affinity Policy - returning 400 error # TODO - Return servers object -import clc import json +import clc class AntiAffinity(object): @@ -121,7 +122,7 @@ def Update(self,name): """ - r = clc.v2.API.Call('PUT','antiAffinityPolicies/%s/%s' % (self.alias,self.id),{'name': name},session=self.session) + _ = clc.v2.API.Call('PUT','antiAffinityPolicies/%s/%s' % (self.alias,self.id),{'name': name},session=self.session) self.name = name @@ -133,7 +134,7 @@ def Delete(self): >>> a = clc.v2.AntiAffinity.GetLocation("WA1")[0] >>> a.Delete() """ - r = clc.v2.API.Call('DELETE','antiAffinityPolicies/%s/%s' % (self.alias,self.id),{},session=self.session) + _ = clc.v2.API.Call('DELETE','antiAffinityPolicies/%s/%s' % (self.alias,self.id),{},session=self.session) def __str__(self): diff --git a/src/clc/APIv2/api.py b/src/clc/APIv2/api.py index 1575226..c5fe2f4 100644 --- a/src/clc/APIv2/api.py +++ b/src/clc/APIv2/api.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- """Private class that executes API calls.""" +from __future__ import print_function, absolute_import, unicode_literals -import requests -import xml.etree.ElementTree -import clc import os import sys +import clc +import requests -class API(): +class API(object): # requests module includes cacert.pem which is visible when run as installed module. # pyinstall single-file deployment needs cacert.pem packaged along and referenced. @@ -84,7 +84,7 @@ def _Login(): @staticmethod - def Call(method,url,payload=None,session=None,debug=False): + def Call(method,url,payload=None,session=None,debug=False): # pylint: disable=too-many-branches """Execute v2 API call. :param url: URL paths associated with the API call @@ -112,7 +112,7 @@ def Call(method,url,payload=None,session=None,debug=False): http_session.headers.update({'Authorization': "Bearer %s" % token}) - if isinstance(payload, basestring): http_session.headers['content-type'] = "Application/json" # added for server ops with str payload + if isinstance(payload, str): http_session.headers['content-type'] = "Application/json" # added for server ops with str payload else: http_session.headers['content-type'] = "application/x-www-form-urlencoded" if method=="GET": diff --git a/src/clc/APIv2/datacenter.py b/src/clc/APIv2/datacenter.py index ca61054..ba5b9f8 100644 --- a/src/clc/APIv2/datacenter.py +++ b/src/clc/APIv2/datacenter.py @@ -14,6 +14,7 @@ datacenter.supports_shared_load_balancer """ +from __future__ import print_function, absolute_import, unicode_literals # TODO - init link to billing, statistics, scheduled activities # TODO - accounts link? @@ -21,7 +22,7 @@ import re import clc -class Datacenter: +class Datacenter(object): # pylint: disable=too-many-instance-attributes @staticmethod def Datacenters(alias=None, session=None): @@ -64,7 +65,7 @@ def __init__(self,location=None,name=None,alias=None,session=None): else: self.location = clc.v2.Account.GetLocation(session=self.session) - if False: + if False: # pylint: disable=using-constant-test # prepopulated info self.name = name self.location = location diff --git a/src/clc/APIv2/disk.py b/src/clc/APIv2/disk.py index 2a6fc33..4a2ab09 100644 --- a/src/clc/APIv2/disk.py +++ b/src/clc/APIv2/disk.py @@ -10,6 +10,7 @@ disk.size - disk size in GB """ +from __future__ import print_function, absolute_import, unicode_literals import re diff --git a/src/clc/APIv2/group.py b/src/clc/APIv2/group.py index 4021835..bec2fe3 100644 --- a/src/clc/APIv2/group.py +++ b/src/clc/APIv2/group.py @@ -28,6 +28,7 @@ group.custom_fields """ +from __future__ import print_function, absolute_import, unicode_literals # vCur: @@ -46,7 +47,6 @@ import re -import json import clc diff --git a/src/clc/APIv2/horizontal_autoscale.py b/src/clc/APIv2/horizontal_autoscale.py index 9a4b08a..9601c87 100644 --- a/src/clc/APIv2/horizontal_autoscale.py +++ b/src/clc/APIv2/horizontal_autoscale.py @@ -2,9 +2,10 @@ HorizontalAutoscale related functions. """ +from __future__ import print_function, absolute_import, unicode_literals -import clc import json +import clc class HorizontalAutoscalePolicy(object): diff --git a/src/clc/APIv2/network.py b/src/clc/APIv2/network.py index c5fc219..fbdf097 100644 --- a/src/clc/APIv2/network.py +++ b/src/clc/APIv2/network.py @@ -21,6 +21,7 @@ network.description """ +from __future__ import print_function, absolute_import, unicode_literals # TODO - create, change, delete NW - pending API spec @@ -146,7 +147,7 @@ def Update(self,name,description=None,location=None): payload = {'name': name} payload['description'] = description if description else self.description - r = clc.v2.API.Call('PUT','/v2-experimental/networks/%s/%s/%s' % (self.alias, location, self.id), payload, session=self.session) + _ = clc.v2.API.Call('PUT','/v2-experimental/networks/%s/%s/%s' % (self.alias, location, self.id), payload, session=self.session) self.name = self.data['name'] = name if description: self.data['description'] = description diff --git a/src/clc/APIv2/public_ip.py b/src/clc/APIv2/public_ip.py index 65116a9..f006296 100644 --- a/src/clc/APIv2/public_ip.py +++ b/src/clc/APIv2/public_ip.py @@ -23,6 +23,7 @@ source_restriction.cidr """ +from __future__ import print_function, absolute_import, unicode_literals # vCur: @@ -34,7 +35,6 @@ import re -import time import json import clc @@ -153,7 +153,6 @@ def Delete(self): """ - public_ip_set = [{'public_ipId': o.id} for o in self.parent.public_ips if o!=self] self.parent.public_ips = [o for o in self.parent.public_ips if o!=self] return(clc.v2.Requests(clc.v2.API.Call('DELETE','servers/%s/%s/publicIPAddresses/%s' % (self.parent.server.alias,self.parent.server.id,self.id), session=self.session), diff --git a/src/clc/APIv2/queue.py b/src/clc/APIv2/queue.py index 92d7277..712ac2d 100644 --- a/src/clc/APIv2/queue.py +++ b/src/clc/APIv2/queue.py @@ -22,6 +22,7 @@ """ +from __future__ import print_function, absolute_import, unicode_literals # TODO - Do something with timing info from Request and Requests? @@ -30,14 +31,14 @@ import clc -class Queue(object): +class Queue(object): # pylint: disable=too-few-public-methods pass class Requests(object): - def __init__(self,requests_lst,alias=None,session=None): + def __init__(self,requests_lst,alias=None,session=None): # pylint: disable=too-many-branches """Create Requests object. Treats one or more requests as an atomic unit. @@ -102,7 +103,7 @@ def __init__(self,requests_lst,alias=None,session=None): def __add__(self,obj): - if type(obj) is int: return(self) # we get this with a sum() call - ignore the first argument + if isinstance(obj, int): return(self) # we get this with a sum() call - ignore the first argument if self.alias != obj.alias: raise(ArithmeticError("Cannot add Requests operating on different aliases")) new_obj = Requests([],alias=self.alias,session=self.session) @@ -176,7 +177,7 @@ def __init__(self,id,alias=None,request_obj=None,session=None): if request_obj: self.data = request_obj else: self.data = {'context_key': None, 'context_val': None} - self.data = dict({'status': None}.items() + self.data.items()) + self.data = dict(list({'status': None}.items()) + list(self.data.items())) def __getattr__(self,var): @@ -250,7 +251,7 @@ def __str__(self): class Requestv2Experimental(Request): """This is the v2-experimental implementation for requests. """ - def __init__(self,id,uri,session=None): + def __init__(self,id,uri,session=None): # pylint: disable=super-init-not-called """Create Request object. Response string feeding this looks like: diff --git a/src/clc/APIv2/server.py b/src/clc/APIv2/server.py index cd4c523..2c7cc34 100644 --- a/src/clc/APIv2/server.py +++ b/src/clc/APIv2/server.py @@ -42,6 +42,7 @@ server.managed_apps """ +from __future__ import print_function, absolute_import, unicode_literals # vCur: # TODO - Server pricing (/v2/billing/btdi/serverPricing/wa1btdiapi207) returns array with static hourly pricing @@ -60,7 +61,6 @@ # TODO - Servers search by custom field import re -import math import json import time import clc @@ -145,7 +145,7 @@ def StopMaintenance(self): return(self._Operation('stopMaintenance')) -class Server(object): +class Server(object): # pylint: disable=too-many-instance-attributes def __init__(self,id,alias=None,server_obj=None,session=None): @@ -203,7 +203,7 @@ def Refresh(self): self.data['changeInfo']['modifiedDate'] = clc.v2.time_utils.ZuluTSToSeconds(self.data['changeInfo']['modifiedDate']) # API call switches between GB and MB. Change to all references are in GB and we drop the units - self.data['details']['memoryGB'] = int(math.floor(self.data['details']['memoryMB']/1024)) + self.data['details']['memoryGB'] = self.data['details']['memoryMB'] // 1024 except: pass @@ -547,6 +547,7 @@ def Create(name,template,group_id,network_id,cpu=None,memory=None,alias=None,pas additional_disks=[],custom_fields=[],ttl=None,managed_os=False,description=None, source_server_password=None,cpu_autoscale_policy_id=None,anti_affinity_policy_id=None, packages=[],configuration_id=None,session=None): + # pylint: disable=too-many-locals,too-many-branches """Creates a new server. https://www.centurylinkcloud.com/api-docs/v2/#servers-create-server @@ -622,6 +623,7 @@ def Clone(self,network_id,name=None,cpu=None,memory=None,group_id=None,alias=Non custom_fields=None,ttl=None,managed_os=False,description=None, source_server_password=None,cpu_autoscale_policy_id=None,anti_affinity_policy_id=None, packages=[],count=1): + # pylint: disable=too-many-locals,too-many-branches """Creates one or more clones of existing server. https://www.centurylinkcloud.com/api-docs/v2/#servers-create-server @@ -643,7 +645,7 @@ def Clone(self,network_id,name=None,cpu=None,memory=None,group_id=None,alias=Non """ - if not name: name = re.search("%s(.+)\d{2}$" % self.alias,self.name).group(1) + if not name: name = re.search(r"%s(.+)\d{2}$" % self.alias,self.name).group(1) #if not description and self.description: description = self.description if not cpu: cpu = self.cpu if not memory: memory = self.memory @@ -661,7 +663,7 @@ def Clone(self,network_id,name=None,cpu=None,memory=None,group_id=None,alias=Non # TODO - need to get network_id of self, not currently exposed via API :( requests_lst = [] - for i in range(0,count): + for _ in range(0,count): requests_lst.append(Server.Create( \ name=name,cpu=cpu,memory=memory,group_id=group_id,network_id=network_id,alias=self.alias, password=password,ip_address=ip_address,storage_type=storage_type,type=type, @@ -698,7 +700,7 @@ def ConvertToTemplate(self,visibility,description=None,password=None): - def Change(self,cpu=None,memory=None,description=None,group_id=None): + def Change(self,cpu=None,memory=None,description=None,group_id=None): # pylint: disable=unused-argument """Change existing server object. One more more fields can be set and method will return with a requests @@ -708,10 +710,9 @@ def Change(self,cpu=None,memory=None,description=None,group_id=None): """ - if group_id: groupId = group_id + if group_id: groupId = group_id # pylint: disable=unused-variable else: groupId = None - payloads = [] requests = [] for key in ("cpu","memory","description","groupId"): diff --git a/src/clc/APIv2/template.py b/src/clc/APIv2/template.py index 493d915..e510c97 100644 --- a/src/clc/APIv2/template.py +++ b/src/clc/APIv2/template.py @@ -9,9 +9,7 @@ template.id (alias of name) """ - - -import clc +from __future__ import print_function, absolute_import, unicode_literals class Templates(object): @@ -46,7 +44,7 @@ def Search(self,key): return(results) -class Template(object): +class Template(object): # pylint: disable=too-few-public-methods def __init__(self,id,template_obj=None): """Create Template object.""" diff --git a/src/clc/APIv2/time_utils.py b/src/clc/APIv2/time_utils.py index 59c8953..3614e65 100644 --- a/src/clc/APIv2/time_utils.py +++ b/src/clc/APIv2/time_utils.py @@ -2,6 +2,7 @@ Time utility functions. """ +from __future__ import print_function, absolute_import, unicode_literals import time diff --git a/src/setup.py b/src/setup.py index d5a9c78..24a2678 100644 --- a/src/setup.py +++ b/src/setup.py @@ -1,4 +1,3 @@ - # # python setup.py sdist # python setup.py bdist_dumb @@ -7,12 +6,12 @@ # follow notes from https://packaging.python.org/distributing/#id69 # and release with `python setup.py sdist upload` - +from __future__ import unicode_literals from setuptools import setup, find_packages setup( name = "clc-sdk", - version = "2.47", + version = "2.48", packages = find_packages("."), install_requires = ['prettytable','clint','argparse','requests'], @@ -31,6 +30,12 @@ keywords = "CenturyLink Cloud SDK CLI", url = "https://github.com/CenturyLinkCloud/clc-python-sdk", - # could also include long_description, download_url, classifiers, etc. + classifiers=[ # See https://pypi.org/pypi?%3Aaction=list_classifiers + "Natural Language :: English", + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", # v2 API only + ], )