From a3d62066a169c26712c30061232146dd46926ff1 Mon Sep 17 00:00:00 2001 From: kballou Date: Tue, 7 Apr 2015 17:59:23 -0600 Subject: Add initial skeleton code * Includes the `ChorusSession` object that underpins all API calls --- MANIFEST.in | 2 + build.py | 35 +++ pylibchorus/__init__.py | 6 + pylibchorus/chorus_client.py | 85 +++++++ pylibchorus/tests/__init__.py | 6 + pylibchorus/tests/chorus_client_tests.py | 74 ++++++ pylibchorus/version.py | 5 + pylint.conf | 382 +++++++++++++++++++++++++++++++ setup.py | 58 +++++ 9 files changed, 653 insertions(+) create mode 100644 MANIFEST.in create mode 100644 build.py create mode 100644 pylibchorus/__init__.py create mode 100644 pylibchorus/chorus_client.py create mode 100644 pylibchorus/tests/__init__.py create mode 100644 pylibchorus/tests/chorus_client_tests.py create mode 100644 pylibchorus/version.py create mode 100644 pylint.conf create mode 100644 setup.py diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..a6cff13 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include README.markdown +recursive-include pylibchorus *.py diff --git a/build.py b/build.py new file mode 100644 index 0000000..84e7617 --- /dev/null +++ b/build.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +'''Xnt build script for project''' + +import xnt + +@xnt.target +def build(): + xnt.setup(['build']) + +@xnt.target +def clean(): + xnt.rm('pylibchorus.egg-info', + 'build', + 'dist', + '.eggs', + '**/__pycache__', + '**/tests/__pycache__', + '**/*.pyc', + '**/tests/*.pyc') + +@xnt.target +def test(): + return xnt.setup(['test']) + +@xnt.target +def lint(): + return xnt.call(['pylint', '--rcfile=pylint.conf', 'pylibchorus']) + +@xnt.target +def package(): + return xnt.setup(['bdist']) + +@xnt.target +def install(): + return xnt.setup(['install', '--user']) diff --git a/pylibchorus/__init__.py b/pylibchorus/__init__.py new file mode 100644 index 0000000..7e473c2 --- /dev/null +++ b/pylibchorus/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +'''PyLibChorus -- Python Chorus API Library''' + +import logging + +LOG = logging.getLogger(__name__) diff --git a/pylibchorus/chorus_client.py b/pylibchorus/chorus_client.py new file mode 100644 index 0000000..5c7119f --- /dev/null +++ b/pylibchorus/chorus_client.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +'''Alpine/Chorus Client API Module''' + +import logging +import requests + +LOG = logging.Logger(name=__name__) + +CONTENT_TYPE = 'application/x-www-form-urlencoded' +JSON_CONTENT_TYPE = 'application/json' + +class ChorusSession(object): + '''Chorus User Session Object''' + + def __init__(self, config): + self.config = config + self.sid = None + self.cookies = None + + def __enter__(self): + '''create session and return sid and cookies''' + + request_data = _login_(self.config.get('alpine', 'username'), + self.config.get('alpine', 'password')) + + LOG.debug("Opening Chorus Session") + post = requests.post(self.get_url(request_data['url']), + params=request_data['params'], + data=request_data['data'], + headers=request_data['headers']) + LOG.info("Status code for session open: %d", post.status_code) + json = post.json() + + self.sid = json['response']['session_id'] + self.cookies = post.cookies + return self + + def __exit__(self, _type, _value, _traceback): + '''Close chorus session''' + request_data = _logout_(self.sid, self.cookies) + LOG.debug("Closing Chorus Session") + delete = requests.delete(self.get_url(request_data['url']), + params=request_data['params'], + headers=request_data['headers'], + cookies=request_data['cookies']) + + LOG.info("Status code for close: %d", delete.status_code) + + def get_url(self, path=""): + '''Return the host and path for the chorus instance''' + + return "http://%s/%s" % (self.config.get('alpine', 'host'), path) + +def _login_(username, password): + '''Create Request Data for ChorusSession''' + return { + 'data': { + 'username': username, + 'password': password, + }, + 'headers': { + 'content-type': CONTENT_TYPE, + }, + 'params': { + 'session_id': '', + }, + 'cookies': None, + 'url': '/sessions?session_id=', + 'method': 'POST', + } + +def _logout_(sid, cookies): + '''Create request data for ChorusSession''' + return { + 'data': None, + 'headers': { + 'content-type': CONTENT_TYPE, + }, + 'params': { + 'session_id': sid, + }, + 'cookies': cookies, + 'url': '/sessions', + 'method': 'DELETE', + } diff --git a/pylibchorus/tests/__init__.py b/pylibchorus/tests/__init__.py new file mode 100644 index 0000000..d4e601a --- /dev/null +++ b/pylibchorus/tests/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +'''PyLibChorus Tests Module''' + +import logging + +logging.basicConfig(level=logging.DEBUG) diff --git a/pylibchorus/tests/chorus_client_tests.py b/pylibchorus/tests/chorus_client_tests.py new file mode 100644 index 0000000..e2eb6e1 --- /dev/null +++ b/pylibchorus/tests/chorus_client_tests.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +'''Chorus Client Test Cases''' + +import logging +from pylibchorus.chorus_client import _login_ +from pylibchorus.chorus_client import _logout_ +import unittest + +LOG = logging.getLogger(__name__) + +class ChorusSessionTests(unittest.TestCase): + '''ChorusSession Test Case''' + + def test_login_returns_request_data(self): + '''Test _login_ returns request data''' + actual = _login_('chorusadmin', 'secret') + self.assertIsNotNone(actual) + self.assertIn('data', actual) + self.assertIn('headers', actual) + self.assertIn('params', actual) + self.assertIn('cookies', actual) + self.assertIn('url', actual) + self.assertIn('method', actual) + self.assertIsNotNone(actual['data']) + self.assertIsNotNone(actual['headers']) + self.assertIsNotNone(actual['params']) + self.assertIsNone(actual['cookies']) + self.assertIsNotNone(actual['url']) + self.assertIsNotNone(actual['method']) + data = actual['data'] + self.assertIn('username', data) + self.assertIn('password', data) + self.assertEquals(data['username'], 'chorusadmin') + self.assertEquals(data['password'], 'secret') + headers = actual['headers'] + self.assertIn('content-type', headers) + self.assertEquals('application/x-www-form-urlencoded', + headers['content-type']) + params = actual['params'] + self.assertIn('session_id', params) + self.assertEquals(params['session_id'], '') + self.assertEquals('/sessions?session_id=', actual['url']) + self.assertEquals('POST', actual['method']) + + + #pylint: disable=C0103 + def test_logout_returns_request_data(self): + '''Test _logout_ returns correct request data''' + sid = 'foobar' + cookies = {'session_id', sid} + actual = _logout_(sid, cookies) + self.assertIsNotNone(actual) + self.assertIn('data', actual) + self.assertIn('headers', actual) + self.assertIn('params', actual) + self.assertIn('cookies', actual) + self.assertIn('url', actual) + self.assertIn('method', actual) + self.assertIsNone(actual['data']) + self.assertIsNotNone(actual['headers']) + self.assertIsNotNone(actual['params']) + self.assertIsNotNone(actual['cookies']) + self.assertIsNotNone(actual['url']) + self.assertIsNotNone(actual['method']) + headers = actual['headers'] + self.assertIn('content-type', headers) + self.assertEquals('application/x-www-form-urlencoded', + headers['content-type']) + params = actual['params'] + self.assertIn('session_id', params) + self.assertEquals(sid, params['session_id']) + self.assertEquals(cookies, actual['cookies']) + self.assertEquals('/sessions', actual['url']) + self.assertEquals('DELETE', actual['method']) diff --git a/pylibchorus/version.py b/pylibchorus/version.py new file mode 100644 index 0000000..ead4b0d --- /dev/null +++ b/pylibchorus/version.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +'''Version Information Definition''' + +__version_info__ = (0, 0, 1) +__version__ = ".".join(str(i) for i in __version_info__) diff --git a/pylint.conf b/pylint.conf new file mode 100644 index 0000000..f2e6a3b --- /dev/null +++ b/pylint.conf @@ -0,0 +1,382 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +jobs=4 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. +optimize-ast=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# 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. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=E1608,W1627,E1601,E1603,E1602,E1605,E1604,E1607,E1606,W1621,W1620,W1623,W1622,W1625,W1624,W1609,W1608,W1607,W1606,W1605,W1604,W1603,W1602,W1601,W1639,W1640,I0021,W1638,I0020,W1618,W1619,W1630,W1626,W1637,W1634,W1635,W1610,W1611,W1612,W1613,W1614,W1615,W1616,W1617,W1632,W1633,W0704,W1628,W1629,W1636 + + +[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 + +# 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=yes + +# 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 + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,input + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,35}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,35}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=79 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[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 module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis +ignored-modules= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# 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=REQUEST,acl_users,aq_parent + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# 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= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# 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 + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# 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-branches=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,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/setup.py b/setup.py new file mode 100644 index 0000000..1c2014a --- /dev/null +++ b/setup.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +'''pylibchorus setup''' + +import os +from setuptools import setup, find_packages + +VERSION = os.path.join(os.path.abspath( + os.path.dirname(__file__)), 'pylibchorus', 'version.py') + +exec(compile(open(VERSION).read(), VERSION, 'exec')) + +def gen_build_num(): + from datetime import datetime + now = datetime.now() + return ''.join(str(x) for x in (now.year, + now.month, + now.day, + now.hour, + now.minute, + now.second)) + + +def read(fname): + return "\n" + open(os.path.join(os.path.dirname(__file__), fname)).read() + +setup( + name="pylibchorus", + version=__version__ + '.' + gen_build_num(), + author="Kenny Ballou", + author_email="kb@alpinenow.com", + url="http://alpinenow.com", + description=("Python Chorus API Library"), + packages=find_packages(), + test_suite="pylibchorus.tests", + package_data={ + }, + long_description=read("README.markdown"), + install_requires=[ + 'requests', + 'setuptools', + ], + classifiers=[ + 'Development Status :: 2 - Pre-Alpha', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Operating System :: MacOS :: MacOS X', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Software Development', + 'Topic :: Software Development :: Libraries', + 'Topic :: Utilities', + ], +) -- cgit v1.2.1