Metadata-Version: 1.1
Name: ricecooker
Version: 0.0.5
Summary: API for adding content to the Kolibri content curation server
Home-page: https://github.com/learningequality/ricecooker
Author: Learning Equality
Author-email: dev@learningequality.org
License: MIT license
Description: Rice Cooker
        ===========
        
        A framework for creating channels on `Kolibri
        Studio <https://contentworkshop.learningequality.org/>`__.
        
        Installation
        ------------
        
        -  `Install ffmpeg <https://ffmpeg.org/>`__ if you don't have it
           already.
        
        -  `Install pip <https://pypi.python.org/pypi/pip>`__ if you don't have
           it already.
        
        -  Run ``pip install ricecooker``
        
        -  You can now reference ricecooker using ``import ricecooker`` in your
           .py files
        
        Using the Rice Cooker
        ---------------------
        
        | The rice cooker is a framework you can use to translate content into
          Kolibri-compatible objects.
        | The following steps will guide you through the creation of a program,
          or sushi chef,
        | that uses the ``ricecooker`` framework.
        | A sample sushi chef has been created
          `here <https://github.com/learningequality/ricecooker/blob/master/examples/sample_program.py>`__.
        
        Step 1: Obtaining an Authorization Token
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        
        You will need an authorization token to create a channel on Kolibri
        Studio. In order to obtain one:
        
        #. Create an account on `Kolibri
           Studio <https://contentworkshop.learningequality.org/>`__.
        #. Navigate to the Tokens tab under your Settings page.
        #. Copy the given authorization token.
        #. Set ``token="auth-token"`` in your call to uploadchannel
           (alternatively, you can create a file with your
           authorization token and set ``token="path/to/file.txt"``).
        
        Step 2: Creating a Sushi Chef class
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        
        | To use the Ricecooker, your chef script must define a sushi chef class
          that is a
        | subclass of the class ``ricecooker.chefs.SushiChef``. Since it
          inheriting from the
        | ``SushiChef`` class, your chef class will have the method ``run``
          which performs all
        | the work of uploading your channel to the content curation server.
        | Your sushi chef class will also inherit the method ``main``, which
          your sushi chef
        | script should call when it runs on the command line.
        
        The sushi chef class for your channel must have the following
        attributes:
        
        -  ``channel_info`` (dict) that looks like this:
        
           ::
        
               channel_info = {
                   'CHANNEL_SOURCE_DOMAIN': '<yourdomain.org>',       # who is providing the content (e.g. learningequality.org)
                   'CHANNEL_SOURCE_ID': '<some unique identifier>',   # channel's unique id
                   'CHANNEL_TITLE': 'Channel name shown in UI',
                   'CHANNEL_THUMBNAIL': 'http://yourdomain.org/img/logo.jpg', # (optional) local path or url to image file
                   'CHANNEL_DESCRIPTION': 'What is this channel about?',      # (optional) description of the channel (optional)
                }
        
        -  | ``construct_channel(**kwargs) -> ChannelNode``: This method is
             responsible for
           | building the structure of your channel (to be discussed below).
        
        | To write the ``construct_channel`` method of your chef class, start by
          importing
        | ``ChannelNode`` from ``ricecooker.classes.nodes`` and create a
          ``ChannelNode`` using
        | the data in ``self.channel_info``. Once you have the ``ChannelNode``
          instance, the
        | rest of your chef's ``construct_channel`` method is responsible for
          constructing
        | the channel by adding various ``Node``\ s using the method
          ``add_child``.
        | ``TopicNode``\ s correspond to folders, while ``ContentNode``\ s
          correspond to different
        | type of content nodes.
        
        | ``ContentNode`` objects (and subclasses like ``VideoNode``,
          ``AudioNode``, ...) store
        | the metadata associate with the content, and are associated with one
          or more
        | ``File`` objects (``VideoFile``, ``AudioFile``, ...).
        
        | For example, here is a simple sushi chef class whose
          ``construct_channel`` builds
        | a tree with a single topic.
        
        ::
        
            from ricecooker.chefs import SushiChef
            from ricecooker.classes.nodes import ChannelNode, TopicNode
        
            class MySushiChef(SushiChef):
                """
                This is my sushi chef...
                """
                channel_info = {
                    'CHANNEL_SOURCE_DOMAIN': '<yourdomain.org>',       # make sure to change this when testing
                    'CHANNEL_SOURCE_ID': '<some unique identifier>',   # channel's unique id
                    'CHANNEL_TITLE': 'Channel name shown in UI',
                    'CHANNEL_THUMBNAIL': 'http://yourdomain.org/img/logo.jpg', # (optional) local path or url to image file
                    'CHANNEL_DESCRIPTION': 'What is this channel about?',      # (optional) description of the channel (optional)
                }
        
                def construct_channel(self, **kwargs):
                    channel_info = self.channel_info
                    # create channel
                    channel = ChannelNode(
                        source_domain = channel_info['CHANNEL_SOURCE_DOMAIN'],
                        source_id = channel_info['CHANNEL_SOURCE_ID'],
                        title = channel_info['CHANNEL_TITLE'],
                        thumbnail = channel_info.get('CHANNEL_THUMBNAIL'),
                        description = channel_info.get('CHANNEL_DESCRIPTION'),
                   )
                   # create a topic and add it to channel
                   potato_topic = TopicNode(source_id="<potatos_id>", title="Potatoes!")
                   channel.add_child(potato_topic)
                   return channel
        
        | You can now run of you chef by creating an instance of the chef class
          and calling
        | it's ``run`` method:
        
        ::
        
            mychef = MySushiChef()
            args = {'token': 'YOURTOKENHERE9139139f3a23232', 'reset': True, 'verbose': True}
            options = {}
            mychef.run(args, options)
        
        | Note: Normally you'll pass ``args`` and ``options`` on the command
          line, but you can
        | pass dict objects with the necessary parameters for testing.
        
        | If you get an error, make sure you've replaced
          ``YOURTOKENHERE9139139f3a23232`` by
        | the token you obtained from the content curation server and you've
          changed
        | ``channel_info['CHANNEL_SOURCE_DOMAIN']`` and/or
          ``channel_info['CHANNEL_SOURCE_ID']``
        | instead of using the default values.
        
        | If the channel run was successful, you should be able to see your
          single-topic
        | channel on the content curation server. The topic node "Potatoes!" is
          nice to
        | look at, but it feels kind of empty. Let's add more nodes to it!
        
        Step 3: Creating Nodes
        ~~~~~~~~~~~~~~~~~~~~~~
        
        | Once your channel is created, you can start adding nodes. To do this,
          you need to
        | convert your data to the rice cooker's objects. Here are the classes
          that are
        | available to you (import from ``ricecooker.classes.nodes``):
        
        -  **TopicNode**: folders to organize to the channel's content
        -  **VideoNode**: content containing mp4 file
        -  **AudioNode**: content containing mp3 file
        -  **DocumentNode**: content containing pdf file
        -  **HTML5AppNode**: content containing zip of html files (html, js,
           css, etc.)
        -  **ExerciseNode**: assessment-based content with questions
        
        Each node has the following attributes:
        
        -  **source\_id** (str): content's original id
        -  **title** (str): content's title
        -  **license** (str or License): content's license id or object
        -  **description** (str): description of content (optional)
        -  **author** (str): who created the content (optional)
        -  **thumbnail** (str or ThumbnailFile): path to thumbnail or file
           object (optional)
        -  **files** ([FileObject]): list of file objects for node (optional)
        -  **extra\_fields** (dict): any additional data needed for node
           (optional)
        -  **domain\_ns** (uuid): who is providing the content (e.g.
           learningequality.org) (optional)
        
        | **IMPORTANT**: nodes representing distinct pieces of content MUST have
          distinct ``source_id``\ s.
        | Each node has a ``content_id`` (computed as a function of the
          ``source_domain`` and the node's ``source_id``) that uniquely
          identifies a piece of content within Kolibri for progress tracking
          purposes. For example, if the same video occurs in multiple places in
          the tree, you would use the same ``source_id`` for those nodes -- but
          content nodes that aren't for that video need to have different
          ``source_id``\ s.
        
        All non-topic nodes must be assigned a license upon initialization. You
        can use the license's id (found under ``le_utils.constants.licenses``)
        or create a license object from ``ricecooker.classes.licenses``
        (recommended). When initializing a license object, you can specify a
        **copyright\_holder** (str), or the person or organization who owns the
        license. If you are unsure which license class to use, a ``get_license``
        method has been provided that takes in a license id and returns a
        corresponding license object.
        
        For example:
        
        ::
        
            from ricecooker.classes.licenses import get_license
            from le_utils.constants import licenses
        
            node = VideoNode(
                license = get_license(licenses.CC_BY, copyright_holder="Khan Academy"),
                ...
            )
        
        Thumbnails can also be passed in as a path to an image (str) or a
        ThumbnailFile object. Files can be passed in upon initialization, but
        can also be added at a later time. More details about how to create a
        file object can be found in the next section. VideoNodes also have a
        **derive\_thumbnail** (boolean) argument, which will automatically
        extract a thumbnail from the video if no thumbnails are provided.
        
        Once you have created the node, add it to a parent node with
        ``parent_node.add_child(child_node)``
        
        Step 4a: Adding Files
        ~~~~~~~~~~~~~~~~~~~~~
        
        To add a file to your node, you must start by creating a file object
        from ``ricecooker.classes.files``. Your sushi chef is responsible for
        determining which file object to create. Here are the available file
        models:
        
        -  **ThumbnailFile**: png or jpg files to add to any kind of node
        -  **AudioFile**: mp3 file
        -  **DocumentFile**: pdf file
        -  **HTMLZipFile**: zip of html files (must have ``index.html`` file at
           topmost level)
        -  **VideoFile**: mp4 file (can be high resolution or low resolution)
        -  **SubtitleFile**: vtt files to be used with VideoFiles
        -  **WebVideoFile**: video downloaded from site such as YouTube or Vimeo
        -  **YouTubeVideoFile**: video downloaded from YouTube using a youtube
           video id
        
        Each file class can be passed a **preset** and **language** at
        initialization (SubtitleFiles must have a language set at
        initialization). A preset determines what kind of file the object is
        (e.g. high resolution video vs. low resolution video). A list of
        available presets can be found at ``le_utils.constants.format_presets``.
        A list of available languages can be found at
        ``le_utils.constants.languages``.
        
        ThumbnailFiles, AudioFiles, DocumentFiles, HTMLZipFiles, VideoFiles, and
        SubtitleFiles must be initialized with a **path** (str). This path can
        be a url or a local path to a file.
        
        ::
        
            from le_utils.constants import languages
        
            file_object = SubtitleFile(
                path = "file:///path/to/file.vtt",
                language = languages.getlang('en').code,
                ...
            )
        
        VideoFiles can also be initialized with **ffmpeg\_settings** (dict),
        which will be used to determine compression settings for the video file.
        
        ::
        
            file_object = VideoFile(
                path = "file:///path/to/file.mp3",
                ffmpeg_settings = {"max_width": 480, "crf": 20},
                ...
            )
        
        WebVideoFiles must be given a **web\_url** (str) to a video on YouTube
        or Vimeo, and YouTubeVideoFiles must be given a **youtube\_id** (str).
        WebVideoFiles and YouTubeVideoFiles can also take in
        **download\_settings** (dict) to determine how the video will be
        downloaded and **high\_resolution** (boolean) to determine what
        resolution to download.
        
        ::
        
            file_object = WebVideoFile(
                web_url = "https://vimeo.com/video-id",
                ...
            )
        
            file_object = YouTubeVideoFile(
                youtube_id = "abcdef",
                ...
            )
        
        Step 4b: Adding Exercises
        ~~~~~~~~~~~~~~~~~~~~~~~~~
        
        ExerciseNodes are special objects that have questions used for
        assessment. To add a question to your exercise, you must first create a
        question model from ``ricecooker.classes.questions``. Your sushi chef is
        responsible for determining which question type to create. Here are the
        available question types:
        
        -  **PerseusQuestion**: special question type for pre-formatted perseus
           questions
        -  **MultipleSelectQuestion**: questions that have multiple correct
           answers (e.g. check all that apply)
        -  **SingleSelectQuestion**: questions that only have one right answer
           (e.g. radio button questions)
        -  **InputQuestion**: questions that have text-based answers (e.g. fill
           in the blank)
        -  **FreeResponseQuestion**: questions that require subjective answers
           (ungraded)
        
        Each question class has the following attributes that can be set at
        initialization:
        
        -  **id** (str): question's unique id
        -  **question** (str): question body, in plaintext or Markdown format;
           math expressions must be in Latex format, surrounded by ``$``, e.g.
           ``$ f(x) = 2 ^ 3 $``.
        -  **answers** ([{'answer':str, 'correct':bool}]): answers to question,
           also in plaintext or Markdown
        -  **hints** (str or [str]): optional hints on how to answer question,
           also in plaintext or Markdown
        
        FreeResponseQuestions do not need any answers set.
        
        ::
        
            question = FreeResponseQuestion(
                question = "Explain why any number times 1 is itself.",
                ...
            )
        
        To set the correct answer(s) for MultipleSelectQuestions, you must
        provide a list of all of the possible choices as well as an array of the
        correct answers (``all_answers [str]``) and ``correct_answers [str]``
        respectively).
        
        ::
        
            question = MultipleSelectQuestion(
                question = "Select all prime numbers.",
                correct_answers = ["2", "3", "5"],
                all_answers = ["1", "2", "3", "4", "5"],
                ...
            )
        
        To set the correct answer(s) for SingleSelectQuestions, you must provide
        a list of all possible choices as well as the correct answer
        (``all_answers [str]`` and ``correct_answer str`` respectively).
        
        ::
        
            question = SingleSelectQuestion(
                question = "What is 2 x 3?",
                correct_answer = "6",
                all_answers = ["2", "3", "5", "6"],
                ...
            )
        
        To set the correct answer(s) for InputQuestions, you must provide an
        array of all of the accepted answers (``answers [str]``).
        
        ::
        
            question = InputQuestion(
                question = "Name a factor of 10.",
                answers = ["1", "2", "5", "10"],
            )
        
        To add images to a question's question, answers, or hints, format the
        image path with ``'![](path/to/some/file.png)'`` and the rice cooker
        will parse them automatically.
        
        In order to set the criteria for completing exercises, you must set
        **exercise\_data** to equal a dict containing a mastery\_model field
        based on the mastery models provided under
        ``le_utils.constants.exercises``. If no data is provided, the rice
        cooker will default to mastery at 3 of 5 correct. For example:
        
        ::
        
            node = ExerciseNode(
                exercise_data={
                    'mastery_model': exercises.M_OF_N,
                    'randomize': True,
                    'm': 3,
                    'n': 5,
                },
                ...
            )
        
        Once you have created the appropriate question object, add it to an
        exercise object with ``exercise_node.add_question(question)``
        
        Step 5: Running your chef script
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        
        | Your sushi chef scripts will run as standalone command line
          application
        | ``mychef.py`` which you can call from the command line.
        
        To make the script file ``mychef.py`` a command line program, you need
        to do three things:
        
        -  Add the line ``#!/usr/bin/env python`` as the first line of
           ``mychef.py``
        -  Add this code block at the bottom of ``mychef.py``:
        
           ::
        
               if __name__ == '__main__':
                   chef = MySushiChef()
                   chef.main()
        
        -  | Make the file ``mychef.py`` executable by running
             ``chmod +x mychef.py`` on the
           | command line.
        
        The final chef script file ``mychef.py`` should look like this:
        
        ::
        
            #!/usr/bin/env python
            ...
            ...
            class MySushiChef(SushiChef):
                channel_info = { ... }
                def construct_channel(**kwargs):
                    ...
                    ...
            ...
            ...
            if __name__ == '__main__':
                chef = MySushiChef()
                chef.main()
        
        You can now call the script by passing the appropriate command line
        arguments:
        
        ::
        
            ./mychef.py -v --token=YOURTOKENHERE9139139f3a23232 --reset
        
        To see the help menu, type
        
        ::
        
            ./mychef.py -h
        
        Here the full list of the supported command line args:
        
        -  ``-h`` (help) will print how to use the rice cooker
        -  ``-v`` (verbose) will print what the rice cooker is doing
        -  ``-u`` (update) will force the ricecooker to redownload all files
           (skip checking the cache)
        -  ``--download-attempts=3`` will set the maximum number of times to
           retry downloading files
        -  ``--warn`` will print out warnings during rice cooking session
        -  ``--compress`` will compress your high resolution videos to save
           space
        -  ``--token`` will authorize you to create your channel (obtained in
           Step 1)
        -  ``--resume`` will resume your previous rice cooking session
        -  ``--step=LAST`` will specify at which step to resume your session
        -  ``--reset`` will automatically start the rice cooker from the
           beginning
        -  ``--prompt`` will prompt you to open your channel once it's been
           uploaded
        -  ``--publish`` will automatically publish your channel once it's been
           uploaded
        -  ``--daemon`` will start the chef in daemon mode (i.e. the chef will
           not execute
           immediately; instead, it will wait to receive commands via the Sushi
           Bar)
        -  ``[OPTIONS]`` any additional key=value options you would like to pass
           to your construct\_channel method
        
        Optional: Resuming the Rice Cooker
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        
        | If your rice cooking session gets interrupted, you can resume from any
          step that
        | has already completed using ``--resume --step=<step>`` option. If step
          is not specified,
        | the rice cooker will resume from the last step you ran. If the
          specified step has
        | not been reached, the rice cooker will resume from. Other choices for
          ``--step``:
        
        -  **LAST**: Resume where the session left off (default)
        -  **INIT**: Resume at beginning of session
        -  **CONSTRUCT\_CHANNEL**: Resume with call to construct channel
        -  **CREATE\_TREE**: Resume at set tree relationships
        -  **DOWNLOAD\_FILES**: Resume at beginning of download process
        -  **GET\_FILE\_DIFF**: Resume at call to get file diff from Kolibri
           Studio
        -  **START\_UPLOAD**: Resume at beginning of uploading files to Kolibri
           Studio
        -  **UPLOADING\_FILES**: Resume at last upload request
        -  **UPLOAD\_CHANNEL**: Resume at beginning of uploading tree to Kolibri
           Studio
        -  **PUBLISH\_CHANNEL**: Resume at option to publish channel
        -  **DONE**: Resume at prompt to open channel
        
        
        =======
        History
        =======
        
        0.1.0 (2016-09-30)
        ------------------
        
        * First release on PyPI.
        
Keywords: ricecooker
Platform: UNKNOWN
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Topic :: Education
