<commit_before># Copyright (c) 2014 Adafruit Industries
# Author: Tony DiCola
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from . import HT16K33
from PIL import Image
import time

class Matrix8x8(HT16K33.HT16K33):
    """Single color 8x8 matrix LED backpack display."""

    def __init__(self, **kwargs):
        """Initialize display.  All arguments will be passed to the HT16K33 class
        initializer, including optional I2C address and bus number parameters.
        """
        super(Matrix8x8, self).__init__(**kwargs)

    def set_pixel(self, x, y, value):
        """Set pixel at position x, y to the given value.  X and Y should be values
        of 0 to 8.  Value should be 0 for off and non-zero for on.
        """
        if x < 0 or x > 7 or y < 0 or y > 7:
            # Ignore out of bounds pixels.
            return
        self.set_led(y * 16 + ((x + 7) % 8), value)

    def set_image(self, image):
        """Set display buffer to Python Image Library image.  Image will be converted
        to 1 bit color and non-zero color values will light the LEDs.
        """
        imwidth, imheight = image.size
        if imwidth != 8 or imheight != 8:
            raise ValueError('Image must be an 8x8 pixels in size.')
        # Convert image to 1 bit color and grab all the pixels.
        pix = image.convert('1').load()
        # Loop through each pixel and write the display buffer pixel.
        for x in [0, 1, 2, 3, 4, 5, 6, 7]:
            for y in [0, 1, 2, 3, 4, 5, 6, 7]:
                color = pix[(x, y)]
                # Handle the color of the pixel, off or on.
                if color == 0:
                    self.set_pixel(x, y, 0)
                else:
                    self.set_pixel(x, y, 1)

    def create_blank_image(self):
        return Image.new("RGB", (8, 8))

    def horizontal_scroll(self, image, padding=True):
        """Returns a list of images which appear to scroll from left to right
        across the input image when displayed on the LED matrix in order.

        The input image is not limited to being 8x8. If the input image is
        larger than this, then all columns will be scrolled through but only
        the top 8 rows of pixels will be displayed.

        Keyword arguments:
        image -- The image to scroll across.
        padding -- If True, the animation will begin with a blank screen and the
            input image will scroll into the blank screen one pixel column at a
            time. Similarly, after scrolling across the whole input image, the
            end of the image will scroll out of a blank screen one column at a
            time. If this is not True, then only the input image will be scroll
            across without beginning or ending with "whitespace."
            (Default = True)
        """

        image_list = list()
        width = image.size[0]
        # Scroll into the blank image.
        if padding:
            for x in range(8):
                section = image.crop((0, 0, x, 8))
                display_section = self.create_blank_image()
                display_section.paste(section, (8 - x, 0, 8, 8))
                image_list.append(display_section)

        #Scroll across the input image.
        for x in range(8, width + 1):
            section = image.crop((x - 8, 0, x, 8))
            display_section = self.create_blank_image()
            display_section.paste(section, (0, 0, 8, 8))
            image_list.append(display_section)

        #Scroll out, leaving the blank image.
        if padding:
            for x in range(width - 7, width + 1):
                section = image.crop((x, 0, width, 8))
                display_section = self.create_blank_image()
                display_section.paste(section, (0, 0, 7 - (x - (width - 7)), 8))
                image_list.append(display_section)

        #Return the list of images created
        return image_list

    def vertical_scroll(self, image, padding=True):
        """Returns a list of images which appear to scroll from top to bottom
        down the input image when displayed on the LED matrix in order.

        The input image is not limited to being 8x8. If the input image is
        largerthan this, then all rows will be scrolled through but only the
        left-most 8 columns of pixels will be displayed.

        Keyword arguments:
        image -- The image to scroll down.
        padding -- If True, the animation will begin with a blank screen and the
            input image will scroll into the blank screen one pixel row at a
            time. Similarly, after scrolling down the whole input image, the end
            of the image will scroll out of a blank screen one row at a time.
            If this is not True, then only the input image will be scroll down
            without beginning or ending with "whitespace." (Default = True)
        """

        image_list = list()
        height = image.size[1]
        # Scroll into the blank image.
        if padding:
            for y in range(8):
                section = image.crop((0, 0, 8, y))
                display_section = self.create_blank_image()
                display_section.paste(section, (0, 8 - y, 8, 8))
                image_list.append(display_section)

        #Scroll across the input image.
        for y in range(8, height + 1):
            section = image.crop((0, y - 8, 8, y))
            display_section = self.create_blank_image()
            display_section.paste(section, (0, 0, 8, 8))
            image_list.append(display_section)

        #Scroll out, leaving the blank image.
        if padding:
            for y in range(height - 7, height + 1):
                section = image.crop((0, y, 8, height))
                display_section = self.create_blank_image()
                display_section.paste(section, (0, 0, 8, 7 - (y - (height - 7))))
                image_list.append(display_section)

        #Return the list of images created
        return image_list

    def animate(self, images, delay=.25):
        """Displays each of the input images in order, pausing for "delay"
        seconds after each image.

        Keyword arguments:
        image -- An iterable collection of Image objects.
        delay -- How many seconds to wait after displaying an image before
            displaying the next one. (Default = .25)
        """
        for image in images:
            # Draw the image on the display buffer.
            self.set_image(image)

            # Draw the buffer to the display hardware.
            self.write_display()
            time.sleep(delay)
<commit_msg>Update Matrix8x8.py<commit_after># Copyright (c) 2014 Adafruit Industries
# Author: Tony DiCola
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from . import HT16K33
from PIL import Image
import time

class Matrix8x8(HT16K33.HT16K33):
    """Single color 8x8 matrix LED backpack display."""

    def __init__(self, **kwargs):
        """Initialize display.  All arguments will be passed to the HT16K33 class
        initializer, including optional I2C address and bus number parameters.
        """
        super(Matrix8x8, self).__init__(**kwargs)

    def set_pixel(self, x, y, value):
        """Set pixel at position x, y to the given value.  X and Y should be values
        of 0 to 7.  Value should be 0 for off and non-zero for on.
        """
        if x < 0 or x > 7 or y < 0 or y > 7:
            # Ignore out of bounds pixels.
            return
        self.set_led(y * 16 + ((x + 7) % 8), value)

    def set_image(self, image):
        """Set display buffer to Python Image Library image.  Image will be converted
        to 1 bit color and non-zero color values will light the LEDs.
        """
        imwidth, imheight = image.size
        if imwidth != 8 or imheight != 8:
            raise ValueError('Image must be an 8x8 pixels in size.')
        # Convert image to 1 bit color and grab all the pixels.
        pix = image.convert('1').load()
        # Loop through each pixel and write the display buffer pixel.
        for x in [0, 1, 2, 3, 4, 5, 6, 7]:
            for y in [0, 1, 2, 3, 4, 5, 6, 7]:
                color = pix[(x, y)]
                # Handle the color of the pixel, off or on.
                if color == 0:
                    self.set_pixel(x, y, 0)
                else:
                    self.set_pixel(x, y, 1)

    def create_blank_image(self):
        return Image.new("RGB", (8, 8))

    def horizontal_scroll(self, image, padding=True):
        """Returns a list of images which appear to scroll from left to right
        across the input image when displayed on the LED matrix in order.

        The input image is not limited to being 8x8. If the input image is
        larger than this, then all columns will be scrolled through but only
        the top 8 rows of pixels will be displayed.

        Keyword arguments:
        image -- The image to scroll across.
        padding -- If True, the animation will begin with a blank screen and the
            input image will scroll into the blank screen one pixel column at a
            time. Similarly, after scrolling across the whole input image, the
            end of the image will scroll out of a blank screen one column at a
            time. If this is not True, then only the input image will be scroll
            across without beginning or ending with "whitespace."
            (Default = True)
        """

        image_list = list()
        width = image.size[0]
        # Scroll into the blank image.
        if padding:
            for x in range(8):
                section = image.crop((0, 0, x, 8))
                display_section = self.create_blank_image()
                display_section.paste(section, (8 - x, 0, 8, 8))
                image_list.append(display_section)

        #Scroll across the input image.
        for x in range(8, width + 1):
            section = image.crop((x - 8, 0, x, 8))
            display_section = self.create_blank_image()
            display_section.paste(section, (0, 0, 8, 8))
            image_list.append(display_section)

        #Scroll out, leaving the blank image.
        if padding:
            for x in range(width - 7, width + 1):
                section = image.crop((x, 0, width, 8))
                display_section = self.create_blank_image()
                display_section.paste(section, (0, 0, 7 - (x - (width - 7)), 8))
                image_list.append(display_section)

        #Return the list of images created
        return image_list

    def vertical_scroll(self, image, padding=True):
        """Returns a list of images which appear to scroll from top to bottom
        down the input image when displayed on the LED matrix in order.

        The input image is not limited to being 8x8. If the input image is
        largerthan this, then all rows will be scrolled through but only the
        left-most 8 columns of pixels will be displayed.

        Keyword arguments:
        image -- The image to scroll down.
        padding -- If True, the animation will begin with a blank screen and the
            input image will scroll into the blank screen one pixel row at a
            time. Similarly, after scrolling down the whole input image, the end
            of the image will scroll out of a blank screen one row at a time.
            If this is not True, then only the input image will be scroll down
            without beginning or ending with "whitespace." (Default = True)
        """

        image_list = list()
        height = image.size[1]
        # Scroll into the blank image.
        if padding:
            for y in range(8):
                section = image.crop((0, 0, 8, y))
                display_section = self.create_blank_image()
                display_section.paste(section, (0, 8 - y, 8, 8))
                image_list.append(display_section)

        #Scroll across the input image.
        for y in range(8, height + 1):
            section = image.crop((0, y - 8, 8, y))
            display_section = self.create_blank_image()
            display_section.paste(section, (0, 0, 8, 8))
            image_list.append(display_section)

        #Scroll out, leaving the blank image.
        if padding:
            for y in range(height - 7, height + 1):
                section = image.crop((0, y, 8, height))
                display_section = self.create_blank_image()
                display_section.paste(section, (0, 0, 8, 7 - (y - (height - 7))))
                image_list.append(display_section)

        #Return the list of images created
        return image_list

    def animate(self, images, delay=.25):
        """Displays each of the input images in order, pausing for "delay"
        seconds after each image.

        Keyword arguments:
        image -- An iterable collection of Image objects.
        delay -- How many seconds to wait after displaying an image before
            displaying the next one. (Default = .25)
        """
        for image in images:
            # Draw the image on the display buffer.
            self.set_image(image)

            # Draw the buffer to the display hardware.
            self.write_display()
            time.sleep(delay)
<|endoftext|>


<commit_before>VERSION = (1, 4, 2)
__version__ = '.'.join(map(str, VERSION))

class LazySettings(object):
    def _load_settings(self):
        from feincms import default_settings
        from django.conf import settings as django_settings

        for key in dir(default_settings):
            if not (key.startswith('FEINCMS_') or key.startswith('_HACK_')):
                continue

            setattr(self, key, getattr(django_settings, key,
                getattr(default_settings, key)))

    def __getattr__(self, attr):
        self._load_settings()
        del self.__class__.__getattr__
        return self.__dict__[attr]

settings = LazySettings()

COMPLETELY_LOADED = False
def ensure_completely_loaded():
    """
    This method ensures all models are completely loaded

    FeinCMS requires Django to be completely initialized before proceeding,
    because of the extension mechanism and the dynamically created content
    types.

    For more informations, have a look at issue #23 on github:
    http://github.com/feincms/feincms/issues#issue/23
    """

    global COMPLETELY_LOADED
    if COMPLETELY_LOADED:
        return True

    from django.core.management.validation import get_validation_errors
    from StringIO import StringIO
    get_validation_errors(StringIO(), None)

    COMPLETELY_LOADED = True
    return True
<commit_msg>feincms 1.4.2 -> treeadmin 0.1<commit_after>__version__ = '0.1'<|endoftext|>


<commit_before>from selenium.webdriver import ActionChains

from drag_and_drop_v2.default_data import START_FEEDBACK, FINISH_FEEDBACK
from .test_base import BaseIntegrationTest
from ..utils import load_resource

class ItemDefinition(object):
    def __init__(self, item_id, zone_id, feedback_positive, feedback_negative, input_value=None):
        self.feedback_negative = feedback_negative
        self.feedback_positive = feedback_positive
        self.zone_id = zone_id
        self.item_id = item_id
        self.input = input_value

class InteractionTestBase(object):
    @classmethod
    def _get_correct_item_for_zone(cls, items_map):
        return {
            item_key: definition for item_key, definition in items_map.items()
            if definition.zone_id is not None
        }

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

        scenario_xml = self._get_scenario_xml()
        self._add_scenario(self.PAGE_ID, self.PAGE_TITLE, scenario_xml)

        self._page = self.go_to_page(self.PAGE_TITLE)
        # Resize window so that the entire drag container is visible.
        # Selenium has issues when dragging to an area that is off screen.
        self.browser.set_window_size(1024, 800)

    def _get_item_by_value(self, item_value):
        items_container = self._page.find_element_by_css_selector('.item-bank')
        return items_container.find_elements_by_xpath("//div[@data-value='{item_id}']".format(item_id=item_value))[0]

    def _get_zone_by_id(self, zone_id):
        zones_container = self._page.find_element_by_css_selector('.target')
        return zones_container.find_elements_by_xpath("//div[@data-zone='{zone_id}']".format(zone_id=zone_id))[0]

    def _get_input_div_by_value(self, item_value):
        element = self._get_item_by_value(item_value)
        return element.find_element_by_class_name('numerical-input')

    def _send_input(self, item_value, value):
        element = self._get_item_by_value(item_value)
        element.find_element_by_class_name('input').send_keys(value)
        element.find_element_by_class_name('submit-input').click()

    def get_feedback_popup(self):
        return self._page.find_element_by_css_selector(".popup-content")

    def get_reset_button(self):
        return self._page.find_element_by_css_selector('.reset-button')

    def drag_item_to_zone(self, item_value, zone_id):
        element = self._get_item_by_value(item_value)
        target = self._get_zone_by_id(zone_id)
        action_chains = ActionChains(self.browser)
        action_chains.drag_and_drop(element, target).perform()

    def parameterized_item_positive_feedback_on_good_move(self, items_map, scroll_down=100):
        # Scroll drop zones into view to make sure Selenium can successfully drop items
        self.scroll_down(pixels=scroll_down)

        for definition in self._get_correct_item_for_zone(items_map).values():
            if not definition.input:
                self.drag_item_to_zone(definition.item_id, definition.zone_id)
                feedback_popup = self.get_feedback_popup()
                self.wait_until_html_in(definition.feedback_positive, feedback_popup)

    def parameterized_item_positive_feedback_on_good_input(self, items_map, scroll_down=100):
        self.scroll_down(pixels=scroll_down)
        feedback_popup = self.get_feedback_popup()
        for definition in self._get_correct_item_for_zone(items_map).values():
            if definition.input:
                self.drag_item_to_zone(definition.item_id, definition.zone_id)
                self._send_input(definition.item_id, definition.input)
                input_div = self._get_input_div_by_value(definition.item_id)
                self.wait_until_has_class('correct', input_div)
                self.wait_until_html_in(definition.feedback_positive, feedback_popup)

    def parameterized_item_negative_feedback_on_bad_move(self, items_map, all_zones, scroll_down=100):
        feedback_popup = self.get_feedback_popup()

        # Scroll drop zones into view to make sure Selenium can successfully drop items
        self.scroll_down(pixels=scroll_down)

        for definition in items_map.values():
            for zone in all_zones:
                if zone == definition.zone_id:
                    continue
                self.drag_item_to_zone(definition.item_id, zone)
                self.wait_until_html_in(definition.feedback_negative, feedback_popup)

    def parameterized_item_positive_feedback_on_bad_input(self, items_map, scroll_down=100):
        feedback_popup = self.get_feedback_popup()

        # Scroll drop zones into view to make sure Selenium can successfully drop items
        self.scroll_down(pixels=scroll_down)

        for definition in self._get_correct_item_for_zone(items_map).values():
            if definition.input:
                self.drag_item_to_zone(definition.item_id, definition.zone_id)
                self._send_input(definition.item_id, '1999999')
                input_div = self._get_input_div_by_value(definition.item_id)
                self.wait_until_has_class('incorrect', input_div)
                self.wait_until_html_in(definition.feedback_negative, feedback_popup)

    def parameterized_final_feedback_and_reset(self, items_map, feedback, scroll_down=100):
        feedback_message = self._get_feedback_message()
        self.assertEqual(self.get_element_html(feedback_message), feedback['intro'])  # precondition check

        items = self._get_correct_item_for_zone(items_map)

        def get_locations():
            return {item_id: self._get_item_by_value(item_id).location for item_id in items.keys()}

        initial_locations = get_locations()

        # Scroll drop zones into view to make sure Selenium can successfully drop items
        self.scroll_down(pixels=scroll_down)

        for item_key, definition in items.items():
            self.drag_item_to_zone(item_key, definition.zone_id)
            if definition.input:
                self._send_input(item_key, definition.input)
                input_div = self._get_input_div_by_value(item_key)
                self.wait_until_has_class('correct', input_div)

        self.wait_until_html_in(feedback['final'], self._get_feedback_message())

        # Scroll "Reset exercise" button into view to make sure Selenium can successfully click it
        self.scroll_down(pixels=scroll_down+150)

        reset = self.get_reset_button()
        reset.click()

        self.wait_until_html_in(feedback['intro'], self._get_feedback_message())

        locations_after_reset = get_locations()
        for item_key in items.keys():
            self.assertDictEqual(locations_after_reset[item_key], initial_locations[item_key])

class BasicInteractionTest(InteractionTestBase):
    """
    Verifying Drag and Drop XBlock rendering against default data - if default data changes this will probably break.
    """
    PAGE_TITLE = 'Drag and Drop v2'
    PAGE_ID = 'drag_and_drop_v2'

    items_map = {
        0: ItemDefinition(0, 'Zone 1', "Yes, it's a 1", "No, 1 does not belong here"),
        1: ItemDefinition(1, 'Zone 2', "Yes, it's a 2", "No, 2 does not belong here"),
        2: ItemDefinition(2, None, "", "You silly, there are no zones for X")
    }

    all_zones = ['Zone 1', 'Zone 2']

    feedback = {
        "intro": START_FEEDBACK,
        "final": FINISH_FEEDBACK,
    }

    def _get_scenario_xml(self):  # pylint: disable=no-self-use
        return "<vertical_demo><drag-and-drop-v2/></vertical_demo>"

    def test_item_positive_feedback_on_good_move(self):
        self.parameterized_item_positive_feedback_on_good_move(self.items_map)

    def test_item_positive_feedback_on_good_input(self):
        self.parameterized_item_positive_feedback_on_good_input(self.items_map)

    def test_item_negative_feedback_on_bad_move(self):
        self.parameterized_item_negative_feedback_on_bad_move(self.items_map, self.all_zones)

    def test_item_positive_feedback_on_bad_input(self):
        self.parameterized_item_positive_feedback_on_bad_input(self.items_map)

    def test_final_feedback_and_reset(self):
        self.parameterized_final_feedback_and_reset(self.items_map, self.feedback)

class CustomDataInteractionTest(BasicInteractionTest, BaseIntegrationTest):
    items_map = {
        0: ItemDefinition(0, 'Zone 1', "Yes 1", "No 1"),
        1: ItemDefinition(1, 'Zone 2', "Yes 2", "No 2", "102"),
        2: ItemDefinition(2, None, "", "No Zone for this")
    }

    all_zones = ['Zone 1', 'Zone 2']

    feedback = {
        "intro": "Some Intro Feed",
        "final": "Some Final Feed"
    }

    def _get_scenario_xml(self):
        return self._get_custom_scenario_xml("integration/data/test_data.json")

class CustomHtmlDataInteractionTest(BasicInteractionTest, BaseIntegrationTest):
    items_map = {
        0: ItemDefinition(0, 'Zone 1', "Yes 1", "No 1"),
        1: ItemDefinition(1, 'Zone 2', "Yes 2", "No 2", "95"),
        2: ItemDefinition(2, None, "", "No Zone for X")
    }

    all_zones = ['Zone 1', 'Zone 2']

    feedback = {
        "intro": "Intro Feed",
        "final": "Final Feed"
    }

    def _get_scenario_xml(self):
        return self._get_custom_scenario_xml("integration/data/test_html_data.json")

class MultipleBlocksDataInteraction(InteractionTestBase, BaseIntegrationTest):
    PAGE_TITLE = 'Drag and Drop v2 Multiple Blocks'
    PAGE_ID = 'drag_and_drop_v2_multi'

    BLOCK1_DATA_FILE = "integration/data/test_data.json"
    BLOCK2_DATA_FILE = "integration/data/test_data_other.json"

    item_maps = {
        'block1': {
            0: ItemDefinition(0, 'Zone 1', "Yes 1", "No 1"),
            1: ItemDefinition(1, 'Zone 2', "Yes 2", "No 2", "102"),
            2: ItemDefinition(2, None, "", "No Zone for this")
        },
        'block2': {
            10: ItemDefinition(10, 'Zone 51', "Correct 1", "Incorrect 1"),
            20: ItemDefinition(20, 'Zone 52', "Correct 2", "Incorrect 2", "102"),
            30: ItemDefinition(30, None, "", "No Zone for this")
        },
    }

    all_zones = {
        'block1': ['Zone 1', 'Zone 2'],
        'block2': ['Zone 51', 'Zone 52']
    }

    feedback = {
        'block1': {"intro": "Some Intro Feed", "final": "Some Final Feed"},
        'block2': {"intro": "Other Intro Feed", "final": "Other Final Feed"},
    }

    def _get_scenario_xml(self):
        blocks_xml = "\n".join([
            "<drag-and-drop-v2 data='{data}'/>".format(data=load_resource(filename))
            for filename in (self.BLOCK1_DATA_FILE, self.BLOCK2_DATA_FILE)
        ])

        return "<vertical_demo>{dnd_blocks}</vertical_demo>".format(dnd_blocks=blocks_xml)

    def _switch_to_block(self, idx):
        self._page = self.browser.find_elements_by_css_selector(self.default_css_selector)[idx]
        self.scroll_down(0)

    def test_item_positive_feedback_on_good_move(self):
        self._switch_to_block(0)
        self.parameterized_item_positive_feedback_on_good_move(self.item_maps['block1'])
        self._switch_to_block(1)
        self.parameterized_item_positive_feedback_on_good_move(self.item_maps['block2'], scroll_down=900)

    def test_item_positive_feedback_on_good_input(self):
        self._switch_to_block(0)
        self.parameterized_item_positive_feedback_on_good_input(self.item_maps['block1'])
        self._switch_to_block(1)
        self.parameterized_item_positive_feedback_on_good_input(self.item_maps['block2'], scroll_down=900)

    def test_item_negative_feedback_on_bad_move(self):
        self._switch_to_block(0)
        self.parameterized_item_negative_feedback_on_bad_move(self.item_maps['block1'], self.all_zones['block1'])
        self._switch_to_block(1)
        self.parameterized_item_negative_feedback_on_bad_move(
            self.item_maps['block2'], self.all_zones['block2'], scroll_down=900
        )

    def test_item_positive_feedback_on_bad_input(self):
        self._switch_to_block(0)
        self.parameterized_item_positive_feedback_on_bad_input(self.item_maps['block1'])
        self._switch_to_block(1)
        self.parameterized_item_positive_feedback_on_bad_input(self.item_maps['block2'], scroll_down=900)

    def test_final_feedback_and_reset(self):
        self._switch_to_block(0)
        self.parameterized_final_feedback_and_reset(self.item_maps['block1'], self.feedback['block1'])
        self._switch_to_block(1)
        self.parameterized_final_feedback_and_reset(self.item_maps['block2'], self.feedback['block2'], scroll_down=900)
<commit_msg>send_input: Wait before obtaining input<commit_after>from selenium.webdriver import ActionChains

from drag_and_drop_v2.default_data import START_FEEDBACK, FINISH_FEEDBACK
from .test_base import BaseIntegrationTest
from ..utils import load_resource

class ItemDefinition(object):
    def __init__(self, item_id, zone_id, feedback_positive, feedback_negative, input_value=None):
        self.feedback_negative = feedback_negative
        self.feedback_positive = feedback_positive
        self.zone_id = zone_id
        self.item_id = item_id
        self.input = input_value

class InteractionTestBase(object):
    @classmethod
    def _get_correct_item_for_zone(cls, items_map):
        return {
            item_key: definition for item_key, definition in items_map.items()
            if definition.zone_id is not None
        }

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

        scenario_xml = self._get_scenario_xml()
        self._add_scenario(self.PAGE_ID, self.PAGE_TITLE, scenario_xml)

        self._page = self.go_to_page(self.PAGE_TITLE)
        # Resize window so that the entire drag container is visible.
        # Selenium has issues when dragging to an area that is off screen.
        self.browser.set_window_size(1024, 800)

    def _get_item_by_value(self, item_value):
        items_container = self._page.find_element_by_css_selector('.item-bank')
        return items_container.find_elements_by_xpath("//div[@data-value='{item_id}']".format(item_id=item_value))[0]

    def _get_zone_by_id(self, zone_id):
        zones_container = self._page.find_element_by_css_selector('.target')
        return zones_container.find_elements_by_xpath("//div[@data-zone='{zone_id}']".format(zone_id=zone_id))[0]

    def _get_input_div_by_value(self, item_value):
        element = self._get_item_by_value(item_value)
        return element.find_element_by_class_name('numerical-input')

    def _send_input(self, item_value, value):
        element = self._get_item_by_value(item_value)
        self.wait_until_visible(element)
        element.find_element_by_class_name('input').send_keys(value)
        element.find_element_by_class_name('submit-input').click()

    def get_feedback_popup(self):
        return self._page.find_element_by_css_selector(".popup-content")

    def get_reset_button(self):
        return self._page.find_element_by_css_selector('.reset-button')

    def drag_item_to_zone(self, item_value, zone_id):
        element = self._get_item_by_value(item_value)
        target = self._get_zone_by_id(zone_id)
        action_chains = ActionChains(self.browser)
        action_chains.drag_and_drop(element, target).perform()

    def parameterized_item_positive_feedback_on_good_move(self, items_map, scroll_down=100):
        # Scroll drop zones into view to make sure Selenium can successfully drop items
        self.scroll_down(pixels=scroll_down)

        for definition in self._get_correct_item_for_zone(items_map).values():
            if not definition.input:
                self.drag_item_to_zone(definition.item_id, definition.zone_id)
                feedback_popup = self.get_feedback_popup()
                self.wait_until_html_in(definition.feedback_positive, feedback_popup)

    def parameterized_item_positive_feedback_on_good_input(self, items_map, scroll_down=100):
        self.scroll_down(pixels=scroll_down)
        feedback_popup = self.get_feedback_popup()
        for definition in self._get_correct_item_for_zone(items_map).values():
            if definition.input:
                self.drag_item_to_zone(definition.item_id, definition.zone_id)
                self._send_input(definition.item_id, definition.input)
                input_div = self._get_input_div_by_value(definition.item_id)
                self.wait_until_has_class('correct', input_div)
                self.wait_until_html_in(definition.feedback_positive, feedback_popup)

    def parameterized_item_negative_feedback_on_bad_move(self, items_map, all_zones, scroll_down=100):
        feedback_popup = self.get_feedback_popup()

        # Scroll drop zones into view to make sure Selenium can successfully drop items
        self.scroll_down(pixels=scroll_down)

        for definition in items_map.values():
            for zone in all_zones:
                if zone == definition.zone_id:
                    continue
                self.drag_item_to_zone(definition.item_id, zone)
                self.wait_until_html_in(definition.feedback_negative, feedback_popup)

    def parameterized_item_positive_feedback_on_bad_input(self, items_map, scroll_down=100):
        feedback_popup = self.get_feedback_popup()

        # Scroll drop zones into view to make sure Selenium can successfully drop items
        self.scroll_down(pixels=scroll_down)

        for definition in self._get_correct_item_for_zone(items_map).values():
            if definition.input:
                self.drag_item_to_zone(definition.item_id, definition.zone_id)
                self._send_input(definition.item_id, '1999999')
                input_div = self._get_input_div_by_value(definition.item_id)
                self.wait_until_has_class('incorrect', input_div)
                self.wait_until_html_in(definition.feedback_negative, feedback_popup)

    def parameterized_final_feedback_and_reset(self, items_map, feedback, scroll_down=100):
        feedback_message = self._get_feedback_message()
        self.assertEqual(self.get_element_html(feedback_message), feedback['intro'])  # precondition check

        items = self._get_correct_item_for_zone(items_map)

        def get_locations():
            return {item_id: self._get_item_by_value(item_id).location for item_id in items.keys()}

        initial_locations = get_locations()

        # Scroll drop zones into view to make sure Selenium can successfully drop items
        self.scroll_down(pixels=scroll_down)

        for item_key, definition in items.items():
            self.drag_item_to_zone(item_key, definition.zone_id)
            if definition.input:
                self._send_input(item_key, definition.input)
                input_div = self._get_input_div_by_value(item_key)
                self.wait_until_has_class('correct', input_div)

        self.wait_until_html_in(feedback['final'], self._get_feedback_message())

        # Scroll "Reset exercise" button into view to make sure Selenium can successfully click it
        self.scroll_down(pixels=scroll_down+150)

        reset = self.get_reset_button()
        reset.click()

        self.wait_until_html_in(feedback['intro'], self._get_feedback_message())

        locations_after_reset = get_locations()
        for item_key in items.keys():
            self.assertDictEqual(locations_after_reset[item_key], initial_locations[item_key])

class BasicInteractionTest(InteractionTestBase):
    """
    Verifying Drag and Drop XBlock rendering against default data - if default data changes this will probably break.
    """
    PAGE_TITLE = 'Drag and Drop v2'
    PAGE_ID = 'drag_and_drop_v2'

    items_map = {
        0: ItemDefinition(0, 'Zone 1', "Yes, it's a 1", "No, 1 does not belong here"),
        1: ItemDefinition(1, 'Zone 2', "Yes, it's a 2", "No, 2 does not belong here"),
        2: ItemDefinition(2, None, "", "You silly, there are no zones for X")
    }

    all_zones = ['Zone 1', 'Zone 2']

    feedback = {
        "intro": START_FEEDBACK,
        "final": FINISH_FEEDBACK,
    }

    def _get_scenario_xml(self):  # pylint: disable=no-self-use
        return "<vertical_demo><drag-and-drop-v2/></vertical_demo>"

    def test_item_positive_feedback_on_good_move(self):
        self.parameterized_item_positive_feedback_on_good_move(self.items_map)

    def test_item_positive_feedback_on_good_input(self):
        self.parameterized_item_positive_feedback_on_good_input(self.items_map)

    def test_item_negative_feedback_on_bad_move(self):
        self.parameterized_item_negative_feedback_on_bad_move(self.items_map, self.all_zones)

    def test_item_positive_feedback_on_bad_input(self):
        self.parameterized_item_positive_feedback_on_bad_input(self.items_map)

    def test_final_feedback_and_reset(self):
        self.parameterized_final_feedback_and_reset(self.items_map, self.feedback)

class CustomDataInteractionTest(BasicInteractionTest, BaseIntegrationTest):
    items_map = {
        0: ItemDefinition(0, 'Zone 1', "Yes 1", "No 1"),
        1: ItemDefinition(1, 'Zone 2', "Yes 2", "No 2", "102"),
        2: ItemDefinition(2, None, "", "No Zone for this")
    }

    all_zones = ['Zone 1', 'Zone 2']

    feedback = {
        "intro": "Some Intro Feed",
        "final": "Some Final Feed"
    }

    def _get_scenario_xml(self):
        return self._get_custom_scenario_xml("integration/data/test_data.json")

class CustomHtmlDataInteractionTest(BasicInteractionTest, BaseIntegrationTest):
    items_map = {
        0: ItemDefinition(0, 'Zone 1', "Yes 1", "No 1"),
        1: ItemDefinition(1, 'Zone 2', "Yes 2", "No 2", "95"),
        2: ItemDefinition(2, None, "", "No Zone for X")
    }

    all_zones = ['Zone 1', 'Zone 2']

    feedback = {
        "intro": "Intro Feed",
        "final": "Final Feed"
    }

    def _get_scenario_xml(self):
        return self._get_custom_scenario_xml("integration/data/test_html_data.json")

class MultipleBlocksDataInteraction(InteractionTestBase, BaseIntegrationTest):
    PAGE_TITLE = 'Drag and Drop v2 Multiple Blocks'
    PAGE_ID = 'drag_and_drop_v2_multi'

    BLOCK1_DATA_FILE = "integration/data/test_data.json"
    BLOCK2_DATA_FILE = "integration/data/test_data_other.json"

    item_maps = {
        'block1': {
            0: ItemDefinition(0, 'Zone 1', "Yes 1", "No 1"),
            1: ItemDefinition(1, 'Zone 2', "Yes 2", "No 2", "102"),
            2: ItemDefinition(2, None, "", "No Zone for this")
        },
        'block2': {
            10: ItemDefinition(10, 'Zone 51', "Correct 1", "Incorrect 1"),
            20: ItemDefinition(20, 'Zone 52', "Correct 2", "Incorrect 2", "102"),
            30: ItemDefinition(30, None, "", "No Zone for this")
        },
    }

    all_zones = {
        'block1': ['Zone 1', 'Zone 2'],
        'block2': ['Zone 51', 'Zone 52']
    }

    feedback = {
        'block1': {"intro": "Some Intro Feed", "final": "Some Final Feed"},
        'block2': {"intro": "Other Intro Feed", "final": "Other Final Feed"},
    }

    def _get_scenario_xml(self):
        blocks_xml = "\n".join([
            "<drag-and-drop-v2 data='{data}'/>".format(data=load_resource(filename))
            for filename in (self.BLOCK1_DATA_FILE, self.BLOCK2_DATA_FILE)
        ])

        return "<vertical_demo>{dnd_blocks}</vertical_demo>".format(dnd_blocks=blocks_xml)

    def _switch_to_block(self, idx):
        self._page = self.browser.find_elements_by_css_selector(self.default_css_selector)[idx]
        self.scroll_down(0)

    def test_item_positive_feedback_on_good_move(self):
        self._switch_to_block(0)
        self.parameterized_item_positive_feedback_on_good_move(self.item_maps['block1'])
        self._switch_to_block(1)
        self.parameterized_item_positive_feedback_on_good_move(self.item_maps['block2'], scroll_down=900)

    def test_item_positive_feedback_on_good_input(self):
        self._switch_to_block(0)
        self.parameterized_item_positive_feedback_on_good_input(self.item_maps['block1'])
        self._switch_to_block(1)
        self.parameterized_item_positive_feedback_on_good_input(self.item_maps['block2'], scroll_down=900)

    def test_item_negative_feedback_on_bad_move(self):
        self._switch_to_block(0)
        self.parameterized_item_negative_feedback_on_bad_move(self.item_maps['block1'], self.all_zones['block1'])
        self._switch_to_block(1)
        self.parameterized_item_negative_feedback_on_bad_move(
            self.item_maps['block2'], self.all_zones['block2'], scroll_down=900
        )

    def test_item_positive_feedback_on_bad_input(self):
        self._switch_to_block(0)
        self.parameterized_item_positive_feedback_on_bad_input(self.item_maps['block1'])
        self._switch_to_block(1)
        self.parameterized_item_positive_feedback_on_bad_input(self.item_maps['block2'], scroll_down=900)

    def test_final_feedback_and_reset(self):
        self._switch_to_block(0)
        self.parameterized_final_feedback_and_reset(self.item_maps['block1'], self.feedback['block1'])
        self._switch_to_block(1)
        self.parameterized_final_feedback_and_reset(self.item_maps['block2'], self.feedback['block2'], scroll_down=900)
<|endoftext|>


<commit_before>"""
This module provides some functions that help us work with the
wordlist data files, so that they don't have to be stored in Git
or in the source package.

These functions won't be used in the course of using the wordfreq
package normally; instead, they're called from commands in setup.py.
"""

from wordfreq import config
import urllib
import os
import sys
import shutil
import tempfile
import tarfile
import logging
import subprocess
logger = logging.getLogger(__name__)

class ProgressTracker(object):
    """
    This class watches the progress of a urllib download task, and updates
    sys.stdout when the percentage changes.
    """
    def __init__(self, url):
        self.url = url
        self.progress = None

    def report_progress(self, count, blockSize, totalSize):
        percent = int(count*blockSize*100/totalSize)
        if percent != self.progress:
            sys.stdout.write("\rDownloading %s... %2d%%" % (self.url, percent))
            sys.stdout.flush()
            self.progress = percent

    def finish(self):
        sys.stdout.write('\n')

def ensure_dir_exists(dest_filename):
    """
    Something we'll need to do often: given a filename we want to write to,
    make sure its containing directory exists.
    """
    base_dir = os.path.dirname(dest_filename)
    if not os.path.exists(base_dir):
        os.makedirs(base_dir)

def download(url, dest_filename):
    """
    Download the file at `url` to `dest_filename`. Show a progress bar
    while downloading.
    """
    base_dir = os.path.dirname(dest_filename)
    if not os.path.exists(base_dir):
        os.makedirs(base_dir)

    tracker = ProgressTracker(url)
    urllib.urlretrieve(url, dest_filename, reporthook=tracker.report_progress)
    tracker.finish()
    logger.info("Saved database to %s" % dest_filename)
    return True

def download_and_extract_raw_data(url=None, root_dir=None):
    """
    Download the .tar.gz of raw data that can be used to build the database.
    """
    if url is None:
        url = config.RAW_DATA_URL

    if root_dir is None:
        root_dir = os.path.dirname(config.RAW_DATA_DIR)

    dest_filename = os.path.join(root_dir, 'wordfreq-data.tar.gz')
    ensure_dir_exists(dest_filename)
    download(url, dest_filename)

    logger.info("Extracting %s" % dest_filename)
    with tarfile.open(dest_filename, 'r') as tarf:
        tarf.extractall(root_dir)

def download_db(url=None, dest_filename=None):
    """
    Download the database itself, so we don't have to build it.
    """
    if url is None:
        url = config.DB_URL

    if dest_filename is None:
        dest_filename = config.DB_FILENAME

    ensure_dir_exists(dest_filename)
    download(url, dest_filename)

def upload_data(upload_path=None):
    """
    Collect the raw data and the database file, and upload them to an
    appropriate directory on the server that hosts downloads.

    This requires that it's running in a reasonable Unix environment,
    and more notably, that it has the proper SSH keys to upload to that
    server.
    """
    if upload_path is None:
        upload_path = config.UPLOAD_PATH

    build_dir = tempfile.mkdtemp('.wordfreq')
    version_dir = os.path.join(build_dir, config.MINOR_VERSION)
    os.makedirs(version_dir)

    source_filename = os.path.join(version_dir, 'wordfreq-data.tar.gz')
    logger.info("Creating %s" % source_filename)
    with tarfile.open(source_filename, 'w:gz') as tarf:
        tarf.add(config.RAW_DATA_DIR)

    logger.info("Copying database file %s" % config.DB_FILENAME)
    subprocess.call([
        '/bin/cp',
        config.DB_FILENAME,
        version_dir
    ])

    logger.info("Uploading to %s" % upload_path)
    subprocess.call([
        '/usr/bin/rsync',
        '-avz',
        version_dir,
        upload_path
    ])

    logger.info("Removing build directory %s" % build_dir)
    shutil.rmtree(build_dir)
<commit_msg>Py3 tweak to urllib import<commit_after>"""
This module provides some functions that help us work with the
wordlist data files, so that they don't have to be stored in Git
or in the source package.

These functions won't be used in the course of using the wordfreq
package normally; instead, they're called from commands in setup.py.
"""

from wordfreq import config
import os
import sys
import shutil
import tempfile
import tarfile
import logging
import subprocess
logger = logging.getLogger(__name__)

if sys.version_info.major == 2:
    from urllib import urlretrieve
else:
    from urllib.request import urlretrieve

class ProgressTracker(object):
    """
    This class watches the progress of a urllib download task, and updates
    sys.stdout when the percentage changes.
    """
    def __init__(self, url):
        self.url = url
        self.progress = None

    def report_progress(self, count, blockSize, totalSize):
        percent = int(count*blockSize*100/totalSize)
        if percent != self.progress:
            sys.stdout.write("\rDownloading %s... %2d%%" % (self.url, percent))
            sys.stdout.flush()
            self.progress = percent

    def finish(self):
        sys.stdout.write('\n')

def ensure_dir_exists(dest_filename):
    """
    Something we'll need to do often: given a filename we want to write to,
    make sure its containing directory exists.
    """
    base_dir = os.path.dirname(dest_filename)
    if not os.path.exists(base_dir):
        os.makedirs(base_dir)

def download(url, dest_filename):
    """
    Download the file at `url` to `dest_filename`. Show a progress bar
    while downloading.
    """
    base_dir = os.path.dirname(dest_filename)
    if not os.path.exists(base_dir):
        os.makedirs(base_dir)

    tracker = ProgressTracker(url)
    urlretrieve(url, dest_filename, reporthook=tracker.report_progress)
    tracker.finish()
    logger.info("Saved database to %s" % dest_filename)
    return True

def download_and_extract_raw_data(url=None, root_dir=None):
    """
    Download the .tar.gz of raw data that can be used to build the database.
    """
    if url is None:
        url = config.RAW_DATA_URL

    if root_dir is None:
        root_dir = os.path.dirname(config.RAW_DATA_DIR)

    dest_filename = os.path.join(root_dir, 'wordfreq-data.tar.gz')
    ensure_dir_exists(dest_filename)
    download(url, dest_filename)

    logger.info("Extracting %s" % dest_filename)
    with tarfile.open(dest_filename, 'r') as tarf:
        tarf.extractall(root_dir)

def download_db(url=None, dest_filename=None):
    """
    Download the database itself, so we don't have to build it.
    """
    if url is None:
        url = config.DB_URL

    if dest_filename is None:
        dest_filename = config.DB_FILENAME

    ensure_dir_exists(dest_filename)
    download(url, dest_filename)

def upload_data(upload_path=None):
    """
    Collect the raw data and the database file, and upload them to an
    appropriate directory on the server that hosts downloads.

    This requires that it's running in a reasonable Unix environment,
    and more notably, that it has the proper SSH keys to upload to that
    server.
    """
    if upload_path is None:
        upload_path = config.UPLOAD_PATH

    build_dir = tempfile.mkdtemp('.wordfreq')
    version_dir = os.path.join(build_dir, config.MINOR_VERSION)
    os.makedirs(version_dir)

    source_filename = os.path.join(version_dir, 'wordfreq-data.tar.gz')
    logger.info("Creating %s" % source_filename)
    with tarfile.open(source_filename, 'w:gz') as tarf:
        tarf.add(config.RAW_DATA_DIR)

    logger.info("Copying database file %s" % config.DB_FILENAME)
    subprocess.call([
        '/bin/cp',
        config.DB_FILENAME,
        version_dir
    ])

    logger.info("Uploading to %s" % upload_path)
    subprocess.call([
        '/usr/bin/rsync',
        '-avz',
        version_dir,
        upload_path
    ])

    logger.info("Removing build directory %s" % build_dir)
    shutil.rmtree(build_dir)
<|endoftext|>


<commit_before>"""The runtime machinery for the XBlock workbench.

Code in this file is a mix of Runtime layer and Workbench layer.

"""

import functools
import itertools

try:
    import simplejson as json
except ImportError:
    import json

import logging

from django.template import loader as django_template_loader, Context as DjangoContext

from xblock.core import XBlock, Scope, ModelType
from xblock.runtime import DbModel, KeyValueStore, Runtime
from xblock.fragment import Fragment

from .util import make_safe_for_html

log = logging.getLogger(__name__)

class Usage(object):
    """Content usages

    Usages represent uses of content in courses.  They are the basic static
    building block for course content.

    TODO: Not the real way we'll store usages!

    """

    # An infinite stream of ids, for giving each Usage an id.
    _ids = itertools.count()

    # Maps ids to Usages, a dict of all instances created, ever.
    _usage_index = {}

    # The set of Usages that have been initialized.
    _inited = set()

    def __init__(self, block_name, children=None, initial_state=None, def_id=None):
        self.id = "usage_%d" % next(self._ids)
        self.parent = None
        self.block_name = block_name
        self.def_id = def_id or ("def_%d" % next(self._ids))
        self.children = children or []
        self.initial_state = initial_state or {}

        # Update our global index of all usages.
        self._usage_index[self.id] = self

        # Create the parent references as we construct children.
        for child in self.children:
            child.parent = self

    def store_initial_state(self):
        # If we've already created the initial state, there's nothing to do.
        if self.id in self._inited:
            return

        # Create an XBlock from this usage, and use it to create the initial
        # state.
        block = create_xblock(self)
        if self.initial_state:
            for name, value in self.initial_state.items():
                setattr(block, name, value)

        block.children = [child.id for child in self.children]
        if self.parent is not None:
            block.parent = self.parent.id

        # We've initialized this instance, keep track.
        self._inited.add(self.id)

        # Also do this recursively down the tree.
        for child in self.children:
            child.store_initial_state()

    def __repr__(self):
        return "<{0.__class__.__name__} {0.id} {0.block_name} {0.def_id} {0.children!r}>".format(self)

    @classmethod
    def find_usage(cls, usage_id):
        return cls._usage_index[usage_id]

    @classmethod
    def reinitialize_all(cls):
        """
        Reset all the inited flags, so that Usages will be initialized again.

        Used to isolate tests from each other.

        """
        cls._inited.clear()

class MemoryKeyValueStore(KeyValueStore):
    """Use a simple in-memory database for a key-value store."""
    def __init__(self, d):
        self.d = d

    def clear(self):
        """Clear all data from the store."""
        self.d.clear()

    def actual_key(self, key):
        k = []
        if key.scope == Scope.children:
            k.append('children')
        elif key.scope == Scope.parent:
            k.append('parent')
        else:
            k.append(["usage", "definition", "type", "all"][key.scope.block])

        if key.block_scope_id is not None:
            k.append(key.block_scope_id)
        if key.student_id:
            k.append(key.student_id)
        return ".".join(k)

    def get(self, key):
        return self.d[self.actual_key(key)][key.field_name]

    def set(self, key, value):
        self.d.setdefault(self.actual_key(key), {})[key.field_name] = value

    def delete(self, key):
        del self.d[self.actual_key(key)][key.field_name]

    def has(self, key):
        return key.field_name in self.d[self.actual_key(key)]

    def as_html(self):
        """Just for our Workbench!"""
        html = json.dumps(self.d, sort_keys=True, indent=4)
        return make_safe_for_html(html)

MEMORY_KVS = MemoryKeyValueStore({})

def create_xblock(usage, student_id=None):
    """Create an XBlock instance.

    This will be invoked to create new instances for every request.

    """
    block_cls = XBlock.load_class(usage.block_name)
    runtime = WorkbenchRuntime(block_cls, student_id, usage)
    model = DbModel(MEMORY_KVS, block_cls, student_id, usage)
    block = block_cls(runtime, model)
    return block

class WorkbenchRuntime(Runtime):
    def __init__(self, block_cls, student_id, usage):
        super(WorkbenchRuntime, self).__init__()

        self.block_cls = block_cls
        self.student_id = student_id
        self.usage = usage

    def render(self, block, context, view_name):
        self._view_name = view_name

        view_fn = getattr(block, view_name, None)
        if view_fn is None:
            view_fn = getattr(block, "fallback_view", None)
            if view_fn is None:
                return Fragment(u"No such view: %s on %s" % (view_name, make_safe_for_html(repr(block))))
            view_fn = functools.partial(view_fn, view_name)

        frag = view_fn(context)

        self._view_name = None
        return self.wrap_child(block, frag, context)

    # TODO: [rocha] runtime should not provide this, each xblock
    # should use whatever they want
    def render_template(self, template_name, **kwargs):
        return django_template_loader.get_template(template_name).render(DjangoContext(kwargs))

    def wrap_child(self, block, frag, context):
        wrapped = Fragment()
        wrapped.add_javascript_url("/static/js/vendor/jquery.min.js")
        wrapped.add_javascript_url("/static/js/vendor/jquery.cookie.js")

        data = {}
        if frag.js_init:
            fn, version = frag.js_init
            wrapped.add_javascript_url("/static/js/runtime/%s.js" % version)
            data['init'] = fn
            data['runtime-version'] = version
            data['usage'] = self.usage.id
            data['block-type'] = self.block_cls.plugin_name

        if block.name:
            data['name'] = block.name

        html = u"
%s
" % (
            "".join(" data-%s='%s'" % item for item in data.items()),
            frag.body_html(),
        )
        wrapped.add_content(html)
        wrapped.add_frag_resources(frag)
        return wrapped

    def handler_url(self, url):
        return "/handler/%s/%s/?student=%s" % (self.usage.id, url, self.student_id)

    def get_block(self, block_id):
        return create_xblock(Usage.find_usage(block_id), self.student_id)

    def query(self, block):
        return _BlockSet(self, [block])

    # TODO: [rocha] other name options: gather
    def collect(self, key, block=None):
        block_cls = block.__class__ if block else self.block_cls

        data_model = AnalyticsDbModel(MEMORY_KVS, block_cls, self.student_id, self.usage)
        value = data_model.get(key)
        children = []
        for child_id in data_model.get('children', []):
            child = self.get_block(child_id)
            children.append(child.runtime.collect(key, child))

        result = {
            'class': block_cls.__name__,
            'value': value,
            'children': children,
        }

        return result

    # TODO: [rocha] other name options: scatter, share
    def publish(self, key, value):
        data = AnalyticsDbModel(MEMORY_KVS, self.block_cls, self.student_id, self.usage)
        data[key] = value

class _BlockSet(object):
    def __init__(self, runtime, blocks):
        self.runtime = runtime
        self.blocks = blocks

    def __iter__(self):
        return iter(self.blocks)

    def parent(self):
        them = set()
        for block in self.blocks:
            if block.parent:
                parent = self.runtime.get_block(block.parent)
                them.add(parent)
        return _BlockSet(self.runtime, them)

    def children(self):
        them = set()
        for block in self.blocks:
            for child_id in getattr(block, "children", ()):
                child = self.runtime.get_block(child_id)
                them.add(child)
        return _BlockSet(self.runtime, them)

    def descendants(self):
        them = set()
        def recur(block):
            for child_id in getattr(block, "children", ()):
                child = self.runtime.get_block(child_id)
                them.add(child)
                recur(child)

        for block in self.blocks:
            recur(block)

        return _BlockSet(self.runtime, them)

    def tagged(self, tag):
        them = set()
        for block in self.blocks:
            if block.name == tag:
                them.add(block)
            if block.tags and tag in block.tags:
                them.add(block)
            elif tag in block._class_tags:
                them.add(block)
        return _BlockSet(self.runtime, them)

    def attr(self, attr_name):
        for block in self.blocks:
            if hasattr(block, attr_name):
                yield getattr(block, attr_name)

class AnalyticsDbModel(DbModel):
    def _key(self, name):
        key = super(AnalyticsDbModel, self)._key('analytics.{0}'.format(name))
        return key

    def _getfield(self, name):
        return ModelType(scope=Scope.user_state)
<commit_msg>Improve the docs for store_initial_state<commit_after>"""The runtime machinery for the XBlock workbench.

Code in this file is a mix of Runtime layer and Workbench layer.

"""

import functools
import itertools

try:
    import simplejson as json
except ImportError:
    import json

import logging

from django.template import loader as django_template_loader, Context as DjangoContext

from xblock.core import XBlock, Scope, ModelType
from xblock.runtime import DbModel, KeyValueStore, Runtime
from xblock.fragment import Fragment

from .util import make_safe_for_html

log = logging.getLogger(__name__)

class Usage(object):
    """Content usages

    Usages represent uses of content in courses.  They are the basic static
    building block for course content.

    TODO: Not the real way we'll store usages!

    """

    # An infinite stream of ids, for giving each Usage an id.
    _ids = itertools.count()

    # Maps ids to Usages, a dict of all instances created, ever.
    _usage_index = {}

    # The set of Usage ids that have been initialized by store_initial_state.
    _inited = set()

    def __init__(self, block_name, children=None, initial_state=None, def_id=None):
        self.id = "usage_%d" % next(self._ids)
        self.parent = None
        self.block_name = block_name
        self.def_id = def_id or ("def_%d" % next(self._ids))
        self.children = children or []
        self.initial_state = initial_state or {}

        # Update our global index of all usages.
        self._usage_index[self.id] = self

        # Create the parent references as we construct children.
        for child in self.children:
            child.parent = self

    def store_initial_state(self):
        """Ensure that the initial state of this Usage is created.

        This method is called before using the Usage.  It will use the
        `initial_state` argument to the Usage to populate XBlock
        attributes, and then recursively do the same for its children.

        All this work is only done once for each Usage no matter how often
        this function is called.

        """
        # If we've already created the initial state, there's nothing to do.
        if self.id in self._inited:
            return

        # Create an XBlock from this usage, and use it to create the initial
        # state. This block is created just so we can use the block to set
        # attributes, which will cause the data to be written through the
        # fields. This isolates us from the storage mechanism: however the
        # block saves its attributes, that's how the initial state will be
        # saved.
        block = create_xblock(self)
        if self.initial_state:
            for name, value in self.initial_state.items():
                setattr(block, name, value)

        block.children = [child.id for child in self.children]
        if self.parent is not None:
            block.parent = self.parent.id

        # We've initialized this instance, keep track.
        self._inited.add(self.id)

        # Also do this recursively down the tree.
        for child in self.children:
            child.store_initial_state()

    def __repr__(self):
        return "<{0.__class__.__name__} {0.id} {0.block_name} {0.def_id} {0.children!r}>".format(self)

    @classmethod
    def find_usage(cls, usage_id):
        return cls._usage_index[usage_id]

    @classmethod
    def reinitialize_all(cls):
        """
        Reset all the inited flags, so that Usages will be initialized again.

        Used to isolate tests from each other.

        """
        cls._inited.clear()

class MemoryKeyValueStore(KeyValueStore):
    """Use a simple in-memory database for a key-value store."""
    def __init__(self, d):
        self.d = d

    def clear(self):
        """Clear all data from the store."""
        self.d.clear()

    def actual_key(self, key):
        k = []
        if key.scope == Scope.children:
            k.append('children')
        elif key.scope == Scope.parent:
            k.append('parent')
        else:
            k.append(["usage", "definition", "type", "all"][key.scope.block])

        if key.block_scope_id is not None:
            k.append(key.block_scope_id)
        if key.student_id:
            k.append(key.student_id)
        return ".".join(k)

    def get(self, key):
        return self.d[self.actual_key(key)][key.field_name]

    def set(self, key, value):
        self.d.setdefault(self.actual_key(key), {})[key.field_name] = value

    def delete(self, key):
        del self.d[self.actual_key(key)][key.field_name]

    def has(self, key):
        return key.field_name in self.d[self.actual_key(key)]

    def as_html(self):
        """Just for our Workbench!"""
        html = json.dumps(self.d, sort_keys=True, indent=4)
        return make_safe_for_html(html)

MEMORY_KVS = MemoryKeyValueStore({})

def create_xblock(usage, student_id=None):
    """Create an XBlock instance.

    This will be invoked to create new instances for every request.

    """
    block_cls = XBlock.load_class(usage.block_name)
    runtime = WorkbenchRuntime(block_cls, student_id, usage)
    model = DbModel(MEMORY_KVS, block_cls, student_id, usage)
    block = block_cls(runtime, model)
    return block

class WorkbenchRuntime(Runtime):
    def __init__(self, block_cls, student_id, usage):
        super(WorkbenchRuntime, self).__init__()

        self.block_cls = block_cls
        self.student_id = student_id
        self.usage = usage

    def render(self, block, context, view_name):
        self._view_name = view_name

        view_fn = getattr(block, view_name, None)
        if view_fn is None:
            view_fn = getattr(block, "fallback_view", None)
            if view_fn is None:
                return Fragment(u"No such view: %s on %s" % (view_name, make_safe_for_html(repr(block))))
            view_fn = functools.partial(view_fn, view_name)

        frag = view_fn(context)

        self._view_name = None
        return self.wrap_child(block, frag, context)

    # TODO: [rocha] runtime should not provide this, each xblock
    # should use whatever they want
    def render_template(self, template_name, **kwargs):
        return django_template_loader.get_template(template_name).render(DjangoContext(kwargs))

    def wrap_child(self, block, frag, context):
        wrapped = Fragment()
        wrapped.add_javascript_url("/static/js/vendor/jquery.min.js")
        wrapped.add_javascript_url("/static/js/vendor/jquery.cookie.js")

        data = {}
        if frag.js_init:
            fn, version = frag.js_init
            wrapped.add_javascript_url("/static/js/runtime/%s.js" % version)
            data['init'] = fn
            data['runtime-version'] = version
            data['usage'] = self.usage.id
            data['block-type'] = self.block_cls.plugin_name

        if block.name:
            data['name'] = block.name

        html = u"
%s
" % (
            "".join(" data-%s='%s'" % item for item in data.items()),
            frag.body_html(),
        )
        wrapped.add_content(html)
        wrapped.add_frag_resources(frag)
        return wrapped

    def handler_url(self, url):
        return "/handler/%s/%s/?student=%s" % (self.usage.id, url, self.student_id)

    def get_block(self, block_id):
        return create_xblock(Usage.find_usage(block_id), self.student_id)

    def query(self, block):
        return _BlockSet(self, [block])

    # TODO: [rocha] other name options: gather
    def collect(self, key, block=None):
        block_cls = block.__class__ if block else self.block_cls

        data_model = AnalyticsDbModel(MEMORY_KVS, block_cls, self.student_id, self.usage)
        value = data_model.get(key)
        children = []
        for child_id in data_model.get('children', []):
            child = self.get_block(child_id)
            children.append(child.runtime.collect(key, child))

        result = {
            'class': block_cls.__name__,
            'value': value,
            'children': children,
        }

        return result

    # TODO: [rocha] other name options: scatter, share
    def publish(self, key, value):
        data = AnalyticsDbModel(MEMORY_KVS, self.block_cls, self.student_id, self.usage)
        data[key] = value

class _BlockSet(object):
    def __init__(self, runtime, blocks):
        self.runtime = runtime
        self.blocks = blocks

    def __iter__(self):
        return iter(self.blocks)

    def parent(self):
        them = set()
        for block in self.blocks:
            if block.parent:
                parent = self.runtime.get_block(block.parent)
                them.add(parent)
        return _BlockSet(self.runtime, them)

    def children(self):
        them = set()
        for block in self.blocks:
            for child_id in getattr(block, "children", ()):
                child = self.runtime.get_block(child_id)
                them.add(child)
        return _BlockSet(self.runtime, them)

    def descendants(self):
        them = set()
        def recur(block):
            for child_id in getattr(block, "children", ()):
                child = self.runtime.get_block(child_id)
                them.add(child)
                recur(child)

        for block in self.blocks:
            recur(block)

        return _BlockSet(self.runtime, them)

    def tagged(self, tag):
        them = set()
        for block in self.blocks:
            if block.name == tag:
                them.add(block)
            if block.tags and tag in block.tags:
                them.add(block)
            elif tag in block._class_tags:
                them.add(block)
        return _BlockSet(self.runtime, them)

    def attr(self, attr_name):
        for block in self.blocks:
            if hasattr(block, attr_name):
                yield getattr(block, attr_name)

class AnalyticsDbModel(DbModel):
    def _key(self, name):
        key = super(AnalyticsDbModel, self)._key('analytics.{0}'.format(name))
        return key

    def _getfield(self, name):
        return ModelType(scope=Scope.user_state)
<|endoftext|>


<commit_before><commit_msg>Bug fix for PESSTOEFOSC2dSPEC on some systems<commit_after><|endoftext|>


<commit_before>#!/usr/bin/env python
'''checkplotlist.py - Waqas Bhatti (wbhatti@astro.princeton.edu) - Dec 2016
License: MIT. See LICENSE for full text.

DESCRIPTION
===========

This makes a checkplot file list for use with the checkplot-viewer.html or the
checkplotserver.py webapps. Checkplots are quick-views of object info, finder
charts, light curves, phased light curves, and periodograms used to examine
their stellar variability. These are produced by several functions in the
astrobase.checkplot module:

checkplot.checkplot_png: makes a checkplot PNG for a single period-finding
                         method

checkplot.twolsp_checkplot_png: does the same for two independent period-finding
                                methods

checkplot.checkplot_pickle: makes a checkplot .pkl.gz for any number of
                            independent period-finding methods

USAGE
=====

If you made checkplots in the PNG format (checkplot-*.png)
----------------------------------------------------------

Copy checkplot-viewer.html and checkplot-viewer.js to the
base directory from where you intend to serve your checkplot images from. Then
invoke this command from that directory:

$ checkplotlist png subdir/containing/the/checkplots 'optional-glob*.png'

This will generate a checkplot-filelist.json file containing the file paths to
the checkplots.

You can then run a temporary Python web server from this base directory to
browse through all the checkplots:

$ python -m SimpleHTTPServer # Python 2
$ python3 -m http.server     # Python 3

then browse to http://localhost:8000/checkplot-viewer.html.

If this directory is already in a path served by a web server, then you can just
browse to the checkplot-viewer.html file normally. Note that a file:/// URL
provided to the browser won't necessarily work in some browsers (especially
Google Chrome) because of security precautions.

If you made checkplots in the pickle format (checkplot-*.pkl)
-------------------------------------------------------------

Invoke this command from that directory like so:

$ checkplotlist pkl subdir/containing/the/checkplots 'optional-glob*.pkl'

Then, from that directory, invoke the checkplotserver webapp (make sure the
astrobase virtualenv is active, so the command below is in your path):

$ checkplotserver [list of options, use --help to see these]

The webapp will start up a Tornado web server running on your computer and
listening on a local address (default: http://localhost:5225). This webapp will
read the checkplot-filelist.json file to find the checkplots.

Browse to http://localhost:5225 (or whatever port you set in checkplotserver
options) to look through or update all your checkplots. Any changes will be
written back to the checkplot .pkl files, making this method of browsing more
suited to more serious variability searches on large numbers of checkplots.

TL;DR
=====

This makes a checkplot file list for use with the checkplot-viewer.html (for
checkplot PNGs) or the checkplotserver.py (for checkplot pickles) webapps.

checkplotlist <pkl|png> <subdir/containing/checkplots/> '[optional checkplot file glob]' '[optional sort specification]'

If you have checkplots that don't have 'checkplot' somewhere in their file name,
use the optional checkplot file glob argument to checkplotlist to provide
this. Make sure to use the quotes around this argument, otherwise the shell will
expand it, e.g.:

$ checkplotlist png my-project/awesome-objects '*awesome-objects*'

For checkplot pickles only: If you want to sort the checkplot pickles in some
special way, e.g. by their existing Stetson J indices in descending order, use
something like:

$ checkplotlist pkl my-project/awesome-objects '*awesome-objects*' 'varinfo.features.stetsonj-desc'

This requires an arg on the commandline of the form: '<sortkey>-<asc|desc>'

where sortkey is some key in the checkplot pickle: this can be a simple key:
e.g. objectid or it can be a composite key: e.g. varinfo.varfeatures.stetsonj
and sortorder is either 'asc' or desc' for ascending/descending sort.

'''

import os
import os.path
import sys
import glob
import json

# to turn a list of keys into a dict address
# from https://stackoverflow.com/a/14692747
# used to walk a checkplotdict for a specific key in the structure
from astrobase import checkplot
import numpy as np
import multiprocessing as mp

from functools import reduce  # forward compatibility for Python 3
from operator import getitem

def dict_get(datadict, keylist):
    return reduce(getitem, keylist, datadict)

def sortkey_worker(task):
    cpf, key = task
    cpd = checkplot._read_checkplot_picklefile(cpf)
    return dict_get(cpd, key)

def main(args=None):

    if not args:
        args = sys.argv

    if len(args) < 3:
        docstring = __doc__
        if docstring:
            print(docstring)
        else:
            print("Usage: %s <pkl|png> <subdir/containing/the/checkplots/> "
                  "'[file glob to use] [checkplot pickle sort key-sort order]'"
                  % args[0])
        sys.exit(2)

    checkplotbasedir = args[2]

    if len(args) == 5:
        sortkey, sortorder = args[4].split('-')
    else:
        sortkey, sortorder = None, None

    if len(args) == 4:
        fileglob = args[3]
    else:
        fileglob = '*checkplot*'

    if args[1] == 'pkl':
        checkplotext = 'pkl'
    elif args[1] == 'png':
        checkplotext = 'png'
    else:
        print("unknown format for checkplots: %s! can't continue!"
              % args[1])
        sys.exit(1)

    currdir = os.getcwd()

    checkplotglob = os.path.join(checkplotbasedir,
                                 '%s.%s' % (fileglob, checkplotext))

    print('searching for checkplots: %s' % checkplotglob)

    searchresults = glob.glob(checkplotglob)

    if searchresults:

        print('found %s checkplot files in %s, '
              'making checkplot-filelist.json...' %
              (len(searchresults), checkplotbasedir))

        outjson = os.path.abspath(
            os.path.join(currdir,'checkplot-filelist.json')
        )

        # see if we should sort the searchresults in some special order
        # this requires an arg on the commandline of the form:
        # '<sortkey>-<asc|desc>'
        # where sortkey is some key in the checkplot pickle:
        #   this can be a simple key: e.g. objectid
        #   or it can be a composite key: e.g. varinfo.varfeatures.stetsonj
        # and sortorder is either 'asc' or desc' for ascending/descending sort
        if sortkey and sortorder:

            print('sorting checkplot pickles by %s in order: %s...' %
                  (sortkey, sortorder))

            # dereference the sort key
            sortkeys = sortkey.split('.')

            pool = mp.Pool()
            tasks = [(x, sortkeys) for x in searchresults]
            sorttargets = pool.map(sortkey_worker, tasks)

            pool.close()
            pool.join()

            sorttargets = np.array(sorttargets)
            sortind = np.argsort(sorttargets)
            if sortorder == 'desc':
                sortind = sortind[::-1]
            searchresults = np.array(searchresults)
            searchresults = searchresults[sortind].tolist()

        # if there's no special sort order defined, use the usual sort order
        else:
            print('no special sort key and order specified, '
                  'sorting checkplot pickles '
                  'using usual alphanumeric sort...')
            searchresults = sorted(searchresults)

        # ask if the checkplot list JSON should be updated
        if os.path.exists(outjson):

            answer = input('There is an existing '
                           'checkplot list file in this '
                           'directory:\n    %s\nDo you want to '
                           'overwrite it? (default: no) [y/n] ' % outjson)

            # if it's OK to overwrite, then do so
            if answer and answer == 'y':

                with open(outjson,'w') as outfd:
                    print('overwriting existing checkplot list')
                    outdict = {'checkplots':searchresults,
                               'nfiles':len(searchresults)}
                    json.dump(outdict,outfd)

            # if it's not OK to overwrite, then
            else:

                # read in the outjson, and add stuff to it for objects that
                # don't have an entry
                print('updating existing checkplot list file')

                with open(outjson,'r') as infd:
                    indict = json.load(infd)

                # update the checkplot list only
                indict['checkplots'] = searchresults
                indict['nfiles'] = len(searchresults)
                # write the updated to back to the file
                with open(outjson,'w') as outfd:
                    json.dump(indict, outfd)

        # if this is a new output file
        else:

            with open(outjson,'w') as outfd:
                outdict = {'checkplots':searchresults,
                           'nfiles':len(searchresults)}
                json.dump(outdict,outfd)

        if os.path.exists(outjson):
            print('checkplot file list written to %s' % outjson)
        else:
            print('ERR! writing the checkplot file list failed!')

    else:

        print('ERR! no checkplots found in %s' % checkplotbasedir)

if __name__ == '__main__':

    args = sys.argv
    main(args=args)
<commit_msg>checkplotlist: fix docstring<commit_after>#!/usr/bin/env python
'''checkplotlist.py - Waqas Bhatti (wbhatti@astro.princeton.edu) - Dec 2016
License: MIT. See LICENSE for full text.

DESCRIPTION
===========

This makes a checkplot file list for use with the checkplot-viewer.html or the
checkplotserver.py webapps. Checkplots are quick-views of object info, finder
charts, light curves, phased light curves, and periodograms used to examine
their stellar variability. These are produced by several functions in the
astrobase.checkplot module:

checkplot.checkplot_png: makes a checkplot PNG for a single period-finding
                         method

checkplot.twolsp_checkplot_png: does the same for two independent period-finding
                                methods

checkplot.checkplot_pickle: makes a checkplot .pkl.gz for any number of
                            independent period-finding methods

USAGE
=====

If you made checkplots in the PNG format (checkplot-*.png)
----------------------------------------------------------

Copy checkplot-viewer.html and checkplot-viewer.js to the
base directory from where you intend to serve your checkplot images from. Then
invoke this command from that directory:

$ checkplotlist png subdir/containing/the/checkplots 'optional-glob*.png'

This will generate a checkplot-filelist.json file containing the file paths to
the checkplots.

You can then run a temporary Python web server from this base directory to
browse through all the checkplots:

$ python -m SimpleHTTPServer # Python 2
$ python3 -m http.server     # Python 3

then browse to http://localhost:8000/checkplot-viewer.html.

If this directory is already in a path served by a web server, then you can just
browse to the checkplot-viewer.html file normally. Note that a file:/// URL
provided to the browser won't necessarily work in some browsers (especially
Google Chrome) because of security precautions.

If you made checkplots in the pickle format (checkplot-*.pkl)
-------------------------------------------------------------

Invoke this command from that directory like so:

$ checkplotlist pkl subdir/containing/the/checkplots 'optional-glob*.pkl'

Then, from that directory, invoke the checkplotserver webapp (make sure the
astrobase virtualenv is active, so the command below is in your path):

$ checkplotserver [list of options, use --help to see these]

The webapp will start up a Tornado web server running on your computer and
listening on a local address (default: http://localhost:5225). This webapp will
read the checkplot-filelist.json file to find the checkplots.

Browse to http://localhost:5225 (or whatever port you set in checkplotserver
options) to look through or update all your checkplots. Any changes will be
written back to the checkplot .pkl files, making this method of browsing more
suited to more serious variability searches on large numbers of checkplots.

TL;DR
=====

This makes a checkplot file list for use with the checkplot-viewer.html (for
checkplot PNGs) or the checkplotserver.py (for checkplot pickles) webapps.

checkplotlist <pkl|png> <subdir/containing/checkplots/> '[optional checkplot file glob]' '[optional sort specification]'

If you have checkplots that don't have 'checkplot' somewhere in their file name,
use the optional checkplot file glob argument to checkplotlist to provide
this. Make sure to use the quotes around this argument, otherwise the shell will
expand it, e.g.:

$ checkplotlist png my-project/awesome-objects '*awesome-objects*'

For checkplot pickles only: If you want to sort the checkplot pickles in some
special way, this requires an arg on the commandline of the form:
'<sortkey>-<asc|desc>' after the checkplot file glob. Here, sortkey is some key
in the checkplot pickle: this can be a simple key: e.g. objectid or it can be a
composite key: e.g. varinfo.varfeatures.stetsonj. sortorder is either 'asc'
or desc' for ascending/descending sort.

Example: sort checkplots by their already determined Stetson J indices in
descending order:

$ checkplotlist pkl my-project/awesome-objects '*awesome-objects*' 'varinfo.features.stetsonj-desc'

'''

import os
import os.path
import sys
import glob
import json

# to turn a list of keys into a dict address
# from https://stackoverflow.com/a/14692747
# used to walk a checkplotdict for a specific key in the structure
from astrobase import checkplot
import numpy as np
import multiprocessing as mp

from functools import reduce  # forward compatibility for Python 3
from operator import getitem

def dict_get(datadict, keylist):
    return reduce(getitem, keylist, datadict)

def sortkey_worker(task):
    cpf, key = task
    cpd = checkplot._read_checkplot_picklefile(cpf)
    return dict_get(cpd, key)

def main(args=None):

    if not args:
        args = sys.argv

    if len(args) < 3:
        docstring = __doc__
        if docstring:
            print(docstring)
        else:
            print("Usage: %s <pkl|png> <subdir/containing/the/checkplots/> "
                  "'[file glob to use] [checkplot pickle sort key-sort order]'"
                  % args[0])
        sys.exit(2)

    checkplotbasedir = args[2]

    if len(args) == 5:
        sortkey, sortorder = args[4].split('-')
    else:
        sortkey, sortorder = None, None

    if len(args) == 4:
        fileglob = args[3]
    else:
        fileglob = '*checkplot*'

    if args[1] == 'pkl':
        checkplotext = 'pkl'
    elif args[1] == 'png':
        checkplotext = 'png'
    else:
        print("unknown format for checkplots: %s! can't continue!"
              % args[1])
        sys.exit(1)

    currdir = os.getcwd()

    checkplotglob = os.path.join(checkplotbasedir,
                                 '%s.%s' % (fileglob, checkplotext))

    print('searching for checkplots: %s' % checkplotglob)

    searchresults = glob.glob(checkplotglob)

    if searchresults:

        print('found %s checkplot files in %s, '
              'making checkplot-filelist.json...' %
              (len(searchresults), checkplotbasedir))

        outjson = os.path.abspath(
            os.path.join(currdir,'checkplot-filelist.json')
        )

        # see if we should sort the searchresults in some special order
        # this requires an arg on the commandline of the form:
        # '<sortkey>-<asc|desc>'
        # where sortkey is some key in the checkplot pickle:
        #   this can be a simple key: e.g. objectid
        #   or it can be a composite key: e.g. varinfo.varfeatures.stetsonj
        # and sortorder is either 'asc' or desc' for ascending/descending sort
        if sortkey and sortorder:

            print('sorting checkplot pickles by %s in order: %s...' %
                  (sortkey, sortorder))

            # dereference the sort key
            sortkeys = sortkey.split('.')

            pool = mp.Pool()
            tasks = [(x, sortkeys) for x in searchresults]
            sorttargets = pool.map(sortkey_worker, tasks)

            pool.close()
            pool.join()

            sorttargets = np.array(sorttargets)
            sortind = np.argsort(sorttargets)
            if sortorder == 'desc':
                sortind = sortind[::-1]
            searchresults = np.array(searchresults)
            searchresults = searchresults[sortind].tolist()

        # if there's no special sort order defined, use the usual sort order
        else:
            print('no special sort key and order specified, '
                  'sorting checkplot pickles '
                  'using usual alphanumeric sort...')
            searchresults = sorted(searchresults)

        # ask if the checkplot list JSON should be updated
        if os.path.exists(outjson):

            answer = input('There is an existing '
                           'checkplot list file in this '
                           'directory:\n    %s\nDo you want to '
                           'overwrite it? (default: no) [y/n] ' % outjson)

            # if it's OK to overwrite, then do so
            if answer and answer == 'y':

                with open(outjson,'w') as outfd:
                    print('overwriting existing checkplot list')
                    outdict = {'checkplots':searchresults,
                               'nfiles':len(searchresults)}
                    json.dump(outdict,outfd)

            # if it's not OK to overwrite, then
            else:

                # read in the outjson, and add stuff to it for objects that
                # don't have an entry
                print('updating existing checkplot list file')

                with open(outjson,'r') as infd:
                    indict = json.load(infd)

                # update the checkplot list only
                indict['checkplots'] = searchresults
                indict['nfiles'] = len(searchresults)
                # write the updated to back to the file
                with open(outjson,'w') as outfd:
                    json.dump(indict, outfd)

        # if this is a new output file
        else:

            with open(outjson,'w') as outfd:
                outdict = {'checkplots':searchresults,
                           'nfiles':len(searchresults)}
                json.dump(outdict,outfd)

        if os.path.exists(outjson):
            print('checkplot file list written to %s' % outjson)
        else:
            print('ERR! writing the checkplot file list failed!')

    else:

        print('ERR! no checkplots found in %s' % checkplotbasedir)

if __name__ == '__main__':

    args = sys.argv
    main(args=args)
<|endoftext|>


<commit_before>"""
trypython パッケージのメインファイルです.

python インタープリターにて

$ python -m trypython

と起動した場合に実行されます。
"""
import argparse

from trypython import mappings

onetime = False
verbose = False

def main():
    """メイン処理. ユーザに入力を求め、指定されたサンプルを実行します"""
    while True:
        # ユーザにサンプル名の入力を行ってもらう
        user_input: str = input('ENTER EXAMPLE NAME: ').strip()

        if not user_input:
            continue

        if user_input.lower() == 'quit':
            break

        # 候補リストを構築
        candidates = [x for x in mappings.keys() if user_input in x]
        if not candidates:
            print(f'no hit...try again.')
            continue
        if 1 < len(candidates):
            print(f'hit {len(candidates)} counts.')
            for x in candidates:
                print(f'\t{x}')
            continue

        # 実行対象を検査
        target = candidates[0]
        if target not in mappings:
            print(f'sorry...not exists - {target}. try again')
            continue

        m = mappings[target]
        if not callable(m):
            print(f'サンプルとして実行不可 {m}')
            continue

        # サンプル実行
        try:
            print(f'\n[START] ==== {target} ====')
            m()
            print(f'[END  ] ==== {target} ====\n')
        except Exception as e:
            print(f'サンプル実行中にエラーが発生しました [{e}]')
        finally:
            if onetime:
                break

    print('\nDONE')

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='trypython -- example executor')
    parser.add_argument('-o', '--onetime', action='store_true', default=True, help='サンプルを一度だけ実行して終了する')
    parser.add_argument('-v', '--verbose', action='store_true', help='冗長なログ出力モード')

    args = parser.parse_args()

    onetime = args.onetime
    verbose = args.verbose
    main()
<commit_msg>Update main.py<commit_after>"""
trypython パッケージのメインファイルです.

python インタープリターにて

$ python -m trypython

と起動した場合に実行されます。
"""
import argparse

from trypython import mappings
from trypython.common.commoncls import timetracer

onetime = False
verbose = False

def main():
    """メイン処理. ユーザに入力を求め、指定されたサンプルを実行します"""
    while True:
        # ユーザにサンプル名の入力を行ってもらう
        user_input: str = input('ENTER EXAMPLE NAME: ').strip()

        if not user_input:
            continue

        if user_input.lower() == 'quit':
            break

        # 候補リストを構築
        candidates = [x for x in mappings.keys() if user_input in x]
        if not candidates:
            print(f'no hit...try again.')
            continue
        if 1 < len(candidates):
            print(f'hit {len(candidates)} counts.')
            for x in candidates:
                print(f'\t{x}')
            continue

        # 実行対象を検査
        target = candidates[0]
        if target not in mappings:
            print(f'sorry...not exists - {target}. try again')
            continue

        m = mappings[target]
        if not callable(m):
            print(f'サンプルとして実行不可 {m}')
            continue

        # サンプル実行
        try:
            with timetracer('EXEC '):
                print(f'\n[START] ==== {target} ====')
                m()
                print(f'[END  ] ==== {target} ====\n')
        except Exception as e:
            print(f'サンプル実行中にエラーが発生しました [{e}]')
        finally:
            if onetime:
                break

    print('\nDONE')

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='trypython -- example executor')
    parser.add_argument('-o', '--onetime', action='store_true',
                        default=True, help='サンプルを一度だけ実行して終了する')
    parser.add_argument('-v', '--verbose',
                        action='store_true', help='冗長なログ出力モード')

    args = parser.parse_args()

    onetime = args.onetime
    verbose = args.verbose
    main()
<|endoftext|>