commit a1fd24c92dbf7796bda365d50d2fdd7c72f1c368 Author: ksyasuda Date: Sat Sep 7 19:34:48 2024 -0700 initial commit diff --git a/.gitea/workflows/build-pypi.yml b/.gitea/workflows/build-pypi.yml new file mode 100644 index 0000000..7153b6f --- /dev/null +++ b/.gitea/workflows/build-pypi.yml @@ -0,0 +1,28 @@ +name: Build and Upload Python Package +on: + push: + branches: + - master +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.x" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine build + + - name: Build the package + run: python -m build + + - name: Upload to Gitea PyPI Registry + run: | + twine upload --repository-url https://gitea.suda.codes/api/packages/sudacode/pypi -u ${{ secrets.PYPI_USERNAME }} -p ${{ secrets.PYPI_PASSWORD }} dist/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..623d403 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.git/* +dist/* +env/* +src/wallabag_api_client.egg-info +src/__pycache__/* +src/wallabag_api_client/__pycache__/* +src/wallabag_api_client/methods/__pycache__/* +tests/test.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4726727 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "wallabag-api-client" +version = "0.0.1" +description = "A Python client to interact with the Wallabag API" +readme = "README.md" +license = { text = "MIT" } +authors = [ + { name = "sudacode", email = "suda@sudacode.com" } +] +dependencies = [ + "requests>=2.20.0" +] +requires-python = ">=3.7" + +[project.urls] +"Homepage" = "https://gitea.suda.codes/sudacode/wallabag-api-client" +"Source" = "https://gitea.suda.codes/sudacode/wallabag-api-client" +"Issue Tracker" = "https://gitea.suda.codes/sudacode/wallabag-api-client/issues" + +[tool.setuptools.packages.find] +where = ["src"] + +[project.optional-dependencies] +dev = [ + "black", + "pytest", +] + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b70b610 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +black==24.8.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +click==8.1.7 +idna==3.8 +iniconfig==2.0.0 +mypy==1.11.2 +mypy-extensions==1.0.0 +packaging==24.1 +pathspec==0.12.1 +platformdirs==4.3.1 +pluggy==1.5.0 +pytest==8.3.2 +requests==2.32.3 +typing_extensions==4.12.2 +urllib3==2.2.2 diff --git a/src/wallabag_api_client/__init__.py b/src/wallabag_api_client/__init__.py new file mode 100644 index 0000000..35399db --- /dev/null +++ b/src/wallabag_api_client/__init__.py @@ -0,0 +1,21 @@ +from .client import WallabagClient +from .methods.annotations import AnnotationsAPI +from .methods.entries import EntriesAPI +from .methods.tags import TagsAPI + + +class WallabagAPI: + def __init__( + self, + client_id, + client_secret, + username, + password, + base_url="https://app.wallabag.it", + ): + self.client = WallabagClient( + client_id, client_secret, username, password, base_url + ) + self.entries = EntriesAPI(self.client) + self.tags = TagsAPI(self.client) + self.annotations = AnnotationsAPI(self.client) diff --git a/src/wallabag_api_client/client.py b/src/wallabag_api_client/client.py new file mode 100644 index 0000000..cb2fd3b --- /dev/null +++ b/src/wallabag_api_client/client.py @@ -0,0 +1,124 @@ +import requests + + +class WallabagClient: + """ + A client to interact with the Wallabag API using OAuth2 for authentication. + + This class handles authentication and sending requests to the API endpoints. + ---------- + Attributes + + client_id : str + The client ID for OAuth2 authentication. + client_secret : str + The client secret for OAuth2 authentication. + username : str + The username of the Wallabag account. + password : str + The password of the Wallabag account. + base_url : str + The base URL of the Wallabag instance. + token : str + The access token obtained from the OAuth2 authentication. + """ + + def __init__( + self, + client_id, + client_secret, + username, + password, + base_url="https://app.wallabag.it", + ): + """ + Initializes the WallabagClient with user credentials and OAuth2 tokens. + + ---------- + Parameters + + client_id : str + The client ID for OAuth2 authentication. + client_secret : str + The client secret for OAuth2 authentication. + username : str + The username of the Wallabag account. + password : str + The password of the Wallabag account. + base_url : str, optional + The base URL of the Wallabag instance (default is "https://app.wallabag.it"). + """ + self.client_id = client_id + self.client_secret = client_secret + self.username = username + self.password = password + self.base_url = base_url + self.token = self.authenticate() + + def authenticate(self): + """ + Authenticates the user and retrieves the access token using OAuth2. + + ---------- + Returns + + str + The access token for making authenticated API requests. + + ---------- + Raises + + requests.exceptions.HTTPError + If the authentication request fails. + """ + auth_url = f"{self.base_url}/oauth/v2/token" + data = { + "grant_type": "password", + "client_id": self.client_id, + "client_secret": self.client_secret, + "username": self.username, + "password": self.password, + } + response = requests.post(auth_url, data=data) + response.raise_for_status() + token_info = response.json() + return token_info["access_token"] + + def make_request(self, method, endpoint, params=None, data=None): + """ + Makes a request to the Wallabag API with the given parameters. + + ---------- + Parameters + + method : str + The HTTP method (GET, POST, DELETE, etc.). + endpoint : str + The API endpoint (e.g., 'entries.json'). + params : dict, optional + The query parameters to include in the request. + data : dict, optional + The data to send with the request (for POST, PUT, etc.). + + ---------- + Returns + + dict + The response from the API as a dictionary. + + ---------- + Raises + + requests.exceptions.HTTPError + If the request fails. + """ + headers = { + "Authorization": f"Bearer {self.token}", + "Content-Type": "application/json", + } + url = f"{self.base_url}/api/{endpoint}" + response = requests.request( + method, url, headers=headers, params=params, json=data + ) + response.raise_for_status() + return response.json() diff --git a/src/wallabag_api_client/methods/__init__.py b/src/wallabag_api_client/methods/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/wallabag_api_client/methods/annotations.py b/src/wallabag_api_client/methods/annotations.py new file mode 100644 index 0000000..219dac4 --- /dev/null +++ b/src/wallabag_api_client/methods/annotations.py @@ -0,0 +1,83 @@ +class AnnotationsAPI: + """ + A class to interact with the Wallabag API for managing annotations on entries. + + ---------- + Attributes + + client : WallabagClient + The client instance to send API requests. + """ + + def __init__(self, client): + """ + Initializes the AnnotationsAPI with the WallabagClient. + + ---------- + Parameters + + client : WallabagClient + The WallabagClient instance to send requests. + """ + self.client = client + + def get_annotations(self, entry_id): + """ + Retrieves all annotations for a specific entry. + + ---------- + Parameters + + entry_id : int + The ID of the entry for which to retrieve annotations. + + ---------- + Returns + + dict + The list of annotations for the specified entry. + """ + return self.client.make_request("GET", f"annotations/{entry_id}.json") + + def add_annotation(self, entry_id, text, quote): + """ + Adds a new annotation to a specific entry. + + ---------- + Parameters + + entry_id : int + The ID of the entry to annotate. + text : str + The text of the annotation. + quote : str + The quote related to the annotation. + + ---------- + Returns + + dict + The details of the newly added annotation. + """ + data = {"text": text, "quote": quote} + return self.client.make_request( + "POST", f"annotations/{entry_id}.json", data=data + ) + + def delete_annotation(self, annotation_id): + """ + Deletes an annotation from a specific entry. + + ---------- + Parameters + + annotation_id : int + The ID of the annotation to delete. + + ---------- + Returns + + dict + The response from the API after deleting the annotation. + """ + return self.client.make_request("DELETE", f"annotations/{annotation_id}.json") diff --git a/src/wallabag_api_client/methods/entries.py b/src/wallabag_api_client/methods/entries.py new file mode 100644 index 0000000..98cfb4d --- /dev/null +++ b/src/wallabag_api_client/methods/entries.py @@ -0,0 +1,98 @@ +class EntriesAPI: + """ + A class to interact with the Wallabag API for managing entries (articles). + + ---------- + Attributes + + client : WallabagClient + The client instance to send API requests. + """ + + def __init__(self, client): + """ + Initializes the EntriesAPI with the WallabagClient. + + ---------- + Parameters + + client : WallabagClient + The WallabagClient instance to send requests. + """ + self.client = client + + def get_entries(self, page=1, per_page=30): + """ + Retrieves a paginated list of entries (articles). + + ---------- + Parameters + + page : int, optional + The page number to retrieve (default is 1). + per_page : int, optional + The number of entries per page (default is 30). + + ---------- + Returns + + dict + The list of entries and pagination info. + """ + params = {"page": page, "perPage": per_page} + return self.client.make_request("GET", "entries.json", params=params) + + def add_entry(self, url): + """ + Adds a new entry to Wallabag by providing a URL. + + ---------- + Parameters + ---------- + url : str + The URL of the article to add. + + ---------- + Returns + + dict + The details of the newly added entry. + """ + data = {"url": url} + return self.client.make_request("POST", "entries.json", data=data) + + def delete_entry(self, entry_id): + """ + Deletes an entry from Wallabag by its entry ID. + + ---------- + Parameters + + entry_id : int + The ID of the entry to delete. + + ---------- + Returns + + dict + The response from the API after deleting the entry. + """ + return self.client.make_request("DELETE", f"entries/{entry_id}.json") + + def get_entry(self, entry_id): + """ + Retrieves the details of a specific entry by its ID. + + ---------- + Parameters + + entry_id : int + The ID of the entry to retrieve. + + ---------- + Returns + + dict + The details of the specified entry. + """ + return self.client.make_request("GET", f"entries/{entry_id}.json") diff --git a/src/wallabag_api_client/methods/tags.py b/src/wallabag_api_client/methods/tags.py new file mode 100644 index 0000000..50c1e1f --- /dev/null +++ b/src/wallabag_api_client/methods/tags.py @@ -0,0 +1,73 @@ +class TagsAPI: + """ + A class to interact with the Wallabag API for managing tags. + + ---------- + Attributes + + client : WallabagClient + The client instance to send API requests. + """ + + def __init__(self, client): + """ + Initializes the TagsAPI with the WallabagClient. + + ---------- + Parameters + + client : WallabagClient + The WallabagClient instance to send requests. + """ + self.client = client + + def get_tags(self): + """ + Retrieves all tags associated with entries in Wallabag. + + ---------- + Returns + + dict + The list of tags. + """ + return self.client.make_request("GET", "tags.json") + + def add_tag(self, entry_id, tag): + """ + Adds a tag to a specific entry. + + ---------- + Parameters + + entry_id : int + The ID of the entry to tag. + tag : str + The tag to add to the entry. + + ---------- + Returns + + dict + The response from the API after adding the tag. + """ + data = {"entry_id": entry_id, "label": tag} + return self.client.make_request("POST", "tags.json", data=data) + + def delete_tag(self, tag_id): + """ + Deletes a tag from an entry. + + ---------- + Parameters + + tag_id : int + The ID of the tag to delete. + + ---------- + Returns + + dict + The response from the API after deleting the tag. + """ + return self.client.make_request("DELETE", f"tags/{tag_id}.json")