<commit_before>
import json
from test.support import EnvironmentVarGuard

from django.test import TestCase, override_settings
import django.core.exceptions as django_exceptions
from django.core.exceptions import ValidationError
from django.contrib.auth import get_user_model
from django.conf import settings

from djmoney.money import Money
from djmoney.contrib.exchange.models import Rate, convert_money
from djmoney.contrib.exchange.exceptions import MissingRate

from .validators import validate_overage, validate_part_name
from . import helpers
from . import version
from . import status
from . import ready

from decimal import Decimal

import InvenTree.tasks

from stock.models import StockLocation
from common.settings import currency_codes

class ValidatorTest(TestCase):

    """ Simple tests for custom field validators """

    def test_part_name(self):
        """ Test part name validator """

        validate_part_name('hello world')

        with self.assertRaises(django_exceptions.ValidationError):
            validate_part_name('This | name is not } valid')

    def test_overage(self):
        """ Test overage validator """

        validate_overage("100%")
        validate_overage("10")
        validate_overage("45.2 %")

        with self.assertRaises(django_exceptions.ValidationError):
            validate_overage("-1")

        with self.assertRaises(django_exceptions.ValidationError):
            validate_overage("-2.04 %")

        with self.assertRaises(django_exceptions.ValidationError):
            validate_overage("105%")

        with self.assertRaises(django_exceptions.ValidationError):
            validate_overage("xxx %")

        with self.assertRaises(django_exceptions.ValidationError):
            validate_overage("aaaa")

class TestHelpers(TestCase):
    """ Tests for InvenTree helper functions """

    def test_image_url(self):
        """ Test if a filename looks like an image """

        for name in ['ape.png', 'bat.GiF', 'apple.WeBP', 'BiTMap.Bmp']:
            self.assertTrue(helpers.TestIfImageURL(name))

        for name in ['no.doc', 'nah.pdf', 'whatpng']:
            self.assertFalse(helpers.TestIfImageURL(name))

    def test_str2bool(self):
        """ Test string to boolean conversion """

        for s in ['yes', 'Y', 'ok', '1', 'OK', 'Ok', 'tRuE', 'oN']:
            self.assertTrue(helpers.str2bool(s))
            self.assertFalse(helpers.str2bool(s, test=False))

        for s in ['nO', '0', 'none', 'noNE', None, False, 'falSe', 'off']:
            self.assertFalse(helpers.str2bool(s))
            self.assertTrue(helpers.str2bool(s, test=False))

        for s in ['wombat', '', 'xxxx']:
            self.assertFalse(helpers.str2bool(s))
            self.assertFalse(helpers.str2bool(s, test=False))

    def test_isnull(self):

        for s in ['null', 'none', '', '-1', 'false']:
            self.assertTrue(helpers.isNull(s))

        for s in ['yes', 'frog', 'llama', 'true']:
            self.assertFalse(helpers.isNull(s))

    def testStaticUrl(self):

        self.assertEqual(helpers.getStaticUrl('test.jpg'), '/static/test.jpg')
        self.assertEqual(helpers.getBlankImage(), '/static/img/blank_image.png')
        self.assertEqual(helpers.getBlankThumbnail(), '/static/img/blank_image.thumbnail.png')

    def testMediaUrl(self):

        self.assertEqual(helpers.getMediaUrl('xx/yy.png'), '/media/xx/yy.png')

    def testDecimal2String(self):

        self.assertEqual(helpers.decimal2string(Decimal('1.2345000')), '1.2345')
        self.assertEqual(helpers.decimal2string('test'), 'test')

class TestQuoteWrap(TestCase):
    """ Tests for string wrapping """

    def test_single(self):

        self.assertEqual(helpers.WrapWithQuotes('hello'), '"hello"')
        self.assertEqual(helpers.WrapWithQuotes('hello"'), '"hello"')

class TestIncrement(TestCase):

    def tests(self):
        """ Test 'intelligent' incrementing function """

        tests = [
            ("", ""),
            (1, "2"),
            ("001", "002"),
            ("1001", "1002"),
            ("ABC123", "ABC124"),
            ("XYZ0", "XYZ1"),
            ("123Q", "123Q"),
            ("QQQ", "QQQ"),
        ]

        for test in tests:
            a, b = test

            result = helpers.increment(a)
            self.assertEqual(result, b)

class TestMakeBarcode(TestCase):
    """ Tests for barcode string creation """

    def test_barcode_extended(self):

        bc = helpers.MakeBarcode(
            "part",
            3,
            {
                "id": 3,
                "url": "www.google.com",
            },
            brief=False
        )

        self.assertIn('part', bc)
        self.assertIn('tool', bc)
        self.assertIn('"tool": "InvenTree"', bc)

        data = json.loads(bc)

        self.assertEqual(data['part']['id'], 3)
        self.assertEqual(data['part']['url'], 'www.google.com')

    def test_barcode_brief(self):

        bc = helpers.MakeBarcode(
            "stockitem",
            7,
        )

        data = json.loads(bc)
        self.assertEqual(len(data), 1)
        self.assertEqual(data['stockitem'], 7)

class TestDownloadFile(TestCase):

    def test_download(self):
        helpers.DownloadFile("hello world", "out.txt")
        helpers.DownloadFile(bytes("hello world".encode("utf8")), "out.bin")

class TestMPTT(TestCase):
    """ Tests for the MPTT tree models """

    fixtures = [
        'location',
    ]

    def setUp(self):
        super().setUp()

        StockLocation.objects.rebuild()

    def test_self_as_parent(self):
        """ Test that we cannot set self as parent """

        loc = StockLocation.objects.get(pk=4)
        loc.parent = loc

        with self.assertRaises(ValidationError):
            loc.save()

    def test_child_as_parent(self):
        """ Test that we cannot set a child as parent """

        parent = StockLocation.objects.get(pk=4)
        child = StockLocation.objects.get(pk=5)

        parent.parent = child

        with self.assertRaises(ValidationError):
            parent.save()

    def test_move(self):
        """ Move an item to a different tree """

        drawer = StockLocation.objects.get(name='Drawer_1')

        # Record the tree ID
        tree = drawer.tree_id

        home = StockLocation.objects.get(name='Home')

        drawer.parent = home
        drawer.save()

        self.assertNotEqual(tree, drawer.tree_id)

class TestSerialNumberExtraction(TestCase):
    """ Tests for serial number extraction code """

    def test_simple(self):

        e = helpers.extract_serial_numbers

        sn = e("1-5", 5, 1)
        self.assertEqual(len(sn), 5, 1)
        for i in range(1, 6):
            self.assertIn(i, sn)

        sn = e("1, 2, 3, 4, 5", 5, 1)
        self.assertEqual(len(sn), 5)

        sn = e("1-5, 10-15", 11, 1)
        self.assertIn(3, sn)
        self.assertIn(13, sn)

        sn = e("1+", 10, 1)
        self.assertEqual(len(sn), 10)
        self.assertEqual(sn, [_ for _ in range(1, 11)])

        sn = e("4, 1+2", 4, 1)
        self.assertEqual(len(sn), 4)
        self.assertEqual(sn, [4, 1, 2, 3])

        sn = e("~", 1, 1)
        self.assertEqual(len(sn), 1)
        self.assertEqual(sn, [1])

        sn = e("~", 1, 3)
        self.assertEqual(len(sn), 1)
        self.assertEqual(sn, [3])

        sn = e("~+", 2, 5)
        self.assertEqual(len(sn), 2)
        self.assertEqual(sn, [5, 6])

        sn = e("~+3", 4, 5)
        self.assertEqual(len(sn), 4)
        self.assertEqual(sn, [5, 6, 7, 8])

    def test_failures(self):

        e = helpers.extract_serial_numbers

        # Test duplicates
        with self.assertRaises(ValidationError):
            e("1,2,3,3,3", 5, 1)

        # Test invalid length
        with self.assertRaises(ValidationError):
            e("1,2,3", 5, 1)

        # Test empty string
        with self.assertRaises(ValidationError):
            e(", , ,", 0, 1)

        # Test incorrect sign in group
        with self.assertRaises(ValidationError):
            e("10-2", 8, 1)

        # Test invalid group
        with self.assertRaises(ValidationError):
            e("1-5-10", 10, 1)

        with self.assertRaises(ValidationError):
            e("10, a, 7-70j", 4, 1)

    def test_combinations(self):
        e = helpers.extract_serial_numbers

        sn = e("1 3-5 9+2", 7, 1)
        self.assertEqual(len(sn), 7)
        self.assertEqual(sn, [1, 3, 4, 5, 9, 10, 11])

        sn = e("1,3-5,9+2", 7, 1)
        self.assertEqual(len(sn), 7)
        self.assertEqual(sn, [1, 3, 4, 5, 9, 10, 11])

        sn = e("~+2", 3, 14)
        self.assertEqual(len(sn), 3)
        self.assertEqual(sn, [14, 15, 16])

        sn = e("~+", 2, 14)
        self.assertEqual(len(sn), 2)
        self.assertEqual(sn, [14, 15])

class TestVersionNumber(TestCase):
    """
    Unit tests for version number functions
    """

    def test_tuple(self):

        v = version.inventreeVersionTuple()
        self.assertEqual(len(v), 3)

        s = '.'.join([str(i) for i in v])

        self.assertTrue(s in version.inventreeVersion())

    def test_comparison(self):
        """
        Test direct comparison of version numbers
        """

        v_a = version.inventreeVersionTuple('1.2.0')
        v_b = version.inventreeVersionTuple('1.2.3')
        v_c = version.inventreeVersionTuple('1.2.4')
        v_d = version.inventreeVersionTuple('2.0.0')

        self.assertTrue(v_b > v_a)
        self.assertTrue(v_c > v_b)
        self.assertTrue(v_d > v_c)
        self.assertTrue(v_d > v_a)

class CurrencyTests(TestCase):
    """
    Unit tests for currency / exchange rate functionality
    """

    def test_rates(self):

        # Initially, there will not be any exchange rate information
        rates = Rate.objects.all()

        self.assertEqual(rates.count(), 0)

        # Without rate information, we cannot convert anything...
        with self.assertRaises(MissingRate):
            convert_money(Money(100, 'USD'), 'AUD')

        with self.assertRaises(MissingRate):
            convert_money(Money(100, 'AUD'), 'USD')

        InvenTree.tasks.update_exchange_rates()

        rates = Rate.objects.all()

        self.assertEqual(rates.count(), len(currency_codes()))

        # Now that we have some exchange rate information, we can perform conversions

        # Forwards
        convert_money(Money(100, 'USD'), 'AUD')

        # Backwards
        convert_money(Money(100, 'AUD'), 'USD')

        # Convert between non base currencies
        convert_money(Money(100, 'CAD'), 'NZD')

        # Convert to a symbol which is not covered
        with self.assertRaises(MissingRate):
            convert_money(Money(100, 'GBP'), 'ZWL')

class TestStatus(TestCase):
    """
    Unit tests for status functions
    """

    def test_check_system_healt(self):
        """test that the system health check is false in testing -> background worker not running"""
        self.assertEqual(status.check_system_health(), False)

    def test_TestMode(self):
        self.assertTrue(ready.isInTestMode())

    def test_Importing(self):
        self.assertEqual(ready.isImportingData(), False)

class TestSettings(TestCase):
    """
    Unit tests for settings
    """

    def setUp(self) -> None:
        self.user_mdl = get_user_model()
        self.env = EnvironmentVarGuard()

    def run_reload(self):
        from plugin import registry

        with self.env:
            settings.USER_ADDED = False
            registry.reload_plugins()

    @override_settings(TESTING_ENV=True)
    def test_set_user_to_few(self):
        # add shortcut
        user_count = self.user_mdl.objects.count
        # enable testing mode
        settings.TESTING_ENV = True

        # nothing set
        self.assertEqual(user_count(), 0)

        # not enough set
        self.env.set('INVENTREE_ADMIN_USER', 'admin')  # set username
        self.run_reload()
        self.assertEqual(user_count(), 0)

        # make sure to clean up
        settings.TESTING_ENV = False
<commit_msg>full set again<commit_after>
import json
from test.support import EnvironmentVarGuard

from django.test import TestCase, override_settings
import django.core.exceptions as django_exceptions
from django.core.exceptions import ValidationError
from django.contrib.auth import get_user_model
from django.conf import settings

from djmoney.money import Money
from djmoney.contrib.exchange.models import Rate, convert_money
from djmoney.contrib.exchange.exceptions import MissingRate

from .validators import validate_overage, validate_part_name
from . import helpers
from . import version
from . import status
from . import ready

from decimal import Decimal

import InvenTree.tasks

from stock.models import StockLocation
from common.settings import currency_codes
from plugin.helpers import IntegrationPluginError

class ValidatorTest(TestCase):

    """ Simple tests for custom field validators """

    def test_part_name(self):
        """ Test part name validator """

        validate_part_name('hello world')

        with self.assertRaises(django_exceptions.ValidationError):
            validate_part_name('This | name is not } valid')

    def test_overage(self):
        """ Test overage validator """

        validate_overage("100%")
        validate_overage("10")
        validate_overage("45.2 %")

        with self.assertRaises(django_exceptions.ValidationError):
            validate_overage("-1")

        with self.assertRaises(django_exceptions.ValidationError):
            validate_overage("-2.04 %")

        with self.assertRaises(django_exceptions.ValidationError):
            validate_overage("105%")

        with self.assertRaises(django_exceptions.ValidationError):
            validate_overage("xxx %")

        with self.assertRaises(django_exceptions.ValidationError):
            validate_overage("aaaa")

class TestHelpers(TestCase):
    """ Tests for InvenTree helper functions """

    def test_image_url(self):
        """ Test if a filename looks like an image """

        for name in ['ape.png', 'bat.GiF', 'apple.WeBP', 'BiTMap.Bmp']:
            self.assertTrue(helpers.TestIfImageURL(name))

        for name in ['no.doc', 'nah.pdf', 'whatpng']:
            self.assertFalse(helpers.TestIfImageURL(name))

    def test_str2bool(self):
        """ Test string to boolean conversion """

        for s in ['yes', 'Y', 'ok', '1', 'OK', 'Ok', 'tRuE', 'oN']:
            self.assertTrue(helpers.str2bool(s))
            self.assertFalse(helpers.str2bool(s, test=False))

        for s in ['nO', '0', 'none', 'noNE', None, False, 'falSe', 'off']:
            self.assertFalse(helpers.str2bool(s))
            self.assertTrue(helpers.str2bool(s, test=False))

        for s in ['wombat', '', 'xxxx']:
            self.assertFalse(helpers.str2bool(s))
            self.assertFalse(helpers.str2bool(s, test=False))

    def test_isnull(self):

        for s in ['null', 'none', '', '-1', 'false']:
            self.assertTrue(helpers.isNull(s))

        for s in ['yes', 'frog', 'llama', 'true']:
            self.assertFalse(helpers.isNull(s))

    def testStaticUrl(self):

        self.assertEqual(helpers.getStaticUrl('test.jpg'), '/static/test.jpg')
        self.assertEqual(helpers.getBlankImage(), '/static/img/blank_image.png')
        self.assertEqual(helpers.getBlankThumbnail(), '/static/img/blank_image.thumbnail.png')

    def testMediaUrl(self):

        self.assertEqual(helpers.getMediaUrl('xx/yy.png'), '/media/xx/yy.png')

    def testDecimal2String(self):

        self.assertEqual(helpers.decimal2string(Decimal('1.2345000')), '1.2345')
        self.assertEqual(helpers.decimal2string('test'), 'test')

class TestQuoteWrap(TestCase):
    """ Tests for string wrapping """

    def test_single(self):

        self.assertEqual(helpers.WrapWithQuotes('hello'), '"hello"')
        self.assertEqual(helpers.WrapWithQuotes('hello"'), '"hello"')

class TestIncrement(TestCase):

    def tests(self):
        """ Test 'intelligent' incrementing function """

        tests = [
            ("", ""),
            (1, "2"),
            ("001", "002"),
            ("1001", "1002"),
            ("ABC123", "ABC124"),
            ("XYZ0", "XYZ1"),
            ("123Q", "123Q"),
            ("QQQ", "QQQ"),
        ]

        for test in tests:
            a, b = test

            result = helpers.increment(a)
            self.assertEqual(result, b)

class TestMakeBarcode(TestCase):
    """ Tests for barcode string creation """

    def test_barcode_extended(self):

        bc = helpers.MakeBarcode(
            "part",
            3,
            {
                "id": 3,
                "url": "www.google.com",
            },
            brief=False
        )

        self.assertIn('part', bc)
        self.assertIn('tool', bc)
        self.assertIn('"tool": "InvenTree"', bc)

        data = json.loads(bc)

        self.assertEqual(data['part']['id'], 3)
        self.assertEqual(data['part']['url'], 'www.google.com')

    def test_barcode_brief(self):

        bc = helpers.MakeBarcode(
            "stockitem",
            7,
        )

        data = json.loads(bc)
        self.assertEqual(len(data), 1)
        self.assertEqual(data['stockitem'], 7)

class TestDownloadFile(TestCase):

    def test_download(self):
        helpers.DownloadFile("hello world", "out.txt")
        helpers.DownloadFile(bytes("hello world".encode("utf8")), "out.bin")

class TestMPTT(TestCase):
    """ Tests for the MPTT tree models """

    fixtures = [
        'location',
    ]

    def setUp(self):
        super().setUp()

        StockLocation.objects.rebuild()

    def test_self_as_parent(self):
        """ Test that we cannot set self as parent """

        loc = StockLocation.objects.get(pk=4)
        loc.parent = loc

        with self.assertRaises(ValidationError):
            loc.save()

    def test_child_as_parent(self):
        """ Test that we cannot set a child as parent """

        parent = StockLocation.objects.get(pk=4)
        child = StockLocation.objects.get(pk=5)

        parent.parent = child

        with self.assertRaises(ValidationError):
            parent.save()

    def test_move(self):
        """ Move an item to a different tree """

        drawer = StockLocation.objects.get(name='Drawer_1')

        # Record the tree ID
        tree = drawer.tree_id

        home = StockLocation.objects.get(name='Home')

        drawer.parent = home
        drawer.save()

        self.assertNotEqual(tree, drawer.tree_id)

class TestSerialNumberExtraction(TestCase):
    """ Tests for serial number extraction code """

    def test_simple(self):

        e = helpers.extract_serial_numbers

        sn = e("1-5", 5, 1)
        self.assertEqual(len(sn), 5, 1)
        for i in range(1, 6):
            self.assertIn(i, sn)

        sn = e("1, 2, 3, 4, 5", 5, 1)
        self.assertEqual(len(sn), 5)

        sn = e("1-5, 10-15", 11, 1)
        self.assertIn(3, sn)
        self.assertIn(13, sn)

        sn = e("1+", 10, 1)
        self.assertEqual(len(sn), 10)
        self.assertEqual(sn, [_ for _ in range(1, 11)])

        sn = e("4, 1+2", 4, 1)
        self.assertEqual(len(sn), 4)
        self.assertEqual(sn, [4, 1, 2, 3])

        sn = e("~", 1, 1)
        self.assertEqual(len(sn), 1)
        self.assertEqual(sn, [1])

        sn = e("~", 1, 3)
        self.assertEqual(len(sn), 1)
        self.assertEqual(sn, [3])

        sn = e("~+", 2, 5)
        self.assertEqual(len(sn), 2)
        self.assertEqual(sn, [5, 6])

        sn = e("~+3", 4, 5)
        self.assertEqual(len(sn), 4)
        self.assertEqual(sn, [5, 6, 7, 8])

    def test_failures(self):

        e = helpers.extract_serial_numbers

        # Test duplicates
        with self.assertRaises(ValidationError):
            e("1,2,3,3,3", 5, 1)

        # Test invalid length
        with self.assertRaises(ValidationError):
            e("1,2,3", 5, 1)

        # Test empty string
        with self.assertRaises(ValidationError):
            e(", , ,", 0, 1)

        # Test incorrect sign in group
        with self.assertRaises(ValidationError):
            e("10-2", 8, 1)

        # Test invalid group
        with self.assertRaises(ValidationError):
            e("1-5-10", 10, 1)

        with self.assertRaises(ValidationError):
            e("10, a, 7-70j", 4, 1)

    def test_combinations(self):
        e = helpers.extract_serial_numbers

        sn = e("1 3-5 9+2", 7, 1)
        self.assertEqual(len(sn), 7)
        self.assertEqual(sn, [1, 3, 4, 5, 9, 10, 11])

        sn = e("1,3-5,9+2", 7, 1)
        self.assertEqual(len(sn), 7)
        self.assertEqual(sn, [1, 3, 4, 5, 9, 10, 11])

        sn = e("~+2", 3, 14)
        self.assertEqual(len(sn), 3)
        self.assertEqual(sn, [14, 15, 16])

        sn = e("~+", 2, 14)
        self.assertEqual(len(sn), 2)
        self.assertEqual(sn, [14, 15])

class TestVersionNumber(TestCase):
    """
    Unit tests for version number functions
    """

    def test_tuple(self):

        v = version.inventreeVersionTuple()
        self.assertEqual(len(v), 3)

        s = '.'.join([str(i) for i in v])

        self.assertTrue(s in version.inventreeVersion())

    def test_comparison(self):
        """
        Test direct comparison of version numbers
        """

        v_a = version.inventreeVersionTuple('1.2.0')
        v_b = version.inventreeVersionTuple('1.2.3')
        v_c = version.inventreeVersionTuple('1.2.4')
        v_d = version.inventreeVersionTuple('2.0.0')

        self.assertTrue(v_b > v_a)
        self.assertTrue(v_c > v_b)
        self.assertTrue(v_d > v_c)
        self.assertTrue(v_d > v_a)

class CurrencyTests(TestCase):
    """
    Unit tests for currency / exchange rate functionality
    """

    def test_rates(self):

        # Initially, there will not be any exchange rate information
        rates = Rate.objects.all()

        self.assertEqual(rates.count(), 0)

        # Without rate information, we cannot convert anything...
        with self.assertRaises(MissingRate):
            convert_money(Money(100, 'USD'), 'AUD')

        with self.assertRaises(MissingRate):
            convert_money(Money(100, 'AUD'), 'USD')

        InvenTree.tasks.update_exchange_rates()

        rates = Rate.objects.all()

        self.assertEqual(rates.count(), len(currency_codes()))

        # Now that we have some exchange rate information, we can perform conversions

        # Forwards
        convert_money(Money(100, 'USD'), 'AUD')

        # Backwards
        convert_money(Money(100, 'AUD'), 'USD')

        # Convert between non base currencies
        convert_money(Money(100, 'CAD'), 'NZD')

        # Convert to a symbol which is not covered
        with self.assertRaises(MissingRate):
            convert_money(Money(100, 'GBP'), 'ZWL')

class TestStatus(TestCase):
    """
    Unit tests for status functions
    """

    def test_check_system_healt(self):
        """test that the system health check is false in testing -> background worker not running"""
        self.assertEqual(status.check_system_health(), False)

    def test_TestMode(self):
        self.assertTrue(ready.isInTestMode())

    def test_Importing(self):
        self.assertEqual(ready.isImportingData(), False)

class TestSettings(TestCase):
    """
    Unit tests for settings
    """

    def setUp(self) -> None:
        self.user_mdl = get_user_model()
        self.env = EnvironmentVarGuard()

    def run_reload(self):
        from plugin import registry

        with self.env:
            settings.USER_ADDED = False
            registry.reload_plugins()

    @override_settings(TESTING_ENV=True)
    def test_set_user_to_few(self):
        # add shortcut
        user_count = self.user_mdl.objects.count
        # enable testing mode
        settings.TESTING_ENV = True

        # nothing set
        self.assertEqual(user_count(), 0)

        # not enough set
        self.env.set('INVENTREE_ADMIN_USER', 'admin')  # set username
        self.run_reload()
        self.assertEqual(user_count(), 0)

        # enough set
        self.env.set('INVENTREE_ADMIN_USER', 'admin')  # set username
        self.env.set('INVENTREE_ADMIN_EMAIL', 'info@example.com')  # set email
        self.env.set('INVENTREE_ADMIN_PASSWORD', 'password123')  # set password
        self.run_reload()
        self.assertEqual(user_count(), 1)

        # double adding should not work
        self.env.set('INVENTREE_ADMIN_USER', 'admin')  # set username
        self.env.set('INVENTREE_ADMIN_EMAIL', 'info@example.com')  # set email
        self.env.set('INVENTREE_ADMIN_PASSWORD', 'password123')  # set password
        with self.assertRaises(IntegrationPluginError):
            self.run_reload()
        self.assertEqual(user_count(), 1)

        # make sure to clean up
        settings.TESTING_ENV = False
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.urls import reverse

from InvenTree.api_tester import InvenTreeAPITestCase

class PluginDetailAPITest(InvenTreeAPITestCase):
    """
    Tests the plugin API endpoints
    """

    roles = [
        'admin.add',
        'admin.view',
        'admin.change',
        'admin.delete',
    ]

    def setUp(self):
        self.MSG_NO_PKG = 'Either packagename of URL must be provided'

        self.PKG_NAME = 'minimal'
        self.PKG_URL = 'git+https://github.com/geoffrey-a-reed/minimal'
        super().setUp()

    def test_plugin_install(self):
        """
        Test the plugin install command
        """
        url = reverse('api-plugin-install')

        # valid - Pypi
        data = self.post(url, {
            'confirm': True,
            'packagename': self.PKG_NAME
        }, expected_code=201).data
        self.assertEqual(data['success'], True)

        # valid - github url
        data = self.post(url, {
            'confirm': True,
            'url': self.PKG_URL
        }, expected_code=201).data
        self.assertEqual(data['success'], True)

        # invalid tries
        # no input
        self.post(url, {}, expected_code=400)

        # no package info
        data = self.post(url, {
            'confirm': True,
        }, expected_code=400).data
        self.assertEqual(data['url'][0].title().upper(), self.MSG_NO_PKG.upper())
        self.assertEqual(data['packagename'][0].title().upper(), self.MSG_NO_PKG.upper())

        # not confirmed
        self.post(url, {
            'packagename': self.PKG_NAME
        }, expected_code=400).data
        data = self.post(url, {
            'packagename': self.PKG_NAME,
            'confirm': False,
        }, expected_code=400).data
        self.assertEqual(data['confirm'][0].title().upper(), 'Installation not confirmed'.upper())

    def test_admin_action(self):
        """
        Test the PluginConfig action commands
        """
        from plugin.models import PluginConfig
        from plugin import registry

        url = reverse('admin:plugin_pluginconfig_changelist')
        fixtures = PluginConfig.objects.all()

        # check if plugins were registered -> in some test setups the startup has no db access
        print(f'[PLUGIN-TEST] currently {len(fixtures)} plugin entries found')
        if not fixtures:
            registry.reload_plugins()
            fixtures = PluginConfig.objects.all()
            print(f'Reloaded plugins - now {len(fixtures)} entries found')

        print([str(a) for a in fixtures])
        fixtures = fixtures[0:1]
        # deactivate plugin
        response = self.client.post(url, {
            'action': 'plugin_deactivate',
            'index': 0,
            '_selected_action': [f.pk for f in fixtures],
        }, follow=True)
        self.assertEqual(response.status_code, 200)

        # deactivate plugin - deactivate again -> nothing will hapen but the nothing 'changed' function is triggered
        response = self.client.post(url, {
            'action': 'plugin_deactivate',
            'index': 0,
            '_selected_action': [f.pk for f in fixtures],
        }, follow=True)
        self.assertEqual(response.status_code, 200)

        # activate plugin
        response = self.client.post(url, {
            'action': 'plugin_activate',
            'index': 0,
            '_selected_action': [f.pk for f in fixtures],
        }, follow=True)
        self.assertEqual(response.status_code, 200)

        # activate everything
        fixtures = PluginConfig.objects.all()
        response = self.client.post(url, {
            'action': 'plugin_activate',
            'index': 0,
            '_selected_action': [f.pk for f in fixtures],
        }, follow=True)
        self.assertEqual(response.status_code, 200)

        fixtures = PluginConfig.objects.filter(active=True)
        # save to deactivate a plugin
        response = self.client.post(reverse('admin:plugin_pluginconfig_change', args=(fixtures.first().pk, )), {
            '_save': 'Save',
        }, follow=True)
        self.assertEqual(response.status_code, 200)

    def test_model(self):
        """
        Test the PluginConfig model
        """
        from plugin.models import PluginConfig
        from plugin import registry

        fixtures = PluginConfig.objects.all()

        # check if plugins were registered
        if not fixtures:
            registry.reload_plugins()
            fixtures = PluginConfig.objects.all()

        # check mixin registry
        plg = fixtures.first()
        mixin_dict = plg.mixins()
        self.assertIn('base', mixin_dict)
        self.assertDictContainsSubset({'base': {'key': 'base', 'human_name': 'base'}}, mixin_dict)

        # check reload on save
        with self.assertWarns(Warning) as cm:
            plg_inactive = fixtures.filter(active=False).first()
            plg_inactive.active = True
            plg_inactive.save()
        self.assertEqual(cm.warning.args[0], 'A reload was triggered')
<commit_msg>Add tests for plugin installs form url with pkgn<commit_after># -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.urls import reverse

from InvenTree.api_tester import InvenTreeAPITestCase

class PluginDetailAPITest(InvenTreeAPITestCase):
    """
    Tests the plugin API endpoints
    """

    roles = [
        'admin.add',
        'admin.view',
        'admin.change',
        'admin.delete',
    ]

    def setUp(self):
        self.MSG_NO_PKG = 'Either packagename of URL must be provided'

        self.PKG_NAME = 'minimal'
        self.PKG_URL = 'git+https://github.com/geoffrey-a-reed/minimal'
        super().setUp()

    def test_plugin_install(self):
        """
        Test the plugin install command
        """
        url = reverse('api-plugin-install')

        # valid - Pypi
        data = self.post(url, {
            'confirm': True,
            'packagename': self.PKG_NAME
        }, expected_code=201).data
        self.assertEqual(data['success'], True)

        # valid - github url
        data = self.post(url, {
            'confirm': True,
            'url': self.PKG_URL
        }, expected_code=201).data
        self.assertEqual(data['success'], True)

        # valid - github url and packagename
        data = self.post(url, {
            'confirm': True,
            'url': self.PKG_URL,
            'packagename': 'minimal',
        }, expected_code=201).data
        self.assertEqual(data['success'], True)

        # invalid tries
        # no input
        self.post(url, {}, expected_code=400)

        # no package info
        data = self.post(url, {
            'confirm': True,
        }, expected_code=400).data
        self.assertEqual(data['url'][0].title().upper(), self.MSG_NO_PKG.upper())
        self.assertEqual(data['packagename'][0].title().upper(), self.MSG_NO_PKG.upper())

        # not confirmed
        self.post(url, {
            'packagename': self.PKG_NAME
        }, expected_code=400).data
        data = self.post(url, {
            'packagename': self.PKG_NAME,
            'confirm': False,
        }, expected_code=400).data
        self.assertEqual(data['confirm'][0].title().upper(), 'Installation not confirmed'.upper())

    def test_admin_action(self):
        """
        Test the PluginConfig action commands
        """
        from plugin.models import PluginConfig
        from plugin import registry

        url = reverse('admin:plugin_pluginconfig_changelist')
        fixtures = PluginConfig.objects.all()

        # check if plugins were registered -> in some test setups the startup has no db access
        print(f'[PLUGIN-TEST] currently {len(fixtures)} plugin entries found')
        if not fixtures:
            registry.reload_plugins()
            fixtures = PluginConfig.objects.all()
            print(f'Reloaded plugins - now {len(fixtures)} entries found')

        print([str(a) for a in fixtures])
        fixtures = fixtures[0:1]
        # deactivate plugin
        response = self.client.post(url, {
            'action': 'plugin_deactivate',
            'index': 0,
            '_selected_action': [f.pk for f in fixtures],
        }, follow=True)
        self.assertEqual(response.status_code, 200)

        # deactivate plugin - deactivate again -> nothing will hapen but the nothing 'changed' function is triggered
        response = self.client.post(url, {
            'action': 'plugin_deactivate',
            'index': 0,
            '_selected_action': [f.pk for f in fixtures],
        }, follow=True)
        self.assertEqual(response.status_code, 200)

        # activate plugin
        response = self.client.post(url, {
            'action': 'plugin_activate',
            'index': 0,
            '_selected_action': [f.pk for f in fixtures],
        }, follow=True)
        self.assertEqual(response.status_code, 200)

        # activate everything
        fixtures = PluginConfig.objects.all()
        response = self.client.post(url, {
            'action': 'plugin_activate',
            'index': 0,
            '_selected_action': [f.pk for f in fixtures],
        }, follow=True)
        self.assertEqual(response.status_code, 200)

        fixtures = PluginConfig.objects.filter(active=True)
        # save to deactivate a plugin
        response = self.client.post(reverse('admin:plugin_pluginconfig_change', args=(fixtures.first().pk, )), {
            '_save': 'Save',
        }, follow=True)
        self.assertEqual(response.status_code, 200)

    def test_model(self):
        """
        Test the PluginConfig model
        """
        from plugin.models import PluginConfig
        from plugin import registry

        fixtures = PluginConfig.objects.all()

        # check if plugins were registered
        if not fixtures:
            registry.reload_plugins()
            fixtures = PluginConfig.objects.all()

        # check mixin registry
        plg = fixtures.first()
        mixin_dict = plg.mixins()
        self.assertIn('base', mixin_dict)
        self.assertDictContainsSubset({'base': {'key': 'base', 'human_name': 'base'}}, mixin_dict)

        # check reload on save
        with self.assertWarns(Warning) as cm:
            plg_inactive = fixtures.filter(active=False).first()
            plg_inactive.active = True
            plg_inactive.save()
        self.assertEqual(cm.warning.args[0], 'A reload was triggered')
<|endoftext|>


<commit_before>"""TestCases for exercising a Recno DB.
"""

import os
import errno
from pprint import pprint
import unittest

from test_all import db, test_support, verbose, get_new_environment_path, get_new_database_path

letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

#----------------------------------------------------------------------

class SimpleRecnoTestCase(unittest.TestCase):
    import sys
    if sys.version_info[:3] < (2, 4, 0):
        def assertFalse(self, expr, msg=None):
            self.failIf(expr,msg=msg)

    def setUp(self):
        self.filename = get_new_database_path()
        self.homeDir = None

    def tearDown(self):
        test_support.unlink(self.filename)
        if self.homeDir:
            test_support.rmtree(self.homeDir)

    def test01_basic(self):
        d = db.DB()

        get_returns_none = d.set_get_returns_none(2)
        d.set_get_returns_none(get_returns_none)

        d.open(self.filename, db.DB_RECNO, db.DB_CREATE)

        for x in letters:
            recno = d.append(x * 60)
            self.assertIsInstance(recno, int)
            self.assertGreaterEqual(recno, 1)
            if verbose:
                print recno,

        if verbose: print

        stat = d.stat()
        if verbose:
            pprint(stat)

        for recno in range(1, len(d)+1):
            data = d[recno]
            if verbose:
                print data

            self.assertIsInstance(data, str)
            self.assertEqual(data, d.get(recno))

        try:
            data = d[0]  # This should raise a KeyError!?!?!
        except db.DBInvalidArgError, val:
            self.assertEqual(val.args[0], db.EINVAL)
            if verbose: print val
        else:
            self.fail("expected exception")

        # test that has_key raises DB exceptions (fixed in pybsddb 4.3.2)
        try:
            d.has_key(0)
        except db.DBError, val:
            pass
        else:
            self.fail("has_key did not raise a proper exception")

        try:
            data = d[100]
        except KeyError:
            pass
        else:
            self.fail("expected exception")

        try:
            data = d.get(100)
        except db.DBNotFoundError, val:
            if get_returns_none:
                self.fail("unexpected exception")
        else:
            self.assertEqual(data, None)

        keys = d.keys()
        if verbose:
            print keys
        self.assertIsInstance(keys, list)
        self.assertIsInstance(keys[0], int)
        self.assertEqual(len(keys), len(d))

        items = d.items()
        if verbose:
            pprint(items)
        self.assertIsInstance(items, list)
        self.assertIsInstance(items[0], tuple)
        self.assertEqual(len(items[0]), 2)
        self.assertIsInstance(items[0][0], int)
        self.assertIsInstance(items[0][1], str)
        self.assertEqual(len(items), len(d))

        self.assertTrue(d.has_key(25))

        del d[25]
        self.assertFalse(d.has_key(25))

        d.delete(13)
        self.assertFalse(d.has_key(13))

        data = d.get_both(26, "z" * 60)
        self.assertEqual(data, "z" * 60, 'was %r' % data)
        if verbose:
            print data

        fd = d.fd()
        if verbose:
            print fd

        c = d.cursor()
        rec = c.first()
        while rec:
            if verbose:
                print rec
            rec = c.next()

        c.set(50)
        rec = c.current()
        if verbose:
            print rec

        c.put(-1, "a replacement record", db.DB_CURRENT)

        c.set(50)
        rec = c.current()
        self.assertEqual(rec, (50, "a replacement record"))
        if verbose:
            print rec

        rec = c.set_range(30)
        if verbose:
            print rec

        # test that non-existent key lookups work (and that
        # DBC_set_range doesn't have a memleak under valgrind)
        rec = c.set_range(999999)
        self.assertEqual(rec, None)
        if verbose:
            print rec

        c.close()
        d.close()

        d = db.DB()
        d.open(self.filename)
        c = d.cursor()

        # put a record beyond the consecutive end of the recno's
        d[100] = "way out there"
        self.assertEqual(d[100], "way out there")

        try:
            data = d[99]
        except KeyError:
            pass
        else:
            self.fail("expected exception")

        try:
            d.get(99)
        except db.DBKeyEmptyError, val:
            if get_returns_none:
                self.fail("unexpected DBKeyEmptyError exception")
            else:
                self.assertEqual(val.args[0], db.DB_KEYEMPTY)
                if verbose: print val
        else:
            if not get_returns_none:
                self.fail("expected exception")

        rec = c.set(40)
        while rec:
            if verbose:
                print rec
            rec = c.next()

        c.close()
        d.close()

    def test02_WithSource(self):
        """
        A Recno file that is given a "backing source file" is essentially a
        simple ASCII file.  Normally each record is delimited by \n and so is
        just a line in the file, but you can set a different record delimiter
        if needed.
        """
        homeDir = get_new_environment_path()
        self.homeDir = homeDir
        source = os.path.join(homeDir, 'test_recno.txt')
        if not os.path.isdir(homeDir):
            os.mkdir(homeDir)
        f = open(source, 'w') # create the file
        f.close()

        d = db.DB()
        # This is the default value, just checking if both int
        d.set_re_delim(0x0A)
        d.set_re_delim('\n')  # and char can be used...
        d.set_re_source(source)
        d.open(self.filename, db.DB_RECNO, db.DB_CREATE)

        data = "The quick brown fox jumped over the lazy dog".split()
        for datum in data:
            d.append(datum)
        d.sync()
        d.close()

        # get the text from the backing source
        text = open(source, 'r').read()
        text = text.strip()
        if verbose:
            print text
            print data
            print text.split('\n')

        self.assertEqual(text.split('\n'), data)

        # open as a DB again
        d = db.DB()
        d.set_re_source(source)
        d.open(self.filename, db.DB_RECNO)

        d[3] = 'reddish-brown'
        d[8] = 'comatose'

        d.sync()
        d.close()

        text = open(source, 'r').read()
        text = text.strip()
        if verbose:
            print text
            print text.split('\n')

        self.assertEqual(text.split('\n'),
           "The quick reddish-brown fox jumped over the comatose dog".split())

    def test03_FixedLength(self):
        d = db.DB()
        d.set_re_len(40)  # fixed length records, 40 bytes long
        d.set_re_pad('-') # sets the pad character...
        d.set_re_pad(45)  # ...test both int and char
        d.open(self.filename, db.DB_RECNO, db.DB_CREATE)

        for x in letters:
            d.append(x * 35)    # These will be padded

        d.append('.' * 40)      # this one will be exact

        try:                    # this one will fail
            d.append('bad' * 20)
        except db.DBInvalidArgError, val:
            self.assertEqual(val.args[0], db.EINVAL)
            if verbose: print val
        else:
            self.fail("expected exception")

        c = d.cursor()
        rec = c.first()
        while rec:
            if verbose:
                print rec
            rec = c.next()

        c.close()
        d.close()

#----------------------------------------------------------------------

def test_suite():
    return unittest.makeSuite(SimpleRecnoTestCase)

if __name__ == '__main__':
    unittest.main(defaultTest='test_suite')
<commit_msg>Fix test_recno.<commit_after>"""TestCases for exercising a Recno DB.
"""

import os
import errno
from pprint import pprint
import unittest

from test_all import db, test_support, verbose, get_new_environment_path, get_new_database_path

letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

#----------------------------------------------------------------------

class SimpleRecnoTestCase(unittest.TestCase):
    import sys
    if sys.version_info[:3] < (2, 4, 0):
        def assertFalse(self, expr, msg=None):
            self.failIf(expr,msg=msg)

    def setUp(self):
        self.filename = get_new_database_path()
        self.homeDir = None

    def tearDown(self):
        test_support.unlink(self.filename)
        if self.homeDir:
            test_support.rmtree(self.homeDir)

    def test01_basic(self):
        d = db.DB()

        get_returns_none = d.set_get_returns_none(2)
        d.set_get_returns_none(get_returns_none)

        d.open(self.filename, db.DB_RECNO, db.DB_CREATE)

        for x in letters:
            recno = d.append(x * 60)
            self.assertTrue(isinstance(recno, int))
            self.assertGreaterEqual(recno, 1)
            if verbose:
                print recno,

        if verbose: print

        stat = d.stat()
        if verbose:
            pprint(stat)

        for recno in range(1, len(d)+1):
            data = d[recno]
            if verbose:
                print data

            self.assertTrue(isinstance(data, str))
            self.assertEqual(data, d.get(recno))

        try:
            data = d[0]  # This should raise a KeyError!?!?!
        except db.DBInvalidArgError, val:
            self.assertEqual(val.args[0], db.EINVAL)
            if verbose: print val
        else:
            self.fail("expected exception")

        # test that has_key raises DB exceptions (fixed in pybsddb 4.3.2)
        try:
            d.has_key(0)
        except db.DBError, val:
            pass
        else:
            self.fail("has_key did not raise a proper exception")

        try:
            data = d[100]
        except KeyError:
            pass
        else:
            self.fail("expected exception")

        try:
            data = d.get(100)
        except db.DBNotFoundError, val:
            if get_returns_none:
                self.fail("unexpected exception")
        else:
            self.assertEqual(data, None)

        keys = d.keys()
        if verbose:
            print keys
        self.assertTrue(isinstance(keys, list))
        self.assertTrue(isinstance(keys[0], int))
        self.assertEqual(len(keys), len(d))

        items = d.items()
        if verbose:
            pprint(items)
        self.assertTrue(isinstance(items, list))
        self.assertTrue(isinstance(items[0], tuple))
        self.assertEqual(len(items[0]), 2)
        self.assertTrue(isinstance(items[0][0], int))
        self.assertTrue(isinstance(items[0][1], str))
        self.assertEqual(len(items), len(d))

        self.assertTrue(d.has_key(25))

        del d[25]
        self.assertFalse(d.has_key(25))

        d.delete(13)
        self.assertFalse(d.has_key(13))

        data = d.get_both(26, "z" * 60)
        self.assertEqual(data, "z" * 60, 'was %r' % data)
        if verbose:
            print data

        fd = d.fd()
        if verbose:
            print fd

        c = d.cursor()
        rec = c.first()
        while rec:
            if verbose:
                print rec
            rec = c.next()

        c.set(50)
        rec = c.current()
        if verbose:
            print rec

        c.put(-1, "a replacement record", db.DB_CURRENT)

        c.set(50)
        rec = c.current()
        self.assertEqual(rec, (50, "a replacement record"))
        if verbose:
            print rec

        rec = c.set_range(30)
        if verbose:
            print rec

        # test that non-existent key lookups work (and that
        # DBC_set_range doesn't have a memleak under valgrind)
        rec = c.set_range(999999)
        self.assertEqual(rec, None)
        if verbose:
            print rec

        c.close()
        d.close()

        d = db.DB()
        d.open(self.filename)
        c = d.cursor()

        # put a record beyond the consecutive end of the recno's
        d[100] = "way out there"
        self.assertEqual(d[100], "way out there")

        try:
            data = d[99]
        except KeyError:
            pass
        else:
            self.fail("expected exception")

        try:
            d.get(99)
        except db.DBKeyEmptyError, val:
            if get_returns_none:
                self.fail("unexpected DBKeyEmptyError exception")
            else:
                self.assertEqual(val.args[0], db.DB_KEYEMPTY)
                if verbose: print val
        else:
            if not get_returns_none:
                self.fail("expected exception")

        rec = c.set(40)
        while rec:
            if verbose:
                print rec
            rec = c.next()

        c.close()
        d.close()

    def test02_WithSource(self):
        """
        A Recno file that is given a "backing source file" is essentially a
        simple ASCII file.  Normally each record is delimited by \n and so is
        just a line in the file, but you can set a different record delimiter
        if needed.
        """
        homeDir = get_new_environment_path()
        self.homeDir = homeDir
        source = os.path.join(homeDir, 'test_recno.txt')
        if not os.path.isdir(homeDir):
            os.mkdir(homeDir)
        f = open(source, 'w') # create the file
        f.close()

        d = db.DB()
        # This is the default value, just checking if both int
        d.set_re_delim(0x0A)
        d.set_re_delim('\n')  # and char can be used...
        d.set_re_source(source)
        d.open(self.filename, db.DB_RECNO, db.DB_CREATE)

        data = "The quick brown fox jumped over the lazy dog".split()
        for datum in data:
            d.append(datum)
        d.sync()
        d.close()

        # get the text from the backing source
        text = open(source, 'r').read()
        text = text.strip()
        if verbose:
            print text
            print data
            print text.split('\n')

        self.assertEqual(text.split('\n'), data)

        # open as a DB again
        d = db.DB()
        d.set_re_source(source)
        d.open(self.filename, db.DB_RECNO)

        d[3] = 'reddish-brown'
        d[8] = 'comatose'

        d.sync()
        d.close()

        text = open(source, 'r').read()
        text = text.strip()
        if verbose:
            print text
            print text.split('\n')

        self.assertEqual(text.split('\n'),
           "The quick reddish-brown fox jumped over the comatose dog".split())

    def test03_FixedLength(self):
        d = db.DB()
        d.set_re_len(40)  # fixed length records, 40 bytes long
        d.set_re_pad('-') # sets the pad character...
        d.set_re_pad(45)  # ...test both int and char
        d.open(self.filename, db.DB_RECNO, db.DB_CREATE)

        for x in letters:
            d.append(x * 35)    # These will be padded

        d.append('.' * 40)      # this one will be exact

        try:                    # this one will fail
            d.append('bad' * 20)
        except db.DBInvalidArgError, val:
            self.assertEqual(val.args[0], db.EINVAL)
            if verbose: print val
        else:
            self.fail("expected exception")

        c = d.cursor()
        rec = c.first()
        while rec:
            if verbose:
                print rec
            rec = c.next()

        c.close()
        d.close()

#----------------------------------------------------------------------

def test_suite():
    return unittest.makeSuite(SimpleRecnoTestCase)

if __name__ == '__main__':
    unittest.main(defaultTest='test_suite')
<|endoftext|>


<commit_before># Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import print_function, division, absolute_import

import glob
import os
import plistlib
import re
import tempfile
import time

from booleanOperations import BooleanOperationManager
from cu2qu.ufo import font_to_quadratic, fonts_to_quadratic
from defcon import Font
from fontTools import subset
from fontTools.misc.transform import Identity
from fontTools.pens.transformPen import TransformPen
from glyphs2ufo.glyphslib import build_masters, build_instances
from mutatorMath.ufo import build as build_designspace
from ufo2ft import compileOTF, compileTTF
from ufo2ft.makeotfParts import FeatureOTFCompiler

class FontProject:
    """Provides methods for building fonts."""

    @staticmethod
    def preprocess(glyphs_path):
        """Return Glyphs source with illegal glyph/class names changed."""

        with open(glyphs_path) as fp:
            text = fp.read()
        names = set(re.findall('\n(?:glyph)?name = "(.+-.+)";\n', text))

        if names:
            num_names = len(names)
            printed_names = sorted(names)[:5]
            if num_names > 5:
                printed_names.append('...')
            print('Found %s glyph names containing hyphens: %s' % (
                num_names, ', '.join(printed_names)))
            print('Replacing all hyphens with underscores.')

        for old_name in names:
            new_name = old_name.replace('-', '_')
            text = text.replace(old_name, new_name)
        return text

    def build_masters(self, glyphs_path, is_italic=False):
        """Build master UFOs from Glyphs source."""

        master_dir = self._output_dir('ufo')
        return build_masters(glyphs_path, master_dir, is_italic)

    def build_instances(self, glyphs_path, is_italic=False):
        """Build instance UFOs from Glyphs source."""

        master_dir = self._output_dir('ufo')
        instance_dir = self._output_dir('ufo', is_instance=True)
        return build_instances(glyphs_path, master_dir, instance_dir, is_italic)

    def remove_overlaps(self, ufos):
        """Remove overlaps in UFOs' glyphs' contours."""

        manager = BooleanOperationManager()
        for ufo in ufos:
            print('>> Removing overlaps for ' + self._font_name(ufo))
            for glyph in ufo:
                contours = list(glyph)
                glyph.clearContours()
                manager.union(contours, glyph.getPointPen())

    def decompose_glyph(self, ufo, glyph):
        """Moves the components of a glyph to its outline."""

        self._deep_copy_contours(ufo, glyph, glyph, Identity)
        glyph.clearComponents()

    def _deep_copy_contours(self, ufo, parent, component, transformation):
        """Copy contours from component to parent, including nested components."""

        for nested in component.components:
            self._deep_copy_contours(
                ufo, parent, ufo[nested.baseGlyph],
                transformation.transform(nested.transformation))

        if component != parent:
            component.draw(TransformPen(parent.getPen(), transformation))

    def build_otfs(self, ufos, **kwargs):
        """Build OpenType binaries with CFF outlines."""

        print('\n>> Building OTFs')

        for ufo in ufos:
            for glyph in ufo:
                self.decompose_glyph(ufo, glyph)
        self.remove_overlaps(ufos)
        for ufo in ufos:
            self.save_otf(ufo, **kwargs)

    def build_ttfs(self, ufos, **kwargs):
        """Build OpenType binaries with TrueType outlines."""

        print('\n>> Building TTFs')

        self.remove_overlaps(ufos)

        start_t = time.time()
        for ufo in ufos:
            print('>> Converting curves for ' + self._font_name(ufo))
            font_to_quadratic(ufo, dump_stats=True)
        print('[took %f seconds]' % (time.time() - start_t))

        for ufo in ufos:
            self.save_otf(ufo, ttf=True, **kwargs)

    def build_interpolatable_ttfs(self, ufos, **kwargs):
        """Build OpenType binaries with interpolatable TrueType outlines."""

        print('\n>> Building interpolation-compatible TTFs')

        print('>> Converting curves to quadratic')
        start_t = time.time()
        fonts_to_quadratic(ufos, dump_stats=True)
        print('[took %f seconds]' % (time.time() - start_t))

        for ufo in ufos:
            self.save_otf(ufo, ttf=True, interpolatable=True, **kwargs)

    def save_otf(
            self, ufo, ttf=False, interpolatable=False, mti_paths=None,
            is_instance=False, use_afdko=False, subset=True):
        """Write an OpenType binary."""

        name = self._font_name(ufo)
        print('>> Saving %s for %s' % (ext.upper(), name))
        mti_feafiles = mti_paths and mti_paths.get(name)

        ext = 'ttf' if ttf else 'otf'
        fea_compiler = FDKFeatureCompiler if use_afdko else FeatureOTFCompiler
        otf_path = self._output_path(ufo, ext, is_instance, interpolatable)
        otf_compiler = compileTTF if ttf else compileOTF
        otf = otf_compiler(ufo, featureCompilerClass=fea_compiler,
                           mtiFeaFiles=mti_feafiles)
        otf.save(otf_path)

        if subset:
            self.subset_otf_from_ufo(otf_path, ufo)

    def subset_otf_from_ufo(self, otf_path, ufo):
        """Subset a font using export flags set by glyphs2ufo."""

        font_lib_prefix = 'com.schriftgestaltung.'
        glyph_lib_prefix = font_lib_prefix + 'Glyphs.'

        keep_glyphs = set(ufo.lib.get(font_lib_prefix + 'Keep Glyphs', []))

        include = []
        glyph_order = ufo.lib['public.glyphOrder']
        for glyph_name in glyph_order:
            glyph = ufo[glyph_name]
            if ((keep_glyphs and glyph_name not in keep_glyphs) or
                not glyph.lib.get(glyph_lib_prefix + 'Export', True)):
                continue
            include.append(glyph_name)

        # copied from nototools.subset
        opt = subset.Options()
        opt.name_IDs = ['*']
        opt.name_legacy = True
        opt.name_languages = ['*']
        opt.layout_features = ['*']
        opt.notdef_outline = True
        opt.recalc_bounds = True
        opt.recalc_timestamp = True
        opt.canonical_order = True

        opt.glyph_names = ufo.lib.get(
            font_lib_prefix + "Don't use Production Names")

        font = subset.load_font(otf_path, opt, lazy=False)
        subsetter = subset.Subsetter(options=opt)
        subsetter.populate(glyphs=include)
        subsetter.subset(font)
        subset.save_font(font, otf_path, opt)

    def run_from_glyphs(
            self, glyphs_path, preprocess=True, interpolate=False, **kwargs):
        """Run toolchain from Glyphs source to OpenType binaries."""

        is_italic = 'Italic' in glyphs_path

        if preprocess:
            print('>> Checking Glyphs source for illegal glyph names')
            glyphs_source = self.preprocess(glyphs_path)
            tmp_glyphs_file = tempfile.NamedTemporaryFile()
            glyphs_path = tmp_glyphs_file.name
            tmp_glyphs_file.write(glyphs_source)
            tmp_glyphs_file.seek(0)

        if interpolate:
            print('>> Interpolating master UFOs from Glyphs source')
            ufos = self.build_instances(glyphs_path, is_italic)
        else:
            print('>> Loading master UFOs from Glyphs source')
            ufos = self.build_masters(glyphs_path, is_italic)

        self.run_from_ufos(ufos, is_instance=interpolate, **kwargs)

    def run_from_designspace(self, designspace_path, **kwargs):
        """Run toolchain from a MutatorMath design space document to OpenType
        binaries.
        """

        print('>> Interpolating master UFOs from design space')
        results = build_designspace(designspace_path)
        ufos = []
        for result in results:
            ufos.extend(result.values())
        self.run_from_ufos(ufos, **kwargs)

    def run_from_ufos(self, ufos, output=(), mti_source=None, **kwargs):
        """Run toolchain from UFO sources to OpenType binaries."""

        if hasattr(ufos[0], 'path'):
            ufo_paths = [ufo.path for ufo in ufos]
        else:
            if isinstance(ufos, str):
                ufo_paths = glob.glob(ufos)
            else:
                ufo_paths = ufos
            ufos = [Font(path) for path in ufo_paths]

        mti_paths = {}
        if mti_source:
            mti_paths = plistlib.readPlist(mti_source)
            src_dir = os.path.dirname(mti_source)
            for paths in mti_paths.values():
                for table in ('GDEF', 'GPOS', 'GSUB'):
                    paths[table] = os.path.join(src_dir, paths[table])

        need_reload = False
        if 'otf' in output:
            self.build_otfs(ufos, mti_paths=mti_paths, **kwargs)
            need_reload = True

        if 'ttf' in output:
            if need_reload:
                ufos = [Font(path) for path in ufo_paths]
            self.build_ttfs(ufos, mti_paths=mti_paths, **kwargs)
            need_reload = True

        if 'ttf-interpolatable' in output:
            if need_reload:
                ufos = [Font(path) for path in ufo_paths]
            self.build_interpolatable_ttfs(ufos, mti_paths=mti_paths, **kwargs)

    def _font_name(self, ufo):
        return '%s-%s' % (ufo.info.familyName.replace(' ', ''),
                          ufo.info.styleName.replace(' ', ''))

    def _output_dir(self, ext, is_instance=False, interpolatable=False):
        """Generate an output directory."""

        dir_prefix = 'instance_' if is_instance else 'master_'
        dir_suffix = '_interpolatable' if interpolatable else ''
        return dir_prefix + ext + dir_suffix

    def _output_path(self, ufo, ext, is_instance=False, interpolatable=False):
        """Generate output path for a font file with given extension."""

        out_dir = self._output_dir(ext, is_instance, interpolatable)
        if not os.path.exists(out_dir):
            os.makedirs(out_dir)
        return os.path.join(out_dir, '%s.%s' % (self._font_name(ufo), ext))

class FDKFeatureCompiler(FeatureOTFCompiler):
    """An OTF compiler which uses the AFDKO to compile feature syntax."""

    def setupFile_featureTables(self):
        if self.mtiFeaFiles is not None:
            super(FDKFeatureCompiler, self).setupFile_featureTables()

        elif not self.features.strip():
            return

        import subprocess
        from fontTools.ttLib import TTFont
        from fontTools.misc.py23 import tostr

        fd, outline_path = tempfile.mkstemp()
        os.close(fd)
        self.outline.save(outline_path)

        fd, feasrc_path = tempfile.mkstemp()
        os.close(fd)

        fd, fea_path = tempfile.mkstemp()
        os.write(fd, self.features)
        os.close(fd)

        report = tostr(subprocess.check_output([
            "makeotf", "-o", feasrc_path, "-f", outline_path,
            "-ff", fea_path]))
        os.remove(outline_path)
        os.remove(fea_path)

        print(report)
        success = "Done." in report
        if success:
            feasrc = TTFont(feasrc_path)
            for table in ["GDEF", "GPOS", "GSUB"]:
                if table in feasrc:
                    self.outline[table] = feasrc[table]

        feasrc.close()
        os.remove(feasrc_path)
        if not success:
            raise ValueError("Feature syntax compilation failed.")
<commit_msg>Have decompose_glyph take in multiple UFOs<commit_after># Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import print_function, division, absolute_import

import glob
import os
import plistlib
import re
import tempfile
import time

from booleanOperations import BooleanOperationManager
from cu2qu.ufo import font_to_quadratic, fonts_to_quadratic
from defcon import Font
from fontTools import subset
from fontTools.misc.transform import Identity
from fontTools.pens.transformPen import TransformPen
from glyphs2ufo.glyphslib import build_masters, build_instances
from mutatorMath.ufo import build as build_designspace
from ufo2ft import compileOTF, compileTTF
from ufo2ft.makeotfParts import FeatureOTFCompiler

class FontProject:
    """Provides methods for building fonts."""

    @staticmethod
    def preprocess(glyphs_path):
        """Return Glyphs source with illegal glyph/class names changed."""

        with open(glyphs_path) as fp:
            text = fp.read()
        names = set(re.findall('\n(?:glyph)?name = "(.+-.+)";\n', text))

        if names:
            num_names = len(names)
            printed_names = sorted(names)[:5]
            if num_names > 5:
                printed_names.append('...')
            print('Found %s glyph names containing hyphens: %s' % (
                num_names, ', '.join(printed_names)))
            print('Replacing all hyphens with underscores.')

        for old_name in names:
            new_name = old_name.replace('-', '_')
            text = text.replace(old_name, new_name)
        return text

    def build_masters(self, glyphs_path, is_italic=False):
        """Build master UFOs from Glyphs source."""

        master_dir = self._output_dir('ufo')
        return build_masters(glyphs_path, master_dir, is_italic)

    def build_instances(self, glyphs_path, is_italic=False):
        """Build instance UFOs from Glyphs source."""

        master_dir = self._output_dir('ufo')
        instance_dir = self._output_dir('ufo', is_instance=True)
        return build_instances(glyphs_path, master_dir, instance_dir, is_italic)

    def remove_overlaps(self, ufos):
        """Remove overlaps in UFOs' glyphs' contours."""

        manager = BooleanOperationManager()
        for ufo in ufos:
            print('>> Removing overlaps for ' + self._font_name(ufo))
            for glyph in ufo:
                contours = list(glyph)
                glyph.clearContours()
                manager.union(contours, glyph.getPointPen())

    def decompose_glyphs(self, ufos):
        """Move components of UFOs' glyphs to their outlines."""

        for ufo in ufos:
            print('>> Decomposing glyphs for ' + self._font_name(ufo))
            for glyph in ufo:
                self._deep_copy_contours(ufo, glyph, glyph, Identity)
                glyph.clearComponents()

    def _deep_copy_contours(self, ufo, parent, component, transformation):
        """Copy contours from component to parent, including nested components."""

        for nested in component.components:
            self._deep_copy_contours(
                ufo, parent, ufo[nested.baseGlyph],
                transformation.transform(nested.transformation))

        if component != parent:
            component.draw(TransformPen(parent.getPen(), transformation))

    def build_otfs(self, ufos, **kwargs):
        """Build OpenType binaries with CFF outlines."""

        print('\n>> Building OTFs')

        self.decompose_glyphs(ufos)
        self.remove_overlaps(ufos)
        for ufo in ufos:
            self.save_otf(ufo, **kwargs)

    def build_ttfs(self, ufos, **kwargs):
        """Build OpenType binaries with TrueType outlines."""

        print('\n>> Building TTFs')

        self.remove_overlaps(ufos)

        start_t = time.time()
        for ufo in ufos:
            print('>> Converting curves for ' + self._font_name(ufo))
            font_to_quadratic(ufo, dump_stats=True)
        print('[took %f seconds]' % (time.time() - start_t))

        for ufo in ufos:
            self.save_otf(ufo, ttf=True, **kwargs)

    def build_interpolatable_ttfs(self, ufos, **kwargs):
        """Build OpenType binaries with interpolatable TrueType outlines."""

        print('\n>> Building interpolation-compatible TTFs')

        print('>> Converting curves to quadratic')
        start_t = time.time()
        fonts_to_quadratic(ufos, dump_stats=True)
        print('[took %f seconds]' % (time.time() - start_t))

        for ufo in ufos:
            self.save_otf(ufo, ttf=True, interpolatable=True, **kwargs)

    def save_otf(
            self, ufo, ttf=False, interpolatable=False, mti_paths=None,
            is_instance=False, use_afdko=False, subset=True):
        """Write an OpenType binary."""

        name = self._font_name(ufo)
        print('>> Saving %s for %s' % (ext.upper(), name))
        mti_feafiles = mti_paths and mti_paths.get(name)

        ext = 'ttf' if ttf else 'otf'
        fea_compiler = FDKFeatureCompiler if use_afdko else FeatureOTFCompiler
        otf_path = self._output_path(ufo, ext, is_instance, interpolatable)
        otf_compiler = compileTTF if ttf else compileOTF
        otf = otf_compiler(ufo, featureCompilerClass=fea_compiler,
                           mtiFeaFiles=mti_feafiles)
        otf.save(otf_path)

        if subset:
            self.subset_otf_from_ufo(otf_path, ufo)

    def subset_otf_from_ufo(self, otf_path, ufo):
        """Subset a font using export flags set by glyphs2ufo."""

        font_lib_prefix = 'com.schriftgestaltung.'
        glyph_lib_prefix = font_lib_prefix + 'Glyphs.'

        keep_glyphs = set(ufo.lib.get(font_lib_prefix + 'Keep Glyphs', []))

        include = []
        glyph_order = ufo.lib['public.glyphOrder']
        for glyph_name in glyph_order:
            glyph = ufo[glyph_name]
            if ((keep_glyphs and glyph_name not in keep_glyphs) or
                not glyph.lib.get(glyph_lib_prefix + 'Export', True)):
                continue
            include.append(glyph_name)

        # copied from nototools.subset
        opt = subset.Options()
        opt.name_IDs = ['*']
        opt.name_legacy = True
        opt.name_languages = ['*']
        opt.layout_features = ['*']
        opt.notdef_outline = True
        opt.recalc_bounds = True
        opt.recalc_timestamp = True
        opt.canonical_order = True

        opt.glyph_names = ufo.lib.get(
            font_lib_prefix + "Don't use Production Names")

        font = subset.load_font(otf_path, opt, lazy=False)
        subsetter = subset.Subsetter(options=opt)
        subsetter.populate(glyphs=include)
        subsetter.subset(font)
        subset.save_font(font, otf_path, opt)

    def run_from_glyphs(
            self, glyphs_path, preprocess=True, interpolate=False, **kwargs):
        """Run toolchain from Glyphs source to OpenType binaries."""

        is_italic = 'Italic' in glyphs_path

        if preprocess:
            print('>> Checking Glyphs source for illegal glyph names')
            glyphs_source = self.preprocess(glyphs_path)
            tmp_glyphs_file = tempfile.NamedTemporaryFile()
            glyphs_path = tmp_glyphs_file.name
            tmp_glyphs_file.write(glyphs_source)
            tmp_glyphs_file.seek(0)

        if interpolate:
            print('>> Interpolating master UFOs from Glyphs source')
            ufos = self.build_instances(glyphs_path, is_italic)
        else:
            print('>> Loading master UFOs from Glyphs source')
            ufos = self.build_masters(glyphs_path, is_italic)

        self.run_from_ufos(ufos, is_instance=interpolate, **kwargs)

    def run_from_designspace(self, designspace_path, **kwargs):
        """Run toolchain from a MutatorMath design space document to OpenType
        binaries.
        """

        print('>> Interpolating master UFOs from design space')
        results = build_designspace(designspace_path)
        ufos = []
        for result in results:
            ufos.extend(result.values())
        self.run_from_ufos(ufos, **kwargs)

    def run_from_ufos(self, ufos, output=(), mti_source=None, **kwargs):
        """Run toolchain from UFO sources to OpenType binaries."""

        if hasattr(ufos[0], 'path'):
            ufo_paths = [ufo.path for ufo in ufos]
        else:
            if isinstance(ufos, str):
                ufo_paths = glob.glob(ufos)
            else:
                ufo_paths = ufos
            ufos = [Font(path) for path in ufo_paths]

        mti_paths = {}
        if mti_source:
            mti_paths = plistlib.readPlist(mti_source)
            src_dir = os.path.dirname(mti_source)
            for paths in mti_paths.values():
                for table in ('GDEF', 'GPOS', 'GSUB'):
                    paths[table] = os.path.join(src_dir, paths[table])

        need_reload = False
        if 'otf' in output:
            self.build_otfs(ufos, mti_paths=mti_paths, **kwargs)
            need_reload = True

        if 'ttf' in output:
            if need_reload:
                ufos = [Font(path) for path in ufo_paths]
            self.build_ttfs(ufos, mti_paths=mti_paths, **kwargs)
            need_reload = True

        if 'ttf-interpolatable' in output:
            if need_reload:
                ufos = [Font(path) for path in ufo_paths]
            self.build_interpolatable_ttfs(ufos, mti_paths=mti_paths, **kwargs)

    def _font_name(self, ufo):
        return '%s-%s' % (ufo.info.familyName.replace(' ', ''),
                          ufo.info.styleName.replace(' ', ''))

    def _output_dir(self, ext, is_instance=False, interpolatable=False):
        """Generate an output directory."""

        dir_prefix = 'instance_' if is_instance else 'master_'
        dir_suffix = '_interpolatable' if interpolatable else ''
        return dir_prefix + ext + dir_suffix

    def _output_path(self, ufo, ext, is_instance=False, interpolatable=False):
        """Generate output path for a font file with given extension."""

        out_dir = self._output_dir(ext, is_instance, interpolatable)
        if not os.path.exists(out_dir):
            os.makedirs(out_dir)
        return os.path.join(out_dir, '%s.%s' % (self._font_name(ufo), ext))

class FDKFeatureCompiler(FeatureOTFCompiler):
    """An OTF compiler which uses the AFDKO to compile feature syntax."""

    def setupFile_featureTables(self):
        if self.mtiFeaFiles is not None:
            super(FDKFeatureCompiler, self).setupFile_featureTables()

        elif not self.features.strip():
            return

        import subprocess
        from fontTools.ttLib import TTFont
        from fontTools.misc.py23 import tostr

        fd, outline_path = tempfile.mkstemp()
        os.close(fd)
        self.outline.save(outline_path)

        fd, feasrc_path = tempfile.mkstemp()
        os.close(fd)

        fd, fea_path = tempfile.mkstemp()
        os.write(fd, self.features)
        os.close(fd)

        report = tostr(subprocess.check_output([
            "makeotf", "-o", feasrc_path, "-f", outline_path,
            "-ff", fea_path]))
        os.remove(outline_path)
        os.remove(fea_path)

        print(report)
        success = "Done." in report
        if success:
            feasrc = TTFont(feasrc_path)
            for table in ["GDEF", "GPOS", "GSUB"]:
                if table in feasrc:
                    self.outline[table] = feasrc[table]

        feasrc.close()
        os.remove(feasrc_path)
        if not success:
            raise ValueError("Feature syntax compilation failed.")
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

#   Copyright (c) 2010-2014, MIT Probabilistic Computing Project
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

from bayeslite.sqlite3_util import sqlite3_exec_1

bayesdb_schema = """
PRAGMA foreign_keys = ON;
PRAGMA application_id = 1113146434; -- #x42594442, `BYDB'
PRAGMA user_version = 3;

BEGIN;
CREATE TABLE bayesdb_metamodel (
	id		INTEGER NOT NULL UNIQUE PRIMARY KEY CHECK (id >= 0),
	name		TEXT COLLATE NOCASE NOT NULL UNIQUE
);

CREATE TABLE bayesdb_table (
	id		INTEGER NOT NULL UNIQUE PRIMARY KEY CHECK (id >= 0),
	name		TEXT COLLATE NOCASE NOT NULL UNIQUE,
				-- REFERENCES sqlite_master(name)
	metamodel_id	INTEGER NOT NULL REFERENCES bayesdb_metamodel(id),
	metadata	BLOB NOT NULL
);

CREATE TABLE bayesdb_table_column (
	id		INTEGER NOT NULL PRIMARY KEY CHECK (id >= 0),
	table_id	INTEGER NOT NULL REFERENCES bayesdb_table(id),
	name		TEXT COLLATE NOCASE NOT NULL,
	colno		INTEGER NOT NULL,
	UNIQUE (table_id, name),
	UNIQUE (table_id, colno)
);

CREATE TABLE bayesdb_model (
	table_id	INTEGER NOT NULL REFERENCES bayesdb_table(id),
	modelno		INTEGER NOT NULL CHECK (modelno >= 0),
	theta		BLOB NOT NULL,
	PRIMARY KEY (table_id, modelno)
);
COMMIT;
"""

### BayesDB SQLite setup

def bayesdb_install_schema(db):
    application_id = 0
    no_application_id = None
    if db.execute("PRAGMA application_id").fetchall():
        application_id = sqlite3_exec_1(db, "PRAGMA application_id")
        no_application_id = False
    else:
        # raise Warning('SQLite is too old!')
        no_application_id = True
    user_version = sqlite3_exec_1(db, "PRAGMA user_version")
    if application_id == 0 and user_version == 0:
        # Assume we just created the database.
        #
        # XXX What if we opened some random other sqlite file which
        # did not have an application_id or user_version set?  Hope
        # everyone else sets application_id and user_version too...
        #
        # XXX Idiotic Python sqlite3 module has no way to execute a
        # string with multiple SQL commands that doesn't muck with the
        # application's transactions -- db.executescript("...") will
        # issue a COMMIT first, if there is a transaction pending, so
        # we can't just write
        #
        #   with sqlite3_transaction(db):
        #       db.executescript(bayesdb_schema)
        #
        # Instead, we abuse the use of sqlite database connections as
        # context managers that commit/rollback if there is a
        # transaction active.  Otherwise we make no use of the sqlite3
        # module's automatic transaction handling.
        with db:
            db.executescript(bayesdb_schema)
        if not no_application_id:
            assert sqlite3_exec_1(db, "PRAGMA application_id") == 0x42594442
        assert sqlite3_exec_1(db, "PRAGMA user_version") == 3
    elif application_id != 0x42594442:
        raise IOError("Invalid application_id: 0x%08x" % application_id)
    elif user_version != 3:
        raise IOError("Unknown database version: %d" % user_version)
<commit_msg>Fix reopening database without support for application_id.<commit_after># -*- coding: utf-8 -*-

#   Copyright (c) 2010-2014, MIT Probabilistic Computing Project
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

from bayeslite.sqlite3_util import sqlite3_exec_1

bayesdb_schema = """
PRAGMA foreign_keys = ON;
PRAGMA application_id = 1113146434; -- #x42594442, `BYDB'
PRAGMA user_version = 3;

BEGIN;
CREATE TABLE bayesdb_metamodel (
	id		INTEGER NOT NULL UNIQUE PRIMARY KEY CHECK (id >= 0),
	name		TEXT COLLATE NOCASE NOT NULL UNIQUE
);

CREATE TABLE bayesdb_table (
	id		INTEGER NOT NULL UNIQUE PRIMARY KEY CHECK (id >= 0),
	name		TEXT COLLATE NOCASE NOT NULL UNIQUE,
				-- REFERENCES sqlite_master(name)
	metamodel_id	INTEGER NOT NULL REFERENCES bayesdb_metamodel(id),
	metadata	BLOB NOT NULL
);

CREATE TABLE bayesdb_table_column (
	id		INTEGER NOT NULL PRIMARY KEY CHECK (id >= 0),
	table_id	INTEGER NOT NULL REFERENCES bayesdb_table(id),
	name		TEXT COLLATE NOCASE NOT NULL,
	colno		INTEGER NOT NULL,
	UNIQUE (table_id, name),
	UNIQUE (table_id, colno)
);

CREATE TABLE bayesdb_model (
	table_id	INTEGER NOT NULL REFERENCES bayesdb_table(id),
	modelno		INTEGER NOT NULL CHECK (modelno >= 0),
	theta		BLOB NOT NULL,
	PRIMARY KEY (table_id, modelno)
);
COMMIT;
"""

### BayesDB SQLite setup

def bayesdb_install_schema(db):
    application_id = 0
    application_id_ok = None
    if db.execute("PRAGMA application_id").fetchall():
        application_id = sqlite3_exec_1(db, "PRAGMA application_id")
        application_id_ok = True
    else:
        # raise Warning('SQLite is too old!')
        application_id_ok = False
    user_version = sqlite3_exec_1(db, "PRAGMA user_version")
    if application_id == 0 and user_version == 0:
        # Assume we just created the database.
        #
        # XXX What if we opened some random other sqlite file which
        # did not have an application_id or user_version set?  Hope
        # everyone else sets application_id and user_version too...
        #
        # XXX Idiotic Python sqlite3 module has no way to execute a
        # string with multiple SQL commands that doesn't muck with the
        # application's transactions -- db.executescript("...") will
        # issue a COMMIT first, if there is a transaction pending, so
        # we can't just write
        #
        #   with sqlite3_transaction(db):
        #       db.executescript(bayesdb_schema)
        #
        # Instead, we abuse the use of sqlite database connections as
        # context managers that commit/rollback if there is a
        # transaction active.  Otherwise we make no use of the sqlite3
        # module's automatic transaction handling.
        with db:
            db.executescript(bayesdb_schema)
        if application_id_ok:
            assert sqlite3_exec_1(db, "PRAGMA application_id") == 0x42594442
        assert sqlite3_exec_1(db, "PRAGMA user_version") == 3
    elif application_id_ok and application_id != 0x42594442:
        raise IOError("Invalid application_id: 0x%08x" % application_id)
    elif user_version != 3:
        raise IOError("Unknown database version: %d" % user_version)
<|endoftext|>