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

Tempfile implementtion #90

Merged
merged 10 commits into from
Mar 1, 2024
4 changes: 1 addition & 3 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
[run]
relative_files = True

[report]
include =
src/*
omit =
*/tests/*
src/pcloud/tests/*
10 changes: 5 additions & 5 deletions .github/workflows/pcloud-test.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: PyCloud package tests & linting
name: PCloud API Python package tests & linting

on:
push:
Expand All @@ -11,14 +11,13 @@ on:

jobs:
build:

runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
max-parallel: 1
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup firefox
Expand All @@ -41,7 +40,7 @@ jobs:
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest src --cov=src/pcloud --cov-report=xml --cov-branch --cov-config=.coveragerc --capture=no -v --timeout=600
pytest src --cov=src/pcloud --cov-report=xml --cov-branch --cov-config=.coveragerc --capture=no -v --timeout=600 --reruns 3 --reruns-delay 5
env:
PCLOUD_USERNAME: ${{ secrets.PCLOUD_USERNAME }}
PCLOUD_PASSWORD: ${{ secrets.PCLOUD_PASSWORD }}
Expand All @@ -58,5 +57,6 @@ jobs:
-Dsonar.organization=tomgross-github
-Dsonar.python.version=3
-Dsonar.python.coverage.reportPaths=coverage.xml
-Dsonar.python.coverage.exclusions=**/tests/*


3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ pyvenv.cfg
Pipfile*
*.whl
.vscode
coverage.xml
coverage.xml
.env
4 changes: 3 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ Changelog
1.3 (unreleased)
----------------

- Nothing changed yet.
- Reimplement pyfs integration [tomgross]
- Update (test) dependencies and run tests only on Python 3.8-3.12 [tomgross]
- Added more API methods [tomgross]


1.2 (2023-06-24)
Expand Down
1 change: 0 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ Features
========

- Can be used as a library
- Comes with a command line script
- Provides a PyFileSystem implementation

Examples
Expand Down
9 changes: 3 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
"Environment :: Other Environment",
"Environment :: Web Environment",
"Programming Language :: Python",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Operating System :: OS Independent",
"License :: OSI Approved :: MIT License",
"Topic :: Software Development :: Libraries :: Python Modules",
Expand All @@ -48,13 +48,10 @@
install_requires=[
"requests",
"requests-toolbelt",
"setuptools"
"setuptools>=68.0.0"
],
extras_require={"pyfs": ["fs"]},
entry_points={
"console_scripts": [
"pcloud-cli = pcloud.api:main",
],
"fs.opener": ["pcloud = pcloud.pcloudfs:PCloudOpener"],
},
)
67 changes: 43 additions & 24 deletions src/pcloud/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from urllib.parse import urlparse
from urllib.parse import urlunsplit

import argparse
import datetime
import logging
import os.path
Expand Down Expand Up @@ -44,9 +43,11 @@ class OnlyPcloudError(NotImplementedError):
"""Feature restricted to pCloud"""


# Helpers
class InvalidFileModeError(Exception):
"""File mode not supported"""


# Helpers
def to_api_datetime(dt):
"""Converter to a datetime structure the pCloud API understands

Expand All @@ -57,19 +58,6 @@ def to_api_datetime(dt):
return dt


def main():
parser = argparse.ArgumentParser(description="pCloud command line client")
parser.add_argument(
"username", help="The username for login into your pCloud account"
)
parser.add_argument(
"password", help="The password for login into your pCloud account"
)
args = parser.parse_args()
pyc = PyCloud(args.username, args.password)
print(pyc)


class PyCloud(object):
endpoints = {
"api": "https://api.pcloud.com/",
Expand Down Expand Up @@ -147,11 +135,11 @@ def _do_request(self, method, authenticate=True, json=True, endpoint=None, **kw)
log.debug("Params: %s", params)
resp = self.session.get(endpoint + method, params=params)
if json:
resp = resp.json()
result = resp.json()
else:
resp = resp.content
log.debug("Response: %s", resp)
return resp
result = resp.content
log.debug("Response: %s", result)
return result

# Authentication
def getdigest(self):
Expand Down Expand Up @@ -251,11 +239,10 @@ def _upload(self, method, files, **kwargs):
kwargs["auth"] = self.auth_token
elif self.access_token: # OAuth2 authentication
kwargs["access_token"] = self.access_token
kwargs.pop("fd", None)
fields = list(kwargs.items())
fields.extend(files)
m = MultipartEncoder(fields=fields)
resp = self.session.post(
resp = requests.post(
self.endpoint + method, data=m, headers={"Content-Type": m.content_type}
)
return resp.json()
Expand Down Expand Up @@ -380,7 +367,7 @@ def gettextfile(self, **kwargs):
def file_open(self, **kwargs):
return self._do_request("file_open", **kwargs)

@RequiredParameterCheck(("fd",))
@RequiredParameterCheck(("fd", "count"))
def file_read(self, **kwargs):
return self._do_request("file_read", json=False, **kwargs)

Expand All @@ -403,6 +390,7 @@ def file_truncate(self, **kwargs):
@RequiredParameterCheck(("fd", "data"))
def file_write(self, **kwargs):
files = [("file", ("upload-file.io", BytesIO(kwargs.pop("data"))))]
kwargs["fd"] = str(kwargs["fd"])
return self._upload("file_write", files, **kwargs)

@RequiredParameterCheck(("fd",))
Expand Down Expand Up @@ -462,6 +450,38 @@ def listshares(self, **kwargs):
return self._do_request("listshares", **kwargs)

# Public links
def getfilepublink(self, **kwargs):
raise OnlyPcloudError(ONLY_PCLOUD_MSG)

def getpublinkdownload(self, **kwargs):
raise OnlyPcloudError(ONLY_PCLOUD_MSG)

@RequiredParameterCheck(("path", "folderid"))
def gettreepublink(self, **kwargs):
raise NotImplementedError

@RequiredParameterCheck(("code",))
def showpublink(self, **kwargs):
return self._do_request("showpublink", authenticate=False, **kwargs)

@RequiredParameterCheck(("code",))
def copypubfile(self, **kwargs):
return self._do_request("copypubfile", **kwargs)

def listpublinks(self, **kwargs):
return self._do_request("listpublinks", **kwargs)

def listplshort(self, **kwargs):
return self._do_request("listplshort", **kwargs)

@RequiredParameterCheck(("linkid",))
def deletepublink(self, **kwargs):
return self._do_request("deletepublink", **kwargs)

@RequiredParameterCheck(("linkid",))
def changepublink(self, **kwargs):
return self._do_request("changepublink", **kwargs)

@RequiredParameterCheck(("path", "folderid"))
def getfolderpublink(self, **kwargs):
expire = kwargs.get("expire")
Expand Down Expand Up @@ -510,5 +530,4 @@ def trash_restore(self, **kwargs):
raise NotImplementedError


if __name__ == "__main__":
main()
# EOF
19 changes: 13 additions & 6 deletions src/pcloud/oauth2.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import _thread
import time

from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
Expand All @@ -10,6 +11,7 @@

PORT = 65432
REDIRECT_URL = f"http://localhost:{PORT}/"
AUTHORIZE_URL = "https://my.pcloud.com/oauth2/authorize"


class HTTPServerHandler(BaseHTTPRequestHandler):
Expand All @@ -30,6 +32,13 @@ def do_GET(self):
self.wfile.write(b"<html><h1>You may now close this window.</h1></html>")


class HTTPServerWithAttributes(HTTPServer):
def __init__(self, *args, **kwargs):
self.access_token = None
self.pc_hostname = None
super().__init__(*args, **kwargs)


class TokenHandler(object):
"""
Class used to handle pClouds oAuth2
Expand All @@ -39,7 +48,7 @@ class TokenHandler(object):

def __init__(self, client_id):
self._id = client_id
self.auth_url = f"https://my.pcloud.com/oauth2/authorize?response_type=code&redirect_uri={self.redirect_url}&client_id={self._id}"
self.auth_url = f"{AUTHORIZE_URL}?response_type=code&redirect_uri={self.redirect_url}&client_id={self._id}"

def open_browser(self):
"""Hook which is called before request is handled."""
Expand All @@ -49,17 +58,15 @@ def close_browser(self):
"""Hook which is called after request is handled."""

def get_access_token(self):
http_server = HTTPServer(("localhost", PORT), HTTPServerHandler)
http_server = HTTPServerWithAttributes(("localhost", PORT), HTTPServerHandler)

# Solution taken from https://stackoverflow.com/a/12651298
# There might be better ways than accessing the internal
# _thread library for starting the http-server non-blocking
# but I did not found any ;-)
def start_server():
http_server.handle_request()

_thread.start_new_thread(start_server, ())
self.open_browser()
while not (http_server.access_token and http_server.pc_hostname):
time.sleep(1)
self.close_browser()
http_server.server_close()
return http_server.access_token, http_server.pc_hostname
Loading
Loading