Exemple #1
0
    def test_using_transactions(self):
        with Using(slave1, [A]) as txn:
            list(B.select())
            A.create(data='a1')

        B.create(data='b1')
        self.assertDatabaseVerb([
            ('slave1', 'BEGIN'),
            ('master', 'SELECT'),
            ('slave1', 'INSERT'),
            ('master', 'INSERT')])

        def fail_with_exc(data):
            with Using(slave2, [A]):
                A.create(data=data)
                raise ValueError('xxx')

        self.assertRaises(ValueError, fail_with_exc, 'a2')
        self.assertDatabaseVerb([
            ('slave2', 'BEGIN'),
            ('slave2', 'INSERT')])

        with Using(slave1, [A, B]):
            a_objs = [a_obj.data for a_obj in A.select()]
            self.assertEqual(a_objs, ['a1'])
Exemple #2
0
    def test_using_context(self):
        models = [A, B]

        with Using(slave1, models, False):
            A.create(data='a1')
            B.create(data='b1')

        self.assertDatabaseVerb([
            ('slave1', 'INSERT'),
            ('slave1', 'INSERT')])

        with Using(slave2, models, False):
            A.create(data='a2')
            B.create(data='b2')
            a_obj = A.select().order_by(A.id).first()
            self.assertEqual(a_obj.data, 'a1')

        self.assertDatabaseVerb([
            ('slave2', 'INSERT'),
            ('slave2', 'INSERT'),
            ('slave2', 'SELECT')])

        with Using(master, models, False):
            query = A.select().order_by(A.data.desc())
            values = [a_obj.data for a_obj in query]

        self.assertEqual(values, ['a2', 'a1'])
        self.assertDatabaseVerb([('master', 'SELECT')])
Exemple #3
0
    def wrapper(*args, **kwargs):
        language = kwargs.get("language", "en")

        path = kwargs.pop("database_path", None)
        if not path:
            path = CONTENT_DATABASE_PATH.format(channel=kwargs.get(
                "channel", CHANNEL),
                                                language=language)

        db = SqliteDatabase(path, pragmas=settings.CONTENT_DB_SQLITE_PRAGMAS)

        kwargs["db"] = db

        db.connect()

        # This should contain all models in the database to make them available to the wrapped function

        with Using(db, [Item, AssessmentItem]):

            try:

                output = function(*args, **kwargs)

            except DoesNotExist:
                output = None

            except OperationalError as e:
                logging.error(
                    "Content DB error: Perhaps content database file found? "
                    "Exception: {e}".format(e=str(e)))
                raise
        db.close()

        return output
Exemple #4
0
 def get_medias_series(self):
     with Using(self.database, [SeriesMedias, SeriesSubtitles],
                with_transaction=False):
         limit = 100
         medias = (SeriesMedias.select().order_by(
             SeriesMedias.series_title.desc()).limit(limit).execute())
         return [{
             "added_timestamp":
             med.added_timestamp.strftime('%Y-%m-%dT%H:%M:%S'),
             "series_title":
             med.series_title,
             "season_number":
             med.season_number,
             "episode_number":
             med.episode_number,
             "episode_title":
             med.episode_title,
             "quality":
             med.quality,
             "video_languages":
             med.video_languages,
             "subtitle_languages":
             sorted(s.language for s in med.subtitles),
             "dirty":
             med.dirty,
         } for med in medias]
Exemple #5
0
def setup_content_paths(context, db):
    """
    Creaters available content items and adds their urls to the context object.

    :param context: A behave context, to which the attributes "available_content_path" and "unavailable_content_path"
        will be added.
    :return: None
    """
    # These paths are "magic" -- the success or failure of actually visiting the content items in the browser
    # depends on these specific values.
    context.unavailable_content_path, context.available_content_path = (
        "khan/foo/bar/unavail",
        "khan/math/arithmetic/addition-subtraction/basic_addition/addition_1/",
    )

    # This function uses 'iterator_content_items' function to return a list of path, update dict pairs
    # It then updates the items with these paths with their update dicts, and then propagates
    # availability changes up the topic tree - this means that we can alter the availability of one item
    # and make all its parent topics available so that it is navigable to in integration tests.
    def iterator_content_items(ids=None, channel="khan", language="en"):
        return [(context.available_content_path, {"available": True})]

    annotate_content_models(db=db,
                            iterator_content_items=iterator_content_items)

    with Using(db, [Item], with_transaction=False):
        context._unavailable_item = Item.create(
            title="Unavailable item",
            description="baz",
            available=False,
            kind="Video",
            id="3",
            slug="unavail",
            path=context.unavailable_content_path)
 def setUp(self, db=None):
     self.db = db
     with Using(db, [Item], with_transaction=False):
         parent = self.parent = Item.create(
             title="Foo",
             description="Bar",
             available=True,
             kind="Topic",
             id="1",
             slug="foo",
             path="foopath"
         )
         self.available_item = Item.create(
             title="available_item",
             description="Bingo",
             available=True,
             kind="Topic",
             id="2",
             slug="avail",
             path="avail",
             parent=parent
         )
         self.unavailable_item = Item.create(
             title="Unavailable item",
             description="baz",
             available=False,
             kind="Topic",
             id="3",
             slug="unavail",
             path="unavail",
             parent=parent
         )
    def test_update_content_availability_true(self):

        with Using(self.db, [Item]):
            actual = dict(
                update_content_availability([
                    unparse_model_data(model_to_dict(self.item))
                ])).get("thepath")
            assert actual.get("available")
Exemple #8
0
 def get_recent_events(self, limit: int):
     with Using(self.database, [Events], with_transaction=False):
         events = (Events.select().limit(limit).order_by(
             Events.timestamp.desc()).execute())
         return [{
             "timestamp": e.timestamp.strftime('%Y-%m-%dT%H:%M:%S'),
             "type": e.type,
             "message": e.message
         } for e in events]
Exemple #9
0
def teardown_content_paths(context, db):
    """
    The opposite of ``setup_content_urls``. Removes content items created there.

    :param context: A behave context, which keeps a reference to the Items so we can clean them up.
    :return: None.
    """
    with Using(db, [Item], with_transaction=False):
        context._unavailable_item.delete_instance()
    def tearDown(self):
        with Using(self.db, [Item], with_transaction=False):
            self.item.delete_instance()

        if self.cleanup:
            try:
                os.remove(self.version_path)
            except OSError:
                pass
Exemple #11
0
def recurse_availability_up_tree(nodes, db) -> [Item]:

    logging.info("Marking availability.")

    nodes = list(nodes)

    def _recurse_availability_up_tree(node):

        available = node.available
        if not node.parent:
            return node
        else:
            parent = node.parent
        Parent = Item.alias()
        children = Item.select().join(
            Parent,
            on=(Item.parent == Parent.pk)).where(Item.parent == parent.pk)
        if not available:
            children_available = children.where(
                Item.available == True).count() > 0
            available = children_available

        total_files = children.aggregate(fn.SUM(Item.total_files))

        child_remote = children.where((
            (Item.available == False) & (Item.kind != "Topic"))
                                      | (Item.kind == "Topic")).aggregate(
                                          fn.SUM(Item.remote_size))
        child_on_disk = children.aggregate(fn.SUM(Item.size_on_disk))

        if parent.available != available:
            parent.available = available
        if parent.total_files != total_files:
            parent.total_files = total_files
        # Ensure that the aggregate sizes are not None
        if parent.remote_size != child_remote and child_remote:
            parent.remote_size = child_remote
        # Ensure that the aggregate sizes are not None
        if parent.size_on_disk != child_on_disk and child_on_disk:
            parent.size_on_disk = child_on_disk
        if parent.is_dirty():
            parent.save()
            _recurse_availability_up_tree(parent)

        return node

    with Using(db, [Item]):
        # at this point, the only thing that can affect a topic's availability
        # are exercises. Videos and other content's availability can only be
        # determined by what's in the client. However, we need to set total_files
        # and remote_sizes. So, loop over exercises and other content,
        # and skip topics as they will be recursed upwards.
        for node in (n for n in nodes if n.kind != NodeType.topic):
            _recurse_availability_up_tree(node)

    return nodes
Exemple #12
0
def teardown_content_db(instance, db):
    """
    Seems to split out in a classmethod because BDD base_environment wants
    to reuse it.
    """
    with Using(db, [Item], with_transaction=False):
        instance.content_unavailable_item.delete_instance()
        instance.content_root.delete_instance()
        for item in (instance.content_exercises + instance.content_videos +
                     instance.content_subsubtopics +
                     instance.content_subtopics):
            item.delete_instance()
Exemple #13
0
def save_models(nodes, db):
    """
    Save all the models in nodes into the db specified.
    """
    # aron: I didn't bother writing tests for this, since it's such a simple
    # function!
    db.create_table(Item, safe=True)
    with Using(db, [Item]):
        for node in nodes:
            try:
                node.save()
            except Exception as e:
                logging.warning("Cannot save {path}, exception: {e}".format(path=node.path, e=e))

            yield node
Exemple #14
0
def save_assessment_items(assessment_items, db):
    """
    Save all the models in nodes into the db specified.
    """
    # aron: I didn't bother writing tests for this, since it's such a simple
    # function!
    db.create_table(AssessmentItem, safe=True)
    with Using(db, [AssessmentItem]):
        for item in assessment_items:
            try:
                item.save()
            except Exception as e:
                logging.warning("Cannot save {id}, exception: {e}".format(id=item.id, e=e))

            yield item
Exemple #15
0
    def test_writes_db_to_archive(self):
        with tempfile.NamedTemporaryFile() as zffobj:
            zf = zipfile.ZipFile(zffobj, "w")

            with tempfile.NamedTemporaryFile() as dbfobj:
                db = SqliteDatabase(dbfobj.name)
                db.connect()
                with Using(db, [Item]):
                    Item.create_table()
                    item = Item(id="test",
                                title="test",
                                description="test",
                                available=False,
                                slug="srug",
                                kind=NodeType.video,
                                path="/test/test")
                    item.save()
                db.close()

                save_db(db, zf)

            zf.close()

            # reopen the db from the zip, see if our object was saved
            with tempfile.NamedTemporaryFile() as f:
                # we should only have one file in the zipfile, the db. Assume
                # that the first file is the db.
                zf = zipfile.ZipFile(zffobj.name)
                dbfobj = zf.open(zf.infolist()[0])
                f.write(dbfobj.read())
                f.seek(0)

                db = SqliteDatabase(f.name)

                with Using(db, [Item]):
                    Item.get(title="test")
    def test_update_content_availability_false(self):

        try:
            os.rename(self.version_path, self.version_path + ".bak")
        except OSError:
            pass

        with Using(self.db, [Item]):
            actual = dict(
                update_content_availability([
                    unparse_model_data(model_to_dict(self.item))
                ])).get("thepath")
            # Update is only generated if changed from False to True, not from False to False, so should return None.
            assert not actual

        try:
            os.rename(self.version_path + ".bak", self.version_path)
        except OSError:
            pass
Exemple #17
0
 def update_fetched_series_subtitles(self,
                                     series_episode_uid,
                                     subtitles_languages,
                                     dirty=True):
     with Using(self.database, [SeriesMedias, SeriesSubtitles],
                with_transaction=False):
         media = (SeriesMedias.select().where(
             SeriesMedias.tv_db_id == series_episode_uid.tv_db_id,
             SeriesMedias.season_number == series_episode_uid.season_number,
             SeriesMedias.episode_number ==
             series_episode_uid.episode_number))
         for lang in subtitles_languages:
             self._get_or_create(
                 SeriesSubtitles,
                 series_media=media,
                 language=lang,
             )
         for m in media:
             m.dirty = dirty
             m.save()
Exemple #18
0
 def get_last_fetched_series(self, limit: int):
     with Using(self.database, [SeriesMedias, SeriesSubtitles],
                with_transaction=False):
         events = (SeriesSubtitles.select().order_by(
             SeriesSubtitles.added_timestamp.desc()).limit(limit).execute())
         return [{
             "added_timestamp":
             e.added_timestamp.strftime('%Y-%m-%dT%H:%M:%S'),
             "series_title":
             e.series_media.series_title,
             "season_number":
             e.series_media.season_number,
             "episode_number":
             e.series_media.episode_number,
             "episode_title":
             e.series_media.episode_title,
             "quality":
             e.series_media.quality,
             "video_languages":
             e.series_media.video_languages,
             "subtitle_language":
             e.language,
         } for e in events]
    def setUp(self, db=None):
        self.db = db
        with Using(db, [Item], with_transaction=False):
            self.item = Item(
                title=self.TITLE,
                available=self.AVAILABLE,
                kind=self.KIND,
                description="test",
                id="counting-out-1-20-objects",
                slug="test",
                path="thepath",
                extra_fields={},
            )
            self.item.save()
        self.version_path = contentload_settings.KHAN_ASSESSMENT_ITEM_VERSION_PATH

        self.cleanup = False

        if not os.path.exists(self.version_path):

            with open(self.version_path, 'w') as f:
                f.write("stuff")
            self.cleanup = True
Exemple #20
0
 def update_series_media(self,
                         series_title,
                         tv_db_id,
                         season_number,
                         episode_number,
                         episode_title,
                         quality,
                         video_languages,
                         media_filename,
                         dirty=True):
     assert media_filename, "media_filename cannot be None"
     with Using(self.database, [SeriesMedias], with_transaction=False):
         media, _ = self._get_or_create(SeriesMedias,
                                        tv_db_id=tv_db_id,
                                        season_number=season_number,
                                        episode_number=episode_number,
                                        media_filename=media_filename)
         media.series_title = series_title
         media.episode_title = episode_title
         media.quality = quality
         media.video_languages = video_languages
         media.dirty = dirty
         media.media_filename = media_filename
         media.save()
 def fail_with_exc(data):
     with Using(slave2, [A]):
         A.create(data=data)
         raise ValueError('xxx')
Exemple #22
0
 def media_exists(self, media_filename):
     with Using(self.database, [SeriesMedias], with_transaction=False):
         medias = (SeriesMedias.select(SeriesMedias.media_filename).where(
             SeriesMedias.media_filename == media_filename)).execute()
         return bool(medias)
Exemple #23
0
 def insert_event(self, thetype: str, message: str):
     with Using(self.database, [Events], with_transaction=False):
         Events.create(type=thetype, message=message)
 def tearDown(self):
     with Using(self.db, [Item], with_transaction=False):
         self.available_item.delete_instance()
         self.unavailable_item.delete_instance()
         self.parent.delete_instance()
Exemple #25
0
def setup_content_db(instance, db):

    # Setup the content.db (defaults to the en version)
    with Using(db, [Item], with_transaction=False):
        # Root node
        instance.content_root = Item.create(title="Khan Academy",
                                            description="",
                                            available=True,
                                            files_complete=0,
                                            total_files="1",
                                            kind="Topic",
                                            parent=None,
                                            id="khan",
                                            slug="khan",
                                            path="khan/",
                                            extra_fields="{}",
                                            youtube_id=None,
                                            remote_size=315846064333,
                                            sort_order=0)
        for _i in range(4):
            slug = "topic{}".format(_i)
            instance.content_subtopics.append(
                Item.create(
                    title="Subtopic {}".format(_i),
                    description="A subtopic",
                    available=True,
                    files_complete=0,
                    total_files="4",
                    kind="Topic",
                    parent=instance.content_root,
                    id=slug,
                    slug=slug,
                    path="khan/{}/".format(slug),
                    extra_fields="{}",
                    remote_size=1,
                    sort_order=_i,
                ))

        # Parts of the content recommendation system currently is hard-coded
        # to look for 3rd level recommendations only and so will fail if we
        # don't have this level of lookup
        for subtopic in instance.content_subtopics:
            for _i in range(4):
                slug = "{}-{}".format(subtopic.id, _i)
                instance.content_subsubtopics.append(
                    Item.create(
                        title="{} Subsubtopic {}".format(subtopic.title, _i),
                        description="A subsubtopic",
                        available=True,
                        files_complete=4,
                        total_files="4",
                        kind="Topic",
                        parent=subtopic,
                        id=slug,
                        slug=slug,
                        path="{}{}/".format(subtopic.path, slug),
                        youtube_id=None,
                        extra_fields="{}",
                        remote_size=1,
                        sort_order=_i,
                    ))

        # We need at least 10 exercises in some of the tests to generate enough
        # data etc.
        # ...and we need at least some exercises in each sub-subtopic
        for parent in instance.content_subsubtopics:
            # Make former created exercise the prerequisite of the next one
            prerequisite = None
            for _i in range(4):
                slug = "{}-exercise-{}".format(parent.id, _i)
                extra_fields = {}
                if prerequisite:
                    extra_fields['prerequisites'] = [prerequisite.id]
                new_exercise = Item.create(
                    title="Exercise {} in {}".format(_i, parent.title),
                    parent=parent,
                    description="Solve this",
                    available=True,
                    kind="Exercise",
                    id=slug,
                    slug=slug,
                    path="{}{}/".format(parent.path, slug),
                    sort_order=_i,
                    **extra_fields)
                instance.content_exercises.append(new_exercise)
                prerequisite = new_exercise
        # Add some videos, too, even though files don't exist
        for parent in instance.content_subsubtopics:
            for _i in range(4):
                slug = "{}-video-{}".format(parent.pk, _i)
                instance.content_videos.append(
                    Item.create(
                        title="Video {} in {}".format(_i, parent.title),
                        parent=random.choice(instance.content_subsubtopics),
                        description="Watch this",
                        available=True,
                        kind="Video",
                        id=slug,
                        slug=slug,
                        path="{}{}/".format(parent.path, slug),
                        extra_fields={
                            "subtitle_urls": [],
                            "content_urls": {
                                "stream": "/foo",
                                "stream_type": "video/mp4"
                            },
                        },
                        sort_order=_i))

    with Using(db, [Item], with_transaction=False):
        instance.content_unavailable_item = Item.create(
            title="Unavailable item",
            description="baz",
            available=False,
            kind="Video",
            id="unavail123",
            slug="unavail",
            path=instance.content_unavailable_content_path,
            parent=random.choice(instance.content_subsubtopics).pk,
        )

    instance.content_available_content_path = random.choice(
        instance.content_exercises).path