ssb-pubmd 0.0.19__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,183 +0,0 @@
1
- import json
2
- from pathlib import Path
3
-
4
- import nbformat
5
- from nbformat import NotebookNode
6
-
7
- from ssb_pubmd.constants import METADATA_FILE
8
- from ssb_pubmd.constants import ContentType
9
- from ssb_pubmd.request_handler import RequestHandler
10
- from ssb_pubmd.request_handler import Response
11
-
12
-
13
- class MarkdownSyncer:
14
- """This class syncs a content file to a CMS (Content Management System).
15
-
16
- The CMS must have an endpoint that satisfies the following constraints:
17
-
18
- - It must accept a post request with fields *_id*, *displayName* and *markdown*.
19
- - The response body must have a key *_id* whose value should be
20
- a unique string identifier of the content.
21
-
22
- Creating and updating content is handled in the following way:
23
-
24
- - On the first request, an empty string is sent as *_id*.
25
- - If the request succeeds, the value of *_id* (in the response) is stored in a JSON file
26
- (created in the same directory as the markdown/notebook file).
27
- - On subsequent requests, the stored value is sent as *_id*.
28
- """
29
-
30
- ID_KEY = "_id"
31
-
32
- def __init__(
33
- self,
34
- post_url: str,
35
- request_handler: RequestHandler,
36
- metadata_file: Path = METADATA_FILE,
37
- ) -> None:
38
- """Creates a markdown syncer instance that connects to the CMS through the post url."""
39
- self._post_url: str = post_url
40
- self._request_handler: RequestHandler = request_handler
41
- self._content_file_path: Path = Path()
42
- self._content_file_type: ContentType = ContentType.MARKDOWN
43
- self._metadata_file_path: Path = metadata_file
44
-
45
- @property
46
- def content_file_path(self) -> Path:
47
- """Returns the path of the content file."""
48
- return self._content_file_path
49
-
50
- @content_file_path.setter
51
- def content_file_path(self, path: Path) -> None:
52
- """Sets the path of the content file."""
53
- if not path.is_file():
54
- raise FileNotFoundError(f"The file {path} does not exist.")
55
-
56
- ext = path.suffix.lower()
57
- for t in ContentType:
58
- if ext == t.value:
59
- self._content_file_type = t
60
- break
61
- else:
62
- allowed_extensions = [t.value for t in ContentType]
63
- sep = ", "
64
- raise ValueError(
65
- f"The file {path} has extension {ext}, but should be one of: {sep.join(allowed_extensions)}."
66
- )
67
-
68
- self._content_file_path = path
69
-
70
- @property
71
- def basename(self) -> str:
72
- """The name of the content file without extension."""
73
- return self._content_file_path.stem
74
-
75
- @property
76
- def display_name(self) -> str:
77
- """Generate a display name for the content."""
78
- return self.basename.replace("_", " ").title()
79
-
80
- @property
81
- def metadata_file_path(self) -> Path:
82
- """The path of the metadata file."""
83
- return self._metadata_file_path
84
-
85
- @property
86
- def metadata_key(self) -> str:
87
- """The key that the content metadata will be stored under in the metadata file."""
88
- return str(self._content_file_path.absolute())
89
-
90
- def _save_content_id(self, content_id: str) -> None:
91
- """Saves the content id to the metadata file."""
92
- with open(self._metadata_file_path) as f:
93
- try:
94
- data = json.load(f)
95
- except json.JSONDecodeError:
96
- data = {}
97
-
98
- data[self.metadata_key] = {
99
- self.ID_KEY: content_id,
100
- }
101
-
102
- with open(self._metadata_file_path, "w") as f:
103
- json.dump(data, f, indent=4)
104
-
105
- def _get_content_id(self) -> str:
106
- """Fetches the content id from the metadata file if it exists, otherwise an empty string."""
107
- with open(self._metadata_file_path) as f:
108
- try:
109
- data = json.load(f)
110
- except json.JSONDecodeError:
111
- data = {}
112
-
113
- metadata: dict[str, str] = data.get(self.metadata_key, {})
114
-
115
- content_id = metadata.get(self.ID_KEY, "")
116
-
117
- return content_id
118
-
119
- def _read_notebook(self) -> NotebookNode:
120
- """Reads the notebook file and returns its content."""
121
- return nbformat.read(self._content_file_path, as_version=nbformat.NO_CONVERT) # type: ignore
122
-
123
- def _get_content_from_notebook_file(self) -> str:
124
- """Extracts all markdown cells from the notebook and returns it as a merged string."""
125
- notebook = self._read_notebook()
126
-
127
- markdown_cells = []
128
- for cell in notebook.cells:
129
- if cell.cell_type == "markdown":
130
- markdown_cells.append(cell.source)
131
-
132
- markdown_content = "\n\n".join(markdown_cells)
133
-
134
- return markdown_content
135
-
136
- def _get_content_from_markdown_file(self) -> str:
137
- """Returns the content of a markdown file."""
138
- with open(self._content_file_path) as file:
139
- markdown_content = file.read()
140
- return markdown_content
141
-
142
- def _get_content(self) -> str:
143
- content = ""
144
- match self._content_file_type:
145
- case ContentType.MARKDOWN:
146
- content = self._get_content_from_markdown_file()
147
- case ContentType.NOTEBOOK:
148
- content = self._get_content_from_notebook_file()
149
- return content
150
-
151
- def _request_data(self) -> dict[str, str]:
152
- """Prepares the request data to be sent to the CMS endpoint."""
153
- return {
154
- "_id": self._get_content_id(),
155
- "displayName": self.display_name,
156
- "markdown": self._get_content(),
157
- }
158
-
159
- def sync_content(self) -> Response:
160
- """Sends the request to the CMS endpoint and returns the content id from the response."""
161
- response = self._request_handler.send_request(
162
- url=self._post_url, data=self._request_data()
163
- )
164
-
165
- if response.status_code != 200:
166
- raise ValueError(
167
- f"Request to the CMS failed with status code {response.status_code}."
168
- )
169
- if response.body is None:
170
- raise ValueError("Response body from CMS could not be parsed.")
171
- if self.ID_KEY not in response.body:
172
- raise ValueError(
173
- f"Response from the CMS does not contain the expected key '{self.ID_KEY}'."
174
- )
175
- result = response.body[self.ID_KEY]
176
- if not isinstance(result, str):
177
- raise ValueError(
178
- f"Response from the CMS does not contain a valid content id: {result}"
179
- )
180
- content_id: str = result
181
- self._save_content_id(content_id)
182
-
183
- return response
@@ -1,56 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import Any
3
- from typing import Protocol
4
-
5
- import requests
6
-
7
-
8
- @dataclass
9
- class Response:
10
- """The response object used in the package."""
11
-
12
- status_code: int
13
- body: dict[str, Any]
14
-
15
-
16
- class RequestHandler(Protocol):
17
- """Interface for handling how a request are sent.
18
-
19
- Implementing classes may handle authentication, sessions, etc.
20
- """
21
-
22
- def send_request(
23
- self,
24
- url: str,
25
- headers: dict[str, str] | None = None,
26
- data: dict[str, str] | None = None,
27
- ) -> Response:
28
- """Sends the request to the specified url, optionally with headers and data, and returns the response."""
29
- ...
30
-
31
-
32
- class BasicRequestHandler:
33
- """Basic, unauthenticated request handler."""
34
-
35
- def send_request(
36
- self,
37
- url: str,
38
- headers: dict[str, str] | None = None,
39
- data: dict[str, str] | None = None,
40
- ) -> Response:
41
- """Sends the request to the specified url without any headers."""
42
- response = requests.post(
43
- url,
44
- data=data,
45
- )
46
-
47
- try:
48
- body = response.json()
49
- body = dict(body)
50
- except Exception:
51
- body = {}
52
-
53
- return Response(
54
- status_code=response.status_code,
55
- body=body,
56
- )
@@ -1,13 +0,0 @@
1
- ssb_pubmd/__init__.py,sha256=hWhlVS_FDH9yq7fBdXwggg5opjZwf7gIKY6ZyJ81Y4w,176
2
- ssb_pubmd/__main__.py,sha256=iIjz73oLP323XyF9cB4LAvO8C47oFhIGDztuzJTOjYo,5089
3
- ssb_pubmd/browser_request_handler.py,sha256=rfTzsXpKNikH7HchzGkoV09O5bsxmpCB-JGzJ-_lyBc,2902
4
- ssb_pubmd/constants.py,sha256=0Fh9dOX0wN9spON96Zk8UIhh2yCmTgJVZd2W2uYdXqk,565
5
- ssb_pubmd/jwt_request_handler.py,sha256=Q3Da2R3tJwh1w40bESm90-JZwXfeYvu6R3K6cQ4-ySM,2844
6
- ssb_pubmd/markdown_syncer.py,sha256=2MCUlVofXDfUpNCoxz4byfVwc9n_eNABcaOfgxKvMy4,6535
7
- ssb_pubmd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- ssb_pubmd/request_handler.py,sha256=yjQaIXi3sRoyt9I4MOjfOXvco_VHPWB6OAMRapCH1GY,1318
9
- ssb_pubmd-0.0.19.dist-info/LICENSE,sha256=tF5bnYv09fgH5ph9t1EpH1MGrVOGTQeswL4dzVeZ_ak,1073
10
- ssb_pubmd-0.0.19.dist-info/METADATA,sha256=m4JJpfLDtetdNIFxJz_BhII5ZK23FsgiVKTLBsa5Id8,4331
11
- ssb_pubmd-0.0.19.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
12
- ssb_pubmd-0.0.19.dist-info/entry_points.txt,sha256=1_NfsiOfqTg948JWXYPwi4QtDk90KHkNn1CQtye8rJ0,48
13
- ssb_pubmd-0.0.19.dist-info/RECORD,,
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- pubmd=ssb_pubmd.__main__:cli
3
-