I've gone over PEP3101 to create an initial unittest for the advanced
formatting. Based on this intro to the formatting syntax, I thought I'd also
share my thoughts. I've also experimented with this against the python
prototype of the formatting.

I have commented out the tests where that implementation fails, but should
work (by my interpretation). If anything these tests will provide a preview
look at the way the formatting looks.

1. The early python implementation does not allow "reusing" an argument
either by index or by keyword name. The PEP has not defined this behavior. I
think it is important to be allowed to reuse any of the argument objects
given to format.

2. The implementation we have always requires a "fill" argument in the
format, if a width is specified. It would be a big improvement if space
characters were default.

3. The specification is deep. It will take an intense amount of unit testing
of corner cases to make sure this is actually doing what is correct. It may
be too complex, but it is hard to know what might be yagni.

4. The PEP still leaves a bit of wiggle room in the design, but since an
implementation is underway, I think more experimentation would be better
before locking down the design.

5. The "strict mode" activation through a global state on the string object
is a bad idea. I would prefer some sort of "flags" argument passed to each
function. I would prefer the "strict" mode where exceptions are raised by
default. But I do not want the strict behavior of requiring all arguments to
be used.

6. Security on the attribute lookups is probably an unending topic. A simple
minimum would be to not allow attribute lookups on names starting with an
underscore.
import unittest
from test import test_support

import StringFormat


# The test implementation does not allow an argument
# index or keyword name to be used more than once. The
# PEP doesn't make any specification on this, but it
# seems too useful to leave as is.


class FormatTest(unittest.TestCase):
    # All tests run through these functions. They can be
    # overridden to change the class of string being tested
    # and the function being used.
    def formatEquals(self, result, text, *args, **kwargs):
        text = str(text)
        result = str(result)
        val = StringFormat.format(text, *args, **kwargs)
        self.assertEquals(val, result)

    def formatRaises(self, exc, text, *args, **kwargs):
        exc = exc or StringFormat.FormatError
        text = str(text)
        prevState = StringFormat.strict_format_errors
        StringFormat.strict_format_errors = True
        try:
            self.assertRaises(exc,
                              lambda: StringFormat.format(
                                  text, *args, **kwargs))
        finally:
            StringFormat.strict_format_errors = prevState


    def test_basic(self):
        # directly from the pep intro
        self.formatEquals(
            "My name is Fred",
            "My name is {0}", "Fred")
        self.formatEquals(
            "My name is Fred :-{}",
            "My name is {0} :-{{}}", "Fred")
        self.formatEquals("abc", "{0:}", "abc")
    
    def test_missingargs(self):
        self.formatRaises(None, "Doesn't use all {0} args", 42, 24)
        self.formatRaises(IndexError, "There is no {4} arg", 42, 24)
        self.formatRaises(KeyError, "There question is {when}", who=True)

    def test_sanity(self):
        # Fairly all encompassing one liner test
        self.formatEquals(
            "This is a test of } 0x3e8 hex ?IndexError? a 200000.000000{",
            "This is a test of }} {0:x} {x} {y[3]} {2[2]} {1:5n}{{",
            1000, 200000, "grag", x="hex", y=[1,2,3])

    def test_attributes(self):
        class Container(object):
            one, _two, four4 = 1, 2, 4
            def __getattr__(self, name):
                if name == "five": return 5
                raise TypeError("Never do this")
        self.formatEquals(
            "Count with me; 1 2 4",
            "Count with me; {0.one} {item._two} {1.four4}",
            Container, Container, item=Container)
        self.formatEquals(
            "Five is 5", "Five is {c.five}", c=Container())
        self.formatRaises(AttributeError,
            "Missing {0.rabbit} lookup", Container)
        self.formatRaises(TypeError,
            "Forbidden {0.secret} lookup", Container())

    def test_items(self):
        d = dict(val="value", sum=1)
        t = tuple(("apple", "ball", "cat"))
        self.formatEquals(
            "The value of apple",
            "The {0[val]} of {t[0]}", d, t=t)
        self.formatEquals(
            "The shiny red ball",
            "The shiny red {0[-2]}", t)

    def test_formatlookup(self):
        self.formatEquals("0032", "{0:{1}}", 32, "0>4d")
        self.formatEquals("**32", "{0:{1}{2}4{3}}", 32, "*", ">", "d")

    def test_char(self):
        self.formatRaises(ValueError, "{0:c}", "a")
        self.formatEquals("a", "{0:c}", ord("a"))
        #self.formatEquals("  a", "{0:3c}", ord("a"))

    def test_binary(self):
        self.formatRaises(ValueError, "{0:b}", "a")
        self.formatEquals("1000", "{0:b}", 8)
        self.formatEquals("00001000", "{0:08b}", 8)
        self.formatEquals("-100000", "{0:b}", -32)
        self.formatEquals("-100000", "{0:b}", -32)

    def test_integer(self):
        self.formatRaises(ValueError, "{0:b}", "a")
        self.formatEquals("8", "{0:d}", 8)
        self.formatEquals("0", "{0:d}", False)
        self.formatEquals("22", "{0:d}", 22.22)
        self.formatEquals("  8", "{0: >3d}", 8)
        self.formatEquals("  8", "{0: >3.3d}", 8)
        #self.formatEquals("+32", "{0:+d}", 32)
        self.formatEquals("-32", "{0:d}", -32)
        #self.formatEquals("42", "{0:()d}", 42)
        #self.formatEquals("(32)", "{0:()d}", -42)
        self.formatEquals("32  ", "{0: <4d}", 32)
        self.formatEquals("  32", "{0: >4d}", 32)
        self.formatEquals("3200", "{0:0<4d}", 32)
        self.formatEquals("0032", "{0:0>4d}", 32)
        #self.formatEquals("0032", "{0:0<+4d}", 32)
        #self.formatEquals("-032", "{0:0<4d}", -32)

    def test_string(self):
        self.formatEquals("15", "{0:s}", 15)
        self.formatEquals("alpha", "{0:s}", "alpha")
        self.formatEquals("True", "{0:s}", True)
        #self.formatEquals("   alpha", "{0:8s}", "alpha")
        self.formatEquals("alpha___", "{0:_<8s}", "alpha")

    def test_float(self):
        self.formatEquals("1515.1515", "{0:f}", 1515.1515)
        self.formatEquals("1515.15", "{0:.2f}", 1515.1515)
        #self.formatEquals("15151515e-4", "{0:e}", 1515.1515)
        #self.formatEquals("15151515E-4", "{0:E}", 1515.1515)
        self.formatEquals("1.5e+16", "{0:g}", 15e15)
        #self.formatEquals("15000000000000000.0", "{0:.1n}", 15e15)

    def test_percent(self):
        self.formatEquals("15.15", "{0:%}", .1515)
        self.formatEquals("15", "{0:.0%}", .1515)
    
    def test_other_types(self):
        self.formatEquals("017", "{0:o}", 15)
        self.formatEquals("0xf", "{0:x}", 15)
        self.formatEquals("0XF", "{0:X}", 15)

    def test_custom_format(self):
        class Custom(object):
            def __format__(self, specifiers):
                return specifiers
        custom = Custom()
        self.formatEquals("magic", "{0:magic}", custom)
        self.formatEquals("custom", "{0:{1}}", custom, "custom")


def test_main():
    test_support.run_unittest(FormatTest)

if __name__ == "__main__":
    test_main()

    
_______________________________________________
Python-3000 mailing list
[email protected]
http://mail.python.org/mailman/listinfo/python-3000
Unsubscribe: 
http://mail.python.org/mailman/options/python-3000/archive%40mail-archive.com

Reply via email to