Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

High-level file operation methods #7

Open
hrchu opened this issue Sep 23, 2021 · 17 comments
Open

High-level file operation methods #7

hrchu opened this issue Sep 23, 2021 · 17 comments
Assignees
Labels
enhancement New feature or request good first issue Good for newcomers hacktoberfest-accepted

Comments

@hrchu
Copy link
Member

hrchu commented Sep 23, 2021

Support high-level methods (in the file system context), so it will be more convenient for users. rename, move, delete_folder, copy, copy_folder, copy_file, for example. Search 'Not implemented' in https://github.com/twonote/solid-file-python/blob/master/src/solid/solid_api.py,

We don't have to implement all of these in one shot. Just part of them is also welcomed.

@hrchu hrchu added the enhancement New feature or request label Sep 23, 2021
@hrchu hrchu changed the title High-level Methods High-level file operation methods Sep 23, 2021
@hrchu hrchu added the good first issue Good for newcomers label Sep 23, 2021
@alexhtwen
Copy link

@alexhtwen
Copy link

請問修改後的檔案該如何上傳或merge?

@alexhtwen
Copy link

可能得花時間消化一下,今天來不及有具體成果。

@alexhtwen
Copy link

alexhtwen commented Oct 5, 2021

不好意思,前幾天我的團隊PM告知馬上有新project要開始,所以大概沒有時間幫忙做這個開源專案了。感謝主持人給予機會,有負所托,非常抱歉。

以下是09-26那天我練習做的code,只是幫忙改了些錯字,加一兩個註解,以及試寫patch_file(),但不知對不對。


from enum import Enum
from typing import Optional, Union, Dict, Callable, Iterable, AsyncIterable, List

import httpx
from httpx import Response, HTTPStatusError

from solid.auth import Auth
from solid.utils.api_util import get_root_url, LINK, get_parent_url, get_item_name
from solid.utils.folder_utils import parse_folder_response

class MERGE(Enum):
REPLACE = 'replace'
KEEP_SOURCE = 'keep_source'
KEEP_TARGET = 'keep_target'

class LINKS(Enum):
EXCLUDE = 'exclude'
INCLUDE = 'include'
INCLUDE_POSSIBLE = 'include_possible'

class AGENT(Enum):
NO_MODIFY = 'no_modify'
TO_TARGET = 'to_target'
TO_SOURCE = 'to_source'

class WriteOptions:
def init(self, create_path: bool = True, with_acl: bool = True, agent: AGENT = AGENT.NO_MODIFY,
with_meta: bool = True, merge: MERGE = MERGE.REPLACE):
self.create_path: bool = create_path
self.with_acl: bool = with_acl
self.agent: AGENT = agent
self.with_meta: bool = with_meta
self.merge: MERGE = merge

class ReadFolderOptions:
def init(self):
self.links: LINKS = LINKS.EXCLUDE.value

class SolidAPIOptions:
def init(self):
self.enable_logging: bool = False

class Links:
def init(self):
self.acl = None
self.meta = None

class Item:
def init(self):
self.url = None
self.name = None
self.parent = None
self.itemType = None # "Container" | "Resource"
self.links: Optional[Links] = None

class FolderData:
def init(self):
self.url = None
self.name = None
self.parent = None
self.links: Links = None
self.type = 'folder'
self.folders: List[Item] = None
self.files: List[Item] = None

RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]

class SolidAPI:
def init(self, auth=None):
if not auth:
auth = Auth()
self.auth = auth

def fetch(self, method, url, options: Dict = None) -> Response:
    if not options:
        options = {}
    # options['verify'] = False

    r = self.auth.client.request(method, url, **options)
    # r= httpx.request(method, url, **options)
    r.raise_for_status()
    return r

def get(self, url, options: Dict = None) -> Response:
    return self.fetch('GET', url, options)

def delete(self, url, options: Dict = None) -> Response:
    return self.fetch('DELETE', url, options)

def post(self, url, options: Dict = None) -> Response:
    return self.fetch('POST', url, options)

def put(self, url, options: Dict = None) -> Response:
    return self.fetch('PUT', url, options)

def patch(self, url, options: Dict = None) -> Response:
    return self.fetch('PATCH', url, options)

def head(self, url, options: Dict = None) -> Response:
    return self.fetch('HEAD', url, options)

def option(self, url, options: Dict = None) -> Response:
    return self.fetch('OPTION', url, options)

def item_exists(self, url) -> bool:
    try:
        self.head(url)
        return True
    except HTTPStatusError as e:
        if e.response.status_code == 404:
            return False
        else:
            raise e

def post_item(self, url, content: RequestContent, content_type, link: LINK,
              options: WriteOptions = WriteOptions(create_path=True)) -> Response:
    parent_url = get_parent_url(url)

    if options.create_path:
        self.create_folder(parent_url)

    request_options = {
        'headers': {
            'Link': link.value,
            'Slug': get_item_name(url),
            'Content-Type': content_type,
        },
        'content': content
    }

    return self.post(parent_url, request_options)

def create_folder(self, url, options: WriteOptions = WriteOptions(merge=MERGE.KEEP_TARGET)) -> Response:
    if url[-1] != '/':
        raise Exception(
            f'Cannot use createFolder to create a file : {url}')

    try:
        res = self.head(url)
        if options.merge != MERGE.REPLACE:
            return res
        self.delete_folder(url, recursive=True)
    except HTTPStatusError as e:
        if e.response.status_code == 404:
            pass
        else:
            raise e

    return self.post_item(url, '', 'text/turtle', LINK.CONTAINER, options)

def post_file(self, url, content: RequestContent, content_type, options: WriteOptions = None) -> Response:
    if url[-1] == '/':
        raise Exception(f'Cannot use postFile to create a folder : {url}')
    return self.post_item(url, content, content_type, LINK.RESOURCE, options)

def create_file(self, url, content: RequestContent, content_type, options: WriteOptions = None) -> Response:
    return self.post_file(url, content, content_type, options)

"""
files
Support upload file, get/delete in higher level api
"""

def put_file(self, url, content: RequestContent, content_type, options: WriteOptions = WriteOptions()) -> Response:
    if url[-1] == '/':
        raise Exception(f'Cannot use putFile to create a folder : {url}')

    if options.merge == MERGE.KEEP_TARGET and self.item_exists(url):
        raise Exception(f'File already exists: {url}')

    request_options = {
        'headers': {
            'Link': LINK.RESOURCE.value,
            'Content-Type': content_type,
        },
        'content': content
    }

    return self.put(url, request_options)

def patch_file(self, url, patch_content, patch_content_type) -> Response:
    # raise Exception('Not implemented')
    if url[-1] == '/':
        raise Exception(f'Cannot use patchFile to create a folder : {url}')

    # if options.merge == MERGE.KEEP_TARGET and self.item_exists(url):
    #     raise Exception(f'File already exists: {url}')

    request_options = {
        'headers': {
            'Link': LINK.RESOURCE.value,
            'Content-Type': patch_content_type,
        },
        'content': patch_content
    }

    return self.patch(url, request_options)

def read_folder(self, url, options: ReadFolderOptions = ReadFolderOptions()) -> FolderData:
    if url[-1] != '/':
        url += '/'   # 保證folder字串的最末是'/'。

    folder_res = self.get(url, {'headers': {'Accept': 'text/turtle'}})
    parsed_folder = parse_folder_response(folder_res, url)

    if options.links in (LINKS.INCLUDE_POSSIBLE, LINKS.INCLUDE):
        raise Exception('Not implemented')

    return parsed_folder

def get_item_links(self, url, options: Dict = None) -> Response:
    raise Exception('Not implemented')

def copy_file(self, _from, to, options: WriteOptions = None) -> Response:
    raise Exception('Not implemented')

def copy_meta_file_for_item(self, old_target_file, new_target_file, options: WriteOptions = None) -> Response:
    raise Exception('Not implemented')

def copy_acl_file_for_item(self, old_target_file, new_target_file, options: WriteOptions = None) -> Response:
    raise Exception('Not implemented')

def copy_links_for_item(self, old_target_file, new_target_file, options: WriteOptions = None) -> List[Response]:
    raise Exception('Not implemented')

def copy_folder(self, _from, to, options: WriteOptions = None) -> List[Response]:
    raise Exception('Not implemented')

def copy(self, _from, to, options: WriteOptions = None) -> List[Response]:
    raise Exception('Not implemented')

def delete_folder(self, url, recursive=False) -> List[Response]:
    if recursive:
        raise Exception('Not implemented')

    if url == get_root_url(url):
        raise Exception('405 Pod cannot be deleted')
    return [self.delete(url)]

def move(self, _from, to, copy_options: WriteOptions = None) -> List[Response]:
    raise Exception('Not implemented')

def rename(self, url, new_name, move_options: WriteOptions = None) -> List[Response]:
    raise Exception('Not implemented')

@hrchu
Copy link
Member Author

hrchu commented Oct 7, 2021

@alexhtwen thank you join us this time!

Never mind, feel free to come back any time while you have free time available.

BTW, could you submit your patch via merge request? Your contribution will be record in your porfile this way.

You can learn how to submit a merge request here: https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request

It's totally ok if you dont's have time to do this. I will include your contribution manually by myself instead.

@alexhtwen
Copy link

alexhtwen commented Oct 7, 2021 via email

@halloju
Copy link
Contributor

halloju commented Oct 16, 2021

👍

@hrchu
Copy link
Member Author

hrchu commented Oct 23, 2021

@alexhtwen
Copy link

alexhtwen commented Apr 29, 2022 via email

@peter0083
Copy link
Contributor

@alexhtwen 已upvote 👍

@alexhtwen
Copy link

alexhtwen commented Apr 29, 2022 via email

@alexhtwen
Copy link

alexhtwen commented Apr 29, 2022 via email

@hrchu
Copy link
Member Author

hrchu commented Apr 30, 2022

等等居然把 issue 變成 group chat 的xddd

BTW 很久沒機會跟大家聊,不知道今年大家有規劃怎麼參加技術社群活動呢?
比較可惜的是這個 project 暫時沒有照 roadmap 讓大家發做出一些 significant difference yet, 可能下半年在看怎麼樣重開機一下

anyway 我目前在籌劃的 coscup 2022 有提供一個新進講者育成計畫,徵求到明天!提供機會給大家參考,歡迎大家或推坑身邊朋友利用連假時間申請一下xd https://www.facebook.com/coscup/posts/10160194773247249

@alexhtwen
Copy link

alexhtwen commented Apr 30, 2022 via email

@hrchu
Copy link
Member Author

hrchu commented Apr 30, 2022

按囉~雖然我是 pycharm 派的xd

@alexhtwen
Copy link

alexhtwen commented Apr 30, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers hacktoberfest-accepted
Projects
None yet
Development

No branches or pull requests

4 participants