Skip to content

Commit

Permalink
Merge pull request #197 from WikiMovimentoBrasil/2025-02-update-code-…
Browse files Browse the repository at this point in the history
…documentation

docs: update code documentation about the Wikibase REST API
  • Loading branch information
arcstur authored Feb 24, 2025
2 parents d124e68 + a7bf13a commit 853afd2
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 39 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ Now that everything is set up, we can start **Quickstatements**. We have 2 ways

Now **Quickstatements** is available at http://localhost:8765/

## Wikibase server

QuickStatements 3.0 uses the Wikibase REST API to interact with a Wikibase server. To define which server it is pointing to, define the `BASE_REST_URL` environment variable, pointing to the `rest.php` endpoint, as in `BASE_REST_URL=https://test.wikidata.org/w/rest.php`.

It uses the Wikibase REST API provided in `/wikibase/v1` and the profile endpoint for the Oauth2 API, provided in `/oauth2` to check autoconfirmation status and authorize users.

Currently it's only possible to point at one Wikibase instance.

## OAuth

This application uses OAuth2 with the Mediawiki provider.
Expand Down
36 changes: 15 additions & 21 deletions src/core/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
import requests
import logging

Expand Down Expand Up @@ -93,10 +92,20 @@ def from_username(cls, username: str):
# OAuth Token
# ---
def refresh_token_if_needed(self):
"""
The OAuth access token token can expire.
This will check if it's near expiration and
make a call for a new one with the refresh token
if necessary.
"""
if self.token.is_expired() and self.token.refresh_token:
self.refresh_token()

def refresh_token(self):
"""
Refreshes the current `Token` using its refresh token.
"""
logger.debug(f"[{self.token}] Refreshing OAuth token...")

try:
Expand Down Expand Up @@ -179,6 +188,10 @@ def wikibase_entity_url(self, entity_id, entity_endpoint):
return self.wikibase_url(endpoint)

def wikibase_request_wrapper(self, method, endpoint, body):
"""
Sends a request to the Wikibase REST API, using the provided
endpoint, method and json body.
"""
kwargs = {
"json": body,
"headers": self.headers(),
Expand All @@ -196,12 +209,10 @@ def wikibase_request_wrapper(self, method, endpoint, body):
self.raise_for_status(res)
return res.json()

def wikibase_post(self, endpoint, body):
return self.wikibase_request_wrapper("POST", endpoint, body)

# ---
# Wikibase GET/reading
# ---

@cache_with_first_arg("value_type_cache")
def get_property_value_type(self, property_id):
"""
Expand Down Expand Up @@ -279,26 +290,9 @@ def get_labels(self, entity_id):
url = self.wikibase_entity_url(entity_id, "/labels")
return self.get(url).json()

def get_statements(self, entity_id):
"""
Returns all statements for an entity in the form of a dictionary.
The key is the property id, and the value is an array with
the statement objects.
"""
url = self.wikibase_entity_url(entity_id, "/statements")
return self.get(url).json()

def get_entity(self, entity_id):
"""
Returns the entire entity json document.
"""
url = self.wikibase_entity_url(entity_id, "")
return self.get(url).json()

# ---
# Wikibase POST/editing
# ---
def add_statement(self, entity_id, body):
endpoint = self.wikibase_entity_endpoint(entity_id, "/statements")
return self.wikibase_post(endpoint, body)
31 changes: 13 additions & 18 deletions src/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ def update_last_id(self, last_id=None):

def run(self, client: Client):
"""
Sends the command to the Wikidata API. This method should not raise exceptions.
Sends the command to the Wikibase API. This method should not raise exceptions.
"""
# If we alredy have an error, just propagate backwards
if self.status == BatchCommand.STATUS_ERROR:
Expand Down Expand Up @@ -1008,14 +1008,18 @@ def entity_patch(self, client: Client):
but the patch needs to be calculated using the original
entity json, since that's what exists in the wikibase server.
TODO: maybe cache that original as well to not make
two requests?
The json patch is a series of operations that tell the API
how to modify the entity's json.
"""
original = self.get_original_entity_json(client)
entity = self.get_previous_entity_json(client)
self.update_entity_json(entity)
return jsonpatch.JsonPatch.from_diff(original, entity).patch

# ----------------
# REST API methods
# ----------------

def api_payload(self, client: Client):
"""
Returns the data that is sent to the Wikibase API through the body.
Expand All @@ -1042,19 +1046,18 @@ def api_body(self, client: Client):

def send_to_api(self, client: Client) -> dict:
"""
Sends the operation to the Wikibase API.
Sends the operation to the Wikibase REST API.
# Raises
- `NotImplementedError` if the operation
is not implemented.
"""
match self.operation:
case self.Operation.CREATE_PROPERTY:
raise NotImplementedError()
case _:
return self.send_basic(client)
return {}
if self.operation == self.Operation.CREATE_PROPERTY:
raise NotImplementedError()
method, endpoint = self.operation_method_and_endpoint(client)
body = self.api_body(client)
return client.wikibase_request_wrapper(method, endpoint, body)

# -----------------
# Auxiliary methods for Wikibase API interaction
Expand All @@ -1075,14 +1078,6 @@ def operation_method_and_endpoint(self, client: Client):
statement_id = self.json["id"]
return ("DELETE", f"/statements/{statement_id}")

def send_basic(self, client: Client):
"""
Sends the request
"""
method, endpoint = self.operation_method_and_endpoint(client)
body = self.api_body(client)
return client.wikibase_request_wrapper(method, endpoint, body)

# -----------------
# Visualization/label methods
# -----------------
Expand Down

0 comments on commit 853afd2

Please sign in to comment.