<commit_before># coding=utf-8

__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'

import logging
import os
import threading
import urllib
import time
import subprocess
import fnmatch
import datetime
import sys
import shutil

import octoprint.util as util

from octoprint.settings import settings
from octoprint.events import eventManager, Events

# currently configured timelapse
current = None

def getFinishedTimelapses():
	files = []
	basedir = settings().getBaseFolder("timelapse")
	for osFile in os.listdir(basedir):
		if not fnmatch.fnmatch(osFile, "*.mpg"):
			continue
		statResult = os.stat(os.path.join(basedir, osFile))
		files.append({
			"name": osFile,
			"size": util.getFormattedSize(statResult.st_size),
			"bytes": statResult.st_size,
			"date": util.getFormattedDateTime(datetime.datetime.fromtimestamp(statResult.st_ctime))
		})
	return files

validTimelapseTypes = ["off", "timed", "zchange"]

updateCallbacks = []

def registerCallback(callback):
	if not callback in updateCallbacks:
		updateCallbacks.append(callback)

def unregisterCallback(callback):
	if callback in updateCallbacks:
		updateCallbacks.remove(callback)

def notifyCallbacks(timelapse):
	if timelapse is None:
		config = None
	else:
		config = timelapse.configData()
	for callback in updateCallbacks:
		try: callback.sendTimelapseConfig(config)
		except: pass

def configureTimelapse(config=None, persist=False):
	global current

	if config is None:
		config = settings().get(["webcam", "timelapse"])

	if current is not None:
		current.unload()

	type = config["type"]
	postRoll = config["postRoll"]
	if type is None or "off" == type:
		current = None
	elif "zchange" == type:
		current = ZTimelapse(postRoll=postRoll)
	elif "timed" == type:
		interval = 10
		if "options" in config and "interval" in config["options"]:
			interval = config["options"]["interval"]
		current = TimedTimelapse(postRoll=postRoll, interval=interval)

	notifyCallbacks(current)

	if persist:
		settings().set(["webcam", "timelapse"], config)
		settings().save()

class Timelapse(object):
	def __init__(self, postRoll=0):
		self._logger = logging.getLogger(__name__)
		self._imageNumber = None
		self._inTimelapse = False
		self._gcodeFile = None

		self._postRoll = postRoll
		self._postRollStart = None
		self._onPostRollDone = None

		self._captureDir = settings().getBaseFolder("timelapse_tmp")
		self._movieDir = settings().getBaseFolder("timelapse")
		self._snapshotUrl = settings().get(["webcam", "snapshot"])

		self._fps = 25

		self._renderThread = None
		self._captureMutex = threading.Lock()

		# subscribe events
		eventManager().subscribe(Events.PRINT_STARTED, self.onPrintStarted)
		eventManager().subscribe(Events.PRINT_FAILED, self.onPrintDone)
		eventManager().subscribe(Events.PRINT_DONE, self.onPrintDone)
		eventManager().subscribe(Events.PRINT_RESUMED, self.onPrintResumed)
		for (event, callback) in self.eventSubscriptions():
			eventManager().subscribe(event, callback)

	def postRoll(self):
		return self._postRoll

	def unload(self):
		if self._inTimelapse:
			self.stopTimelapse(doCreateMovie=False)

		# unsubscribe events
		eventManager().unsubscribe(Events.PRINT_STARTED, self.onPrintStarted)
		eventManager().unsubscribe(Events.PRINT_FAILED, self.onPrintDone)
		eventManager().unsubscribe(Events.PRINT_DONE, self.onPrintDone)
		eventManager().unsubscribe(Events.PRINT_RESUMED, self.onPrintResumed)
		for (event, callback) in self.eventSubscriptions():
			eventManager().unsubscribe(event, callback)

	def onPrintStarted(self, event, payload):
		"""
		Override this to perform additional actions upon start of a print job.
		"""
		self.startTimelapse(payload["file"])

	def onPrintDone(self, event, payload):
		"""
		Override this to perform additional actions upon the stop of a print job.
		"""
		self.stopTimelapse(success=(event==Events.PRINT_DONE))

	def onPrintResumed(self, event, payload):
		"""
		Override this to perform additional actions upon the pausing of a print job.
		"""
		if not self._inTimelapse:
			self.startTimelapse(payload["file"])

	def eventSubscriptions(self):
		"""
		Override this method to subscribe to additional events by returning an array of (event, callback) tuples.

		Events that are already subscribed:
		  * PrintStarted - self.onPrintStarted
		  * PrintResumed - self.onPrintResumed
		  * PrintFailed - self.onPrintDone
		  * PrintDone - self.onPrintDone
		"""
		return []

	def configData(self):
		"""
		Override this method to return the current timelapse configuration data. The data should have the following
		form:

		    type: "<type of timelapse>",
		    options: { <additional options> }
		"""
		return None

	def startTimelapse(self, gcodeFile):
		self._logger.debug("Starting timelapse for %s" % gcodeFile)
		self.cleanCaptureDir()

		self._imageNumber = 0
		self._inTimelapse = True
		self._gcodeFile = os.path.basename(gcodeFile)

	def stopTimelapse(self, doCreateMovie=True, success=True):
		self._logger.debug("Stopping timelapse")

		self._inTimelapse = False

		def resetImageNumber():
			self._imageNumber = None

		def createMovie():
			self._renderThread = threading.Thread(target=self._createMovie, kwargs={"success": success})
			self._renderThread.daemon = True
			self._renderThread.start()

		def resetAndCreate():
			resetImageNumber()
			createMovie()

		if self._postRoll > 0:
			self._postRollStart = time.time()
			if doCreateMovie:
				self._onPostRollDone = resetAndCreate
			else:
				self._onPostRollDone = resetImageNumber
			self.processPostRoll()
		else:
			self._postRollStart = None
			if doCreateMovie:
				resetAndCreate()
			else:
				resetImageNumber()

	def processPostRoll(self):
		pass

	def captureImage(self):
		if self._captureDir is None:
			self._logger.warn("Cannot capture image, capture directory is unset")
			return

		with self._captureMutex:
			filename = os.path.join(self._captureDir, "tmp_%05d.jpg" % (self._imageNumber))
			self._imageNumber += 1
		self._logger.debug("Capturing image to %s" % filename)
		captureThread = threading.Thread(target=self._captureWorker, kwargs={"filename": filename})
		captureThread.daemon = True
		captureThread.start()
		return filename

	def _captureWorker(self, filename):
		eventManager().fire(Events.CAPTURE_START, {"file": filename});
		urllib.urlretrieve(self._snapshotUrl, filename)
		self._logger.debug("Image %s captured from %s" % (filename, self._snapshotUrl))
		eventManager().fire(Events.CAPTURE_DONE, {"file": filename});

	def _createMovie(self, success=True):
		ffmpeg = settings().get(["webcam", "ffmpeg"])
		bitrate = settings().get(["webcam", "bitrate"])
		if ffmpeg is None or bitrate is None:
			self._logger.warn("Cannot create movie, path to ffmpeg or desired bitrate is unset")
			return

		input = os.path.join(self._captureDir, "tmp_%05d.jpg")
		if success:
			output = os.path.join(self._movieDir, "%s_%s.mpg" % (os.path.splitext(self._gcodeFile)[0], time.strftime("%Y%m%d%H%M%S")))
		else:
			output = os.path.join(self._movieDir, "%s_%s-failed.mpg" % (os.path.splitext(self._gcodeFile)[0], time.strftime("%Y%m%d%H%M%S")))

		# prepare ffmpeg command
		command = [
			ffmpeg, '-i', input, '-vcodec', 'mpeg2video', '-pix_fmt', 'yuv420p', '-r', str(self._fps), '-y', '-b:v', bitrate,
			'-f', 'vob']

		filters = []

		# flip video if configured
		if settings().getBoolean(["webcam", "flipH"]):
			filters.append('hflip')
		if settings().getBoolean(["webcam", "flipV"]):
			filters.append('vflip')

		# add watermark if configured
		watermarkFilter = None
		if settings().getBoolean(["webcam", "watermark"]):
			watermark = os.path.join(os.path.dirname(__file__), "static", "img", "watermark.png")
			if sys.platform == "win32":
				# Because ffmpeg hiccups on windows' drive letters and backslashes we have to give the watermark
				# path a special treatment. Yeah, I couldn't believe it either...
				watermark = watermark.replace("\\", "/").replace(":", "\\\\:")

			watermarkFilter = "movie=%s [wm]; [%%(inputName)s][wm] overlay=10:main_h-overlay_h-10" % watermark

		filterstring = None
		if len(filters) > 0:
			if watermarkFilter is not None:
				filterstring = "[in] %s [postprocessed]; %s [out]" % (",".join(filters), watermarkFilter % {"inputName": "postprocessed"})
			else:
				filterstring = "[in] %s [out]" % ",".join(filters)
		elif watermarkFilter is not None:
			filterstring = watermarkFilter % {"inputName": "in"} + " [out]"

		if filterstring is not None:
			self._logger.debug("Applying videofilter chain: %s" % filterstring)
			command.extend(["-vf", filterstring])

		# finalize command with output file
		self._logger.debug("Rendering movie to %s" % output)
		command.append(output)
		eventManager().fire(Events.MOVIE_RENDERING, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output)})
		try:
			subprocess.check_call(command)
			eventManager().fire(Events.MOVIE_DONE, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output)})
		except subprocess.CalledProcessError as (e):
			self._logger.warn("Could not render movie, got return code %r" % e.returncode)
			eventManager().fire(Events.MOVIE_FAILED, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output), "returncode": e.returncode})

	def cleanCaptureDir(self):
		if not os.path.isdir(self._captureDir):
			self._logger.warn("Cannot clean capture directory, it is unset")
			return

		for filename in os.listdir(self._captureDir):
			if not fnmatch.fnmatch(filename, "*.jpg"):
				continue
			os.remove(os.path.join(self._captureDir, filename))

class ZTimelapse(Timelapse):
	def __init__(self, postRoll=0):
		Timelapse.__init__(self, postRoll=postRoll)
		self._logger.debug("ZTimelapse initialized")

	def eventSubscriptions(self):
		return [
			("ZChange", self._onZChange)
		]

	def configData(self):
		return {
			"type": "zchange"
		}

	def processPostRoll(self):
		Timelapse.processPostRoll(self)

		filename = os.path.join(self._captureDir, "tmp_%05d.jpg" % self._imageNumber)
		self._imageNumber += 1
		with self._captureMutex:
			self._captureWorker(filename)

		for i in range(self._postRoll * self._fps):
			newFile = os.path.join(self._captureDir, "tmp_%05d.jpg" % (self._imageNumber))
			self._imageNumber += 1
			shutil.copyfile(filename, newFile)

		if self._onPostRollDone is not None:
			self._onPostRollDone()

	def _onZChange(self, event, payload):
		self.captureImage()

class TimedTimelapse(Timelapse):
	def __init__(self, postRoll=0, interval=1):
		Timelapse.__init__(self, postRoll=postRoll)
		self._interval = interval
		if self._interval < 1:
			self._interval = 1 # force minimum interval of 1s
		self._timerThread = None
		self._logger.debug("TimedTimelapse initialized")

	def interval(self):
		return self._interval

	def configData(self):
		return {
			"type": "timed",
			"options": {
				"interval": self._interval
			}
		}

	def onPrintStarted(self, event, payload):
		Timelapse.onPrintStarted(self, event, payload)
		if self._timerThread is not None:
			return

		self._timerThread = threading.Thread(target=self._timerWorker)
		self._timerThread.daemon = True
		self._timerThread.start()

	def onPrintDone(self, event, payload):
		Timelapse.onPrintDone(self, event, payload)
		self._timerThread = None

	def _timerWorker(self):
		self._logger.debug("Starting timer for interval based timelapse")
		while self._inTimelapse or (self._postRollStart and time.time() - self._postRollStart <= self._postRoll * self._fps):
			self.captureImage()
			time.sleep(self._interval)

		if self._postRollStart is not None and self._onPostRollDone is not None:
			self._onPostRollDone()
			self._postRollStart = None
<commit_msg>Fix for saved timelapse configs vs. new post roll setting<commit_after># coding=utf-8

__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'

import logging
import os
import threading
import urllib
import time
import subprocess
import fnmatch
import datetime
import sys
import shutil

import octoprint.util as util

from octoprint.settings import settings
from octoprint.events import eventManager, Events

# currently configured timelapse
current = None

def getFinishedTimelapses():
	files = []
	basedir = settings().getBaseFolder("timelapse")
	for osFile in os.listdir(basedir):
		if not fnmatch.fnmatch(osFile, "*.mpg"):
			continue
		statResult = os.stat(os.path.join(basedir, osFile))
		files.append({
			"name": osFile,
			"size": util.getFormattedSize(statResult.st_size),
			"bytes": statResult.st_size,
			"date": util.getFormattedDateTime(datetime.datetime.fromtimestamp(statResult.st_ctime))
		})
	return files

validTimelapseTypes = ["off", "timed", "zchange"]

updateCallbacks = []

def registerCallback(callback):
	if not callback in updateCallbacks:
		updateCallbacks.append(callback)

def unregisterCallback(callback):
	if callback in updateCallbacks:
		updateCallbacks.remove(callback)

def notifyCallbacks(timelapse):
	if timelapse is None:
		config = None
	else:
		config = timelapse.configData()
	for callback in updateCallbacks:
		try: callback.sendTimelapseConfig(config)
		except: pass

def configureTimelapse(config=None, persist=False):
	global current

	if config is None:
		config = settings().get(["webcam", "timelapse"])

	if current is not None:
		current.unload()

	type = config["type"]

	postRoll = 0
	if "postRoll" in config:
		postRoll = config["postRoll"]

	if type is None or "off" == type:
		current = None
	elif "zchange" == type:
		current = ZTimelapse(postRoll=postRoll)
	elif "timed" == type:
		interval = 10
		if "options" in config and "interval" in config["options"]:
			interval = config["options"]["interval"]
		current = TimedTimelapse(postRoll=postRoll, interval=interval)

	notifyCallbacks(current)

	if persist:
		settings().set(["webcam", "timelapse"], config)
		settings().save()

class Timelapse(object):
	def __init__(self, postRoll=0):
		self._logger = logging.getLogger(__name__)
		self._imageNumber = None
		self._inTimelapse = False
		self._gcodeFile = None

		self._postRoll = postRoll
		self._postRollStart = None
		self._onPostRollDone = None

		self._captureDir = settings().getBaseFolder("timelapse_tmp")
		self._movieDir = settings().getBaseFolder("timelapse")
		self._snapshotUrl = settings().get(["webcam", "snapshot"])

		self._fps = 25

		self._renderThread = None
		self._captureMutex = threading.Lock()

		# subscribe events
		eventManager().subscribe(Events.PRINT_STARTED, self.onPrintStarted)
		eventManager().subscribe(Events.PRINT_FAILED, self.onPrintDone)
		eventManager().subscribe(Events.PRINT_DONE, self.onPrintDone)
		eventManager().subscribe(Events.PRINT_RESUMED, self.onPrintResumed)
		for (event, callback) in self.eventSubscriptions():
			eventManager().subscribe(event, callback)

	def postRoll(self):
		return self._postRoll

	def unload(self):
		if self._inTimelapse:
			self.stopTimelapse(doCreateMovie=False)

		# unsubscribe events
		eventManager().unsubscribe(Events.PRINT_STARTED, self.onPrintStarted)
		eventManager().unsubscribe(Events.PRINT_FAILED, self.onPrintDone)
		eventManager().unsubscribe(Events.PRINT_DONE, self.onPrintDone)
		eventManager().unsubscribe(Events.PRINT_RESUMED, self.onPrintResumed)
		for (event, callback) in self.eventSubscriptions():
			eventManager().unsubscribe(event, callback)

	def onPrintStarted(self, event, payload):
		"""
		Override this to perform additional actions upon start of a print job.
		"""
		self.startTimelapse(payload["file"])

	def onPrintDone(self, event, payload):
		"""
		Override this to perform additional actions upon the stop of a print job.
		"""
		self.stopTimelapse(success=(event==Events.PRINT_DONE))

	def onPrintResumed(self, event, payload):
		"""
		Override this to perform additional actions upon the pausing of a print job.
		"""
		if not self._inTimelapse:
			self.startTimelapse(payload["file"])

	def eventSubscriptions(self):
		"""
		Override this method to subscribe to additional events by returning an array of (event, callback) tuples.

		Events that are already subscribed:
		  * PrintStarted - self.onPrintStarted
		  * PrintResumed - self.onPrintResumed
		  * PrintFailed - self.onPrintDone
		  * PrintDone - self.onPrintDone
		"""
		return []

	def configData(self):
		"""
		Override this method to return the current timelapse configuration data. The data should have the following
		form:

		    type: "<type of timelapse>",
		    options: { <additional options> }
		"""
		return None

	def startTimelapse(self, gcodeFile):
		self._logger.debug("Starting timelapse for %s" % gcodeFile)
		self.cleanCaptureDir()

		self._imageNumber = 0
		self._inTimelapse = True
		self._gcodeFile = os.path.basename(gcodeFile)

	def stopTimelapse(self, doCreateMovie=True, success=True):
		self._logger.debug("Stopping timelapse")

		self._inTimelapse = False

		def resetImageNumber():
			self._imageNumber = None

		def createMovie():
			self._renderThread = threading.Thread(target=self._createMovie, kwargs={"success": success})
			self._renderThread.daemon = True
			self._renderThread.start()

		def resetAndCreate():
			resetImageNumber()
			createMovie()

		if self._postRoll > 0:
			self._postRollStart = time.time()
			if doCreateMovie:
				self._onPostRollDone = resetAndCreate
			else:
				self._onPostRollDone = resetImageNumber
			self.processPostRoll()
		else:
			self._postRollStart = None
			if doCreateMovie:
				resetAndCreate()
			else:
				resetImageNumber()

	def processPostRoll(self):
		pass

	def captureImage(self):
		if self._captureDir is None:
			self._logger.warn("Cannot capture image, capture directory is unset")
			return

		with self._captureMutex:
			filename = os.path.join(self._captureDir, "tmp_%05d.jpg" % (self._imageNumber))
			self._imageNumber += 1
		self._logger.debug("Capturing image to %s" % filename)
		captureThread = threading.Thread(target=self._captureWorker, kwargs={"filename": filename})
		captureThread.daemon = True
		captureThread.start()
		return filename

	def _captureWorker(self, filename):
		eventManager().fire(Events.CAPTURE_START, {"file": filename});
		urllib.urlretrieve(self._snapshotUrl, filename)
		self._logger.debug("Image %s captured from %s" % (filename, self._snapshotUrl))
		eventManager().fire(Events.CAPTURE_DONE, {"file": filename});

	def _createMovie(self, success=True):
		ffmpeg = settings().get(["webcam", "ffmpeg"])
		bitrate = settings().get(["webcam", "bitrate"])
		if ffmpeg is None or bitrate is None:
			self._logger.warn("Cannot create movie, path to ffmpeg or desired bitrate is unset")
			return

		input = os.path.join(self._captureDir, "tmp_%05d.jpg")
		if success:
			output = os.path.join(self._movieDir, "%s_%s.mpg" % (os.path.splitext(self._gcodeFile)[0], time.strftime("%Y%m%d%H%M%S")))
		else:
			output = os.path.join(self._movieDir, "%s_%s-failed.mpg" % (os.path.splitext(self._gcodeFile)[0], time.strftime("%Y%m%d%H%M%S")))

		# prepare ffmpeg command
		command = [
			ffmpeg, '-i', input, '-vcodec', 'mpeg2video', '-pix_fmt', 'yuv420p', '-r', str(self._fps), '-y', '-b:v', bitrate,
			'-f', 'vob']

		filters = []

		# flip video if configured
		if settings().getBoolean(["webcam", "flipH"]):
			filters.append('hflip')
		if settings().getBoolean(["webcam", "flipV"]):
			filters.append('vflip')

		# add watermark if configured
		watermarkFilter = None
		if settings().getBoolean(["webcam", "watermark"]):
			watermark = os.path.join(os.path.dirname(__file__), "static", "img", "watermark.png")
			if sys.platform == "win32":
				# Because ffmpeg hiccups on windows' drive letters and backslashes we have to give the watermark
				# path a special treatment. Yeah, I couldn't believe it either...
				watermark = watermark.replace("\\", "/").replace(":", "\\\\:")

			watermarkFilter = "movie=%s [wm]; [%%(inputName)s][wm] overlay=10:main_h-overlay_h-10" % watermark

		filterstring = None
		if len(filters) > 0:
			if watermarkFilter is not None:
				filterstring = "[in] %s [postprocessed]; %s [out]" % (",".join(filters), watermarkFilter % {"inputName": "postprocessed"})
			else:
				filterstring = "[in] %s [out]" % ",".join(filters)
		elif watermarkFilter is not None:
			filterstring = watermarkFilter % {"inputName": "in"} + " [out]"

		if filterstring is not None:
			self._logger.debug("Applying videofilter chain: %s" % filterstring)
			command.extend(["-vf", filterstring])

		# finalize command with output file
		self._logger.debug("Rendering movie to %s" % output)
		command.append(output)
		eventManager().fire(Events.MOVIE_RENDERING, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output)})
		try:
			subprocess.check_call(command)
			eventManager().fire(Events.MOVIE_DONE, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output)})
		except subprocess.CalledProcessError as (e):
			self._logger.warn("Could not render movie, got return code %r" % e.returncode)
			eventManager().fire(Events.MOVIE_FAILED, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output), "returncode": e.returncode})

	def cleanCaptureDir(self):
		if not os.path.isdir(self._captureDir):
			self._logger.warn("Cannot clean capture directory, it is unset")
			return

		for filename in os.listdir(self._captureDir):
			if not fnmatch.fnmatch(filename, "*.jpg"):
				continue
			os.remove(os.path.join(self._captureDir, filename))

class ZTimelapse(Timelapse):
	def __init__(self, postRoll=0):
		Timelapse.__init__(self, postRoll=postRoll)
		self._logger.debug("ZTimelapse initialized")

	def eventSubscriptions(self):
		return [
			("ZChange", self._onZChange)
		]

	def configData(self):
		return {
			"type": "zchange"
		}

	def processPostRoll(self):
		Timelapse.processPostRoll(self)

		filename = os.path.join(self._captureDir, "tmp_%05d.jpg" % self._imageNumber)
		self._imageNumber += 1
		with self._captureMutex:
			self._captureWorker(filename)

		for i in range(self._postRoll * self._fps):
			newFile = os.path.join(self._captureDir, "tmp_%05d.jpg" % (self._imageNumber))
			self._imageNumber += 1
			shutil.copyfile(filename, newFile)

		if self._onPostRollDone is not None:
			self._onPostRollDone()

	def _onZChange(self, event, payload):
		self.captureImage()

class TimedTimelapse(Timelapse):
	def __init__(self, postRoll=0, interval=1):
		Timelapse.__init__(self, postRoll=postRoll)
		self._interval = interval
		if self._interval < 1:
			self._interval = 1 # force minimum interval of 1s
		self._timerThread = None
		self._logger.debug("TimedTimelapse initialized")

	def interval(self):
		return self._interval

	def configData(self):
		return {
			"type": "timed",
			"options": {
				"interval": self._interval
			}
		}

	def onPrintStarted(self, event, payload):
		Timelapse.onPrintStarted(self, event, payload)
		if self._timerThread is not None:
			return

		self._timerThread = threading.Thread(target=self._timerWorker)
		self._timerThread.daemon = True
		self._timerThread.start()

	def onPrintDone(self, event, payload):
		Timelapse.onPrintDone(self, event, payload)
		self._timerThread = None

	def _timerWorker(self):
		self._logger.debug("Starting timer for interval based timelapse")
		while self._inTimelapse or (self._postRollStart and time.time() - self._postRollStart <= self._postRoll * self._fps):
			self.captureImage()
			time.sleep(self._interval)

		if self._postRollStart is not None and self._onPostRollDone is not None:
			self._onPostRollDone()
			self._postRollStart = None
<|endoftext|>


<commit_before>
import gzip
import hashlib
from datetime import date, timedelta

import requests
import boto3

def chunks(l):
    for i in range(0, len(l), 10):
        yield l[i:i+10]

def create_path_row_tree(scenes):
    """
    Create a search tree using path/row as indices.
    """
    tree = {
        '%03d' % p: {
            '%03d' % r: []
            for r in range(1, 249)
        } for p in range(1, 234)
    }

    for s in scenes:
        path, row = s[3:6], s[6:9]
        tree[path][row].append(s)

    return tree

def in_scene_tree(scene, tree):
    path, row = scene[3:6], scene[6:9]
    return True if scene in tree[path][row] else False

def get_scene_list():
    """
    Get the scene list hosted on landsat-pds. This will be used
    to ensure scenes are not ingested more than once.
    """
    SCENE_LIST_URL = 'http://landsat-pds.s3.amazonaws.com/scene_list.gz'
    scene_list_path = '/tmp/scene_list.gz'
    with open(scene_list_path, 'wb') as f:
        r = requests.get(SCENE_LIST_URL, stream=True)
        for block in r.iter_content(1024):
            f.write(block)

    with gzip.open(scene_list_path, 'rb') as f:
        scene_list = [ s.decode('utf-8').split(',')[0] for s in f.readlines() ]
    scene_list.pop(0)

    return scene_list

def poll_usgs():
    """
    Poll the bulk metadata file for recent scenes. Only
    a portion of the file is downloaded.
    """
    L8_METADATA_URL = "http://landsat.usgs.gov/metadata_service/bulk_metadata_files/LANDSAT_8.csv"

    output = b""
    r = requests.get(L8_METADATA_URL, stream=True)
    for i, chunk in enumerate(r.iter_content(1024)):
        output += chunk
        if i == 1500:
            break

    entries = output.decode('utf-8').split('\n')
    entries.pop(0)
    sceneids = [ s.split(',')[0] for s in entries ]

    today = date.today()
    yesterday = today - timedelta(1)
    year = str(yesterday.timetuple().tm_year)
    doy = str(yesterday.timetuple().tm_yday)

    criterion = year + doy
    return [ s for s in sceneids if criterion in s ]

def main(event, context):

    entity_ids = poll_usgs()
    scene_list = get_scene_list()

    tree = create_path_row_tree(scene_list)
    entity_ids = [ s for s in entity_ids if not in_scene_tree(s, tree) ]

    # Construct the SQS URL. The Cloudformation template defines same name
    # for this Lambda function and SQS.
    _, _, resource, region, account_id, _, name = context.invoked_function_arn.split(":")
    queue_url = "https://sqs.%s.amazonaws.com/%s/%s" % (region, account_id, name)

    client = boto3.client('sqs')

    responses = []
    for chunk in chunks(entity_ids):
        entries = [
            {
                "Id": hashlib.md5(entity_id).hexdigest()[0:6],
                "MessageBody": entity_id
            }
            for entity_id in chunk
        ]
        params = {
            "QueueUrl": queue_url,
            "Entries": entries
        }
        r = client.send_message_batch(**params)
        responses.append(r)

    return responses
<commit_msg>expand the date range when checking for scenes. this is now possible since the scene list is checked with the tree structure<commit_after>
import gzip
import hashlib

import requests
import boto3

def chunks(l):
    for i in range(0, len(l), 10):
        yield l[i:i+10]

def create_path_row_tree(scenes):
    """
    Create a search tree using path/row as indices.
    """
    tree = {
        '%03d' % p: {
            '%03d' % r: []
            for r in range(1, 249)
        } for p in range(1, 234)
    }

    for s in scenes:
        path, row = s[3:6], s[6:9]
        tree[path][row].append(s)

    return tree

def in_scene_tree(scene, tree):
    path, row = scene[3:6], scene[6:9]
    return True if scene in tree[path][row] else False

def get_scene_list():
    """
    Get the scene list hosted on landsat-pds. This will be used
    to ensure scenes are not ingested more than once.
    """
    SCENE_LIST_URL = 'http://landsat-pds.s3.amazonaws.com/scene_list.gz'
    scene_list_path = '/tmp/scene_list.gz'
    with open(scene_list_path, 'wb') as f:
        r = requests.get(SCENE_LIST_URL, stream=True)
        for block in r.iter_content(1024):
            f.write(block)

    with gzip.open(scene_list_path, 'rb') as f:
        scene_list = [ s.decode('utf-8').split(',')[0] for s in f.readlines() ]
    scene_list.pop(0)

    return scene_list

def poll_usgs():
    """
    Poll the bulk metadata file for recent scenes. Only
    a portion of the file is downloaded.
    """
    L8_METADATA_URL = "http://landsat.usgs.gov/metadata_service/bulk_metadata_files/LANDSAT_8.csv"

    output = b""
    r = requests.get(L8_METADATA_URL, stream=True)
    for i, chunk in enumerate(r.iter_content(1024)):
        output += chunk

        # Read approximately 5MB of the CSV. This is roughly 14 days
        # of scenes. Being liberal with the date range allows us to
        # pick up any scenes that might have been accidently skipped
        # over the last ~2 weeks.
        if i == 5000:
            break

    entries = output.decode('utf-8').split('\n')
    entries.pop(0)
    return [ s.split(',')[0] for s in entries ]

def main(event, context):

    entity_ids = poll_usgs()
    scene_list = get_scene_list()

    tree = create_path_row_tree(scene_list)
    entity_ids = [ s for s in entity_ids if not in_scene_tree(s, tree) ]

    # Construct the SQS URL. The Cloudformation template defines same name
    # for this Lambda function and SQS.
    _, _, resource, region, account_id, _, name = context.invoked_function_arn.split(":")
    queue_url = "https://sqs.%s.amazonaws.com/%s/%s" % (region, account_id, name)

    client = boto3.client('sqs')

    responses = []
    for chunk in chunks(entity_ids):
        entries = [
            {
                "Id": hashlib.md5(entity_id).hexdigest()[0:6],
                "MessageBody": entity_id
            }
            for entity_id in chunk
        ]
        params = {
            "QueueUrl": queue_url,
            "Entries": entries
        }
        r = client.send_message_batch(**params)
        responses.append(r)

    return responses
<|endoftext|>


<commit_before>import hmac as _hmac
import time as _time
import hashlib as _hashlib
import requests as _requests
import functools as _functools
import itertools as _itertools
import threading as _threading

from .utils import AutoCastDict
from .exceptions import *

_PUBLIC_URL = 'https://poloniex.com/public'
_PRIVATE_URL = 'https://poloniex.com/tradingApi'

class PoloniexPublic(object):

    """Client to connect to Poloniex public APIs"""

    def __init__(self, public_url=_PUBLIC_URL, limit=6):
        """Initialize Poloniex client."""
        self._public_url = public_url
        self._public_session = _requests.Session()
        self._semaphore = _threading.Semaphore(limit)

    @staticmethod
    def _sanitize_parameters(params):
        """Sanitize the params removing none values."""
        return {key: val for key, val in params.items() if val is not None}

    def _public(self, command, **params):
        """Invoke the 'command' public API with optional params."""
        params = self._sanitize_parameters(params)
        params['command'] = command
        response = self._public_session.get(self._public_url, params=params)
        return response.json(object_hook=AutoCastDict)

    def returnTicker(self):
        """Returns the ticker for all markets."""
        return self._public('returnTicker')

    def return24hVolume(self):
        """Returns the 24-hour volume for all markets, plus totals for
        primary currencies."""
        return self._public('return24hVolume')

    def returnOrderBook(self, currencyPair='all', depth='50'):
        """Returns the order book for a given market, as well as a sequence
        number for use with the Push API and an indicator specifying whether
        the market is frozen. You may set currencyPair to "all" to get the
        order books of all markets."""
        return self._public('returnOrderBook', currencyPair=currencyPair,
                            depth=depth)

    def returnTradeHistory(self, currencyPair, start=None, end=None):
        """Returns the past 200 trades for a given market, or up to 50,000
        trades between a range specified in UNIX timestamps by the "start"
        and "end" GET parameters."""
        return self._public('returnTradeHistory', currencyPair=currencyPair,
                            start=start, end=end)

    def returnChartData(self, currencyPair, period, start, end):
        """Returns candlestick chart data. Required GET parameters are
        "currencyPair", "period" (candlestick period in seconds; valid values
        are 300, 900, 1800, 7200, 14400, and 86400), "start", and "end".
        "Start" and "end" are given in UNIX timestamp format and used to
        specify the date range for the data returned."""
        return self._public('returnChartData', currencyPair=currencyPair,
                            period=period, start=start, end=end)

    def returnCurrencies(self):
        """Returns information about currencies."""
        return self._public('returnCurrencies')

    def returnLoanOrders(self, currency):
        """Returns the list of loan offers and demands for a given currency,
        specified by the "currency" GET parameter."""
        return self._public('returnLoanOrders', currency=currency)

class Poloniex(PoloniexPublic):

    """Client to connect to Poloniex private APIs."""

    class _PoloniexAuth(_requests.auth.AuthBase):

        """Poloniex Request Authentication."""

        def __init__(self, secret):
            self._secret = secret

        def __call__(self, request):
            signature = _hmac.new(self._secret, request.body, _hashlib.sha512)
            request.headers['Sign'] = signature.hexdigest()
            return request

    def __init__(self, apikey=None, secret=None, public_url=_PUBLIC_URL,
                 private_url=_PRIVATE_URL, limit=6):
        """Initialize the Poloniex private client."""
        PoloniexPublic.__init__(self, public_url, limit)
        self._private_url = private_url
        self._private_session = _requests.Session()
        self._private_session.headers['Key'] = self._apikey = apikey
        self._secret = secret
        self._nonces = _itertools.count(int(_time.time() * 1000))

    def _private(self, command, **params):
        """Invoke the 'command' public API with optional params."""
        if not self._apikey or not self._secret:
            raise PoloniexCredentialsException('missing apikey/secret')

        params = self._sanitize_parameters(params)
        params.update({'command': command, 'nonce': next(self._nonces)})
        response = self._private_session.post(self._private_url,
                                              data=params, auth=Poloniex._PoloniexAuth(self._secret))
        return response.json(object_hook=AutoCastDict)

    def returnBalances(self):
        """Returns all of your available balances."""
        return self._private('returnBalances')

    def returnCompleteBalances(self, account=None):
        """Returns all of your balances, including available balance, balance
        on orders, and the estimated BTC value of your balance. By default,
        this call is limited to your exchange account; set the "account" POST
        parameter to "all" to include your margin and lending accounts."""
        return self._private('returnCompleteBalances', account=account)

    def returnDepositAddresses(self):
        """Returns all of your deposit addresses."""
        return self._private('returnDepositAddresses')

    def generateNewAddress(self, currency):
        """Generates a new deposit address for the currency specified by the
        "currency" POST parameter. Only one address per currency per day may be
        generated, and a new address may not be generated before the
        previously-generated one has been used."""
        return self._private('generateNewAddress', currency=currency)

    def returnDepositsWithdrawals(self, start, end):
        """Returns your deposit and withdrawal history within a range,
        specified by the "start" and "end" POST parameters, both of which
        should be given as UNIX timestamps."""
        return self._private('returnDepositsWithdrawals', start=start, end=end)

    def returnOpenOrders(self, currencyPair='all'):
        """Returns your open orders for a given market, specified by the
        "currencyPair" POST parameter, e.g. "BTC_XCP". Set "currencyPair" to
        "all" to return open orders for all markets."""
        return self._private('returnOpenOrders', currencyPair=currencyPair)

    def returnTradeHistory(self, currencyPair='all', start=None, end=None):
        """Returns your trade history for a given market, specified by the
        "currencyPair" POST parameter. You may specify "all" as the
        currencyPair to receive your trade history for all markets. You may
        optionally specify a range via "start" and/or "end" POST parameters,
        given in UNIX timestamp format; if you do not specify a range, it will
        be limited to one day."""
        return self._private('returnTradeHistory', currencyPair=currencyPair,
                             start=start, end=end)

    def returnTradeHistoryPublic(self, currencyPair, start=None, end=None):
        """Returns the past 200 trades for a given market, or up to 50,000
        trades between a range specified in UNIX timestamps by the "start"
        and "end" GET parameters."""
        PoloniexPublic.returnTradeHistory(self, currencyPair, start, end)

    def returnLendingHistory(self, start, stop, limit=None):
        """Returns your lending history within a time range specified by the
        "start" and "end" POST parameters as UNIX timestamps. "limit" may also
        be specified to limit the number of rows returned. """
        return self._private('returnLendingHistory', start=start, stop=stop, limit=limit)

    def toggleAutoRenew(self, orderNumber):
        """Toggles the autoRenew setting on an active loan, specified by the
        "orderNumber" POST parameter. If successful, "message" will indicate
        the new autoRenew setting. """
        return self._private('toggleAutoRenew', orderNumber=orderNumber)
<commit_msg>adds reqs from closeMarginPosition to toggleAutoRenew<commit_after>import hmac as _hmac
import time as _time
import hashlib as _hashlib
import requests as _requests
import functools as _functools
import itertools as _itertools
import threading as _threading

from .utils import AutoCastDict
from .exceptions import *

_PUBLIC_URL = 'https://poloniex.com/public'
_PRIVATE_URL = 'https://poloniex.com/tradingApi'

class PoloniexPublic(object):

    """Client to connect to Poloniex public APIs"""

    def __init__(self, public_url=_PUBLIC_URL, limit=6):
        """Initialize Poloniex client."""
        self._public_url = public_url
        self._public_session = _requests.Session()
        self._semaphore = _threading.Semaphore(limit)

    @staticmethod
    def _sanitize_parameters(params):
        """Sanitize the params removing none values."""
        return {key: val for key, val in params.items() if val is not None}

    def _public(self, command, **params):
        """Invoke the 'command' public API with optional params."""
        params = self._sanitize_parameters(params)
        params['command'] = command
        response = self._public_session.get(self._public_url, params=params)
        return response.json(object_hook=AutoCastDict)

    def returnTicker(self):
        """Returns the ticker for all markets."""
        return self._public('returnTicker')

    def return24hVolume(self):
        """Returns the 24-hour volume for all markets, plus totals for
        primary currencies."""
        return self._public('return24hVolume')

    def returnOrderBook(self, currencyPair='all', depth='50'):
        """Returns the order book for a given market, as well as a sequence
        number for use with the Push API and an indicator specifying whether
        the market is frozen. You may set currencyPair to "all" to get the
        order books of all markets."""
        return self._public('returnOrderBook', currencyPair=currencyPair,
                            depth=depth)

    def returnTradeHistory(self, currencyPair, start=None, end=None):
        """Returns the past 200 trades for a given market, or up to 50,000
        trades between a range specified in UNIX timestamps by the "start"
        and "end" GET parameters."""
        return self._public('returnTradeHistory', currencyPair=currencyPair,
                            start=start, end=end)

    def returnChartData(self, currencyPair, period, start, end):
        """Returns candlestick chart data. Required GET parameters are
        "currencyPair", "period" (candlestick period in seconds; valid values
        are 300, 900, 1800, 7200, 14400, and 86400), "start", and "end".
        "Start" and "end" are given in UNIX timestamp format and used to
        specify the date range for the data returned."""
        return self._public('returnChartData', currencyPair=currencyPair,
                            period=period, start=start, end=end)

    def returnCurrencies(self):
        """Returns information about currencies."""
        return self._public('returnCurrencies')

    def returnLoanOrders(self, currency):
        """Returns the list of loan offers and demands for a given currency,
        specified by the "currency" GET parameter."""
        return self._public('returnLoanOrders', currency=currency)

class Poloniex(PoloniexPublic):

    """Client to connect to Poloniex private APIs."""

    class _PoloniexAuth(_requests.auth.AuthBase):

        """Poloniex Request Authentication."""

        def __init__(self, secret):
            self._secret = secret

        def __call__(self, request):
            signature = _hmac.new(self._secret, request.body, _hashlib.sha512)
            request.headers['Sign'] = signature.hexdigest()
            return request

    def __init__(self, apikey=None, secret=None, public_url=_PUBLIC_URL,
                 private_url=_PRIVATE_URL, limit=6):
        """Initialize the Poloniex private client."""
        PoloniexPublic.__init__(self, public_url, limit)
        self._private_url = private_url
        self._private_session = _requests.Session()
        self._private_session.headers['Key'] = self._apikey = apikey
        self._secret = secret
        self._nonces = _itertools.count(int(_time.time() * 1000))

    def _private(self, command, **params):
        """Invoke the 'command' public API with optional params."""
        if not self._apikey or not self._secret:
            raise PoloniexCredentialsException('missing apikey/secret')

        params = self._sanitize_parameters(params)
        params.update({'command': command, 'nonce': next(self._nonces)})
        response = self._private_session.post(self._private_url,
                                              data=params, auth=Poloniex._PoloniexAuth(self._secret))
        return response.json(object_hook=AutoCastDict)

    def returnBalances(self):
        """Returns all of your available balances."""
        return self._private('returnBalances')

    def returnCompleteBalances(self, account=None):
        """Returns all of your balances, including available balance, balance
        on orders, and the estimated BTC value of your balance. By default,
        this call is limited to your exchange account; set the "account" POST
        parameter to "all" to include your margin and lending accounts."""
        return self._private('returnCompleteBalances', account=account)

    def returnDepositAddresses(self):
        """Returns all of your deposit addresses."""
        return self._private('returnDepositAddresses')

    def generateNewAddress(self, currency):
        """Generates a new deposit address for the currency specified by the
        "currency" POST parameter. Only one address per currency per day may be
        generated, and a new address may not be generated before the
        previously-generated one has been used."""
        return self._private('generateNewAddress', currency=currency)

    def returnDepositsWithdrawals(self, start, end):
        """Returns your deposit and withdrawal history within a range,
        specified by the "start" and "end" POST parameters, both of which
        should be given as UNIX timestamps."""
        return self._private('returnDepositsWithdrawals', start=start, end=end)

    def returnOpenOrders(self, currencyPair='all'):
        """Returns your open orders for a given market, specified by the
        "currencyPair" POST parameter, e.g. "BTC_XCP". Set "currencyPair" to
        "all" to return open orders for all markets."""
        return self._private('returnOpenOrders', currencyPair=currencyPair)

    def returnTradeHistory(self, currencyPair='all', start=None, end=None):
        """Returns your trade history for a given market, specified by the
        "currencyPair" POST parameter. You may specify "all" as the
        currencyPair to receive your trade history for all markets. You may
        optionally specify a range via "start" and/or "end" POST parameters,
        given in UNIX timestamp format; if you do not specify a range, it will
        be limited to one day."""
        return self._private('returnTradeHistory', currencyPair=currencyPair,
                             start=start, end=end)

    def returnTradeHistoryPublic(self, currencyPair, start=None, end=None):
        """Returns the past 200 trades for a given market, or up to 50,000
        trades between a range specified in UNIX timestamps by the "start"
        and "end" GET parameters."""
        PoloniexPublic.returnTradeHistory(self, currencyPair, start, end)

    def closeMarginPosition(self, currencyPair):
        """Closes your margin position in a given market (specified by the
        "currencyPair" POST parameter) using a market order. This call will
        also return success if you do not have an open position in the
        specified market."""
        return self._private('closeMarginPosition', currencyPair=currencyPair)

    def createLoanOffer(self, currency, amount, duration, autoRenew,
                        lendingRate):
        """Creates a loan offer for a given currency. Required POST parameters
        are "currency", "amount", "duration", "autoRenew" (0 or 1), and
        "lendingRate". """
        return self._private('createLoanOffer', currency=currency,
                             amount=amount, duration=duration,
                             autoRenew=autoRenew, lendingRate=lendingRate)

    def cancelLoanOffer(self, orderNumber):
        """Cancels a loan offer specified by the "orderNumber" POST
        parameter."""
        return self._private('cancelLoanOffer', orderNumber=orderNumber)

    def returnOpenLoanOffers(self):
        """Returns your open loan offers for each currency. """
        return self._private('returnOpenLoanOffers')

    def returnActiveLoans(self):
        """Returns your active loans for each currency."""
        return self._private('returnActiveLoans')

    def returnLendingHistory(self, start, stop, limit=None):
        """Returns your lending history within a time range specified by the
        "start" and "end" POST parameters as UNIX timestamps. "limit" may also
        be specified to limit the number of rows returned. """
        return self._private('returnLendingHistory', start=start, stop=stop,
                             limit=limit)

    def toggleAutoRenew(self, orderNumber):
        """Toggles the autoRenew setting on an active loan, specified by the
        "orderNumber" POST parameter. If successful, "message" will indicate
        the new autoRenew setting. """
        return self._private('toggleAutoRenew', orderNumber=orderNumber)
<|endoftext|>


<commit_before>#!/usr/bin/env python3

import os
import sys
from argparse import ArgumentParser
from configparser import ConfigParser

import actions
from tf2server import Tf2Server

def main():
    """
    Parse command line options, read config and run desired action.
    """
    description = 'tf2director is a script that helps managing multiple Team Fortress 2 server instances.'

    parser = ArgumentParser(description=description)
    parser.add_argument('server', help='server to be used or "all"', metavar='server')
    parser.add_argument('action', choices=['start', 'stop', 'restart', 'console', 'update', 'status'],
                        help='action to do', metavar='action')

    args = parser.parse_args()

    home = os.path.expanduser('~')
    config_file = os.path.join(home, 'tf2director.ini')
    if not os.path.isfile(config_file):
        print('Config file missing (' + config_file + ')')
        sys.exit(1)

    config = ConfigParser()
    config.read(config_file)

    if 'all' in config:
        raise ValueError('A server cannot be named \'all\'!')

    if args.server not in config and args.server != 'all':
        raise ValueError('Server \'{0}\' is not configured'.format(args.server))

    servers = []

    if args.server == 'all':
        for s in config.sections():
            c = config[s]
            server = Tf2Server(s, os.path.expanduser(c['path']))
            server.ip = c['ip']
            server.port = c['port']
            server.initial_map = c['initial_map']
            server.cfg_file = c['server_config']
            server.max_players = c['max_players']

            servers.append(server)
    else:
        c = config[args.server]
        path = c['path']
        server = Tf2Server(args.server, os.path.expanduser(path))
        server.ip = c['ip']
        server.port = c['port']
        server.initial_map = c['initial_map']
        server.cfg_file = c['server_config']
        server.max_players = c['max_players']

        servers.append(server)

    try:
        if args.action == 'start':
            actions.start(servers)

        elif args.action == 'stop':
            actions.stop(servers)

        elif args.action == 'restart':
            actions.restart(servers)

        elif args.action == 'console':
            if len(servers) == 1:
                server = servers[0]
                server.attach()

        elif args.action == 'update':
            actions.update(servers)

        elif args.action == 'status':
            actions.status(servers)

    except ValueError as error:
        print('{0}'.format(error))

if __name__ == '__main__':
    main()
<commit_msg>update cfg<commit_after>#!/usr/bin/env python3

import os
import sys
from argparse import ArgumentParser
from configparser import ConfigParser

import actions
from tf2server import Tf2Server

def main():
    """
    Parse command line options, read config and run desired action.
    """
    description = 'tf2director is a script that helps managing multiple Team Fortress 2 server instances.'

    parser = ArgumentParser(description=description)
    parser.add_argument('server', help='server to be used or "all"', metavar='server')
    parser.add_argument('action', choices=['start', 'stop', 'restart', 'console', 'update', 'status'],
                        help='action to do', metavar='action')

    args = parser.parse_args()

    home = os.path.expanduser('~')
    config_file = os.path.join(home, 'tf2director.ini')
    if not os.path.isfile(config_file):
        print('Config file missing (' + config_file + ')')
        sys.exit(1)

    config = ConfigParser()
    config.read(config_file)

    if 'all' in config:
        raise ValueError('A server cannot be named \'all\'!')

    if args.server not in config and args.server != 'all':
        raise ValueError('Server \'{0}\' is not configured'.format(args.server))

    servers = []

    if args.server == 'all':
        for s in config.sections():
            c = config[s]
            server = Tf2Server(s, os.path.expanduser(c['path']))
            server.ip = c['ip']
            server.port = c['port']
            server.initial_map = c['initial_map']
            server.cfg_file = c['server_config']
            server.max_players = c['max_players']

            servers.append(server)
    else:
        c = config[args.server]
        path = c['path']
        server = Tf2Server(args.server, os.path.expanduser(path))
        server.ip = c['ip']
        server.port = c['port']
        server.initial_map = c['initial_map']
        server.cfg_file = c['server_config']
        server.max_players = c['max_players']
        server.tv_port = c['tv_port'] if 'tv_port' in c else server.port + 5

        servers.append(server)

    try:
        if args.action == 'start':
            actions.start(servers)

        elif args.action == 'stop':
            actions.stop(servers)

        elif args.action == 'restart':
            actions.restart(servers)

        elif args.action == 'console':
            if len(servers) == 1:
                server = servers[0]
                server.attach()

        elif args.action == 'update':
            actions.update(servers)

        elif args.action == 'status':
            actions.status(servers)

    except ValueError as error:
        print('{0}'.format(error))

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before>import six as _six
import hmac as _hmac
import time as _time
import hashlib as _hashlib
import weakref as _weakref
import datetime as _datetime
import requests as _requests
import itertools as _itertools
import threading as _threading

from .utils import AutoCastDict as _AutoCastDict
from .exceptions import (PoloniexCredentialsException,
                         PoloniexCommandException)

_PUBLIC_URL = 'https://poloniex.com/public'
_PRIVATE_URL = 'https://poloniex.com/tradingApi'

def _api_wrapper(fn):
    """API function decorator that performs rate limiting and error checking."""

    def _convert(value):
        if isinstance(value, _datetime.date):
            return value.strftime('%s')
        return value

    @_six.wraps(fn)
    def _fn(self, command, **params):
        # sanitize the params by removing the None values
        params = dict((key, _convert(value))
                      for key, value in _six.iteritems(params)
                      if value is not None)

        try:
            self._semaphore.acquire()
            resp = fn(self, command, **params).json(object_hook=_AutoCastDict)
            if 'error' in resp:
                raise PoloniexCommandException(resp['error'])
            return resp

        finally:
            timer = _threading.Timer(1.0, self._semaphore.release)
            timer.setDaemon(True)
            timer.start()
            self._timers.add(timer)

    return _fn

class PoloniexPublic(object):

    """Client to connect to Poloniex public APIs"""

    def __init__(self, public_url=_PUBLIC_URL, limit=6,
                 session_class=_requests.Session,
                 session=None):
        """Initialize Poloniex client."""
        self._public_url = public_url
        self._semaphore = _threading.Semaphore(limit)
        self._timers = _weakref.WeakSet()
        self.session = session or session_class()

    def __del__(self):
        for timer in self._timers:
            timer.cancel()
            timer.join()

    @_api_wrapper
    def _public(self, command, **params):
        """Invoke the 'command' public API with optional params."""
        params['command'] = command
        return self.session.get(self._public_url, params=params)

    def returnTicker(self):
        """Returns the ticker for all markets."""
        return self._public('returnTicker')

    def return24hVolume(self):
        """Returns the 24-hour volume for all markets, plus totals for
        primary currencies."""
        return self._public('return24hVolume')

    def returnOrderBook(self, currencyPair='all', depth='50'):
        """Returns the order book for a given market, as well as a sequence
        number for use with the Push API and an indicator specifying whether
        the market is frozen. You may set currencyPair to "all" to get the
        order books of all markets."""
        return self._public('returnOrderBook', currencyPair=currencyPair,
                            depth=depth)

    def returnTradeHistory(self, currencyPair, start=None, end=None):
        """Returns the past 200 trades for a given market, or up to 50,000
        trades between a range specified in UNIX timestamps by the "start"
        and "end" GET parameters."""
        return self._public('returnTradeHistory', currencyPair=currencyPair,
                            start=start, end=end)

    def returnChartData(self, currencyPair, period, start=0, end=2**32-1):
        """Returns candlestick chart data. Required GET parameters are
        "currencyPair", "period" (candlestick period in seconds; valid values
        are 300, 900, 1800, 7200, 14400, and 86400), "start", and "end".
        "Start" and "end" are given in UNIX timestamp format and used to
        specify the date range for the data returned."""
        return self._public('returnChartData', currencyPair=currencyPair,
                            period=period, start=start, end=end)

    def returnCurrencies(self):
        """Returns information about currencies."""
        return self._public('returnCurrencies')

    def returnLoanOrders(self, currency):
        """Returns the list of loan offers and demands for a given currency,
        specified by the "currency" GET parameter."""
        return self._public('returnLoanOrders', currency=currency)

class Poloniex(PoloniexPublic):

    """Client to connect to Poloniex private APIs."""

    class _PoloniexAuth(_requests.auth.AuthBase):

        """Poloniex Request Authentication."""

        def __init__(self, apikey, secret):
            self._apikey, self._secret = apikey, secret

        def __call__(self, request):
            signature = _hmac.new(self._secret, request.body, _hashlib.sha512)
            request.headers.update({"Key": self._apikey,
                                    "Sign": signature.hexdigest()})
            return request

    def __init__(self, apikey=None, secret=None,
                 public_url=_PUBLIC_URL, private_url=_PRIVATE_URL,
                 limit=6, session_class=_requests.Session, session=None):
        """Initialize the Poloniex private client."""
        super(Poloniex, self).__init__(public_url, limit, session_class, session)
        self._private_url = private_url
        self._apikey = apikey
        self._secret = secret
        self._nonces = _itertools.count(int(_time.time() * 1000))

    @_api_wrapper
    def _private(self, command, **params):
        """Invoke the 'command' public API with optional params."""
        if not self._apikey or not self._secret:
            raise PoloniexCredentialsException('missing apikey/secret')

        params.update({'command': command, 'nonce': next(self._nonces)})
        return self.session.post(
            self._private_url, data=params,
            auth=Poloniex._PoloniexAuth(self._apikey, self._secret))

    def returnBalances(self):
        """Returns all of your available balances."""
        return self._private('returnBalances')

    def returnCompleteBalances(self, account=None):
        """Returns all of your balances, including available balance, balance
        on orders, and the estimated BTC value of your balance. By default,
        this call is limited to your exchange account; set the "account" POST
        parameter to "all" to include your margin and lending accounts."""
        return self._private('returnCompleteBalances', account=account)

    def returnDepositAddresses(self):
        """Returns all of your deposit addresses."""
        return self._private('returnDepositAddresses')

    def generateNewAddress(self, currency):
        """Generates a new deposit address for the currency specified by the
        "currency" POST parameter. Only one address per currency per day may be
        generated, and a new address may not be generated before the
        previously-generated one has been used."""
        return self._private('generateNewAddress', currency=currency)

    def returnDepositsWithdrawals(self, start=0, end=2**32-1):
        """Returns your deposit and withdrawal history within a range,
        specified by the "start" and "end" POST parameters, both of which
        should be given as UNIX timestamps."""
        return self._private('returnDepositsWithdrawals', start=start, end=end)

    def returnDeposits(self, start=0, end=2**32-1):
        """Returns your deposit history within a range, specified by the
        "start" and "end" POST parameters, both of which should be given as
        UNIX timestamps."""
        return self.returnDepositsWithdrawals(start, end)['deposits']

    def returnWithdrawals(self, start=0, end=2**32-1):
        """Returns your withdrawal history within a range, specified by the
        "start" and "end" POST parameters, both of which should be given as
        UNIX timestamps."""
        return self.returnDepositsWithdrawals(start, end)['withdrawals']

    def returnOpenOrders(self, currencyPair='all'):
        """Returns your open orders for a given market, specified by the
        "currencyPair" POST parameter, e.g. "BTC_XCP". Set "currencyPair" to
        "all" to return open orders for all markets."""
        return self._private('returnOpenOrders', currencyPair=currencyPair)

    def returnTradeHistory(self, currencyPair='all', start=None, end=None):
        """Returns your trade history for a given market, specified by the
        "currencyPair" POST parameter. You may specify "all" as the
        currencyPair to receive your trade history for all markets. You may
        optionally specify a range via "start" and/or "end" POST parameters,
        given in UNIX timestamp format; if you do not specify a range, it will
        be limited to one day."""
        return self._private('returnTradeHistory', currencyPair=currencyPair,
                             start=start, end=end)

    def returnTradeHistoryPublic(self, currencyPair, start=None, end=None):
        """Returns the past 200 trades for a given market, or up to 50,000
        trades between a range specified in UNIX timestamps by the "start"
        and "end" GET parameters."""
        return super(Poloniex, self).returnTradeHistory(currencyPair, start, end)

    def returnOrderTrades(self, orderNumber):
        """Returns all trades involving a given order, specified by the
        "orderNumber" POST parameter. If no trades for the order have occurred
        or you specify an order that does not belong to you, you will receive
        an error. """
        return self._private('returnOrderTrades', orderNumber=orderNumber)

    def buy(self, currencyPair, rate, amount, fillOrKill=None,
            immediateOrCancel=None, postOnly=None):
        """Places a limit buy order in a given market. Required POST parameters
        are "currencyPair", "rate", and "amount". If successful, the method
        will return the order number.
        You may optionally set "fillOrKill", "immediateOrCancel", "postOnly"
        to 1. A fill-or-kill order will either fill in its entirety or be
        completely aborted. An immediate-or-cancel order can be partially or
        completely filled, but any portion of the order that cannot be filled
        immediately will be canceled rather than left on the order book.
        A post-only order will only be placed if no portion of it fills
        immediately; this guarantees you will never pay the taker fee on any
        part of the order that fills."""
        return self._private('buy', currencyPair=currencyPair, rate=rate,
                             amount=amount, fillOrKill=fillOrKill,
                             immediateOrCancel=immediateOrCancel,
                             postOnly=postOnly)

    def sell(self, currencyPair, rate, amount, fillOrKill=None,
             immediateOrCancel=None, postOnly=None):
        """Places a sell order in a given market. Parameters and output are
        the same as for the buy method."""
        return self._private('sell', currencyPair=currencyPair, rate=rate,
                             amount=amount, fillOrKill=fillOrKill,
                             immediateOrCancel=immediateOrCancel,
                             postOnly=postOnly)

    def cancelOrder(self, orderNumber):
        """Cancels an order you have placed in a given market. Required POST
        parameter is "orderNumber"."""
        return self._private('cancelOrder', orderNumber=orderNumber)

    def moveOrder(self, orderNumber, rate, amount=None, postOnly=None,
                  immediateOrCancel=None):
        """Cancels an order and places a new one of the same type in a single
        atomic transaction, meaning either both operations will succeed or both
         will fail. Required POST parameters are "orderNumber" and "rate"; you
         may optionally specify "amount" if you wish to change the amount of
         the new order. "postOnly" or "immediateOrCancel" may be specified for
         exchange orders, but will have no effect on margin orders. """
        return self._private('moveOrder', orderNumber=orderNumber, rate=rate,
                             amount=amount, postOnly=postOnly,
                             immediateOrCancel=immediateOrCancel)

    def withdraw(self, currency, amount, address, paymentId=None):
        """Immediately places a withdrawal for a given currency, with no email
        confirmation. In order to use this method, the withdrawal privilege
        must be enabled for your API key. Required POST parameters are
        "currency", "amount", and "address". For XMR withdrawals, you may
        optionally specify "paymentId"."""
        return self._private('withdraw', currency=currency, amount=amount,
                             address=address, paymentId=paymentId)

    def returnFeeInfo(self):
        """If you are enrolled in the maker-taker fee schedule, returns your
        current trading fees and trailing 30-day volume in BTC. This
        information is updated once every 24 hours."""
        return self._private('returnFeeInfo')

    def returnAvailableAccountBalances(self, account=None):
        """Returns your balances sorted by account. You may optionally specify
        the "account" POST parameter if you wish to fetch only the balances of
        one account. Please note that balances in your margin account may not
        be accessible if you have any open margin positions or orders."""
        return self._private('returnAvailableAccountBalances', account=account)

    def returnTradableBalances(self):
        """Returns your current tradable balances for each currency in each
        market for which margin trading is enabled. Please note that these
        balances may vary continually with market conditions."""
        return self._private('returnTradableBalances')

    def transferBalance(self, currency, amount, fromAccount, toAccount):
        """Transfers funds from one account to another (e.g. from your exchange
         account to your margin account). Required POST parameters are
         "currency", "amount", "fromAccount", and "toAccount"."""
        return self._private('transferBalance', currency=currency,
                             amount=amount, fromAccount=fromAccount,
                             toAccount=toAccount)

    def returnMarginAccountSummary(self):
        """Returns a summary of your entire margin account. This is the same
        information you will find in the Margin Account section of the Margin
        Trading page, under the Markets list. """
        return self._private('returnMarginAccountSummary')

    def marginBuy(self, currencyPair, rate, amount, lendingRate=None):
        """Places a margin buy order in a given market. Required POST
        parameters are "currencyPair", "rate", and "amount". You may optionally
         specify a maximum lending rate using the "lendingRate" parameter.
         If successful, the method will return the order number and any trades
         immediately resulting from your order."""
        return self._private('marginBuy', currencyPair=currencyPair, rate=rate,
                             amount=amount, lendingRate=lendingRate)

    def marginSell(self):
        """Places a margin sell order in a given market. Parameters and output
        are the same as for the marginBuy method."""
        return self._private('marginSell')

    def getMarginPosition(self, currencyPair):
        """Returns information about your margin position in a given market,
        specified by the "currencyPair" POST parameter. You may set
        "currencyPair" to "all" if you wish to fetch all of your margin
        positions at once. If you have no margin position in the specified
        market, "type" will be set to "none". "liquidationPrice" is an
        estimate, and does not necessarily represent the price at which an
        actual forced liquidation will occur. If you have no liquidation
        price, the value will be -1. """
        return self._private('getMarginPosition', currencyPair=currencyPair)

    def closeMarginPosition(self, currencyPair):
        """Closes your margin position in a given market (specified by the
        "currencyPair" POST parameter) using a market order. This call will
        also return success if you do not have an open position in the
        specified market."""
        return self._private('closeMarginPosition', currencyPair=currencyPair)

    def createLoanOffer(self, currency, amount, duration, autoRenew,
                        lendingRate):
        """Creates a loan offer for a given currency. Required POST parameters
        are "currency", "amount", "duration", "autoRenew" (0 or 1), and
        "lendingRate". """
        return self._private('createLoanOffer', currency=currency,
                             amount=amount, duration=duration,
                             autoRenew=autoRenew, lendingRate=lendingRate)

    def cancelLoanOffer(self, orderNumber):
        """Cancels a loan offer specified by the "orderNumber" POST
        parameter."""
        return self._private('cancelLoanOffer', orderNumber=orderNumber)

    def returnOpenLoanOffers(self):
        """Returns your open loan offers for each currency. """
        return self._private('returnOpenLoanOffers')

    def returnActiveLoans(self):
        """Returns your active loans for each currency."""
        return self._private('returnActiveLoans')

    def returnLendingHistory(self, start=0, end=2**32-1, limit=None):
        """Returns your lending history within a time range specified by the
        "start" and "end" POST parameters as UNIX timestamps. "limit" may also
        be specified to limit the number of rows returned. """
        return self._private('returnLendingHistory', start=start, end=end,
                             limit=limit)

    def toggleAutoRenew(self, orderNumber):
        """Toggles the autoRenew setting on an active loan, specified by the
        "orderNumber" POST parameter. If successful, "message" will indicate
        the new autoRenew setting. """
        return self._private('toggleAutoRenew', orderNumber=orderNumber)
<commit_msg>Ensure thread safety of nonces and private API requests<commit_after>import six as _six
import hmac as _hmac
import time as _time
import hashlib as _hashlib
import weakref as _weakref
import datetime as _datetime
import requests as _requests
import itertools as _itertools
import threading as _threading

from .utils import AutoCastDict as _AutoCastDict
from .exceptions import (PoloniexCredentialsException,
                         PoloniexCommandException)

_PUBLIC_URL = 'https://poloniex.com/public'
_PRIVATE_URL = 'https://poloniex.com/tradingApi'

def _api_wrapper(fn):
    """API function decorator that performs rate limiting and error checking."""

    def _convert(value):
        if isinstance(value, _datetime.date):
            return value.strftime('%s')
        return value

    @_six.wraps(fn)
    def _fn(self, command, **params):
        # sanitize the params by removing the None values
        params = dict((key, _convert(value))
                      for key, value in _six.iteritems(params)
                      if value is not None)

        try:
            self._semaphore.acquire()
            resp = fn(self, command, **params).json(object_hook=_AutoCastDict)
            if 'error' in resp:
                raise PoloniexCommandException(resp['error'])
            return resp

        finally:
            timer = _threading.Timer(1.0, self._semaphore.release)
            timer.setDaemon(True)
            timer.start()
            self._timers.add(timer)

    return _fn

class PoloniexPublic(object):

    """Client to connect to Poloniex public APIs"""

    def __init__(self, public_url=_PUBLIC_URL, limit=6,
                 session_class=_requests.Session,
                 session=None):
        """Initialize Poloniex client."""
        self._public_url = public_url
        self._semaphore = _threading.Semaphore(limit)
        self._timers = _weakref.WeakSet()
        self.session = session or session_class()

    def __del__(self):
        for timer in self._timers:
            timer.cancel()
            timer.join()

    @_api_wrapper
    def _public(self, command, **params):
        """Invoke the 'command' public API with optional params."""
        params['command'] = command
        return self.session.get(self._public_url, params=params)

    def returnTicker(self):
        """Returns the ticker for all markets."""
        return self._public('returnTicker')

    def return24hVolume(self):
        """Returns the 24-hour volume for all markets, plus totals for
        primary currencies."""
        return self._public('return24hVolume')

    def returnOrderBook(self, currencyPair='all', depth='50'):
        """Returns the order book for a given market, as well as a sequence
        number for use with the Push API and an indicator specifying whether
        the market is frozen. You may set currencyPair to "all" to get the
        order books of all markets."""
        return self._public('returnOrderBook', currencyPair=currencyPair,
                            depth=depth)

    def returnTradeHistory(self, currencyPair, start=None, end=None):
        """Returns the past 200 trades for a given market, or up to 50,000
        trades between a range specified in UNIX timestamps by the "start"
        and "end" GET parameters."""
        return self._public('returnTradeHistory', currencyPair=currencyPair,
                            start=start, end=end)

    def returnChartData(self, currencyPair, period, start=0, end=2**32-1):
        """Returns candlestick chart data. Required GET parameters are
        "currencyPair", "period" (candlestick period in seconds; valid values
        are 300, 900, 1800, 7200, 14400, and 86400), "start", and "end".
        "Start" and "end" are given in UNIX timestamp format and used to
        specify the date range for the data returned."""
        return self._public('returnChartData', currencyPair=currencyPair,
                            period=period, start=start, end=end)

    def returnCurrencies(self):
        """Returns information about currencies."""
        return self._public('returnCurrencies')

    def returnLoanOrders(self, currency):
        """Returns the list of loan offers and demands for a given currency,
        specified by the "currency" GET parameter."""
        return self._public('returnLoanOrders', currency=currency)

class Poloniex(PoloniexPublic):

    """Client to connect to Poloniex private APIs."""

    class _PoloniexAuth(_requests.auth.AuthBase):

        """Poloniex Request Authentication."""

        def __init__(self, apikey, secret):
            self._apikey, self._secret = apikey, secret

        def __call__(self, request):
            signature = _hmac.new(self._secret, request.body, _hashlib.sha512)
            request.headers.update({"Key": self._apikey,
                                    "Sign": signature.hexdigest()})
            return request

    def __init__(self, apikey=None, secret=None,
                 public_url=_PUBLIC_URL, private_url=_PRIVATE_URL,
                 limit=6, session_class=_requests.Session, session=None,
                 nonce_iter=None, nonce_lock=None):
        """Initialize the Poloniex private client."""
        super(Poloniex, self).__init__(public_url, limit, session_class, session)
        self._private_url = private_url
        self._apikey = apikey
        self._secret = secret
        self.nonce_lock = nonce_lock or _threading.RLock()
        self.nonce_iter = nonce_iter or _itertools.count(int(_time.time() * 1000))

    @_api_wrapper
    def _private(self, command, **params):
        """Invoke the 'command' public API with optional params."""
        if not self._apikey or not self._secret:
            raise PoloniexCredentialsException('missing apikey/secret')

        with self.nonce_lock:
            params.update({'command': command, 'nonce': next(self.nonce_iter)})
            return self.session.post(
                self._private_url, data=params,
                auth=Poloniex._PoloniexAuth(self._apikey, self._secret))

    def returnBalances(self):
        """Returns all of your available balances."""
        return self._private('returnBalances')

    def returnCompleteBalances(self, account=None):
        """Returns all of your balances, including available balance, balance
        on orders, and the estimated BTC value of your balance. By default,
        this call is limited to your exchange account; set the "account" POST
        parameter to "all" to include your margin and lending accounts."""
        return self._private('returnCompleteBalances', account=account)

    def returnDepositAddresses(self):
        """Returns all of your deposit addresses."""
        return self._private('returnDepositAddresses')

    def generateNewAddress(self, currency):
        """Generates a new deposit address for the currency specified by the
        "currency" POST parameter. Only one address per currency per day may be
        generated, and a new address may not be generated before the
        previously-generated one has been used."""
        return self._private('generateNewAddress', currency=currency)

    def returnDepositsWithdrawals(self, start=0, end=2**32-1):
        """Returns your deposit and withdrawal history within a range,
        specified by the "start" and "end" POST parameters, both of which
        should be given as UNIX timestamps."""
        return self._private('returnDepositsWithdrawals', start=start, end=end)

    def returnDeposits(self, start=0, end=2**32-1):
        """Returns your deposit history within a range, specified by the
        "start" and "end" POST parameters, both of which should be given as
        UNIX timestamps."""
        return self.returnDepositsWithdrawals(start, end)['deposits']

    def returnWithdrawals(self, start=0, end=2**32-1):
        """Returns your withdrawal history within a range, specified by the
        "start" and "end" POST parameters, both of which should be given as
        UNIX timestamps."""
        return self.returnDepositsWithdrawals(start, end)['withdrawals']

    def returnOpenOrders(self, currencyPair='all'):
        """Returns your open orders for a given market, specified by the
        "currencyPair" POST parameter, e.g. "BTC_XCP". Set "currencyPair" to
        "all" to return open orders for all markets."""
        return self._private('returnOpenOrders', currencyPair=currencyPair)

    def returnTradeHistory(self, currencyPair='all', start=None, end=None):
        """Returns your trade history for a given market, specified by the
        "currencyPair" POST parameter. You may specify "all" as the
        currencyPair to receive your trade history for all markets. You may
        optionally specify a range via "start" and/or "end" POST parameters,
        given in UNIX timestamp format; if you do not specify a range, it will
        be limited to one day."""
        return self._private('returnTradeHistory', currencyPair=currencyPair,
                             start=start, end=end)

    def returnTradeHistoryPublic(self, currencyPair, start=None, end=None):
        """Returns the past 200 trades for a given market, or up to 50,000
        trades between a range specified in UNIX timestamps by the "start"
        and "end" GET parameters."""
        return super(Poloniex, self).returnTradeHistory(currencyPair, start, end)

    def returnOrderTrades(self, orderNumber):
        """Returns all trades involving a given order, specified by the
        "orderNumber" POST parameter. If no trades for the order have occurred
        or you specify an order that does not belong to you, you will receive
        an error. """
        return self._private('returnOrderTrades', orderNumber=orderNumber)

    def buy(self, currencyPair, rate, amount, fillOrKill=None,
            immediateOrCancel=None, postOnly=None):
        """Places a limit buy order in a given market. Required POST parameters
        are "currencyPair", "rate", and "amount". If successful, the method
        will return the order number.
        You may optionally set "fillOrKill", "immediateOrCancel", "postOnly"
        to 1. A fill-or-kill order will either fill in its entirety or be
        completely aborted. An immediate-or-cancel order can be partially or
        completely filled, but any portion of the order that cannot be filled
        immediately will be canceled rather than left on the order book.
        A post-only order will only be placed if no portion of it fills
        immediately; this guarantees you will never pay the taker fee on any
        part of the order that fills."""
        return self._private('buy', currencyPair=currencyPair, rate=rate,
                             amount=amount, fillOrKill=fillOrKill,
                             immediateOrCancel=immediateOrCancel,
                             postOnly=postOnly)

    def sell(self, currencyPair, rate, amount, fillOrKill=None,
             immediateOrCancel=None, postOnly=None):
        """Places a sell order in a given market. Parameters and output are
        the same as for the buy method."""
        return self._private('sell', currencyPair=currencyPair, rate=rate,
                             amount=amount, fillOrKill=fillOrKill,
                             immediateOrCancel=immediateOrCancel,
                             postOnly=postOnly)

    def cancelOrder(self, orderNumber):
        """Cancels an order you have placed in a given market. Required POST
        parameter is "orderNumber"."""
        return self._private('cancelOrder', orderNumber=orderNumber)

    def moveOrder(self, orderNumber, rate, amount=None, postOnly=None,
                  immediateOrCancel=None):
        """Cancels an order and places a new one of the same type in a single
        atomic transaction, meaning either both operations will succeed or both
         will fail. Required POST parameters are "orderNumber" and "rate"; you
         may optionally specify "amount" if you wish to change the amount of
         the new order. "postOnly" or "immediateOrCancel" may be specified for
         exchange orders, but will have no effect on margin orders. """
        return self._private('moveOrder', orderNumber=orderNumber, rate=rate,
                             amount=amount, postOnly=postOnly,
                             immediateOrCancel=immediateOrCancel)

    def withdraw(self, currency, amount, address, paymentId=None):
        """Immediately places a withdrawal for a given currency, with no email
        confirmation. In order to use this method, the withdrawal privilege
        must be enabled for your API key. Required POST parameters are
        "currency", "amount", and "address". For XMR withdrawals, you may
        optionally specify "paymentId"."""
        return self._private('withdraw', currency=currency, amount=amount,
                             address=address, paymentId=paymentId)

    def returnFeeInfo(self):
        """If you are enrolled in the maker-taker fee schedule, returns your
        current trading fees and trailing 30-day volume in BTC. This
        information is updated once every 24 hours."""
        return self._private('returnFeeInfo')

    def returnAvailableAccountBalances(self, account=None):
        """Returns your balances sorted by account. You may optionally specify
        the "account" POST parameter if you wish to fetch only the balances of
        one account. Please note that balances in your margin account may not
        be accessible if you have any open margin positions or orders."""
        return self._private('returnAvailableAccountBalances', account=account)

    def returnTradableBalances(self):
        """Returns your current tradable balances for each currency in each
        market for which margin trading is enabled. Please note that these
        balances may vary continually with market conditions."""
        return self._private('returnTradableBalances')

    def transferBalance(self, currency, amount, fromAccount, toAccount):
        """Transfers funds from one account to another (e.g. from your exchange
         account to your margin account). Required POST parameters are
         "currency", "amount", "fromAccount", and "toAccount"."""
        return self._private('transferBalance', currency=currency,
                             amount=amount, fromAccount=fromAccount,
                             toAccount=toAccount)

    def returnMarginAccountSummary(self):
        """Returns a summary of your entire margin account. This is the same
        information you will find in the Margin Account section of the Margin
        Trading page, under the Markets list. """
        return self._private('returnMarginAccountSummary')

    def marginBuy(self, currencyPair, rate, amount, lendingRate=None):
        """Places a margin buy order in a given market. Required POST
        parameters are "currencyPair", "rate", and "amount". You may optionally
         specify a maximum lending rate using the "lendingRate" parameter.
         If successful, the method will return the order number and any trades
         immediately resulting from your order."""
        return self._private('marginBuy', currencyPair=currencyPair, rate=rate,
                             amount=amount, lendingRate=lendingRate)

    def marginSell(self):
        """Places a margin sell order in a given market. Parameters and output
        are the same as for the marginBuy method."""
        return self._private('marginSell')

    def getMarginPosition(self, currencyPair):
        """Returns information about your margin position in a given market,
        specified by the "currencyPair" POST parameter. You may set
        "currencyPair" to "all" if you wish to fetch all of your margin
        positions at once. If you have no margin position in the specified
        market, "type" will be set to "none". "liquidationPrice" is an
        estimate, and does not necessarily represent the price at which an
        actual forced liquidation will occur. If you have no liquidation
        price, the value will be -1. """
        return self._private('getMarginPosition', currencyPair=currencyPair)

    def closeMarginPosition(self, currencyPair):
        """Closes your margin position in a given market (specified by the
        "currencyPair" POST parameter) using a market order. This call will
        also return success if you do not have an open position in the
        specified market."""
        return self._private('closeMarginPosition', currencyPair=currencyPair)

    def createLoanOffer(self, currency, amount, duration, autoRenew,
                        lendingRate):
        """Creates a loan offer for a given currency. Required POST parameters
        are "currency", "amount", "duration", "autoRenew" (0 or 1), and
        "lendingRate". """
        return self._private('createLoanOffer', currency=currency,
                             amount=amount, duration=duration,
                             autoRenew=autoRenew, lendingRate=lendingRate)

    def cancelLoanOffer(self, orderNumber):
        """Cancels a loan offer specified by the "orderNumber" POST
        parameter."""
        return self._private('cancelLoanOffer', orderNumber=orderNumber)

    def returnOpenLoanOffers(self):
        """Returns your open loan offers for each currency. """
        return self._private('returnOpenLoanOffers')

    def returnActiveLoans(self):
        """Returns your active loans for each currency."""
        return self._private('returnActiveLoans')

    def returnLendingHistory(self, start=0, end=2**32-1, limit=None):
        """Returns your lending history within a time range specified by the
        "start" and "end" POST parameters as UNIX timestamps. "limit" may also
        be specified to limit the number of rows returned. """
        return self._private('returnLendingHistory', start=start, end=end,
                             limit=limit)

    def toggleAutoRenew(self, orderNumber):
        """Toggles the autoRenew setting on an active loan, specified by the
        "orderNumber" POST parameter. If successful, "message" will indicate
        the new autoRenew setting. """
        return self._private('toggleAutoRenew', orderNumber=orderNumber)
<|endoftext|>


<commit_before>"""TurboGears 2 is a reinvention of TurboGears and a return to TurboGears' roots.

TurboGears is a project that is built upon a foundation of library development
best-of-breed component selection, and the re-use of already existing code.
And it was always intended to be a small collection of tools, docs, and helpers
that made developing with that best-of-breed stack easy.

In retrospect, some of the code that was added to the main TurboGears project
should have been released as independent projects that integrate with
TurboGears. This would have allowed those pieces to grow independently of
TurboGears, and would have allowed TurboGears to remain smaller and easier
to develop and debug.

TurboGears 0.5 release was just a few hundred lines of Python code, but it
built on thousands of lines of code in other libraries. Those libraries had
alreay been deployed, used, and tested, and were known to be
"production ready."

TurboGears2 returns to that philosophy. It is built on Pylons, but it brings
a best-of-breed approach to Pylons. TurboGears 2 is commited to the following
Python components and libraries, which are backwards compatable with
TurboGears 1.1:

    * Models: SQLAlchemy
    * Template engines: Genshi
    * URL Dispatching: Object dispatch
    * Form Handling: ToscaWidgets

The zen of TurboGears is::

    Keep simple things simple and complex things possible
    Give good defaults everywhere and allow choices where they matter
    Do your best to do things the right way,
    But when there's no "one right way," don't pretend there is.

Mark Ramm described the relationship between TurboGears and Pylons this way
"TurboGears 2 is to Pylons as Ubuntu is to Debian."

In other words we're focused on user experience, and creating a novice-friendly
environment. We ship a smaller subset of components, and thus are better able
to focus, test, and document things so that new users have the best possible
experience.

Meanwhile Pylons provides the power and flexibility of the underlying core.

And like Ubuntu, we don't intend to hide that power and flexibility from
advanced users, but we know that they want things set up to just work too.

Sensible defaults actually encourage code re-use within TurboGears because
they make it possible for a group of TurboGears components to share assumptions
about how things will work.
"""
from tg.controllers import TGController, redirect, url
from tg.decorators import validate, expose
from tg.flash import flash, get_flash, get_status

import paste

from pylons.wsgiapp import PylonsApp
from pylons import tmpl_context
from pylons import g as app_globals
from pylons import request
from pylons import session

from warnings import warn

class TurboGearsApplication(PylonsApp):
    """basis TurboGears application class which is derived from PylonsApp"""
    def __init__(self, root, **kwargs):
        # see ticket #1687
        kwargs['use_webob'] = False
        PylonsApp.__init__(self, **kwargs)
        self.root = root

    def resolve(self, environ, start_response):
        return self.root

    def __call__(self, environ, start_response):
        warn("TurboGearsApplication is depricated UsePylonsApp",
                DeprecationWarning)
        environ['pylons.routes_dict'] = {}
        self.setup_app_env(environ, start_response)

        # Initialize config if this is called by "paster shell"
        # as indicated by the fact that the shell sets a special
        # PATH_INFO
        if 'paste.testing_variables' in environ:
            self.load_test_env(environ)
            if environ['PATH_INFO'] == '/_test_vars':
                paste.registry.restorer.save_registry_state(environ)
                start_response('200 OK', [('Content-type', 'text/plain')])
                return ['%s' % paste.registry.restorer.get_request_id(environ)]

        # Pylons expects fresh instances of the controller on every request,
        # so instantiate a new instance of the root controller, and let it
        # handle the request.
        return self.root.__class__()(environ, start_response)

__all__ = [
    'expose', 'validate', 'TGController', 'tmpl_context', 'app_globals',
    'request', 'TurboGearsApplication', 'session'
]
<commit_msg>Typo in message + more information on what to do to remove deprecation.<commit_after>"""TurboGears 2 is a reinvention of TurboGears and a return to TurboGears' roots.

TurboGears is a project that is built upon a foundation of library development
best-of-breed component selection, and the re-use of already existing code.
And it was always intended to be a small collection of tools, docs, and helpers
that made developing with that best-of-breed stack easy.

In retrospect, some of the code that was added to the main TurboGears project
should have been released as independent projects that integrate with
TurboGears. This would have allowed those pieces to grow independently of
TurboGears, and would have allowed TurboGears to remain smaller and easier
to develop and debug.

TurboGears 0.5 release was just a few hundred lines of Python code, but it
built on thousands of lines of code in other libraries. Those libraries had
alreay been deployed, used, and tested, and were known to be
"production ready."

TurboGears2 returns to that philosophy. It is built on Pylons, but it brings
a best-of-breed approach to Pylons. TurboGears 2 is commited to the following
Python components and libraries, which are backwards compatable with
TurboGears 1.1:

    * Models: SQLAlchemy
    * Template engines: Genshi
    * URL Dispatching: Object dispatch
    * Form Handling: ToscaWidgets

The zen of TurboGears is::

    Keep simple things simple and complex things possible
    Give good defaults everywhere and allow choices where they matter
    Do your best to do things the right way,
    But when there's no "one right way," don't pretend there is.

Mark Ramm described the relationship between TurboGears and Pylons this way
"TurboGears 2 is to Pylons as Ubuntu is to Debian."

In other words we're focused on user experience, and creating a novice-friendly
environment. We ship a smaller subset of components, and thus are better able
to focus, test, and document things so that new users have the best possible
experience.

Meanwhile Pylons provides the power and flexibility of the underlying core.

And like Ubuntu, we don't intend to hide that power and flexibility from
advanced users, but we know that they want things set up to just work too.

Sensible defaults actually encourage code re-use within TurboGears because
they make it possible for a group of TurboGears components to share assumptions
about how things will work.
"""
from tg.controllers import TGController, redirect, url
from tg.decorators import validate, expose
from tg.flash import flash, get_flash, get_status

import paste

from pylons.wsgiapp import PylonsApp
from pylons import tmpl_context
from pylons import g as app_globals
from pylons import request
from pylons import session

from warnings import warn

class TurboGearsApplication(PylonsApp):
    """basis TurboGears application class which is derived from PylonsApp"""
    def __init__(self, root, **kwargs):
        # see ticket #1687
        kwargs['use_webob'] = False
        PylonsApp.__init__(self, **kwargs)
        self.root = root

    def resolve(self, environ, start_response):
        return self.root

    def __call__(self, environ, start_response):
        warn("TurboGearsApplication is deprecated use pylons.wsgiapp.PylonsApp",
                DeprecationWarning)
        environ['pylons.routes_dict'] = {}
        self.setup_app_env(environ, start_response)

        # Initialize config if this is called by "paster shell"
        # as indicated by the fact that the shell sets a special
        # PATH_INFO
        if 'paste.testing_variables' in environ:
            self.load_test_env(environ)
            if environ['PATH_INFO'] == '/_test_vars':
                paste.registry.restorer.save_registry_state(environ)
                start_response('200 OK', [('Content-type', 'text/plain')])
                return ['%s' % paste.registry.restorer.get_request_id(environ)]

        # Pylons expects fresh instances of the controller on every request,
        # so instantiate a new instance of the root controller, and let it
        # handle the request.
        return self.root.__class__()(environ, start_response)

__all__ = [
    'expose', 'validate', 'TGController', 'tmpl_context', 'app_globals',
    'request', 'TurboGearsApplication', 'session'
]
<|endoftext|>