view compat.py @ 76:0e5ae28e0b2b

Points are now weighted on a test context basis In particular, this has allowed for simple extensions to the format of testconf to award points to whole test groups without at the same time compromising the future ability of giving partial score for correct but slow solutions. Specifically, the groupweight configuration variable has been added and normally has the format {groupindex: points} where groupindex is the group's index in the tests configuration variable. The backwards incompatible change is that test contexts are no longer guaranteed to learn the score awarded or the maximum possible score for every test case and may instead be notified about them in batches. In other news, the pointmap and groupweight configuration variables can (now) be given as sequences in addition to mappings. (Technically, the distinction currently made is dict versus everything else.) Items of a sequence pointmap/groupweight correspond directly to the test cases/ groups defined in the tests configuration variable; in particular, when groups are used, tests=[1],[2,3];pointmap={1:1,2:2,3:3} can now be written as pointmap=tests=[1],[2,3]. Missing items are handled in the same way in which they are handled when the variable is a mapping. Note that the items of groupweight correspond to whole test groups rather than individual test cases. In other news again, the wording of problem total lines has been changed from '<unweighted> points; weighted score: <weighted>' to '<weighted> points (<unweighted> before weighting)', and group total lines now properly report fractional numbers of points (this is a bug fix).
author Oleg Oshmyan <chortos@inbox.lv>
date Sat, 08 Jan 2011 16:03:35 +0200
parents b9d5857f7b9a
children d46bd7ee3e69
line wrap: on
line source

#! /usr/bin/env python
# Copyright (c) 2010 Chortos-2 <chortos@inbox.lv>

# A compatibility layer for Python 2.5+. This is what lets test.py
# run on all versions of Python starting with 2.5, including Python 3.

# A few notes regarding some compatibility-driven peculiarities
# in the use of the language that can be seen in all modules:
#
# * Except statements never specify target; instead, when needed,
#   the exception is taken from sys.exc_info(). Blame the incompatible
#   syntaxes of the except clause in Python 2.5 and Python 3 and the lack
#   of preprocessor macros in Python of any version ;P.
#
# * Keyword-only parameters are never used, even for parameters
#   that should never be given in as arguments. The reason is
#   the laziness of some Python developers who have failed to finish
#   implementing them in Python 2 even though they had several years
#   of time and multiple version releases to sneak them in.
#
# * Abstract classes are only implemented for Python 2.6 and 2.7.
#   ABC's require the abc module and the specification of metaclasses,
#   but in Python 2.5, the abc module does not exist, while in Python 3,
#   metaclasses are specified using a syntax totally incompatible
#   with Python 2 and not usable conditionally via exec() and such
#   because it is a detail of the syntax of the class statement itself.

# Some code was adapted from Python 2.7.1 and its documentation.
# This code is clearly marked as such in preceding comments and is
# covered by copyright as follows:
#
# Copyright (c) 2001-2010 Python Software Foundation; all rights reserved.
#
# The code is used according to the PSF License Agreement
# for Python 2.7.1, whose full text is available from your local
# installation of Python (enter 'license()' in the interactive
# interpreter) or from the Web at the following URL:
#
# http://docs.python.org/2.7.1/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python

try:
	import builtins
except ImportError:
	import __builtin__ as builtins

pseudobuiltins = ('say', 'basestring', 'range', 'map', 'zip', 'filter', 'next',
                  'items', 'keys', 'values', 'zip_longest', 'callable')
__all__ = pseudobuiltins + ('ABCMeta', 'abstractmethod', 'CompatBuiltins')

try:
	# Python 3
	exec('say = print')
except SyntaxError:
	try:
		# Python 2.6/2.7
		# An alternative is exec('from __future__ import print_function; say = print');
		# if problems arise with the current line, one should try replacing it
		# with this one with the future import before abandoning the idea altogether
		say = getattr(builtins, 'print')
	except Exception:
		# Python 2.5
		import sys
		# This should fully emulate the print function of Python 2.6 in Python 2.3+
		# The error messages are taken from Python 2.6
		# The name bindings at the bottom of this file are in effect
		def saytypeerror(value, name):
			return TypeError(' '.join((name, 'must be None, str or unicode, not', type(value).__name__)))
		def say(*values, **kwargs):
			sep  = kwargs.pop('sep' , None)
			end  = kwargs.pop('end' , None)
			file = kwargs.pop('file', None)
			if kwargs: raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.popitem()[0])
			if sep  is None: sep  = ' '
			if end  is None: end  = '\n'
			if file is None: file = sys.stdout
			if not isinstance(sep, basestring): raise saytypeerror(sep, 'sep')
			if not isinstance(end, basestring): raise saytypeerror(end, 'end')
			file.write(sep.join(map(str, values)) + end)

try:
	from os.path import relpath
except ImportError:
	# Python 2.5
	import os.path as _path
	
	# Adapted from Python 2.7.1
	
	if hasattr(_path, 'splitunc'):
		def _abspath_split(path):
			abs = _path.abspath(_path.normpath(path))
			prefix, rest = _path.splitunc(abs)
			is_unc = bool(prefix)
			if not is_unc:
				prefix, rest = _path.splitdrive(abs)
			return is_unc, prefix, [x for x in rest.split(_path.sep) if x]
	else:
		def _abspath_split(path):
			prefix, rest = _path.splitdrive(_path.abspath(_path.normpath(path)))
			return False, prefix, [x for x in rest.split(_path.sep) if x]
	
	def relpath(path, start=_path.curdir):
		"""Return a relative version of a path"""
		
		if not path:
			raise ValueError("no path specified")
		
		start_is_unc, start_prefix, start_list = _abspath_split(start)
		path_is_unc, path_prefix, path_list = _abspath_split(path)
		
		if path_is_unc ^ start_is_unc:
			raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)"
	                                                    		% (path, start))
		if path_prefix.lower() != start_prefix.lower():
			if path_is_unc:
				raise ValueError("path is on UNC root %s, start on UNC root %s"
		                                    		% (path_prefix, start_prefix))
			else:
				raise ValueError("path is on drive %s, start on drive %s"
		                                    		% (path_prefix, start_prefix))
		# Work out how much of the filepath is shared by start and path.
		i = 0
		for e1, e2 in zip(start_list, path_list):
			if e1.lower() != e2.lower():
				break
			i += 1
		
		rel_list = [_path.pardir] * (len(start_list)-i) + path_list[i:]
		if not rel_list:
			return _path.curdir
		return _path.join(*rel_list)
	
	_path.relpath = relpath

def import_urllib():
	try:
		# Python 3
		import urllib.request
		return urllib.request, lambda url: urllib.request.urlopen(url).read().decode('ascii')
	except ImportError:
		# Python 2
		import urllib
		return urllib, lambda url: urllib.urlopen(url).read()

try:
	from abc import ABCMeta, abstractmethod
except ImportError:
	ABCMeta, abstractmethod = None, lambda x: x

try:
	basestring = basestring
except NameError:
	basestring = str

# xrange is set to support simple testconf.py's written for test.py 1.x
try:
	xrange = range = xrange
except NameError:
	xrange = range = range

try:
	callable = callable
except NameError:
	from collections import Callable
	callable = lambda obj: isinstance(obj, Callable)

try:
	next = next
except NameError:
	next = lambda obj: obj.next()

try:
	from itertools import imap as map
except ImportError:
	map = map

try:
	from itertools import izip as zip
except ImportError:
	zip = zip

try:
	from itertools import ifilter as filter
except ImportError:
	filter = filter

try:
	items = dict.iteritems
except AttributeError:
	items = dict.items

try:
	keys = dict.iterkeys
except AttributeError:
	keys = dict.keys

try:
	values = dict.itervalues
except AttributeError:
	values = dict.values

try:
	# Python 3
	from itertools import zip_longest
except ImportError:
	try:
		# Python 2.6/2.7
		from itertools import izip_longest as zip_longest
	except ImportError:
		# Python 2.5
		from itertools import chain, repeat
		# Adapted from the documentation of itertools.izip_longest
		def zip_longest(*args, **kwargs):
			fillvalue = kwargs.get('fillvalue')
			def sentinel(counter=([fillvalue]*(len(args)-1)).pop):
				yield counter()
			fillers = repeat(fillvalue)
			iters = [chain(it, sentinel(), fillers) for it in args]
			try:
				for tup in zip(*iters):
					yield tup
			except IndexError:
				pass

# Automatically import * from this module into testconf.py's
class CompatBuiltins(object):
	__slots__ = 'originals'
	globals = globals()
	def __enter__(self):
		self.originals = {}
		for name in pseudobuiltins:
			try:
				self.originals[name] = getattr(builtins, name)
			except AttributeError:
				pass
			setattr(builtins, name, self.globals[name])
		return self
	def __exit__(self, exc_type, exc_val, exc_tb):
		for name in self.originals:
			setattr(builtins, name, self.originals[name])