<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Simple ET parser to handle ATP Test over serial
# Leandro Heck (leoheck@gmail.com)

## Create a fake terminal to test
# sudo apt install socat
# socat -d -d pty,raw,echo=0 pty,raw,echo=0

import argparse
import itertools
import time

import os.path
import stat

try:
	import serial
except Exception, e:
	print NEED_PYSERIAL
	raise e

parser = argparse.ArgumentParser(description='Enable ATPG tests')
parser.add_argument('-v', '--verbose', action='count', default=0, help='Increase verbosity. Number of -v will increase verbosity')
parser.add_argument('-d', '--device', default="/dev/ttyUSB0", help='Default: /dev/ttyUSB0')
parser.add_argument('-b', '--baud', default="9600", help='Default: 9600')
parser.add_argument('-f', '--atpg_file', help='Cadence ET ATPG file')
args = parser.parse_args()

try:
	stat.S_ISBLK(os.stat(args.device).st_mode)
except:
	print "Device", args.device, "is missing"
	exit()

# http://www.varesano.net/blog/fabio/serial%20rs232%20connections%20python
# configure the serial connections (the parameters differs on the device you are connecting to)
ser = serial.Serial(
	port=args.device,
	baudrate=args.baud
)

if not ser.isOpen():
	ser.open()
ser.isOpen()

# Execusao por arquivo (script)
if args.atpg_file:

	fname = args.atpg_file

	with open(fname) as f:
		lines = f.readlines()

	for i, line in enumerate(lines):

		# print line
		line_splited = line.split()

		if not line_splited:
			# print
			continue

		cmd = int(line_splited[0])

		if cmd == 100:
			if args.verbose >= 1:
				print cmd, "Encounter Test String"
			continue

		elif cmd == 200:
			if args.verbose >= 1:
				print cmd, "Input config values:", line_splited[1]

		elif cmd == 201:
			if args.verbose >= 1:
				print cmd, "Input config values:", line_splited[1]

		elif cmd == 202:
			if args.verbose >= 1:
				print cmd, "Expected output values:", line_splited[1]

		elif cmd == 203:
			if args.verbose >= 1:
				print cmd, "Undefined single character:", line_splited[1]

		elif cmd == 300:
			if args.verbose >= 1:
				print cmd, "Input/output stream", line_splited[1], line_splited[2][:20] + "..."
			if args.verbose >= 2:
				print "BITS:", len(line_splited[2])

		elif cmd == 301:
			if args.verbose >= 1:
				print cmd, "Input/output stream", line_splited[1], line_splited[2][:20] + "..."
				if args.verbose >= 2:
					print "BITS:", len(line_splited[2])

		elif cmd == 400:
			if args.verbose >= 1:
				print cmd, "Start test"

		elif cmd == 600:
			if args.verbose >= 1:
				print cmd, "Set clock pulses:", map(int,line_splited[1:])

		elif cmd == 900:
			if args.verbose >= 1:
				print cmd, "Test pattern:", line_splited[1]

		elif cmd == 901:
			if args.verbose >= 1:
				print cmd, "Test pattern:", line_splited[1]

		# NAO É COMANDO
		elif len(line_splited[0]) > 3:
			if args.verbose >= 2:
				print "BITS", len(line_splited[0])
			continue

		else:
			print "Warning, missing command"
			continue

# Abre no modo iterativo
else:

	print "INTERACTIVE MODE"
	print "Enter your commands below."
	print "RESET THE BOARD FIRST."
	print "Insert \"exit\" to leave the application."

	input = 1
	while 1:
		# get keyboard input
		input = raw_input("cmd> ")
			# Python 3 users
			# input = input(">> ")
		if input == 'exit':
			ser.close()
			exit()
		else:
			# send the character to the device
			# (note that I happend a \r\n carriage return and line feed to the characters - this is requested by my device)
			ser.write(input + '\r\n')
			out = ''
			# let's wait one second before reading output (let's give device time to answer)
			time.sleep(1)
			while ser.inWaiting() > 0:
				out += ser.read(1)

			if out != '':
				print out<commit_msg>updating<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Simple ET parser to handle ATP Test over serial
# Leandro Heck (leoheck@gmail.com)

## Create a fake terminal to test
# sudo apt install socat
# socat -d -d pty,raw,echo=0 pty,raw,echo=0

import argparse
import itertools
import time

import os.path
import stat

try:
	import serial
except Exception, e:
	print NEED_PYSERIAL
	raise e

parser = argparse.ArgumentParser(description='Enable ATPG tests')
parser.add_argument('-v', '--verbose', action='count', default=0, help='Increase verbosity. Number of -v will increase verbosity')
parser.add_argument('-d', '--device', default="/dev/ttyUSB0", help='Default: /dev/ttyUSB0')
parser.add_argument('-b', '--baud', default="9600", help='Default: 9600')
parser.add_argument('-f', '--atpg_file', help='Cadence ET ATPG file')
parser.add_argument('-t', '--test_mode', action='store_false', help='Disable serial commands to test ATPG parsing')
args = parser.parse_args()

if not args.test_mode:

	try:
		stat.S_ISBLK(os.stat(args.device).st_mode)
	except:
		print "Device", args.device, "is missing"
		exit()

	# http://www.varesano.net/blog/fabio/serial%20rs232%20connections%20python
	# configure the serial connections (the parameters differs on the device you are connecting to)
	ser = serial.Serial(
		port=args.device,
		baudrate=args.baud
	)

	if not ser.isOpen():
		ser.open()
	ser.isOpen()

# Codigos de comando dentro do arquivo de ATPG (Cadence ET):
# 000
# 100 COMMENT
# 200 stim_PIs -------------------------- Exemplo: 0XXXXXXXX111XX
# 201 stim_CIs -------------------------- Exemplo: 0XXXXXXXX1XXXX
# 202 resp_POs -------------------------- Exemplo: 11110100010
# 203 global_term_[Z] ------------------- Valor do sinal, todos em alta impedancia (z)
# 300 MODENUM_[1] stim_SLs stim_SLs ----- Exemplo: 1 1100110011... (acho que tem blocos de 1000 dados)
# 301 MODENUM_[1] resp_MLs resp_MLs ----- Exemplo: 1 1100110011... (acho que tem blocos de 1000 dados)
# 400 ----------------------------------- test_cycle
# 500 ----------------------------------- Não encontrado nos nossos arquivos de teste
# 501 ----------------------------------- Não encontrado nos nossos arquivos de teste
# 600 MODENUM_[1] SEQNUM_[1|100|2] MAX -- Exemplo: 1 2 1919
# 900 PATTERN --------------------------- Exemplo: 1.1.1.2.1.9
# 901 PATTERN --------------------------- Exemplo: 1.1.1.2.1.9

# Execusao por arquivo (script)
if args.atpg_file:

	fname = args.atpg_file

	with open(fname) as f:
		lines = f.readlines()

	for i, line in enumerate(lines):

		# print line
		line_splited = line.split()

		if not line_splited:
			# print
			continue

		cmd = int(line_splited[0])

		# 000
		if cmd == 000:
			if args.verbose >= 1:
				print cmd, "Starting tests?"

		# 100 COMMENT
		if cmd == 100:
			if args.verbose >= 1:
				print cmd, "Encounter Test String"
			continue

		# 200 stim_PIs -------------------------- Exemplo: 0XXXXXXXX111XX
		elif cmd == 200:
			stim_PIs = line_splited[1]
			if args.verbose >= 1:
				print cmd, "Inputs stimuli:", stim_PIs

		# 201 stim_CIs -------------------------- Exemplo: 0XXXXXXXX1XXXX
		elif cmd == 201:
			stim_CIs = line_splited[1]
			if args.verbose >= 1:
				print cmd, "Input comparison stimuli:", stim_CIs

		# 202 resp_POs -------------------------- Exemplo: 11110100010
		elif cmd == 202:
			resp_POs = line_splited[1]
			if args.verbose >= 1:
				print cmd, "Output responses:", resp_POs

		# 203 global_term_[Z] ------------------- Valor do sinal, todos em alta impedancia (z)
		elif cmd == 203:
			global_term = line_splited[1]
			if args.verbose >= 1:
				print cmd, "Undefined single character", global_term

		# 300 MODENUM_[1] stim_SLs stim_SLs ----- Exemplo: 1 1100110011... \n 1100110011... (acho que tem blocos de 1000 dados)
		elif cmd == 300:
			modenum = line_splited[1]
			if args.verbose >= 1:
				print cmd, "Input/output stream", modenum, line_splited[2][:20] + "..."
			if args.verbose >= 2:
				print "BITS:", len(line_splited[2])

		# 301 MODENUM_[1] resp_MLs resp_MLs ----- Exemplo: 1 1100110011... (acho que tem blocos de 1000 dados)
		elif cmd == 301:
			modenum = line_splited[1]
			if args.verbose >= 1:
				print cmd, "Input/output stream", modenum, line_splited[2][:20] + "..."
				if args.verbose >= 2:
					print "BITS:", len(line_splited[2])

		# 400 ----------------------------------- test_cycle
		elif cmd == 400:
			if args.verbose >= 1:
				print cmd, "Start test"
			print "\n"

		# 500 ----------------------------------- Não encontrado nos nossos arquivos de teste
		elif cmd == 500:
			print "Command", cmd, "not implemented yet."

		# 501 ----------------------------------- Não encontrado nos nossos arquivos de teste
		elif cmd == 501:
			print "Command", cmd, "not implemented yet."

		# 600 MODENUM_[1] SEQNUM_[1|100|2] MAX -- Exemplo: 1 2 1919
		elif cmd == 600:
			modenum = line_splited[1]
			seqnum = line_splited[2]
			cycles = line_splited[3]
			if args.verbose >= 1:
				print cmd, "Set clock pulses:", map(int,line_splited[1:])

		# 900 PATTERN --------------------------- Exemplo: 1.1.1.2.1.9
		elif cmd == 900:
			if args.verbose >= 1:
				print cmd, "Test pattern:", line_splited[1]

		# 901 PATTERN --------------------------- Exemplo: 1.1.1.2.1.9
		elif cmd == 901:
			if args.verbose >= 1:
				print cmd, "Test pattern:", line_splited[1]

		# NAO É COMANDO
		elif len(line_splited[0]) > 3:
			if args.verbose >= 2:
				print "BITS", len(line_splited[0])
			continue

		else:
			print "Warning, missing command"
			continue

# Abre no modo iterativo
else:

	print "INTERACTIVE MODE"
	print "Enter your commands below."
	print "RESET THE BOARD FIRST."
	print "Insert \"exit\" to leave the application."

	input = 1
	while 1:
		# get keyboard input
		input = raw_input("cmd> ")
			# Python 3 users
			# input = input(">> ")
		if input == 'exit':
			ser.close()
			exit()
		else:
			# send the character to the device
			# (note that I happend a \r\n carriage return and line feed to the characters - this is requested by my device)
			ser.write(input + '\r\n')
			out = ''
			# let's wait one second before reading output (let's give device time to answer)
			time.sleep(1)
			while ser.inWaiting() > 0:
				out += ser.read(1)

			if out != '':
				print out<|endoftext|>


<commit_before>#MenuTitle: Pixelate
# -*- coding: utf-8 -*-
__doc__="""
Turns outline glyphs into pixel glyphs by inserting pixel components, resetting widths and moving outlines to the background.
"""

import vanilla
import GlyphsApp

class Pixelate( object ):
	def __init__( self ):
		# Window 'self.w':
		windowWidth  = 250
		windowHeight = 150
		windowWidthResize  = 100 # user can resize width by this value
		windowHeightResize = 0   # user can resize height by this value
		self.w = vanilla.FloatingWindow(
			( windowWidth, windowHeight ), # default window size
			"Pixelate", # window title
			minSize = ( windowWidth, windowHeight ), # minimum size (for resizing)
			maxSize = ( windowWidth + windowWidthResize, windowHeight + windowHeightResize ), # maximum size (for resizing)
			autosaveName = "com.mekkablue.Pixelate.mainwindow" # stores last window position and size
		)

		# UI elements:
		self.w.text_1 = vanilla.TextBox( (15-1, 12+2, 140, 14), "Pixel grid step", sizeStyle='small' )
		self.w.text_2 = vanilla.TextBox( (15-1, 12+26, 140, 14), "Pixel component name", sizeStyle='small' )
		self.w.pixelRasterWidth = vanilla.EditText( (140+10, 12, -15, 15+4), "50", sizeStyle = 'small')
		self.w.pixelComponentName = vanilla.EditText( (140+10, 12+24, -15, 15+4), "pixel", sizeStyle = 'small', callback=self.EnableButton )
		self.w.resetWidths = vanilla.CheckBox( (15, 12+52, -15, 20), "Snap widths to pixel grid", value=False, callback=self.SavePreferences, sizeStyle='small' )

		# Run Button:
		self.w.runButton = vanilla.Button((-120-15, -20-15, -15, -15), "Insert Pixels", sizeStyle='regular', callback=self.PixelateMain )
		self.w.setDefaultButton( self.w.runButton )
		self.EnableButton(None)

		# Load Settings:
		if not self.LoadPreferences():
			print "Note: 'Pixelate' could not load preferences. Will resort to defaults"

		# Open window and focus on it:
		self.w.open()
		self.w.makeKey()

	def SavePreferences( self, sender ):
		try:
			Glyphs.defaults["com.mekkablue.Pixelate.pixelComponentName"] = self.w.pixelComponentName.get()
			Glyphs.defaults["com.mekkablue.Pixelate.pixelRasterWidth"] = self.w.pixelRasterWidth.get()
			Glyphs.defaults["com.mekkablue.Pixelate.resetWidths"] = self.w.resetWidths.get()
		except:
			return False

		return True

	def LoadPreferences( self ):
		try:
			self.w.pixelComponentName.set( Glyphs.defaults["com.mekkablue.Pixelate.pixelComponentName"] )
			self.w.pixelRasterWidth.set( Glyphs.defaults["com.mekkablue.Pixelate.pixelRasterWidth"] )
			self.w.resetWidths.set( Glyphs.defaults["com.mekkablue.Pixelate.resetWidths"] )
		except:
			return False

		return True

	def EnableButton( self, sender ):
		"""
		Checks if the pixel component entered by the user actually exists,
		and enables/disables the Run button accordingly.
		"""
		onOff = False
		pixelNameEntered = str(self.w.pixelComponentName.get())
		currentGlyphNames = [g.name for g in Glyphs.font.glyphs] # glyph names of frontmost font
		if pixelNameEntered in currentGlyphNames:
			onOff = True
		self.w.runButton.enable(onOff)

	def PixelateMain( self, sender ):
		try:
			thisFont = Glyphs.font # frontmost font
			listOfSelectedLayers = thisFont.selectedLayers # active layers of currently selected glyphs
			pixelRasterWidth = float(self.w.pixelRasterWidth.get())
			pixelNameEntered = str(self.w.pixelComponentName.get())
			pixel = thisFont.glyphs[pixelNameEntered]
			widthsShouldBeReset = self.w.resetWidths.get()

			for thisLayer in listOfSelectedLayers: # loop through layers
				thisGlyph = thisLayer.parent

				if len(thisLayer.paths) > 0:

					# get all possible pixel positions within layer bounds:
					thisLayerBezierPath = thisLayer.bezierPath() # necessary for containsPoint_() function
					layerBounds = thisLayer.bounds
					xStart = int(round( layerBounds.origin.x / pixelRasterWidth ))
					yStart = int(round( layerBounds.origin.y / pixelRasterWidth ))
					xIterations = int(round( layerBounds.size.width / pixelRasterWidth ))
					yIterations = int(round( layerBounds.size.height / pixelRasterWidth ))
					pixelCount = 0

					# move current layer to background and clean foreground:
					thisLayer.clearBackground()
					try: # circumvent typo in API:
						thisLayer.swapForgroundWithBackground()
					except:
						thisLayer.swapForegroundWithBackground()

					# snap width to pixel grid:
					widthReport = ""
					if widthsShouldBeReset:
						originalWidth = thisLayer.width
						pixelatedWidth = round( originalWidth / pixelRasterWidth ) * pixelRasterWidth
						thisLayer.setWidth_( pixelatedWidth )
						if originalWidth != thisLayer.width: # only if it really changed
							widthReport = ", snapped width to %.1f" % pixelatedWidth

					for x in range(xStart, xStart + xIterations):
						for y in range( yStart, yStart + yIterations):
							# if the pixel center is black, insert a pixel component here:
							pixelCenter = NSPoint( (x+0.5) * pixelRasterWidth, (y+0.5) * pixelRasterWidth )
							print x, y
							if thisLayerBezierPath.containsPoint_( pixelCenter ):
								pixelCount += 1
								pixelComponent = GSComponent( pixel, NSPoint( x * pixelRasterWidth, y * pixelRasterWidth ) )
								thisLayer.addComponent_( pixelComponent )

					print "%s: added %i pixels to layer '%s'%s." % ( thisGlyph.name, pixelCount, thisLayer.name, widthReport )
				else:
					print "No paths in '%s' (layer '%s'), skipping." % ( thisGlyph.name, thisLayer.name )

			if not self.SavePreferences( self ):
				print "Note: 'Pixelate' could not write preferences."

			self.w.close() # delete if you want window to stay open
		except Exception, e:
			# brings macro window to front and reports error:
			Glyphs.showMacroWindow()
			print "Pixelate Error: %s" % e

Pixelate()<commit_msg>Better handling of components, more options<commit_after>#MenuTitle: Pixelate
# -*- coding: utf-8 -*-
__doc__="""
Turns outline glyphs into pixel glyphs by inserting pixel components, resetting widths and moving outlines to the background.
"""

import vanilla
import GlyphsApp

class Pixelate( object ):
	def __init__( self ):
		# Window 'self.w':
		windowWidth  = 250
		windowHeight = 185
		windowWidthResize  = 100 # user can resize width by this value
		windowHeightResize = 0   # user can resize height by this value
		self.w = vanilla.FloatingWindow(
			( windowWidth, windowHeight ), # default window size
			"Pixelate", # window title
			minSize = ( windowWidth, windowHeight ), # minimum size (for resizing)
			maxSize = ( windowWidth + windowWidthResize, windowHeight + windowHeightResize ), # maximum size (for resizing)
			autosaveName = "com.mekkablue.Pixelate.mainwindow" # stores last window position and size
		)

		# UI elements:
		self.w.text_1 = vanilla.TextBox( (15-1, 12+2, 140, 14), "Pixel grid step", sizeStyle='small' )
		self.w.text_2 = vanilla.TextBox( (15-1, 12+26, 140, 14), "Pixel component name", sizeStyle='small' )
		self.w.pixelRasterWidth = vanilla.EditText( (140+10, 12, -15, 15+4), "50", sizeStyle = 'small')
		self.w.pixelComponentName = vanilla.EditText( (140+10, 12+24, -15, 15+4), "pixel", sizeStyle = 'small', callback=self.EnableButton )
		self.w.resetWidths = vanilla.CheckBox( (15, 63, -15, 20), "Snap widths to pixel grid", value=True, callback=self.SavePreferences, sizeStyle='small' )
		self.w.decomposeComponents = vanilla.CheckBox( (15, 63+20, -15, 20), "Decompose compounds", value=True, callback=self.SavePreferences, sizeStyle='small' )
		self.w.keepWindowOpen = vanilla.CheckBox( (15, 63+40, -15, 20), "Keep window open", value=True, callback=self.SavePreferences, sizeStyle='small' )

		# Run Button:
		self.w.runButton = vanilla.Button((-120-15, -20-15, -15, -15), "Insert Pixels", sizeStyle='regular', callback=self.PixelateMain )
		self.w.setDefaultButton( self.w.runButton )

		# Load Settings:
		if not self.LoadPreferences():
			print "Note: 'Pixelate' could not load preferences. Will resort to defaults"

		# Check if run button should be enabled:
		self.EnableButton(None)

		# Open window and focus on it:
		self.w.open()
		self.w.makeKey()

	def SavePreferences( self, sender ):
		try:
			Glyphs.defaults["com.mekkablue.Pixelate.pixelComponentName"] = self.w.pixelComponentName.get()
			Glyphs.defaults["com.mekkablue.Pixelate.pixelRasterWidth"] = self.w.pixelRasterWidth.get()
			Glyphs.defaults["com.mekkablue.Pixelate.resetWidths"] = self.w.resetWidths.get()
			Glyphs.defaults["com.mekkablue.Pixelate.decomposeComponents"] = self.w.decomposeComponents.get()
			Glyphs.defaults["com.mekkablue.Pixelate.keepWindowOpen"] = self.w.keepWindowOpen.get()
		except:
			return False

		return True

	def LoadPreferences( self ):
		try:
			NSUserDefaults.standardUserDefaults().registerDefaults_(
				{
					"com.mekkablue.Pixelate.pixelComponentName": "pixel",
					"com.mekkablue.Pixelate.pixelRasterWidth": "50",
					"com.mekkablue.Pixelate.resetWidths": True,
					"com.mekkablue.Pixelate.decomposeComponents": True,
					"com.mekkablue.Pixelate.keepWindowOpen": True
				}
			)
			self.w.pixelComponentName.set( Glyphs.defaults["com.mekkablue.Pixelate.pixelComponentName"] )
			self.w.pixelRasterWidth.set( Glyphs.defaults["com.mekkablue.Pixelate.pixelRasterWidth"] )
			self.w.resetWidths.set( Glyphs.defaults["com.mekkablue.Pixelate.resetWidths"] )
			self.w.decomposeComponents.set( Glyphs.defaults["com.mekkablue.Pixelate.decomposeComponents"] )
			self.w.keepWindowOpen.set( Glyphs.defaults["com.mekkablue.Pixelate.keepWindowOpen"] )
		except:
			return False

		return True

	def EnableButton( self, sender ):
		"""
		Checks if the pixel component entered by the user actually exists,
		and enables/disables the Run button accordingly.
		"""
		onOff = False
		pixelNameEntered = str(self.w.pixelComponentName.get())
		currentGlyphNames = [g.name for g in Glyphs.font.glyphs] # glyph names of frontmost font
		if pixelNameEntered in currentGlyphNames:
			onOff = True
		self.w.runButton.enable(onOff)

	def PixelateMain( self, sender ):
		try:
			thisFont = Glyphs.font # frontmost font
			listOfSelectedLayers = thisFont.selectedLayers # active layers of currently selected glyphs
			pixelRasterWidth = float( self.w.pixelRasterWidth.get() )
			pixelNameEntered = str( self.w.pixelComponentName.get() )
			pixel = thisFont.glyphs[ pixelNameEntered ]
			widthsShouldBeReset = self.w.resetWidths.get()
			componentsMustBeDecomposed = self.w.decomposeComponents.get()

			for thisLayer in listOfSelectedLayers: # loop through layers
				thisGlyph = thisLayer.parent

				if len(thisLayer.paths) > 0 or (componentsMustBeDecomposed and len(thisLayer.components) > 0):

					# get all possible pixel positions within layer bounds:
					thisLayerBezierPath = thisLayer.copyDecomposedLayer().bezierPath() # necessary for containsPoint_() function
					layerBounds = thisLayer.bounds
					xStart = int(round( layerBounds.origin.x / pixelRasterWidth ))
					yStart = int(round( layerBounds.origin.y / pixelRasterWidth ))
					xIterations = int(round( layerBounds.size.width / pixelRasterWidth ))
					yIterations = int(round( layerBounds.size.height / pixelRasterWidth ))
					pixelCount = 0

					# move current layer to background and clean foreground:
					thisLayer.clearBackground()
					try: # circumvent typo in API:
						thisLayer.swapForgroundWithBackground()
					except:
						thisLayer.swapForegroundWithBackground()

					# snap width to pixel grid:
					widthReport = ""
					if widthsShouldBeReset:
						originalWidth = thisLayer.width
						pixelatedWidth = round( originalWidth / pixelRasterWidth ) * pixelRasterWidth
						thisLayer.setWidth_( pixelatedWidth )
						if originalWidth != thisLayer.width: # only if it really changed
							widthReport = ", snapped width to %.1f" % pixelatedWidth

					# add pixels:
					for x in range(xStart, xStart + xIterations):
						for y in range( yStart, yStart + yIterations):
							# if the pixel center is black, insert a pixel component here:
							pixelCenter = NSPoint( (x+0.5) * pixelRasterWidth, (y+0.5) * pixelRasterWidth )
							if thisLayerBezierPath.containsPoint_( pixelCenter ):
								pixelCount += 1
								pixelComponent = GSComponent( pixel, NSPoint( x * pixelRasterWidth, y * pixelRasterWidth ) )
								thisLayer.addComponent_( pixelComponent )

					print "%s: added %i pixels to layer '%s'%s." % ( thisGlyph.name, pixelCount, thisLayer.name, widthReport )
				else:
					print "No paths in '%s' (layer '%s'), skipping." % ( thisGlyph.name, thisLayer.name )

			# save prefs:
			if not self.SavePreferences( self ):
				print "Note: 'Pixelate' could not write preferences."

			# keep window open if requested:
			if not self.w.keepWindowOpen.get():
				self.w.close()
		except Exception, e:
			# brings macro window to front and reports error:
			Glyphs.showMacroWindow()
			print "Pixelate Error: %s" % e

Pixelate()<|endoftext|>


<commit_before>__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
import os, struct, sys, time

from serial import Serial
from serial import SerialException

import ispBase, intelHex

class Stk500v2(ispBase.IspBase):
	def __init__(self):
		self.serial = None
		self.seq = 1
		self.lastAddr = -1
		self.progressCallback = None

	def connect(self, port = 'COM22', speed = 115200):
		if self.serial is not None:
			self.close()
		try:
			self.serial = Serial(str(port), speed, timeout=1, writeTimeout=10000)
		except SerialException as e:
			raise ispBase.IspError("Failed to open serial port")
		except:
			raise ispBase.IspError("Unexpected error while connecting to serial port:" + port + ":" + str(sys.exc_info()[0]))
		self.seq = 1

		#Reset the controller
		self.serial.setDTR(1)
		time.sleep(0.1)
		self.serial.setDTR(0)
		time.sleep(0.2)

		self.sendMessage([1])
		if self.sendMessage([0x10, 0xc8, 0x64, 0x19, 0x20, 0x00, 0x53, 0x03, 0xac, 0x53, 0x00, 0x00]) != [0x10, 0x00]:
			self.close()
			raise ispBase.IspError("Failed to enter programming mode")
		self.serial.timeout = 5

	def close(self):
		if self.serial is not None:
			self.serial.close()
			self.serial = None

	#Leave ISP does not reset the serial port, only resets the device, and returns the serial port after disconnecting it from the programming interface.
	#	This allows you to use the serial port without opening it again.
	def leaveISP(self):
		if self.serial is not None:
			if self.sendMessage([0x11]) != [0x11, 0x00]:
				raise ispBase.IspError("Failed to leave programming mode")
			ret = self.serial
			self.serial = None
			return ret
		return None

	def isConnected(self):
		return self.serial is not None

	def sendISP(self, data):
		recv = self.sendMessage([0x1D, 4, 4, 0, data[0], data[1], data[2], data[3]])
		return recv[2:6]

	def writeFlash(self, flashData):
		#Set load addr to 0, in case we have more then 64k flash we need to enable the address extension
		pageSize = self.chip['pageSize'] * 2
		flashSize = pageSize * self.chip['pageCount']
		if flashSize > 0xFFFF:
			self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00])
		else:
			self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00])

		loadCount = (len(flashData) + pageSize - 1) / pageSize
		for i in xrange(0, loadCount):
			recv = self.sendMessage([0x13, pageSize >> 8, pageSize & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flashData[(i * pageSize):(i * pageSize + pageSize)])
			if self.progressCallback != None:
				self.progressCallback(i + 1, loadCount*2)

	def verifyFlash(self, flashData):
		#Set load addr to 0, in case we have more then 64k flash we need to enable the address extension
		flashSize = self.chip['pageSize'] * 2 * self.chip['pageCount']
		if flashSize > 0xFFFF:
			self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00])
		else:
			self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00])

		loadCount = (len(flashData) + 0xFF) / 0x100
		for i in xrange(0, loadCount):
			recv = self.sendMessage([0x14, 0x01, 0x00, 0x20])[2:0x102]
			if self.progressCallback != None:
				self.progressCallback(loadCount + i + 1, loadCount*2)
			for j in xrange(0, 0x100):
				if i * 0x100 + j < len(flashData) and flashData[i * 0x100 + j] != recv[j]:
					raise ispBase.IspError('Verify error at: 0x%x' % (i * 0x100 + j))

	def sendMessage(self, data):
		message = struct.pack(">BBHB", 0x1B, self.seq, len(data), 0x0E)
		for c in data:
			message += struct.pack(">B", c)
		checksum = 0
		for c in message:
			checksum ^= ord(c)
		message += struct.pack(">B", checksum)
		try:
			self.serial.write(message)
			self.serial.flush()
		except Serial.SerialTimeoutException:
			raise ispBase.IspError('Serial send timeout')
		self.seq = (self.seq + 1) & 0xFF
		return self.recvMessage()

	def recvMessage(self):
		state = 'Start'
		checksum = 0
		while True:
			s = self.serial.read()
			if len(s) < 1:
				raise ispBase.IspError("Timeout")
			b = struct.unpack(">B", s)[0]
			checksum ^= b
			#print(hex(b))
			if state == 'Start':
				if b == 0x1B:
					state = 'GetSeq'
					checksum = 0x1B
			elif state == 'GetSeq':
				state = 'MsgSize1'
			elif state == 'MsgSize1':
				msgSize = b << 8
				state = 'MsgSize2'
			elif state == 'MsgSize2':
				msgSize |= b
				state = 'Token'
			elif state == 'Token':
				if b != 0x0E:
					state = 'Start'
				else:
					state = 'Data'
					data = []
			elif state == 'Data':
				data.append(b)
				if len(data) == msgSize:
					state = 'Checksum'
			elif state == 'Checksum':
				if checksum != 0:
					state = 'Start'
				else:
					return data

def main():
	programmer = Stk500v2()
	if sys.argv[1] == 'AUTO':
		import _winreg
		key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM")
		i=0
		while True:
			values = _winreg.EnumValue(key, i)
			if 'USBSER' in values[0]:
				programmer.connect(port = values[1])
				break
			i+=1
	else:
		programmer.connect(port = sys.argv[1])
	programmer.programChip(intelHex.readHex(sys.argv[2]))
	sys.exit(1)

if __name__ == '__main__':
	main()
<commit_msg>Update commandline programmer.<commit_after>__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
import os, struct, sys, time

from serial import Serial
from serial import SerialException

import ispBase, intelHex

class Stk500v2(ispBase.IspBase):
	def __init__(self):
		self.serial = None
		self.seq = 1
		self.lastAddr = -1
		self.progressCallback = None

	def connect(self, port = 'COM22', speed = 115200):
		if self.serial is not None:
			self.close()
		try:
			self.serial = Serial(str(port), speed, timeout=1, writeTimeout=10000)
		except SerialException as e:
			raise ispBase.IspError("Failed to open serial port")
		except:
			raise ispBase.IspError("Unexpected error while connecting to serial port:" + port + ":" + str(sys.exc_info()[0]))
		self.seq = 1

		#Reset the controller
		self.serial.setDTR(1)
		time.sleep(0.1)
		self.serial.setDTR(0)
		time.sleep(0.2)

		self.sendMessage([1])
		if self.sendMessage([0x10, 0xc8, 0x64, 0x19, 0x20, 0x00, 0x53, 0x03, 0xac, 0x53, 0x00, 0x00]) != [0x10, 0x00]:
			self.close()
			raise ispBase.IspError("Failed to enter programming mode")
		self.serial.timeout = 5

	def close(self):
		if self.serial is not None:
			self.serial.close()
			self.serial = None

	#Leave ISP does not reset the serial port, only resets the device, and returns the serial port after disconnecting it from the programming interface.
	#	This allows you to use the serial port without opening it again.
	def leaveISP(self):
		if self.serial is not None:
			if self.sendMessage([0x11]) != [0x11, 0x00]:
				raise ispBase.IspError("Failed to leave programming mode")
			ret = self.serial
			self.serial = None
			return ret
		return None

	def isConnected(self):
		return self.serial is not None

	def sendISP(self, data):
		recv = self.sendMessage([0x1D, 4, 4, 0, data[0], data[1], data[2], data[3]])
		return recv[2:6]

	def writeFlash(self, flashData):
		#Set load addr to 0, in case we have more then 64k flash we need to enable the address extension
		pageSize = self.chip['pageSize'] * 2
		flashSize = pageSize * self.chip['pageCount']
		if flashSize > 0xFFFF:
			self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00])
		else:
			self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00])

		loadCount = (len(flashData) + pageSize - 1) / pageSize
		for i in xrange(0, loadCount):
			recv = self.sendMessage([0x13, pageSize >> 8, pageSize & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flashData[(i * pageSize):(i * pageSize + pageSize)])
			if self.progressCallback != None:
				self.progressCallback(i + 1, loadCount*2)

	def verifyFlash(self, flashData):
		#Set load addr to 0, in case we have more then 64k flash we need to enable the address extension
		flashSize = self.chip['pageSize'] * 2 * self.chip['pageCount']
		if flashSize > 0xFFFF:
			self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00])
		else:
			self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00])

		loadCount = (len(flashData) + 0xFF) / 0x100
		for i in xrange(0, loadCount):
			recv = self.sendMessage([0x14, 0x01, 0x00, 0x20])[2:0x102]
			if self.progressCallback != None:
				self.progressCallback(loadCount + i + 1, loadCount*2)
			for j in xrange(0, 0x100):
				if i * 0x100 + j < len(flashData) and flashData[i * 0x100 + j] != recv[j]:
					raise ispBase.IspError('Verify error at: 0x%x' % (i * 0x100 + j))

	def sendMessage(self, data):
		message = struct.pack(">BBHB", 0x1B, self.seq, len(data), 0x0E)
		for c in data:
			message += struct.pack(">B", c)
		checksum = 0
		for c in message:
			checksum ^= ord(c)
		message += struct.pack(">B", checksum)
		try:
			self.serial.write(message)
			self.serial.flush()
		except Serial.SerialTimeoutException:
			raise ispBase.IspError('Serial send timeout')
		self.seq = (self.seq + 1) & 0xFF
		return self.recvMessage()

	def recvMessage(self):
		state = 'Start'
		checksum = 0
		while True:
			s = self.serial.read()
			if len(s) < 1:
				raise ispBase.IspError("Timeout")
			b = struct.unpack(">B", s)[0]
			checksum ^= b
			#print(hex(b))
			if state == 'Start':
				if b == 0x1B:
					state = 'GetSeq'
					checksum = 0x1B
			elif state == 'GetSeq':
				state = 'MsgSize1'
			elif state == 'MsgSize1':
				msgSize = b << 8
				state = 'MsgSize2'
			elif state == 'MsgSize2':
				msgSize |= b
				state = 'Token'
			elif state == 'Token':
				if b != 0x0E:
					state = 'Start'
				else:
					state = 'Data'
					data = []
			elif state == 'Data':
				data.append(b)
				if len(data) == msgSize:
					state = 'Checksum'
			elif state == 'Checksum':
				if checksum != 0:
					state = 'Start'
				else:
					return data

def portList():
	ret = []
	import _winreg
	key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM")
	i=0
	while True:
		try:
			values = _winreg.EnumValue(key, i)
		except:
			return ret
		if 'USBSER' in values[0]:
			ret.append(values[1])
		i+=1
	return ret

def runProgrammer(port, filename):
	programmer = Stk500v2()
	programmer.connect(port = port)
	programmer.programChip(intelHex.readHex(filename))
	programmer.close()

def main():
	import threading
	if sys.argv[1] == 'AUTO':
		print portList()
		for port in portList():
			threading.Thread(target=runProgrammer, args=(port,sys.argv[2])).start()
			time.sleep(5)
	else:
		programmer = Stk500v2()
		programmer.connect(port = sys.argv[1])
		programmer.programChip(intelHex.readHex(sys.argv[2]))
		sys.exit(1)

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


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

"""
The Cython debugger

The current directory should contain a directory named 'cython_debug', or a
path to the cython project directory should be given (the parent directory of
cython_debug).

Additional gdb args can be provided only if a path to the project directory is
given.
"""

import os
import sys
import glob
import tempfile
import textwrap
import subprocess
import optparse

def make_command_file(path_to_debug_info, prefix_code='', no_import=False):
    if not no_import:
        pattern = os.path.join(path_to_debug_info,
                               'cython_debug',
                               'cython_debug_info_*')
        debug_files = glob.glob(pattern)

        if not debug_files:
            sys.exit('%s.\nNo debug files were found in %s. Aborting.' % (
                                   usage, os.path.abspath(path_to_debug_info)))

    fd, tempfilename = tempfile.mkstemp()
    f = os.fdopen(fd, 'w')
    f.write(prefix_code)
    f.write('set breakpoint pending on\n')
    f.write("set print pretty on\n")
    f.write('python from Cython.Debugger import libcython, libpython\n')

    if no_import:
        # don't do this, this overrides file command in .gdbinit
        # f.write("file %s\n" % sys.executable)
        pass
    else:
        path = os.path.join(path_to_debug_info, "cython_debug", "interpreter")
        interpreter = open(path).read()
        f.write("file %s\n" % interpreter)
        f.write('\n'.join('cy import %s\n' % fn for fn in debug_files))
        f.write(textwrap.dedent('''\
            python
            import sys
            try:
                gdb.lookup_type('PyModuleObject')
            except RuntimeError:
                sys.stderr.write(
                    'Python was not compiled with debug symbols (or it was '
                    'stripped). Some functionality may not work (properly).\\n')
            end
        '''))

    f.close()

    return tempfilename

usage = "Usage: cygdb [options] [PATH [GDB_ARGUMENTS]]"

def main(path_to_debug_info=None, gdb_argv=None, no_import=False):
    """
    Start the Cython debugger. This tells gdb to import the Cython and Python
    extensions (libcython.py and libpython.py) and it enables gdb's pending
    breakpoints.

    path_to_debug_info is the path to the Cython build directory
    gdb_argv is the list of options to gdb
    no_import tells cygdb whether it should import debug information
    """
    parser = optparse.OptionParser(usage=usage)
    parser.add_option("--gdb-executable",
        dest="gdb", default='gdb',
        help="gdb executable to use [default: gdb]")

    (options, args) = parser.parse_args()
    if path_to_debug_info is None:
        if len(args) > 1:
            path_to_debug_info = args[0]
        else:
            path_to_debug_info = os.curdir

    if gdb_argv is None:
        gdb_argv = args[1:]

    if path_to_debug_info == '--':
        no_import = True

    tempfilename = make_command_file(path_to_debug_info, no_import=no_import)
    p = subprocess.Popen([options.gdb, '-command', tempfilename] + gdb_argv)
    while True:
        try:
            p.wait()
        except KeyboardInterrupt:
            pass
        else:
            break
    os.remove(tempfilename)
<commit_msg>Cygdb.py: Add "--" to cygdb usage string before gdb args<commit_after>#!/usr/bin/env python

"""
The Cython debugger

The current directory should contain a directory named 'cython_debug', or a
path to the cython project directory should be given (the parent directory of
cython_debug).

Additional gdb args can be provided only if a path to the project directory is
given.
"""

import os
import sys
import glob
import tempfile
import textwrap
import subprocess
import optparse

def make_command_file(path_to_debug_info, prefix_code='', no_import=False):
    if not no_import:
        pattern = os.path.join(path_to_debug_info,
                               'cython_debug',
                               'cython_debug_info_*')
        debug_files = glob.glob(pattern)

        if not debug_files:
            sys.exit('%s.\nNo debug files were found in %s. Aborting.' % (
                                   usage, os.path.abspath(path_to_debug_info)))

    fd, tempfilename = tempfile.mkstemp()
    f = os.fdopen(fd, 'w')
    f.write(prefix_code)
    f.write('set breakpoint pending on\n')
    f.write("set print pretty on\n")
    f.write('python from Cython.Debugger import libcython, libpython\n')

    if no_import:
        # don't do this, this overrides file command in .gdbinit
        # f.write("file %s\n" % sys.executable)
        pass
    else:
        path = os.path.join(path_to_debug_info, "cython_debug", "interpreter")
        interpreter = open(path).read()
        f.write("file %s\n" % interpreter)
        f.write('\n'.join('cy import %s\n' % fn for fn in debug_files))
        f.write(textwrap.dedent('''\
            python
            import sys
            try:
                gdb.lookup_type('PyModuleObject')
            except RuntimeError:
                sys.stderr.write(
                    'Python was not compiled with debug symbols (or it was '
                    'stripped). Some functionality may not work (properly).\\n')
            end
        '''))

    f.close()

    return tempfilename

usage = "Usage: cygdb [options] [PATH [-- GDB_ARGUMENTS]]"

def main(path_to_debug_info=None, gdb_argv=None, no_import=False):
    """
    Start the Cython debugger. This tells gdb to import the Cython and Python
    extensions (libcython.py and libpython.py) and it enables gdb's pending
    breakpoints.

    path_to_debug_info is the path to the Cython build directory
    gdb_argv is the list of options to gdb
    no_import tells cygdb whether it should import debug information
    """
    parser = optparse.OptionParser(usage=usage)
    parser.add_option("--gdb-executable",
        dest="gdb", default='gdb',
        help="gdb executable to use [default: gdb]")

    (options, args) = parser.parse_args()
    if path_to_debug_info is None:
        if len(args) > 1:
            path_to_debug_info = args[0]
        else:
            path_to_debug_info = os.curdir

    if gdb_argv is None:
        gdb_argv = args[1:]

    if path_to_debug_info == '--':
        no_import = True

    tempfilename = make_command_file(path_to_debug_info, no_import=no_import)
    p = subprocess.Popen([options.gdb, '-command', tempfilename] + gdb_argv)
    while True:
        try:
            p.wait()
        except KeyboardInterrupt:
            pass
        else:
            break
    os.remove(tempfilename)
<|endoftext|>


<commit_before>from collections import OrderedDict

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from checks import check_oseries
from solver import LmfitSolve
from tseries import Constant

class Model:
    def __init__(self, oseries, xy=(0, 0), metadata=None, freq=None,
                 fillnan='drop'):
        """
        Initiates a time series model.

        Parameters
        ----------
        oseries: pd.Series
            pandas Series object containing the dependent time series. The
            observation can be non-equidistant.
        xy: Optional[tuple]
            XY location of the oseries in lat-lon format.
        metadata: Optional[dict]
            Dictionary containing metadata of the model.
        freq: Optional[str]
            String containing the desired frequency. By default freq=None and the
            observations are used as they are. The required string format is found
            at http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset
            -aliases
        fillnan: Optional[str or float]
            Methods or float number to fill nan-values. Default values is
            'drop'. Currently supported options are: 'interpolate', float,
            'mean' and, 'drop'. Interpolation is performed with a standard linear
            interpolation.

        """
        self.oseries = check_oseries(oseries, freq, fillnan)
        self.xy = xy
        self.metadata = metadata
        self.odelt = self.oseries.index.to_series().diff() / np.timedelta64(1, 'D')
        # delt converted to days
        self.tseriesdict = OrderedDict()
        self.noisemodel = None
        self.noiseparameters = None
        self.tmin = None
        self.tmax = None

    def add_tseries(self, tseries):
        """
        adds a time series model component to the Model.

        """
        self.tseriesdict[tseries.name] = tseries

    def add_noisemodel(self, noisemodel):
        """
        Adds a noise model to the time series Model.

        """
        self.noisemodel = noisemodel

    def simulate(self, parameters=None, tmin=None, tmax=None, freq='D'):
        """

        Parameters
        ----------
        t: Optional[pd.series.index]
            Time indices to use for the simulation of the time series model.
        p: Optional[array]
            Array of the parameters used in the time series model.
        noise:

        Returns
        -------
        Pandas Series object containing the simulated time series.

        """

        # Default option when not tmin and tmax is provided
        if tmin is None:
            tmin = self.tmin
        if tmax is None:
            tmax = self.tmax
        assert (tmin is not None) and (
            tmax is not None), 'model needs to be solved first'

        tindex = pd.date_range(tmin, tmax, freq=freq)

        if parameters is None:
            parameters = self.parameters.optimal.values
        h = pd.Series(data=0, index=tindex)
        istart = 0  # Track parameters index to pass to ts object
        for ts in self.tseriesdict.values():
            h += ts.simulate(parameters[istart: istart + ts.nparam], tindex)
            istart += ts.nparam
        return h

    def residuals(self, parameters=None, tmin=None, tmax=None, noise=True):
        """
        Method to calculate the residuals.

        """
        if tmin is None:
            tmin = self.oseries.index.min()
        if tmax is None:
            tmax = self.oseries.index.max()
        tindex = self.oseries[tmin: tmax].index  # times used for calibration

        if parameters is None:
            parameters = self.parameters.optimal.values

        # h_observed - h_simulated
        r = self.oseries[tindex] - self.simulate(parameters, tmin, tmax)[tindex]
        # print 'step1:', sum(r**2)
        if noise and (self.noisemodel is not None):
            r = self.noisemodel.simulate(r, self.odelt[tindex],
                                         parameters[-self.noisemodel.nparam:],
                                         tindex)
        # print 'step2:', sum(r**2)
        if np.isnan(sum(r ** 2)):
            print 'nan problem in residuals'  # quick and dirty check
        return r

    def sse(self, parameters=None, tmin=None, tmax=None, noise=True):
        res = self.residuals(parameters, tmin=tmin, tmax=tmax, noise=noise)
        return sum(res ** 2)

    def initialize(self, initial=True, noise=True):
        if not initial:
            optimal = self.parameters.optimal
        self.nparam = sum(ts.nparam for ts in self.tseriesdict.values())
        if self.noisemodel is not None:
            self.nparam += self.noisemodel.nparam
        self.parameters = pd.DataFrame(columns=['initial', 'pmin', 'pmax',
                                                'vary', 'optimal', 'name'])
        for ts in self.tseriesdict.values():
            self.parameters = self.parameters.append(ts.parameters)
        if self.noisemodel and noise:
            self.parameters = self.parameters.append(self.noisemodel.parameters)
        if not initial:
            self.parameters.initial = optimal

    def solve(self, tmin=None, tmax=None, solver=LmfitSolve, report=True,
              noise=True, initial=True, solve=True):
        """
        Methods to solve the time series model.

        Parameters
        ----------
        tmin: Optional[str]
            String with a start date for the simulation period (E.g. '1980')
        tmax: Optional[str]
            String with an end date for the simulation period (E.g. '2010')
        solver: Optional[solver class]
            Class used to solve the model. Default is lmfit (LmfitSolve)
        report: Boolean
            Print a report to the screen after optimization finished.
        noise: Boolean
            Use the nose model (True) or not (False).
        initialize: Boolean
            Reset initial parameteres.

        """
        if noise and (self.noisemodel is None):
            print 'Warning, solution with noise model while noise model is not ' \
                  'defined. No noise model is used'

        # Check series with tmin, tmax
        self.set_tmin_tmax(tmin, tmax)

        # Initialize parameters
        self.initialize(initial=initial, noise=noise)

        # Solve model
        fit = solver(self, tmin=self.tmin, tmax=self.tmax, noise=noise)

        self.parameters.optimal = fit.optimal_params
        self.report = fit.report
        if report: print self.report

        # self.stats = Statistics(self)

    def set_tmin_tmax(self, tmin=None, tmax=None):
        """
        Function to check if the dependent and independent time series match.

        - tmin and tmax are in oseries.index for optimization.
        - at least one stress is available for simulation between tmin and tmax.
        -

        Parameters
        ----------
        tmin
        tmax

        Returns
        -------

        """

        # Store tmax and tmin. If none is provided, use oseries to set them.
        if tmin is None:
            tmin = self.oseries.index.min()
        else:
            tmin = pd.tslib.Timestamp(tmin)
            assert (tmin >= self.oseries.index[0]) and (tmin <= self.oseries.index[
                -1]), 'Error: Specified tmin is outside of the oseries'
        if tmax is None:
            tmax = self.oseries.index.max()
        else:
            tmax = pd.tslib.Timestamp(tmax)
            assert (tmax >= self.oseries.index[0]) and (tmax <= self.oseries.index[
                -1]), 'Error: Specified tmin is outside of the oseries'
        assert tmax > tmin, 'Error: Specified tmax not larger than specified tmin'
        assert len(self.oseries[
                   tmin: tmax]) > 0, 'Error: no observations between tmin and tmax'

        self.tmin = tmin
        self.tmax = tmax

        # TODO
        # Check if at least one stress overlaps with the oseries data

    def get_response(self, name):
        try:
            p = self.parameters.loc[self.parameters.name == name, 'optimal'].values
            return self.tseriesdict[name].simulate(p)
        except KeyError:
            print "Name not in tseriesdict, available names are: %s" \
                  % self.tseriesdict.keys()

    def get_response_function(self, name):
        try:
            p = self.parameters.loc[self.parameters.name == name, 'optimal'].values
            return self.tseriesdict[name].rfunc.block(p)
        except KeyError:
            print "Name not in tseriesdict, available names are: %s" \
                  % self.tseriesdict.keys()

    def get_stress(self, name):
        try:
            p = self.parameters.loc[self.parameters.name == name, 'optimal'].values
            return self.tseriesdict[name].get_stress(p)
        except KeyError:
            print "Name not in tseriesdict, available names are: %s" \
                  % self.tseriesdict.keys()

    def plot(self, tmin=None, tmax=None, oseries=True, simulate=True):
        """

        Parameters
        ----------
        oseries: Boolean
            True to plot the observed time series.

        Returns
        -------
        Plot of the simulated and optionally the observed time series

        """
        plt.figure()
        if simulate:
            h = self.simulate(tmin=tmin, tmax=tmax)
            h.plot()
        if oseries:
            self.oseries.plot(linestyle='', marker='.', color='k', markersize=3)
        plt.show()

    def plot_results(self, tmin=None, tmax=None, savefig=False):
        """

        Parameters
        ----------
        tmin/tmax: str
            start and end time for plotting
        savefig: Optional[Boolean]
            True to save the figure, False is default. Figure is saved in the
            current working directory when running your python scripts.

        Returns
        -------

        """
        plt.figure('Model Results', facecolor='white')
        gs = plt.GridSpec(3, 4, wspace=0.2)

        # Plot the Groundwater levels
        h = self.simulate(tmin=tmin, tmax=tmax)
        ax1 = plt.subplot(gs[:2, :-1])
        h.plot(label='modeled head')
        self.oseries.plot(linestyle='', marker='.', color='k', markersize=3,
                          label='observed head')
        # ax1.xaxis.set_visible(False)
        plt.legend(loc=(0, 1), ncol=3, frameon=False, handlelength=3)
        plt.ylabel('Head [m]')

        # Plot the residuals and innovations
        residuals = self.residuals(tmin=tmin, tmax=tmax)
        ax2 = plt.subplot(gs[2, :-1])  # , sharex=ax1)
        residuals.plot(color='k', label='residuals')
        # Ruben Calje commented next three lines on 31-10-2016:
        #if self.noisemodel is not None:
            #innovations = self.noisemodel.simulate(residuals, self.odelt)
            #innovations.plot(label='innovations')
        plt.legend(loc=(0, 1), ncol=3, frameon=False, handlelength=3)
        plt.ylabel('Error [m]')
        plt.xlabel('Time [Years]')

        # Plot the Impulse Response Function
        ax3 = plt.subplot(gs[0, -1])
        n = 0
        for ts in self.tseriesdict.values():
            p = self.parameters[n:n + ts.nparam]
            n += ts.nparam
            if "rfunc" in dir(ts):
                plt.plot(ts.rfunc.block(p.optimal))
        ax3.set_xticks(ax3.get_xticks()[::2])
        ax3.set_yticks(ax3.get_yticks()[::2])
        plt.title('Block Response')

        # Plot the Model Parameters (Experimental)
        ax4 = plt.subplot(gs[1:2, -1])
        ax4.xaxis.set_visible(False)
        ax4.yaxis.set_visible(False)
        text = np.vstack((self.parameters.keys(), [round(float(i), 4) for i in
                                                  self.parameters.optimal.values])).T
        colLabels = ("Parameter", "Value")
        ytable = ax4.table(cellText=text, colLabels=colLabels, loc='center')
        ytable.scale(1, 1.1)

        # Table of the numerical diagnostic statistics.
        ax5 = plt.subplot(gs[2, -1])
        ax5.xaxis.set_visible(False)
        ax5.yaxis.set_visible(False)
        # Ruben Calje commented next two lines on 31-10-2016:
        #plt.text(0.05, 0.8, 'AIC: %.2f' % self.fit.aic)
        #plt.text(0.05, 0.6, 'BIC: %.2f' % self.fit.bic)
        plt.show()
        if savefig:
            plt.savefig('.eps' % (self.name), bbox_inches='tight')

    def plot_decomposition(self,tmin=None, tmax=None, freq='D'):
        # Default option when not tmin and tmax is provided
        if tmin is None:
            tmin = self.tmin
        if tmax is None:
            tmax = self.tmax
        assert (tmin is not None) and (
            tmax is not None), 'model needs to be solved first'

        tindex = pd.date_range(tmin, tmax, freq=freq)

        ts = self.tseriesdict
        n = len(ts)
        f, axarr = plt.subplots(1 + n, sharex=True)

        # plot combination in top-graph
        axarr[0].set_title('Observations and simulation')

        h = self.simulate(tmin=tmin, tmax=tmax)
        h.plot(ax=axarr[0], label='simulation')
        self.oseries.plot(linestyle='', marker='.', color='k', markersize=3, ax=axarr[0], label='observations')
        handles, labels = axarr[0].get_legend_handles_labels()
        leg=axarr[0].legend(handles, labels, loc=2)
        leg.get_frame().set_alpha(0.5)

        parameters = self.parameters.optimal.values
        istart = 0  # Track parameters index to pass to ts object
        iax = 1
        for ts in self.tseriesdict.values():
            h = ts.simulate(parameters[istart: istart + ts.nparam], tindex)
            axarr[iax].set_title(ts.name)
            if isinstance(ts, Constant):
                xlim = axarr[iax].get_xlim()
                axarr[iax].plot(xlim,[h,h])
            else:
                h.plot(ax=axarr[iax])
            istart += ts.nparam
            iax += 1
        # show the figure
        plt.show()
<commit_msg>added space in model.py<commit_after>from collections import OrderedDict

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from checks import check_oseries
from solver import LmfitSolve
from tseries import Constant

class Model:
    def __init__(self, oseries, xy=(0, 0), metadata=None, freq=None,
                 fillnan='drop'):
        """
        Initiates a time series model.

        Parameters
        ----------
        oseries: pd.Series
            pandas Series object containing the dependent time series. The
            observation can be non-equidistant.
        xy: Optional[tuple]
            XY location of the oseries in lat-lon format.
        metadata: Optional[dict]
            Dictionary containing metadata of the model.
        freq: Optional[str]
            String containing the desired frequency. By default freq=None and the
            observations are used as they are. The required string format is found
            at http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset
            -aliases
        fillnan: Optional[str or float]
            Methods or float number to fill nan-values. Default values is
            'drop'. Currently supported options are: 'interpolate', float,
            'mean' and, 'drop'. Interpolation is performed with a standard linear
            interpolation.

        """
        self.oseries = check_oseries(oseries, freq, fillnan)
        self.xy = xy
        self.metadata = metadata
        self.odelt = self.oseries.index.to_series().diff() / np.timedelta64(1, 'D')
        # delt converted to days
        self.tseriesdict = OrderedDict()
        self.noisemodel = None
        self.noiseparameters = None
        self.tmin = None
        self.tmax = None

    def add_tseries(self, tseries):
        """
        adds a time series model component to the Model.

        """
        self.tseriesdict[tseries.name] = tseries

    def add_noisemodel(self, noisemodel):
        """
        Adds a noise model to the time series Model.

        """
        self.noisemodel = noisemodel

    def simulate(self, parameters=None, tmin=None, tmax=None, freq='D'):
        """

        Parameters
        ----------
        t: Optional[pd.series.index]
            Time indices to use for the simulation of the time series model.
        p: Optional[array]
            Array of the parameters used in the time series model.
        noise:

        Returns
        -------
        Pandas Series object containing the simulated time series.

        """

        # Default option when not tmin and tmax is provided
        if tmin is None:
            tmin = self.tmin
        if tmax is None:
            tmax = self.tmax
        assert (tmin is not None) and (
            tmax is not None), 'model needs to be solved first'

        tindex = pd.date_range(tmin, tmax, freq=freq)

        if parameters is None:
            parameters = self.parameters.optimal.values
        h = pd.Series(data=0, index=tindex)
        istart = 0  # Track parameters index to pass to ts object
        for ts in self.tseriesdict.values():
            h += ts.simulate(parameters[istart: istart + ts.nparam], tindex)
            istart += ts.nparam
        return h

    def residuals(self, parameters=None, tmin=None, tmax=None, noise=True):
        """
        Method to calculate the residuals.

        """
        if tmin is None:
            tmin = self.oseries.index.min()
        if tmax is None:
            tmax = self.oseries.index.max()
        tindex = self.oseries[tmin: tmax].index  # times used for calibration

        if parameters is None:
            parameters = self.parameters.optimal.values

        # h_observed - h_simulated
        r = self.oseries[tindex] - self.simulate(parameters, tmin, tmax)[tindex]
        # print 'step1:', sum(r**2)
        if noise and (self.noisemodel is not None):
            r = self.noisemodel.simulate(r, self.odelt[tindex],
                                         parameters[-self.noisemodel.nparam:],
                                         tindex)
        # print 'step2:', sum(r**2)
        if np.isnan(sum(r ** 2)):
            print 'nan problem in residuals'  # quick and dirty check
        return r

    def sse(self, parameters=None, tmin=None, tmax=None, noise=True):
        res = self.residuals(parameters, tmin=tmin, tmax=tmax, noise=noise)
        return sum(res ** 2)

    def initialize(self, initial=True, noise=True):
        if not initial:
            optimal = self.parameters.optimal
        self.nparam = sum(ts.nparam for ts in self.tseriesdict.values())
        if self.noisemodel is not None:
            self.nparam += self.noisemodel.nparam
        self.parameters = pd.DataFrame(columns=['initial', 'pmin', 'pmax',
                                                'vary', 'optimal', 'name'])
        for ts in self.tseriesdict.values():
            self.parameters = self.parameters.append(ts.parameters)
        if self.noisemodel and noise:
            self.parameters = self.parameters.append(self.noisemodel.parameters)
        if not initial:
            self.parameters.initial = optimal

    def solve(self, tmin=None, tmax=None, solver=LmfitSolve, report=True,
              noise=True, initial=True, solve=True):
        """
        Methods to solve the time series model.

        Parameters
        ----------
        tmin: Optional[str]
            String with a start date for the simulation period (E.g. '1980')
        tmax: Optional[str]
            String with an end date for the simulation period (E.g. '2010')
        solver: Optional[solver class]
            Class used to solve the model. Default is lmfit (LmfitSolve)
        report: Boolean
            Print a report to the screen after optimization finished.
        noise: Boolean
            Use the nose model (True) or not (False).
        initialize: Boolean
            Reset initial parameteres.

        """
        if noise and (self.noisemodel is None):
            print 'Warning, solution with noise model while noise model is not ' \
                  'defined. No noise model is used'

        # Check series with tmin, tmax
        self.set_tmin_tmax(tmin, tmax)

        # Initialize parameters
        self.initialize(initial=initial, noise=noise)

        # Solve model
        fit = solver(self, tmin=self.tmin, tmax=self.tmax, noise=noise)

        self.parameters.optimal = fit.optimal_params
        self.report = fit.report
        if report: print self.report

        # self.stats = Statistics(self)

    def set_tmin_tmax(self, tmin=None, tmax=None):
        """
        Function to check if the dependent and independent time series match.

        - tmin and tmax are in oseries.index for optimization.
        - at least one stress is available for simulation between tmin and tmax.
        -

        Parameters
        ----------
        tmin
        tmax

        Returns
        -------

        """

        # Store tmax and tmin. If none is provided, use oseries to set them.
        if tmin is None:
            tmin = self.oseries.index.min()
        else:
            tmin = pd.tslib.Timestamp(tmin)
            assert (tmin >= self.oseries.index[0]) and (tmin <= self.oseries.index[
                -1]), 'Error: Specified tmin is outside of the oseries'
        if tmax is None:
            tmax = self.oseries.index.max()
        else:
            tmax = pd.tslib.Timestamp(tmax)
            assert (tmax >= self.oseries.index[0]) and (tmax <= self.oseries.index[
                -1]), 'Error: Specified tmin is outside of the oseries'
        assert tmax > tmin, 'Error: Specified tmax not larger than specified tmin'
        assert len(self.oseries[
                   tmin: tmax]) > 0, 'Error: no observations between tmin and tmax'

        self.tmin = tmin
        self.tmax = tmax

        # TODO
        # Check if at least one stress overlaps with the oseries data

    def get_response(self, name):
        try:
            p = self.parameters.loc[self.parameters.name == name, 'optimal'].values
            return self.tseriesdict[name].simulate(p)
        except KeyError:
            print "Name not in tseriesdict, available names are: %s" \
                  % self.tseriesdict.keys()

    def get_response_function(self, name):
        try:
            p = self.parameters.loc[self.parameters.name == name, 'optimal'].values
            return self.tseriesdict[name].rfunc.block(p)
        except KeyError:
            print "Name not in tseriesdict, available names are: %s" \
                  % self.tseriesdict.keys()

    def get_stress(self, name):
        try:
            p = self.parameters.loc[self.parameters.name == name, 'optimal'].values
            return self.tseriesdict[name].get_stress(p)
        except KeyError:
            print "Name not in tseriesdict, available names are: %s" \
                  % self.tseriesdict.keys()

    def plot(self, tmin=None, tmax=None, oseries=True, simulate=True):
        """

        Parameters
        ----------
        oseries: Boolean
            True to plot the observed time series.

        Returns
        -------
        Plot of the simulated and optionally the observed time series

        """
        plt.figure()
        if simulate:
            h = self.simulate(tmin=tmin, tmax=tmax)
            h.plot()
        if oseries:
            self.oseries.plot(linestyle='', marker='.', color='k', markersize=3)
        plt.show()

    def plot_results(self, tmin=None, tmax=None, savefig=False):
        """

        Parameters
        ----------
        tmin/tmax: str
            start and end time for plotting
        savefig: Optional[Boolean]
            True to save the figure, False is default. Figure is saved in the
            current working directory when running your python scripts.

        Returns
        -------

        """
        plt.figure('Model Results', facecolor='white')
        gs = plt.GridSpec(3, 4, wspace=0.2)

        # Plot the Groundwater levels
        h = self.simulate(tmin=tmin, tmax=tmax)
        ax1 = plt.subplot(gs[:2, :-1])
        h.plot(label='modeled head')
        self.oseries.plot(linestyle='', marker='.', color='k', markersize=3,
                          label='observed head')
        # ax1.xaxis.set_visible(False)
        plt.legend(loc=(0, 1), ncol=3, frameon=False, handlelength=3)
        plt.ylabel('Head [m]')

        # Plot the residuals and innovations
        residuals = self.residuals(tmin=tmin, tmax=tmax)
        ax2 = plt.subplot(gs[2, :-1])  # , sharex=ax1)
        residuals.plot(color='k', label='residuals')
        # Ruben Calje commented next three lines on 31-10-2016:
        #if self.noisemodel is not None:
            #innovations = self.noisemodel.simulate(residuals, self.odelt)
            #innovations.plot(label='innovations')
        plt.legend(loc=(0, 1), ncol=3, frameon=False, handlelength=3)
        plt.ylabel('Error [m]')
        plt.xlabel('Time [Years]')

        # Plot the Impulse Response Function
        ax3 = plt.subplot(gs[0, -1])
        n = 0
        for ts in self.tseriesdict.values():
            p = self.parameters[n:n + ts.nparam]
            n += ts.nparam
            if "rfunc" in dir(ts):
                plt.plot(ts.rfunc.block(p.optimal))
        ax3.set_xticks(ax3.get_xticks()[::2])
        ax3.set_yticks(ax3.get_yticks()[::2])
        plt.title('Block Response')

        # Plot the Model Parameters (Experimental)
        ax4 = plt.subplot(gs[1:2, -1])
        ax4.xaxis.set_visible(False)
        ax4.yaxis.set_visible(False)
        text = np.vstack((self.parameters.keys(), [round(float(i), 4) for i in
                                                  self.parameters.optimal.values])).T
        colLabels = ("Parameter", "Value")
        ytable = ax4.table(cellText=text, colLabels=colLabels, loc='center')
        ytable.scale(1, 1.1)

        # Table of the numerical diagnostic statistics.
        ax5 = plt.subplot(gs[2, -1])
        ax5.xaxis.set_visible(False)
        ax5.yaxis.set_visible(False)
        # Ruben Calje commented next two lines on 31-10-2016:
        #plt.text(0.05, 0.8, 'AIC: %.2f' % self.fit.aic)
        #plt.text(0.05, 0.6, 'BIC: %.2f' % self.fit.bic)
        plt.show()
        if savefig:
            plt.savefig('.eps' % (self.name), bbox_inches='tight')

    def plot_decomposition(self, tmin=None, tmax=None, freq='D'):
        # Default option when not tmin and tmax is provided
        if tmin is None:
            tmin = self.tmin
        if tmax is None:
            tmax = self.tmax
        assert (tmin is not None) and (
            tmax is not None), 'model needs to be solved first'

        tindex = pd.date_range(tmin, tmax, freq=freq)

        ts = self.tseriesdict
        n = len(ts)
        f, axarr = plt.subplots(1 + n, sharex=True)

        # plot combination in top-graph
        axarr[0].set_title('Observations and simulation')

        h = self.simulate(tmin=tmin, tmax=tmax)
        h.plot(ax=axarr[0], label='simulation')
        self.oseries.plot(linestyle='', marker='.', color='k', markersize=3, ax=axarr[0], label='observations')
        handles, labels = axarr[0].get_legend_handles_labels()
        leg=axarr[0].legend(handles, labels, loc=2)
        leg.get_frame().set_alpha(0.5)

        parameters = self.parameters.optimal.values
        istart = 0  # Track parameters index to pass to ts object
        iax = 1
        for ts in self.tseriesdict.values():
            h = ts.simulate(parameters[istart: istart + ts.nparam], tindex)
            axarr[iax].set_title(ts.name)
            if isinstance(ts, Constant):
                xlim = axarr[iax].get_xlim()
                axarr[iax].plot(xlim,[h,h])
            else:
                h.plot(ax=axarr[iax])
            istart += ts.nparam
            iax += 1
        # show the figure
        plt.show()
<|endoftext|>


<commit_before>""" Generic monkey patching functions for doing it (mostly) safely """

import logging
import inspect
from types import MethodType, ModuleType, FunctionType
from importlib import import_module
from importlib.util import find_spec
from contextlib import suppress

__all__ = ['patchy', 'super_patchy']

logger = logging.getLogger(__name__)

def patchy(target, source=None):
    """ If source is not supplied, auto updates cannot be applied """
    if isinstance(target, str):
        target = resolve(target)
    if isinstance(source, str):
        source = resolve(source)
    if isinstance(target, ModuleType):
        return PatchModule(target, source)
    elif isinstance(target, type) and source:
        return PatchClass(target, source)

def super_patchy(*args, do_call=True, **kwargs):
    """ super() for patches!
        When called from within a patched in function will return or call the
        function that it replaced, preserving self/cls arguments
    """
    caller_frame = inspect.currentframe().f_back
    caller = inspect.getargvalues(caller_frame)

    old_func = patchy_records[caller_frame.f_code]

    if caller.args[0] in ['self', 'cls']:
        # If caller has the appearance of being bound (to instance or class)
        old_func = MethodType(old_func, caller.locals[caller.args[0]])
    if do_call:
        return old_func(*args, **kwargs)
    return old_func

def resolve(name, package=None):
    """ Turn a dotted name into a module or class reference """

    if isinstance(package, str):
        package = import_module(package)
    package_name = package.__name__ if package else None

    with suppress(AttributeError, ImportError):
        return import_module('{r}{n}'.format(r='.' if package else '', n=name), package_name)

    with suppress(ImportError):
        if '.' in name:
            mod, name = name.rsplit('.', maxsplit=1)
            package = import_module('{r}{n}'.format(r='.' if package else '', n=mod), package_name)
        elif not isinstance(package, ModuleType):
            package = import_module(package)

        with suppress(AttributeError):
            return getattr(package, name)

    raise ImportError('{name} is not a valid class or module name{within}'.format(
        name=name,
        within=', within {}'.format(package.__name__) if package else ''))

# inspect.getmembers includes values in mro
# obj.__dict__ includes hidden attributes
# obj.__dict__ returns wrapped objects
# Use obj.__dict__ with getattr() to avoid mro and wrappings
def get_attrs(obj, types, exclude_hidden=True):
    """ Get the locally declared attributes of an object filtered by type """
    attrs = ((k, getattr(obj, k)) for k in obj.__dict__ if not exclude_hidden or not k.startswith('_'))
    return [k for k, v in attrs if isinstance(v, types)]

class PatchyRecords(dict):
    def __getitem__(self, key):
        with suppress(AttributeError):
            key = key.__code__
        with suppress(KeyError):
            return super().__getitem__(id(key))
        raise RuntimeError('Patched func cannot find its predecessor')

    def __setitem__(self, key, value):
        # Strip inbult decorators
        if isinstance(value, (classmethod, staticmethod)):
            value = value.__func__
        with suppress(AttributeError):
            key = key.__code__
        return super().__setitem__(id(key), value)

    def __delitem__(self, key):
        with suppress(AttributeError):
            key = key.__code__
        return super().__delitem__(id(key))

    def __contains__(self, key):
        with suppress(AttributeError):
            key = key.__code__
        return super().__contains__(id(key))

patchy_records = PatchyRecords()

class PatchBase:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    def auto(self, source=None, *, merge=True, types=object):
        """ Apply all attributes of specified types from source to target.
            Defaults to merging collections.
        """
        attrs = get_attrs(source or self.source, types)
        self.apply(attrs, merge=merge)

    def add(self, *attrs, **kattrs):
        self.apply(attrs, kattrs)

    def merge(self, *attrs, **kattrs):
        self.apply(attrs, kattrs, merge=True)

    def apply(self, attrs=None, kattrs=None, merge=False):
        """ Apply new attributes or classes to the target """
        for attr in attrs:
            kattrs = kattrs or {}
            # Treat objects as assigned to their name
            if hasattr(attr, "__name__"):
                kattrs[attr.__name__] = attr
            else:
                kattrs[attr] = getattr(self.source, attr)
        for attr, value in kattrs.items():
            old_value = inspect.getattr_static(self.target, attr, None)
            # If callable, preserve old func
            if callable(value) and callable(old_value):
                # Prevent duplicate patching
                if value in patchy_records:
                    return
                patchy_records[value] = old_value
            # Merge collections instead of replacing
            if merge:
                if isinstance(value, dict) and isinstance(old_value, dict):
                    getattr(self.target, attr).update(value)
                elif isinstance(value, list) and isinstance(old_value, list):
                    getattr(self.target, attr).extend(value)

            # Apply patched value
            setattr(self.target, attr, value)

class PatchModule(PatchBase):
    def __init__(self, target, source=None, module_sep='_'):
        self.target = target
        self.source = source
        self.module_sep = module_sep

    def cls(self, target, source=None):
        if isinstance(target, str):
            target = resolve(target, package=self.target)
        if self.source and source is None:
            with suppress(ImportError):
                source_str = '{mod}.{cls}'.format(
                    mod=target.__module__.replace('.', self.module_sep),
                    cls=target.__name__)
                source = resolve(source_str, package=self.source)
            if not source:
                with suppress(AttributeError):
                    source = getattr(self.source, target.__name__)
        elif isinstance(source, str):
            source = resolve(source, package=self.source)
        if isinstance(target, type):
            return PatchClass(target, source)
        raise TypeError('Must be a valid class or class name')

    def mod(self, target, source=None):
        if isinstance(target, str):
            target = resolve(target, package=self.target.__name__)
        if isinstance(source, str):
            source = resolve(source, package=self.source.__name__)

        elif source is None:
            #parent = import_module('..', package=self.source.__name__)
            # Deal with nested modules in a pack
            # Test for corresponding module relative to current source
            source_name = target.__name__.replace('.', self.module_sep)
            with suppress(ImportError):
                source = resolve(source_name, package=self.source.__name__)

        if isinstance(target, ModuleType):
            if source:
                logger.info('Patching {} using {}'.format(target.__name__, source.__name__))
            return PatchModule(target, source, self.module_sep)

class PatchClass(PatchBase):
    def __init__(self, target, source):
        self.target = target
        self.source = source

    def mod(self):
        return self.target.__module__

    # Replacing
    def add_desc(self, *attrs, **kattrs):
        for attr, value in kattrs.items():
            setattr(self.target, attr, value)
        for attr in attrs:
            # Treat objects as assigned to their name
            if hasattr(attr, "__name__"):
                (attr, value) = (attr.__name__, attr)
            else:
                value = self.source.__dict__[attr]
            old_val = self.target.__dict__.get(attr, None)
            setattr(self.target, attr, value)
<commit_msg>Merging with abcs, and class merging<commit_after>""" Generic monkey patching functions for doing it (mostly) safely """

import logging
import inspect
from types import MethodType, ModuleType, FunctionType
from collections import abc
from importlib import import_module
from importlib.util import find_spec
from contextlib import suppress

__all__ = ['patchy', 'super_patchy']

logger = logging.getLogger(__name__)

def patchy(target, source=None):
    """ If source is not supplied, auto updates cannot be applied """
    if isinstance(target, str):
        target = resolve(target)
    if isinstance(source, str):
        source = resolve(source)
    if isinstance(target, ModuleType):
        return PatchModule(target, source)
    elif isinstance(target, type) and source:
        return PatchClass(target, source)

def super_patchy(*args, do_call=True, **kwargs):
    """ super() for patches!
        When called from within a patched in function will return or call the
        function that it replaced, preserving self/cls arguments
    """
    caller_frame = inspect.currentframe().f_back
    caller = inspect.getargvalues(caller_frame)

    old_func = patchy_records[caller_frame.f_code]

    if caller.args[0] in ['self', 'cls']:
        # If caller has the appearance of being bound (to instance or class)
        old_func = MethodType(old_func, caller.locals[caller.args[0]])
    if do_call:
        return old_func(*args, **kwargs)
    return old_func

def resolve(name, package=None):
    """ Turn a dotted name into a module or class reference """

    if isinstance(package, str):
        package = import_module(package)
    package_name = package.__name__ if package else None

    with suppress(AttributeError, ImportError):
        return import_module('{r}{n}'.format(r='.' if package else '', n=name), package_name)

    with suppress(ImportError):
        if '.' in name:
            mod, name = name.rsplit('.', maxsplit=1)
            package = import_module('{r}{n}'.format(r='.' if package else '', n=mod), package_name)
        elif not isinstance(package, ModuleType):
            package = import_module(package)

        with suppress(AttributeError):
            return getattr(package, name)

    raise ImportError('{name} is not a valid class or module name{within}'.format(
        name=name,
        within=', within {}'.format(package.__name__) if package else ''))

# inspect.getmembers includes values in mro
# obj.__dict__ includes hidden attributes
# obj.__dict__ returns wrapped objects
# Use obj.__dict__ with getattr() to avoid mro and wrappings
def get_attrs(obj, types, exclude_hidden=True):
    """ Get the locally declared attributes of an object filtered by type """
    attrs = ((k, getattr(obj, k)) for k in obj.__dict__ if not exclude_hidden or not k.startswith('_'))
    return [k for k, v in attrs if isinstance(v, types)]

class PatchyRecords(dict):
    def __getitem__(self, key):
        with suppress(AttributeError):
            key = key.__code__
        with suppress(KeyError):
            return super().__getitem__(id(key))
        raise RuntimeError('Patched func cannot find its predecessor')

    def __setitem__(self, key, value):
        # Strip inbult decorators
        if isinstance(value, (classmethod, staticmethod)):
            value = value.__func__
        with suppress(AttributeError):
            key = key.__code__
        return super().__setitem__(id(key), value)

    def __delitem__(self, key):
        with suppress(AttributeError):
            key = key.__code__
        return super().__delitem__(id(key))

    def __contains__(self, key):
        with suppress(AttributeError):
            key = key.__code__
        return super().__contains__(id(key))

patchy_records = PatchyRecords()

class PatchBase:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    def auto(self, source=None, *, merge=True, types=object):
        """ Apply all attributes of specified types from source to target.
            Defaults to merging collections.
        """
        attrs = get_attrs(source or self.source, types)
        self.apply(attrs, merge=merge)

    def add(self, *attrs, **kattrs):
        self.apply(attrs, kattrs)

    def merge(self, *attrs, **kattrs):
        self.apply(attrs, kattrs, merge=True)

    def apply(self, attrs=None, kattrs=None, merge=False):
        """ Apply new attributes or classes to the target """
        for attr in attrs:
            kattrs = kattrs or {}
            # Treat objects as assigned to their name
            if hasattr(attr, "__name__"):
                kattrs[attr.__name__] = attr
            else:
                kattrs[attr] = getattr(self.source, attr)
        for attr, value in kattrs.items():
            old_value = inspect.getattr_static(self.target, attr, None)
            # If callable, preserve old func
            if callable(value) and callable(old_value):
                # Prevent duplicate patching
                if value in patchy_records:
                    return
                patchy_records[value] = old_value

            # Merge collections and classes instead of replacing
            if merge:
                if isinstance(old_value, abc.Container):
                    if isinstance(value, abc.Mapping) and isinstance(old_value, abc.MutableMapping):
                        old_value.update(value)
                        logger.info('Merging mapping {mod}.{attr}'.format(mod=self.target.__name__, attr=attr))
                    elif isinstance(value, abc.Sequence) and isinstance(old_value, abc.MutableSequence):
                        old_value.extend(value)
                        logger.info('Merging sequence {mod}.{attr}'.format(mod=self.target.__name__, attr=attr))
                    elif isinstance(value, abc.Set) and isinstance(old_value, abc.MutableSet):
                        old_value.update(value)
                        logger.info('Merging set {mod}.{attr}'.format(mod=self.target.__name__, attr=attr))
                    else:
                        setattr(self.target, attr, value)
                        logger.info("Couldn't merge collection {target}.{attr}, replaced instead".format(
                            target=self.target.__name__,
                            attr=attr))
                    continue
                elif isinstance(old_value, type):
                    logger.info('Merging class for {target}.{attr}'.format(
                        target=self.target.__name__, attr=attr))
                    self.cls(old_value, value).auto()
                    continue
            logger.info('Setting value {target}.{attr}'.format(target=self.target.__name__, attr=attr))
            # Apply patched value
            setattr(self.target, attr, value)

class PatchModule(PatchBase):
    def __init__(self, target, source=None, module_sep='_'):
        self.target = target
        self.source = source
        self.module_sep = module_sep

    def cls(self, target, source=None):
        if isinstance(target, str):
            target = resolve(target, package=self.target)
        if self.source and source is None:
            with suppress(ImportError):
                source_str = '{mod}.{cls}'.format(
                    mod=target.__module__.replace('.', self.module_sep),
                    cls=target.__name__)
                source = resolve(source_str, package=self.source)
            if not source:
                with suppress(AttributeError):
                    source = getattr(self.source, target.__name__)
        elif isinstance(source, str):
            source = resolve(source, package=self.source)
        if isinstance(target, type):
            return PatchClass(target, source)
        raise TypeError('Must be a valid class or class name')

    def mod(self, target, source=None):
        if isinstance(target, str):
            target = resolve(target, package=self.target.__name__)
        if isinstance(source, str):
            source = resolve(source, package=self.source.__name__)

        elif source is None:
            #parent = import_module('..', package=self.source.__name__)
            # Deal with nested modules in a pack
            # Test for corresponding module relative to current source
            source_name = target.__name__.replace('.', self.module_sep)
            with suppress(ImportError):
                source = resolve(source_name, package=self.source.__name__)

        if isinstance(target, ModuleType):
            if source:
                logger.info('Patching {} using {}'.format(target.__name__, source.__name__))
            return PatchModule(target, source, self.module_sep)

class PatchClass(PatchBase):
    def __init__(self, target, source):
        self.target = target
        self.source = source

    def mod(self):
        return self.target.__module__

    # Replacing
    def add_desc(self, *attrs, **kattrs):
        for attr, value in kattrs.items():
            setattr(self.target, attr, value)
        for attr in attrs:
            # Treat objects as assigned to their name
            if hasattr(attr, "__name__"):
                (attr, value) = (attr.__name__, attr)
            else:
                value = self.source.__dict__[attr]
            old_val = self.target.__dict__.get(attr, None)
            setattr(self.target, attr, value)
<|endoftext|>