Skip to content
This repository has been archived by the owner on May 28, 2022. It is now read-only.

Adds schema methods to Option and Config classes. #646

Merged
merged 1 commit into from
Nov 20, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion src/freeseer/framework/config/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# freeseer - vga/presentation capture software
#
# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre
# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre
# http://fosslc.org
#
# This program is free software: you can redistribute it and/or modify
Expand Down Expand Up @@ -56,6 +56,13 @@ def presentation(self, value):
"""Returns a modified version of value that will not itself be persisted."""
return value

def schema(self):
"""Returns the json schema for an Option."""
schema = {'type': self.SCHEMA_TYPE}
if self.default != self.NotSpecified:
schema['default'] = self.default
return schema

# Override these!

@abc.abstractmethod
Expand Down Expand Up @@ -193,6 +200,26 @@ def save(self):
else:
raise StorageNotSetError()

@classmethod
def schema(cls):
"""Returns the json schema for this Config instance."""
required = []

schema = {
'type': 'object',
'properties': {},
}

for name, instance in cls.options.iteritems():
schema['properties'][name] = instance.schema()
if instance.is_required():
required.append(name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not add these to the schema directly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally tried having 'required': [] as part of the schema, and filling it as needed when options are required. But json-schema validate() throws exceptions if a schema it receives as input has an empty list of 'required'.


if required:
schema['required'] = required

return schema


class ConfigStorage(object):
"""Defines an interface for loading and storing Config instances."""
Expand Down
14 changes: 13 additions & 1 deletion src/freeseer/framework/config/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# freeseer - vga/presentation capture software
#
# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre
# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre
# http://fosslc.org
#
# This program is free software: you can redistribute it and/or modify
Expand All @@ -30,6 +30,7 @@

class StringOption(Option):
"""Represents a string value."""
SCHEMA_TYPE = 'string'

def is_valid(self, value):
return isinstance(value, str) or hasattr(value, '__str__')
Expand All @@ -43,6 +44,7 @@ def decode(self, value):

class IntegerOption(Option):
"""Represents an integer number value."""
SCHEMA_TYPE = 'integer'

def is_valid(self, value):
return isinstance(value, int)
Expand All @@ -59,6 +61,7 @@ def decode(self, value):

class FloatOption(Option):
"""Represents a floating point number value."""
SCHEMA_TYPE = 'number'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't been following this project closely so perhaps I'm missing something, but would this be better as 'float'?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSON schema spec doesn't actually include floats. Instead, it has 'number' for any numbers, both whole and fractional, and 'integer' for just integers. It's described here:
http://json-schema.org/latest/json-schema-core.html#anchor8

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that's a handy page. Carry on.


def is_valid(self, value):
return isinstance(value, float)
Expand All @@ -75,6 +78,7 @@ def decode(self, value):

class BooleanOption(Option):
"""Represents a boolean value."""
SCHEMA_TYPE = 'boolean'

def is_valid(self, value):
return isinstance(value, bool)
Expand All @@ -88,6 +92,7 @@ def decode(self, value):

class FolderOption(Option):
"""Represents the path to a folder."""
SCHEMA_TYPE = 'string'

def __init__(self, default=Option.NotSpecified, auto_create=False):
self.auto_create = auto_create
Expand Down Expand Up @@ -119,6 +124,7 @@ def presentation(self, value):

class ChoiceOption(StringOption):
"""Represents a selection from a pre-defined list of strings."""
SCHEMA_TYPE = 'enum'

def __init__(self, choices, default=Option.NotSpecified):
self.choices = choices
Expand All @@ -133,3 +139,9 @@ def decode(self, value):
return choice
else:
raise InvalidDecodeValueError(value)

def schema(self):
schema = {'enum': self.choices}
if self.default != Option.NotSpecified:
schema['default'] = self.default
return schema
17 changes: 16 additions & 1 deletion src/freeseer/tests/framework/config/options/test_boolean.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# freeseer - vga/presentation capture software
#
# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre
# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre
# http://fosslc.org
#
# This program is free software: you can redistribute it and/or modify
Expand All @@ -24,6 +24,9 @@

import unittest

from jsonschema import validate
from jsonschema import ValidationError

from freeseer.framework.config.options import BooleanOption
from freeseer.tests.framework.config.options import OptionTest

Expand Down Expand Up @@ -53,6 +56,12 @@ class TestBooleanOptionNoDefault(unittest.TestCase, OptionTest):
def setUp(self):
self.option = BooleanOption()

def test_schema(self):
"""Tests BooleanOption schema method."""
self.assertRaises(ValidationError, validate, 4, self.option.schema())
self.assertIsNone(validate(True, self.option.schema()))
self.assertDictEqual(self.option.schema(), {'type': 'boolean'})


class TestBooleanOptionWithDefault(TestBooleanOptionNoDefault):
"""Test BooleanOption with a default value."""
Expand All @@ -63,3 +72,9 @@ def setUp(self):
def test_default(self):
"""Tests that the default was set correctly."""
self.assertEqual(self.option.default, False)

def test_schema(self):
"""Tests BooleanOption schema method."""
self.assertRaises(ValidationError, validate, 4, self.option.schema())
self.assertIsNone(validate(True, self.option.schema()))
self.assertDictEqual(self.option.schema(), {'default': False, 'type': 'boolean'})
30 changes: 29 additions & 1 deletion src/freeseer/tests/framework/config/options/test_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# freeseer - vga/presentation capture software
#
# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre
# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre
# http://fosslc.org
#
# This program is free software: you can redistribute it and/or modify
Expand All @@ -24,6 +24,9 @@

import unittest

from jsonschema import validate
from jsonschema import ValidationError

from freeseer.framework.config.options import ChoiceOption
from freeseer.tests.framework.config.options import OptionTest

Expand Down Expand Up @@ -52,6 +55,18 @@ def setUp(self):
'world',
])

def test_schema(self):
"""Tests a ChoiceOption schema method."""
expected = {
'enum': [
'hello',
'world',
],
}
self.assertRaises(ValidationError, validate, 'error', self.option.schema())
self.assertIsNone(validate('world', self.option.schema()))
self.assertDictEqual(self.option.schema(), expected)


class TestChoiceOptionWithDefault(TestChoiceOptionNoDefault):
"""Tests ChoiceOption with a default value."""
Expand All @@ -65,3 +80,16 @@ def setUp(self):
def test_default(self):
"""Tests that the default was set correctly."""
self.assertEqual(self.option.default, 'hello')

def test_schema(self):
"""Tests a ChoiceOption schema method."""
expected = {
'default': 'hello',
'enum': [
'hello',
'world',
],
}
self.assertRaises(ValidationError, validate, 'error', self.option.schema())
self.assertIsNone(validate('world', self.option.schema()))
self.assertDictEqual(self.option.schema(), expected)
72 changes: 72 additions & 0 deletions src/freeseer/tests/framework/config/options/test_float.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# freeseer - vga/presentation capture software
#
# Copyright (C) 2014 Free and Open Source Software Learning Centre
# http://fosslc.org
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

# For support, questions, suggestions or any other inquiries, visit:
# http://wiki.github.com/Freeseer/freeseer/

import unittest

from jsonschema import validate
from jsonschema import ValidationError

from freeseer.framework.config.options import FloatOption
from freeseer.tests.framework.config.options import OptionTest


class TestFloatOptionNoDefault(unittest.TestCase, OptionTest):
"""Tests FloatOption without a default value."""

valid_success = [x / 10.0 for x in xrange(-100, 100)]

encode_success = zip(valid_success, map(str, valid_success))

decode_success = zip(map(str, valid_success), valid_success)
decode_failure = [
'hello',
'1world',
'test2',
]

def setUp(self):
self.option = FloatOption()

def test_schema(self):
"""Tests FloatOption schema method."""
self.assertRaises(ValidationError, validate, 'error', self.option.schema())
self.assertIsNone(validate(5.5, self.option.schema()))
self.assertDictEqual(self.option.schema(), {'type': 'number'})


class TestFloatOptionWithDefault(TestFloatOptionNoDefault):
"""Tests FloatOption with a default value."""

def setUp(self):
self.option = FloatOption(1234.5)

def test_default(self):
"""Tests that the default was set correctly."""
self.assertEqual(self.option.default, 1234.5)

def test_schema(self):
"""Tests FloatOption schema method."""
self.assertRaises(ValidationError, validate, 'error', self.option.schema())
self.assertIsNone(validate(5.0, self.option.schema()))
self.assertDictEqual(self.option.schema(), {'default': 1234.5, 'type': 'number'})
17 changes: 16 additions & 1 deletion src/freeseer/tests/framework/config/options/test_folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# freeseer - vga/presentation capture software
#
# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre
# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre
# http://fosslc.org
#
# This program is free software: you can redistribute it and/or modify
Expand All @@ -27,6 +27,9 @@
import tempfile
import unittest

from jsonschema import validate
from jsonschema import ValidationError

from freeseer.framework.config.options import FolderOption
from freeseer.tests.framework.config.options import OptionTest

Expand Down Expand Up @@ -57,6 +60,12 @@ def test_presentation(self):
self.assertEqual(presentation_value, path)
self.assertFalse(os.path.exists(presentation_value))

def test_schema(self):
"""Tests StringOption schema method."""
self.assertRaises(ValidationError, validate, 1, self.option.schema())
self.assertIsNone(validate('/tmp2', self.option.schema()))
self.assertDictEqual(self.option.schema(), {'type': 'string'})


class TestFolderOptionAutoCreate(TestFolderOptionNoDefault):
"""Tests FolderOption without a default value, and with auto_create turned on."""
Expand Down Expand Up @@ -88,3 +97,9 @@ def setUp(self):
def test_default(self):
"""Tests that the default was set correctly."""
self.assertEqual(self.option.default, '/tmp')

def test_schema(self):
"""Tests StringOption schema method."""
self.assertRaises(ValidationError, validate, 1, self.option.schema())
self.assertIsNone(validate('/tmp2', self.option.schema()))
self.assertDictEqual(self.option.schema(), {'default': '/tmp', 'type': 'string'})
17 changes: 16 additions & 1 deletion src/freeseer/tests/framework/config/options/test_integer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# freeseer - vga/presentation capture software
#
# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre
# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre
# http://fosslc.org
#
# This program is free software: you can redistribute it and/or modify
Expand All @@ -24,6 +24,9 @@

import unittest

from jsonschema import validate
from jsonschema import ValidationError

from freeseer.framework.config.options import IntegerOption
from freeseer.tests.framework.config.options import OptionTest

Expand All @@ -45,6 +48,12 @@ class TestIntegerOptionNoDefault(unittest.TestCase, OptionTest):
def setUp(self):
self.option = IntegerOption()

def test_schema(self):
"""Tests IntegerOption schema method."""
self.assertRaises(ValidationError, validate, 1.0, self.option.schema())
self.assertIsNone(validate(5, self.option.schema()))
self.assertDictEqual(self.option.schema(), {'type': 'integer'})


class TestIntegerOptionWithDefault(TestIntegerOptionNoDefault):
"""Tests IntegerOption with a default value."""
Expand All @@ -55,3 +64,9 @@ def setUp(self):
def test_default(self):
"""Tests that the default was set correctly."""
self.assertEqual(self.option.default, 1234)

def test_schema(self):
"""Tests IntegerOption schema method."""
self.assertRaises(ValidationError, validate, 1.0, self.option.schema())
self.assertIsNone(validate(5, self.option.schema()))
self.assertDictEqual(self.option.schema(), {'default': 1234, 'type': 'integer'})
Loading