Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
70ff42f
Improving py2/3 support with addition and usage of dyn.compat module.
Aug 25, 2014
6e33068
Updating HISTORY and __init__ for versioning info.
Aug 25, 2014
4c36d67
Adding QPS report functionality to Zones
Aug 27, 2014
a160e18
Added SecondaryZone docstring
Aug 27, 2014
f0999f3
Adding unix timestamp creation function to dyn.tm.utils
Aug 27, 2014
1822574
DNSSEC Timeline report now accepts datetime instances as input for st…
Aug 27, 2014
61e0684
RTTM log report now accepts datetime instances as input for start and…
Aug 27, 2014
6c4df4f
Merging v1.0.2 changes
Aug 27, 2014
f6381e7
Adding PyCharm files to gitignore
Sep 5, 2014
bc63283
Adding __str__, __repr__, __unicode__ methods to API based objects. A…
Sep 5, 2014
f7caa38
Adding some additional information to the README
Sep 5, 2014
5827f47
Adding example of new GSLB functionality
Sep 15, 2014
eac2d86
Adding password encryption. Still some testing left to do, but I'm ho…
Sep 15, 2014
fadbfa0
Fixed some py3 imports
Sep 15, 2014
517d786
cleaned_args no longer overwrites the actual arguments
Sep 15, 2014
84d52a2
Not sure how long this has been broken for but a sessions private __a…
Sep 15, 2014
3b7cbf7
Adding more information to the advanced topics page, primarily pertai…
Sep 15, 2014
09a610e
Added the ability for users to specify their own password encryption …
Sep 15, 2014
aa032f5
Adding __getstate__ and __setstate__ methods for pickleability of any…
Sep 15, 2014
791cb22
Fixing PyCharm idea folder in gitignore.
Sep 16, 2014
e66ed91
Bug fix for possible dict.copy bug
Sep 16, 2014
115bd06
Bug fix for the bug fix for possible dict.copy bug
Sep 16, 2014
4d5803c
Added datetime import. Added declarations to tell PyCharm to calm dow…
Sep 16, 2014
c8325cc
Adding __all__ attribute and file encoding
Sep 16, 2014
f150800
Bug fix for DNSSEC creation via Zone.add_service
Sep 16, 2014
f2b3f91
Bug fix for possible close_session key error
Sep 16, 2014
8a54427
Adding bug fixes line to HISTORY
Sep 16, 2014
cd2aeb3
Removed redundant json/simplejson checks in compat.py
Sep 16, 2014
e91e287
One last commit to fix the possible KeyErrors when closing a session
Sep 16, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ nosetests.xml
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
.pydevproject

# PyCharm
.idea/
14 changes: 13 additions & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
Release History
---------------
1.1.0 (2014-09-16)
++++++++++++++++++

* Internally improved Python2/3 compaability with the intoduction of the dyn.compat module
* Timestamps for various report types are accepted as Python datetime.datetime instances
* Added qps report access to Zones
* Added __str__, __repr__, __unicode__, and __bytes__ methods to all API object types
* Added conditional password encryption to allow for better in-app security
* Added the ability for users to specify their own password encryption keys
* Added __getstate__ and __setstate__ methods to SessionEngine, allowing sessions to be serialized
* Misc bug fixes

1.0.3 (2014-09-05)
++++++++++++++++++

Expand All @@ -9,7 +21,7 @@ Release History
++++++++++++++++++

* Added reports module
* Updated installation documentation.
* Updated installation documentation

1.0.1 (2014-08-06)
++++++++++++++++++
Expand Down
27 changes: 27 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,30 @@ services.
Requires Python 2.6 or higher, or the "simplejson" package.

For full documentation and examples see the dyn module on `Read The Docs <http://dyn.readthedocs.org>`_.

Installation
------------

To install the dyn SDK, simply:

.. code-block:: bash

$ pip install dyn


Documentation
-------------

Documentation is available on `Read The Docs`_

Contribute
----------

#. Check for open issues or open a new issue to start a discussion around a feature idea or a bug.
#. For bug reports especially it's encouraged for you to include a code snippet highlighting the bug.
#. For feature requests it's encouraged for you to include sample code highlighting a use case for the new feature.
#. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it).
#. Send a pull request and bug the maintainer until it gets merged and published. :)

.. _`the repository`: https://github.com/dyninc/dyn-python
.. _`Read The Docs`: http://dyn.readthedocs.org>
50 changes: 46 additions & 4 deletions docs/sessions.rst → docs/advanced.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
.. _sessions:

Advanced Sessions Overview
==========================
Advanced Topics
===============
This Section serves as a collective for advanced topics that most developers
using this library will never need to know about, but that may be useful for
developers who are destined to maintain this package

Sessions
--------

The way in which sessions are handled in this library are designed to be super
easy to use for developers who use this library, however, have become relatively
Expand All @@ -10,15 +16,15 @@ mainly for developers who would like to contribute to this code base, or who are
just curious as to what is actually going on under the hood.

Parent Class
------------
^^^^^^^^^^^^
Both :class:`dyn.tm.session.DynectSession` and :class:`dyn.mm.session.MMSession`
are subclasses of :class:`dyn.core.SessionEngine`. The :class:`dyn.core.SessionEngine`
provides an easy to use internal API for preparing, sending, and processing outbound
API calls. This class was added in v1.0.0 and greatly reduced the amount of logic
and duplicated code that made looking at these sessions so overly complex.

Parent Type
-----------
^^^^^^^^^^^
Since v0.4.0 sessions had always been implemented as a Singleton type. At this point
you're probably asing "Why?" And that's a bit of a complicated question. One of the main
reasons that these sessions were implemented as a Singleton was to make it easier for
Expand Down Expand Up @@ -120,3 +126,39 @@ other instances, since those instances are tied to the classes themselves instea
of held in the *globals* of the session modules. In addition this allows users
to have multiple active sessions across multiple threads, which was previously
impossible in the prior implementation.


Password Encryption
-------------------
The DynECT REST API only accepts passwords in plain text, and currently there is
no way around that. However, for those of you that are particularly mindful of
security (and even those of you who aren't) can probably see some serious pitfalls
to this. As far as most users of this library are concerned the passwords stored in
their :class:`~dyn.tm.session.DynectSession` objects will only ever live in memory,
so it's really not a huge deal that their passwords are stored in plain text. However,
for users looking to do more advanced things, such as serialize and store their session
objects in something less secure, such as a database, then these plain text passwords
are far less than ideal. Because of this in version 1.1.0 we've added optional
AES-256 password encryption for all :class:`~dyn.tm.session.DynectSession`
instances. All you need to do to enable password encryption is install
`PyCrypto <http://www.dlitz.net/software/pycrypto/>`_. The rest will happen
automatically.

Key Generation
^^^^^^^^^^^^^^
Also in version 1.1.0 an optional key field parameter was added to the
:class:`~dyn.tm.session.DynectSession` __init__ method. This field will allow
you to specify the key that your password will be encrypted using. However,
you may also choose to let the dyn module handle the key generation for you as
well using the :func:`~dyn.encrypt.generate_key` function which generates a,
nearly, random 50 character key that can be easily consumed by the
:class:`~dyn.encrypt.AESCipher` class (the class responsible for performing
the actual encryption and decryption.

Encrypt Module
^^^^^^^^^^^^^^
.. autofunction:: dyn.encrypt.generate_key

.. autoclass:: dyn.encrypt.AESCipher
:members:
:undoc-members:
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Contents:
quickstart
tm
mm
sessions
advanced


Indices and tables
Expand Down
1 change: 0 additions & 1 deletion docs/tm/reports.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@ List Functions
.. autofunction:: dyn.tm.reports.get_rttm_rrset
.. autofunction:: dyn.tm.reports.get_qps
.. autofunction:: dyn.tm.reports.get_zone_notes

20 changes: 20 additions & 0 deletions docs/tm/services/gslb/gslb.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,23 @@ the dyn.tm System and how to edit some of the same fields mentioned above.
>>> fqdn = zone + '.'
>>> gslb = GSLB(zone, fqdn)

Replacing a GSLB Monitor
^^^^^^^^^^^^^^^^^^^^^^^^
If you'd like to create a brand new :class:`Monitor` for your GSLB service, rather
than update your existing one, the following example shows how simple it is to
accomplish this task
::

>>> from dyn.tm.services.gslb import GSLB, Monitor
>>> zone = 'example.com'
>>> fqdn = zone + '.'
>>> gslb = GSLB(zone, fqdn)
>>> gslb.monitor.protocol
'HTTP'
>>> expected_text = "This is the text you're looking for."
>>> new_monitor = Monitor('HTTPS', 10, timeout=500, port=5005,
expected=expected_text)
>>> gslb.monitor = new_monitor
>>> gslb.monitor.protocol
'HTTPS'

2 changes: 1 addition & 1 deletion dyn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

Requires Python 2.6 or higher, or the "simplejson" package.
"""
version_info = (1, 0, 3)
version_info = (1, 1, 0)
__name__ = 'dyn'
__doc__ = 'A python wrapper for the DynDNS and DynEmail APIs'
__author__ = 'Jonathan Nappi, Cole Tuininga'
Expand Down
66 changes: 66 additions & 0 deletions dyn/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
"""python 2-3 compatability layer. The bulk of this was borrowed from
kennethreitz's requests module
"""
import sys

# -------
# Pythons
# -------

# Syntax sugar.
_ver = sys.version_info

#: Python 2.x?
is_py2 = (_ver[0] == 2)

#: Python 3.x?
is_py3 = (_ver[0] == 3)

# -----------------
# Version Specifics
# -----------------

if is_py2:
# If we have no JSON-esque module installed, we can't do anything
try:
import json
except ImportError as ex:
try:
import simplejson as json
except ImportError:
raise ex
from httplib import HTTPConnection, HTTPSConnection, HTTPException
from urllib import urlencode, pathname2url

string_types = (str, unicode)

def prepare_to_send(args):
return bytes(args)

def prepare_for_loads(body, encoding):
return body

def force_unicode(s, encoding='UTF-8'):
try:
s = unicode(s)
except UnicodeDecodeError:
s = str(s).decode(encoding, 'replace')

return s

elif is_py3:
from http.client import HTTPConnection, HTTPSConnection, HTTPException
from urllib.parse import urlencode
from urllib.request import pathname2url
import json
string_types = (str,)

def prepare_to_send(args):
return bytes(args, 'UTF-8')

def prepare_for_loads(body, encoding):
return body.decode(encoding)

def force_unicode(s, encoding='UTF-8'):
return str(s)
67 changes: 39 additions & 28 deletions dyn/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,15 @@
library, it is not recommened and could possible result in some strange
behavior.
"""
import sys
import time
import locale
import logging
import threading
from datetime import datetime
try:
import json
except ImportError:
try:
import simplejson as json
except ImportError:
sys.exit('Could not find json or simplejson libraries.')
if sys.version_info[0] == 2:
from httplib import HTTPConnection, HTTPSConnection, HTTPException
elif sys.version_info[0] == 3:
from http.client import HTTPConnection, HTTPSConnection, HTTPException
# API Libs
from dyn import __version__

from . import __version__
from .compat import (HTTPConnection, HTTPSConnection, HTTPException, json,
is_py2, is_py3, prepare_to_send, force_unicode)


def cleared_class_dict(dict_obj):
Expand Down Expand Up @@ -107,7 +97,7 @@ def close_session(cls):
key = getattr(cls, '__metakey__')
closed = cls._instances.get(key, {}).pop(cur_thread, None)
if len(cls._instances.get(key, {})) == 0:
del cls._instances[key]
cls._instances.pop(key, None)
return closed

@property
Expand Down Expand Up @@ -164,11 +154,7 @@ def _handle_response(self, response, uri, method, raw_args, final):
if self.poll_incomplete:
response, body = self.poll_response(response, body)
self._last_response = response
ret_val = None
if sys.version_info[0] == 2:
ret_val = json.loads(body)
elif sys.version_info[0] == 3:
ret_val = json.loads(body.decode('UTF-8'))
ret_val = json.loads(body.decode('UTF-8'))

self._meta_update(uri, method, ret_val)
# Handle retrying if ZoneProp is blocking the current task
Expand Down Expand Up @@ -225,15 +211,24 @@ def execute(self, uri, method, args=None, final=False):
:param final: boolean flag representing whether or not we have already
failed executing once or not
"""
if self._conn is None:
self.connect()

uri = self._validate_uri(uri)

# Make sure the method is valid
self._validate_method(method)

self.logger.debug('uri: {}, method: {}, args: {}'.format(uri, method,
args))
# Prepare arguments to send to API
raw_args, args, uri = self._prepare_arguments(args, method, uri)

# Don't display password when debug logging
cleaned_args = json.loads(args)
if 'password' in cleaned_args:
cleaned_args['password'] = '*****'

self.logger.debug('uri: {}, method: {}, args: {}'.format(uri, method,
cleaned_args))
# Send the command and deal with results
self.send_command(uri, method, args)

Expand Down Expand Up @@ -314,11 +309,7 @@ def send_command(self, uri, method, args):
self._conn.putheader('Content-length', '%d' % len(args))
self._conn.endheaders()

if sys.version_info[0] == 2:
self._conn.send(bytes(args))
elif sys.version_info[0] == 3:
# noinspection PyArgumentList
self._conn.send(bytes(args, 'UTF-8'))
self._conn.send(prepare_to_send(args))

def wait_for_job_to_complete(self, job_id, timeout=120):
"""When a response comes back with a status of "incomplete" we need to
Expand All @@ -343,7 +334,27 @@ def wait_for_job_to_complete(self, job_id, timeout=120):
response = self.execute(uri, 'GET', api_args)
return response

def __getstate__(cls):
"""Because HTTP/HTTPS connections are not serializeable, we need to
strip the connection instance out before we ship the pickled data
"""
d = cls.__dict__.copy()
d.pop('_conn')
return d

def __setstate__(cls, state):
"""Because the HTTP/HTTPS connection was stripped out in __getstate__ we
must manually re-enter it as None and let the sessions execute method
handle rebuilding it later
"""
cls.__dict__ = state
cls.__dict__['_conn'] = None

def __str__(self):
"""str override"""
return '<{}>'.format(self.name)
return force_unicode('<{}>').format(self.name)
__repr__ = __unicode__ = __str__

def __bytes__(self):
"""bytes override"""
return bytes(self.__str__())
Loading