Esempio n. 1
0
def get_bundle_file_data(bundle_uuid, path, use_draft=None):
    """
    Read all the data in the given bundle file and return it as a
    binary string.

    Do not use this for large files!
    """

    if use_draft:
        try:
            draft_model = _draft_queryset().get(bundle__uuid=bundle_uuid, name=use_draft)
        except models.Draft.DoesNotExist:
            pass
        else:
            draft_repo = DraftRepo(SnapshotRepo())
            staged_draft = draft_model.staged_draft
            with draft_repo.open(staged_draft, path) as file:
                return file.read()

    bundle_version_model = _get_bundle_version_model(bundle_uuid, 0)

    snapshot_repo = SnapshotRepo()
    snapshot = bundle_version_model.snapshot()
    with snapshot_repo.open(snapshot, path) as file:
        return file.read()
Esempio n. 2
0
 def destroy(self, request, uuid):  # pylint: disable=arguments-differ
     """
     This removes any files that were staged along with the database entry.
     """
     draft_repo = DraftRepo(SnapshotRepo())
     draft_repo.delete(uuid)
     return super().destroy(request, uuid)
Esempio n. 3
0
        def to_representation(self, value):
            """StagedDraft JSON serialization."""
            staged_draft = value
            draft_repo = DraftRepo(SnapshotRepo())
            if staged_draft.base_snapshot is None:
                base_snapshot_repr = None
            else:
                base_snapshot_repr = staged_draft.base_snapshot.hash_digest.hex(
                )

            basic_info = {
                'base_snapshot': base_snapshot_repr,
                'created_at': staged_draft.created_at,
                'updated_at': staged_draft.updated_at,
            }
            basic_info['files'] = {
                path: {
                    "url": draft_repo.url(staged_draft, path),
                    "size": file_info.size,
                    "hash_digest": file_info.hash_digest.hex(),
                    "modified": path in staged_draft.files_to_overwrite
                }
                for path, file_info in staged_draft.files.items()
            }
            return basic_info
Esempio n. 4
0
        def to_representation(self, value):
            """Snapshot JSON serialization."""
            snapshot = value
            snapshot_repo = SnapshotRepo()
            info = {
                'hash_digest': snapshot.hash_digest.hex(),
                'created_at': snapshot.created_at,
            }

            info['files'] = {
                path: {
                    "url": self._expand_url(snapshot_repo.url(snapshot, path)),
                    "size": file_info.size,
                    "hash_digest": file_info.hash_digest.hex(),
                }
                for path, file_info in snapshot.files.items()
            }

            info['links'] = {
                link.name: {
                    "direct":
                    self._serialized_dep(link.direct_dependency),
                    "indirect": [
                        self._serialized_dep(dep)
                        for dep in link.indirect_dependencies
                    ]
                }
                for link in snapshot.links
            }

            return info
Esempio n. 5
0
def _bundle_version_data_from_model(bundle_version_model):
    """
    Create and return BundleVersionData from bundle version model.
    """
    snapshot = bundle_version_model.snapshot()
    snapshot_repo = SnapshotRepo()

    return BundleVersionData(
        bundle_uuid=bundle_version_model.bundle.uuid,
        version=bundle_version_model.version_num,
        change_description=bundle_version_model.change_description,
        created_at=snapshot.created_at,
        files={
            path: BundleFileData(
                path=path,
                url=_build_absolute_uri(snapshot_repo.url(snapshot, path)),
                size=file_info.size,
                hash_digest=file_info.hash_digest.hex(),
            ) for path, file_info in snapshot.files.items()
        },
        links={
            link.name: BundleLinkData(
                name=link.name,
                direct=link.direct_dependency,
                indirect=link.indirect_dependencies,
            )
            for link in snapshot.links
        },
    )
Esempio n. 6
0
def _draft_data_from_model(draft_model):
    """
    Create and return DraftData from draft model.
    """
    draft_repo = DraftRepo(SnapshotRepo())
    staged_draft = draft_model.staged_draft

    return DraftData(
        uuid=draft_model.uuid,
        bundle_uuid=draft_model.bundle.uuid,
        name=draft_model.name,
        created_at=draft_model.staged_draft.created_at,
        updated_at=draft_model.staged_draft.updated_at,
        files={
            path: DraftFileData(
                path=path,
                size=file_info.size,
                url=_build_absolute_uri(draft_repo.url(staged_draft, path)),
                hash_digest=file_info.hash_digest,
                modified=path in draft_model.staged_draft.files_to_overwrite,
            )
            for path, file_info in staged_draft.files.items()
        },
        links={
            link.name: DraftLinkData(
                name=link.name,
                direct=link.direct_dependency,
                indirect=link.indirect_dependencies,
                modified=link.name in staged_draft.links_to_overwrite.modified_set,
            )
            for link in staged_draft.composed_links()
        }
    )
Esempio n. 7
0
def set_draft_link(draft_uuid, link_name, bundle_uuid, version):
    """
    Create or replace the link with the given name in the specified draft so
    that it points to the specified bundle version. To delete a link, pass
    bundle_uuid=None, version=None.

    If you don't know the draft's UUID, look it up using
    get_or_create_bundle_draft()

    Does not return anything.
    """
    data = {
        'links': {
            link_name: {"bundle_uuid": str(bundle_uuid), "version": version} if bundle_uuid is not None else None,
        },
    }
    serializer = DraftFileUpdateSerializer(data=data)
    serializer.is_valid(raise_exception=True)
    files_to_write = serializer.validated_data['files']
    dependencies_to_write = serializer.validated_data['links']

    draft_repo = DraftRepo(SnapshotRepo())
    try:
        draft_repo.update(draft_uuid, files_to_write, dependencies_to_write)
    except LinkCycleError as exc:
        raise serializers.ValidationError("Link cycle detected: Cannot create draft.") from exc
Esempio n. 8
0
def write_draft_file(draft_uuid, path, contents):
    """
    Create or overwrite the file at 'path' in the specified draft with the given
    contents. To delete a file, pass contents=None.

    If you don't know the draft's UUID, look it up using
    get_or_create_bundle_draft()

    Does not return anything.
    """
    data = {
        'files': {
            path: _encode_str_for_draft(contents) if contents is not None else None,
        },
    }
    serializer = DraftFileUpdateSerializer(data=data)
    serializer.is_valid(raise_exception=True)
    files_to_write = serializer.validated_data['files']
    dependencies_to_write = serializer.validated_data['links']

    draft_repo = DraftRepo(SnapshotRepo())
    try:
        draft_repo.update(draft_uuid, files_to_write, dependencies_to_write)
    except LinkCycleError as exc:
        raise serializers.ValidationError("Link cycle detected: Cannot create draft.") from exc
Esempio n. 9
0
def delete_draft(draft_uuid):
    """
    Delete the specified draft, removing any staged changes/files/deletes.

    Does not return any value.
    """
    draft_model = _get_draft_model(draft_uuid)
    draft_repo = DraftRepo(SnapshotRepo())
    draft_repo.delete(draft_uuid)
    draft_model.delete()
Esempio n. 10
0
def commit_draft(draft_uuid):
    """
    Commit all of the pending changes in the draft, creating a new version of
    the associated bundle.

    Does not return any value.
    """
    draft_repo = DraftRepo(SnapshotRepo())
    staged_draft = draft_repo.get(draft_uuid)

    if not staged_draft.files_to_overwrite and not staged_draft.links_to_overwrite:
        raise DraftHasNoChangesToCommit("Draft {} does not have any changes to commit.".format(draft_uuid))

    new_snapshot, _updated_draft = draft_repo.commit(staged_draft)
    models.BundleVersion.create_new_version(
        new_snapshot.bundle_uuid, new_snapshot.hash_digest
    )
Esempio n. 11
0
    def commit(self, request, uuid):
        """
        Commit the Draft and create a new BundleVersion that points to it.

        In the future, we may want to separate these two steps so that we can
        create multiple BundleVersions at once in a single transaction, however
        given our modeling conventions of a course being in a single Bundle,
        that's not something that we need to implement immediately.

        We currently return a summary of the things that were created, however
        we may need to rethink this interface if the commit process is going to
        take so long as to require async processing.

        TODO: Test with large Bundles.
        """
        draft_repo = DraftRepo(SnapshotRepo())
        staged_draft = draft_repo.get(uuid)

        # Is this the appropriate response when trying to commit a Draft with
        # no changes?
        if not staged_draft.files_to_overwrite and not staged_draft.links_to_overwrite:
            raise serializers.ValidationError(
                "Draft has no changes to commit.")

        new_snapshot, _updated_draft = draft_repo.commit(staged_draft)
        new_bv = BundleVersion.create_new_version(new_snapshot.bundle_uuid,
                                                  new_snapshot.hash_digest)

        # This is a placeholder response. May need to revisit after trying
        # some large commits.
        result = {
            'bundle_version':
            reverse(
                'api:v1:bundleversion-detail',
                args=[new_snapshot.bundle_uuid, new_bv.version_num],
                request=request,
            ),
            'updated_draft':
            reverse(
                'api:v1:draft-detail',
                args=[uuid],
                request=request,
            )
        }
        return Response(result, status=status.HTTP_201_CREATED)
Esempio n. 12
0
        def to_representation(self, value):
            """Snapshot JSON serialization."""
            snapshot = value
            snapshot_repo = SnapshotRepo()
            basic_info = {
                'hash_digest': snapshot.hash_digest.hex(),
                'created_at': snapshot.created_at,
            }

            basic_info['files'] = {
                path: {
                    "url": snapshot_repo.url(snapshot, path),
                    "size": file_info.size,
                    "hash_digest": file_info.hash_digest.hex(),
                }
                for path, file_info in snapshot.files.items()
            }

            return basic_info
Esempio n. 13
0
    def partial_update(self, request, uuid):
        """
        Create, update, and delete files in a Draft.

        The data payload in the request should be a JSON dictionary with file
        paths as keys and base64 encoded payloads as values. A null value means
        that path should be deleted.

        PATCH is a bit unusual for this resource in that the only thing you can
        patch are file data. You cannot change the draft name or the Bundle it
        belongs to, and the only way to update the `base_snapshot` is to commit
        the Draft.

        There is intentionally no PUT support for file data, for a few reasons:

        1. We can't guarantee the semantics of a PUT against a concurrent PATCH,
        particularly for large numbers of files. Our files are in an object
        store that do not support multi-file transactions. We can't really even
        guarantee it for multiple concurrent PATCHes -- there's a possibility of
        a race condition there.

        2. Bundles can become very large, and a PUT might become prohibitively
        large. Having everything as a PATCH lets us set somewhat sane per-PATCH
        request limits and let the client handle the case where we need to do
        multiple requests to make the necessary changes.

        3. It's just simpler to have only one way to update the files.
        """
        serializer = DraftFileUpdateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        files_to_write = serializer.validated_data['files']
        dependencies_to_write = serializer.validated_data['links']

        draft_repo = DraftRepo(SnapshotRepo())
        try:
            draft_repo.update(uuid, files_to_write, dependencies_to_write)
        except LinkCycleError:
            raise serializers.ValidationError(
                "Link cycle detected: Cannot create draft.")

        return Response(status=status.HTTP_204_NO_CONTENT)
Esempio n. 14
0
        def to_representation(self, value):
            """StagedDraft JSON serialization."""
            staged_draft = value
            draft_repo = DraftRepo(SnapshotRepo())
            if staged_draft.base_snapshot is None:
                base_snapshot_repr = None
            else:
                base_snapshot_repr = staged_draft.base_snapshot.hash_digest.hex(
                )

            basic_info = {
                'base_snapshot': base_snapshot_repr,
                'created_at': staged_draft.created_at,
                'updated_at': staged_draft.updated_at,
            }
            basic_info['files'] = {
                path: {
                    "url": self._expand_url(draft_repo.url(staged_draft,
                                                           path)),
                    "size": file_info.size,
                    "hash_digest": file_info.hash_digest.hex(),
                    "modified": path in staged_draft.files_to_overwrite
                }
                for path, file_info in staged_draft.composed_files().items()
            }
            basic_info['links'] = {
                link.name: {
                    "direct":
                    self._serialized_dep(link.direct_dependency),
                    "indirect": [
                        self._serialized_dep(dep)
                        for dep in link.indirect_dependencies
                    ],
                    "modified":
                    link.name in staged_draft.links_to_overwrite.modified_set
                }
                for link in staged_draft.composed_links()
                if link.direct_dependency
            }

            return basic_info