setup basic project, with code in core. setup.py, make file, start of a readme.
This commit is contained in:
parent
78f6ac9347
commit
5cccc4f8e2
5
Makefile
Normal file
5
Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
init:
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
test:
|
||||||
|
py.test tests
|
7
README.rst
Normal file
7
README.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Celigo Backup
|
||||||
|
-------------
|
||||||
|
|
||||||
|
This module interacts with the Celigo API.
|
||||||
|
|
||||||
|
If you have any Celigo flows that have a NetSuite connection, and you wish
|
||||||
|
to backup the field mappings for the flow, then use this.
|
1
celigo/__init__.py
Normal file
1
celigo/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .core import BackupCeligo
|
211
celigo/core.py
Normal file
211
celigo/core.py
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import glob
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
from slugify import slugify
|
||||||
|
|
||||||
|
import prompt
|
||||||
|
|
||||||
|
L = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BackupCeligo(object):
|
||||||
|
"""
|
||||||
|
This module interacts with the Celigo API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, data_dir, api_key, base_url=None):
|
||||||
|
if not base_url:
|
||||||
|
base_url = "https://api.integrator.io/v1/"
|
||||||
|
|
||||||
|
self.api_key = api_key
|
||||||
|
self.data_dir = data_dir
|
||||||
|
self.base_url = base_url
|
||||||
|
self.imports_cache = {}
|
||||||
|
self.setup_requests_session()
|
||||||
|
self.ensure_directories_exist()
|
||||||
|
|
||||||
|
def ensure_directories_exist(self):
|
||||||
|
""" Make the directory if it doesn't exist """
|
||||||
|
subdirs = ('imports', 'connections')
|
||||||
|
|
||||||
|
for subdir in subdirs:
|
||||||
|
_dir = os.path.join(self.data_dir, subdir)
|
||||||
|
if not os.path.exists(_dir):
|
||||||
|
os.makedirs(_dir)
|
||||||
|
|
||||||
|
def setup_requests_session(self):
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.session.headers.update({
|
||||||
|
"Authorization": "Bearer {API_KEY}".format(
|
||||||
|
API_KEY=self.api_key),
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _celigo_api_get(self, path):
|
||||||
|
"""
|
||||||
|
Make a GET request to the celigo API
|
||||||
|
|
||||||
|
:param path: The rest of the path after BASE_URL
|
||||||
|
:return conf_dict: response as a dict of the json returned from the
|
||||||
|
api.
|
||||||
|
"""
|
||||||
|
response = self.session.get(url=self.base_url + path)
|
||||||
|
response.raise_for_status()
|
||||||
|
conf_dict = response.json()
|
||||||
|
L.info("Got conf_dict from %s", self.base_url + path)
|
||||||
|
return conf_dict
|
||||||
|
|
||||||
|
def _celigo_api_put(self, path, body):
|
||||||
|
"""
|
||||||
|
Make a GET request to the celigo API
|
||||||
|
|
||||||
|
:param path: The rest of the path after BASE_URL
|
||||||
|
:return conf_dict: response as a dict of the json returned from the
|
||||||
|
api.
|
||||||
|
"""
|
||||||
|
response = self.session.put(
|
||||||
|
url=self.base_url + path,
|
||||||
|
data=json.dumps(body)
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
L.info("Restored backup to %s", self.base_url + path)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def backup(self, auto=False):
|
||||||
|
"""
|
||||||
|
Get all the flow data from Celigo.
|
||||||
|
Then loop over each flow and cache it's Import data in an instance
|
||||||
|
varable.
|
||||||
|
Once this is cached, save the imports.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
flows = self._celigo_api_get("flows/")
|
||||||
|
for flow in flows:
|
||||||
|
self.cache_import_remote(flow)
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
L.info('HTTP Request failed')
|
||||||
|
raise
|
||||||
|
L.info("Got all imports, writing now")
|
||||||
|
L.info("We have imports for: %s", ", ".join(self.imports_cache.keys()))
|
||||||
|
|
||||||
|
for flow_name, (import_id, import_conf) in self.imports_cache.items():
|
||||||
|
self.save_import(flow_name, auto)
|
||||||
|
|
||||||
|
def restore(self, auto=False):
|
||||||
|
"""
|
||||||
|
Get all the import files in the import direcotry,
|
||||||
|
and store them in the local self.imports_cache cache.
|
||||||
|
Once they are stored, prompt on which to restore to Celigo.
|
||||||
|
"""
|
||||||
|
import_dir = os.path.join(self.data_dir, "imports")
|
||||||
|
|
||||||
|
for fname in glob.glob("{}/*_*.json".format(import_dir)):
|
||||||
|
self.cache_import_json(fname)
|
||||||
|
|
||||||
|
while self.imports_cache.keys():
|
||||||
|
if auto:
|
||||||
|
action = "All"
|
||||||
|
else:
|
||||||
|
options = self.imports_cache.keys()
|
||||||
|
options = sorted(options)
|
||||||
|
options.append("All")
|
||||||
|
options.append("None")
|
||||||
|
action = prompt("Backup which import(s)?", options)
|
||||||
|
|
||||||
|
if action == "None":
|
||||||
|
# We're done here.
|
||||||
|
return
|
||||||
|
elif action == "All":
|
||||||
|
# Process them all.
|
||||||
|
for key in self.imports_cache.keys():
|
||||||
|
self.restore_to_celigo(key)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Process one and remove it from the cache so we don't reupload
|
||||||
|
self.restore_to_celigo(action)
|
||||||
|
del self.imports_cache[action]
|
||||||
|
|
||||||
|
def cache_import_remote(self, flow):
|
||||||
|
"""
|
||||||
|
Stores the import in self.imports_cache before write.
|
||||||
|
"""
|
||||||
|
flow_name = slugify(flow['name'])
|
||||||
|
import_id = flow['_importId']
|
||||||
|
import_conf = self._celigo_api_get(
|
||||||
|
"imports/{id}/distributed".format(
|
||||||
|
id=import_id))
|
||||||
|
|
||||||
|
self.imports_cache[flow_name] = (import_id, import_conf)
|
||||||
|
|
||||||
|
def save_import(self, flow_name, auto=False):
|
||||||
|
"""
|
||||||
|
Write the import to a .json file with name_id.json format.
|
||||||
|
Prompt for overwrite.
|
||||||
|
:param flow_name: the slugified name of the flow as a key
|
||||||
|
:param auto: if auto is true, don't prompt for overwrite
|
||||||
|
"""
|
||||||
|
import_id, import_conf = self.imports_cache[flow_name]
|
||||||
|
|
||||||
|
filename = os.path.join(
|
||||||
|
self.data_dir,
|
||||||
|
"imports",
|
||||||
|
"%s_%s.json" % (flow_name, import_id))
|
||||||
|
|
||||||
|
write = True
|
||||||
|
|
||||||
|
# By default, we prompt for overwrites
|
||||||
|
if os.path.isfile(filename) and not auto:
|
||||||
|
|
||||||
|
# Check that we should overwrite
|
||||||
|
overwrite = prompt(
|
||||||
|
"File {filename} exists, overwrite file?".format(
|
||||||
|
filename=filename)
|
||||||
|
)
|
||||||
|
write = bool(overwrite == "Yes")
|
||||||
|
|
||||||
|
if write:
|
||||||
|
self.write_json(filename, import_conf)
|
||||||
|
else:
|
||||||
|
L.info("You chose not to save this file.")
|
||||||
|
|
||||||
|
def write_json(self, filename, contents):
|
||||||
|
"""
|
||||||
|
Write a file of json data and say that we wrote it.
|
||||||
|
"""
|
||||||
|
with io.open(filename, "w", encoding='utf-8') as f:
|
||||||
|
f.write(unicode(json.dumps(contents, f, indent=4,
|
||||||
|
ensure_ascii=False, encoding='utf-8')))
|
||||||
|
L.info("Wrote {}".format(filename))
|
||||||
|
|
||||||
|
def cache_import_json(self, fname):
|
||||||
|
"""
|
||||||
|
Load the import from a .json file with name_id.json format.
|
||||||
|
"""
|
||||||
|
with io.open(fname, "r", encoding='utf-8') as f:
|
||||||
|
json_data = json.load(f)
|
||||||
|
fname = os.path.split(fname)[1][:-5]
|
||||||
|
flow_name, import_id = fname.split("_")
|
||||||
|
# Store locally in the instance cache
|
||||||
|
self.imports_cache[flow_name] = (import_id, json_data)
|
||||||
|
|
||||||
|
def restore_to_celigo(self, key):
|
||||||
|
"""
|
||||||
|
Make the call to celigo to update the data
|
||||||
|
:params key: slugified name to use as a key
|
||||||
|
"""
|
||||||
|
raise NotImplemented("NOT IMPLEMENTED YET")
|
||||||
|
# yes this works but I don't want to allow this in testing.
|
||||||
|
import_id, import_conf = self.imports_cache[key]
|
||||||
|
response = self._celigo_api_put(
|
||||||
|
"imports/{id}/distributed".format(
|
||||||
|
id=import_id),
|
||||||
|
import_conf)
|
||||||
|
L.info("Restored: ")
|
||||||
|
L.info(response.json()['_id'])
|
||||||
|
L.info("")
|
41
celigo/prompt.py
Normal file
41
celigo/prompt.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import collections
|
||||||
|
|
||||||
|
|
||||||
|
def _get_input():
|
||||||
|
""" Putting in a method so we can mock it easier"""
|
||||||
|
return raw_input("(Type a number) (Ctrl+c to cancel): ").rstrip("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def prompt(message, options=None, retries=3):
|
||||||
|
"""
|
||||||
|
Accept a list of options (or defaults to Yes/No) and continue asking
|
||||||
|
until one of the appropriate answers are accepted, or failing after
|
||||||
|
3 times.
|
||||||
|
:params message: The message to show as a prompt
|
||||||
|
:params options: An iterator of strings to show as each question in the
|
||||||
|
prompt
|
||||||
|
:return selected: Return the selected answer from options.
|
||||||
|
"""
|
||||||
|
if options is None:
|
||||||
|
options = ("Yes", "No")
|
||||||
|
mapping = collections.OrderedDict()
|
||||||
|
for idx, v in enumerate(options):
|
||||||
|
mapping[str(idx + 1)] = v
|
||||||
|
option_strings = "\n".join(" {0} - {1}".format(k, v)
|
||||||
|
for k, v in mapping.items())
|
||||||
|
print(message)
|
||||||
|
print(option_strings)
|
||||||
|
option = None
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
index = _get_input()
|
||||||
|
option = mapping[index]
|
||||||
|
except KeyError:
|
||||||
|
print("Invalid Option, try again (Ctrl+C to cancel)")
|
||||||
|
retries -= 1
|
||||||
|
if retries:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
break
|
||||||
|
return option
|
15
requirements.txt
Normal file
15
requirements.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
flake8==2.5.4
|
||||||
|
mccabe==0.4.0
|
||||||
|
pbr==1.9.1
|
||||||
|
pep8==1.7.0
|
||||||
|
py==1.4.31
|
||||||
|
pyflakes==1.0.0
|
||||||
|
pytest==2.9.1
|
||||||
|
python-slugify==1.2.0
|
||||||
|
requests==2.10.0
|
||||||
|
six==1.10.0
|
||||||
|
stevedore==1.12.0
|
||||||
|
Unidecode==0.4.19
|
||||||
|
virtualenv==15.0.1
|
||||||
|
virtualenv-clone==0.2.6
|
||||||
|
virtualenvwrapper==4.7.1
|
76
setup.py
Normal file
76
setup.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
"""A setuptools based setup module.
|
||||||
|
See:
|
||||||
|
https://packaging.python.org/en/latest/distributing.html
|
||||||
|
https://github.com/pypa/sampleproject
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Always prefer setuptools over distutils
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
# To use a consistent encoding
|
||||||
|
from codecs import open
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
here = path.abspath(path.dirname(__file__))
|
||||||
|
|
||||||
|
# Get the long description from the README file
|
||||||
|
with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
|
||||||
|
long_description = f.read()
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='celigo',
|
||||||
|
|
||||||
|
# Versions should comply with PEP440. For a discussion on single-sourcing
|
||||||
|
# the version across setup.py and the project code, see
|
||||||
|
# https://packaging.python.org/en/latest/single_source_version.html
|
||||||
|
version='1.2.0',
|
||||||
|
|
||||||
|
description='Celigo API interaction',
|
||||||
|
long_description=long_description,
|
||||||
|
|
||||||
|
# The project's main homepage.
|
||||||
|
url='https://gitlab.com/tyrelsouza/celigo',
|
||||||
|
|
||||||
|
# Author details
|
||||||
|
author='Tyrel Souza',
|
||||||
|
author_email='tyrel@tyrelsouza.com',
|
||||||
|
|
||||||
|
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||||
|
classifiers=[
|
||||||
|
# How mature is this project? Common values are
|
||||||
|
# 3 - Alpha
|
||||||
|
# 4 - Beta
|
||||||
|
# 5 - Production/Stable
|
||||||
|
'Development Status :: 3 - Alpha',
|
||||||
|
|
||||||
|
# Indicate who your project is intended for
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
|
||||||
|
# Specify the Python versions you support here. In particular, ensure
|
||||||
|
# that you indicate whether you support Python 2, Python 3 or both.
|
||||||
|
'Programming Language :: Python :: 2',
|
||||||
|
'Programming Language :: Python :: 2.7',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.3',
|
||||||
|
'Programming Language :: Python :: 3.4',
|
||||||
|
'Programming Language :: Python :: 3.5',
|
||||||
|
],
|
||||||
|
|
||||||
|
# What does your project relate to?
|
||||||
|
keywords='api celigo netsuite',
|
||||||
|
|
||||||
|
# You can just specify the packages manually here if your project is
|
||||||
|
# simple. Or you can use find_packages().
|
||||||
|
packages=['celigo'],
|
||||||
|
|
||||||
|
# Alternatively, if you want to distribute just a my_module.py, uncomment
|
||||||
|
# this:
|
||||||
|
# py_modules=["my_module"],
|
||||||
|
|
||||||
|
# List run-time dependencies here. These will be installed by pip when
|
||||||
|
# your project is installed. For an analysis of "install_requires" vs pip's
|
||||||
|
# requirements files see:
|
||||||
|
# https://packaging.python.org/en/latest/requirements.html
|
||||||
|
install_requires=['requirements',
|
||||||
|
'python-slugify'],
|
||||||
|
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user