python-frontmatter 1.1.0__py3-none-any.whl → 1.2.0__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.
frontmatter/__init__.py CHANGED
@@ -4,12 +4,13 @@ Python Frontmatter: Parse and manage posts with YAML frontmatter
4
4
  """
5
5
  from __future__ import annotations
6
6
 
7
- import codecs
8
7
  import io
9
- from typing import TYPE_CHECKING, Iterable
8
+ import pathlib
9
+ from os import PathLike
10
+ from typing import TYPE_CHECKING, Iterable, TextIO
10
11
 
11
- from .util import u
12
- from .default_handlers import YAMLHandler, JSONHandler, TOMLHandler
12
+ from .default_handlers import JSONHandler, TOMLHandler, YAMLHandler
13
+ from .util import can_open, is_readable, is_writable, u
13
14
 
14
15
 
15
16
  if TYPE_CHECKING:
@@ -96,7 +97,7 @@ def parse(
96
97
  return metadata, content.strip()
97
98
 
98
99
 
99
- def check(fd: str | io.IOBase, encoding: str = "utf-8") -> bool:
100
+ def check(fd: TextIO | PathLike[str] | str, encoding: str = "utf-8") -> bool:
100
101
  """
101
102
  Check if a file-like object or filename has a frontmatter,
102
103
  return True if exists, False otherwise.
@@ -109,13 +110,17 @@ def check(fd: str | io.IOBase, encoding: str = "utf-8") -> bool:
109
110
  True
110
111
 
111
112
  """
112
- if hasattr(fd, "read"):
113
+ if is_readable(fd):
113
114
  text = fd.read()
114
115
 
115
- else:
116
- with codecs.open(fd, "r", encoding) as f:
116
+ elif can_open(fd):
117
+ with open(fd, "r", encoding=encoding) as f:
117
118
  text = f.read()
118
119
 
120
+ else:
121
+ # no idea what we're dealing with
122
+ return False
123
+
119
124
  return checks(text, encoding)
120
125
 
121
126
 
@@ -138,7 +143,7 @@ def checks(text: str, encoding: str = "utf-8") -> bool:
138
143
 
139
144
 
140
145
  def load(
141
- fd: str | io.IOBase,
146
+ fd: str | io.IOBase | pathlib.Path,
142
147
  encoding: str = "utf-8",
143
148
  handler: BaseHandler | None = None,
144
149
  **defaults: object,
@@ -154,13 +159,16 @@ def load(
154
159
  ... post = frontmatter.load(f)
155
160
 
156
161
  """
157
- if hasattr(fd, "read"):
162
+ if is_readable(fd):
158
163
  text = fd.read()
159
164
 
160
- else:
161
- with codecs.open(fd, "r", encoding) as f:
165
+ elif can_open(fd):
166
+ with open(fd, "r", encoding=encoding) as f:
162
167
  text = f.read()
163
168
 
169
+ else:
170
+ raise ValueError(f"Cannot open filename using type {type(fd)}")
171
+
164
172
  handler = handler or detect_format(text, handlers)
165
173
  return loads(text, encoding, handler, **defaults)
166
174
 
@@ -188,7 +196,7 @@ def loads(
188
196
 
189
197
  def dump(
190
198
  post: Post,
191
- fd: str | io.IOBase,
199
+ fd: str | PathLike[str] | TextIO,
192
200
  encoding: str = "utf-8",
193
201
  handler: BaseHandler | None = None,
194
202
  **kwargs: object,
@@ -199,11 +207,11 @@ def dump(
199
207
 
200
208
  ::
201
209
 
202
- >>> from io import BytesIO
210
+ >>> from io import StringIO
203
211
  >>> post = frontmatter.load('tests/yaml/hello-world.txt')
204
- >>> f = BytesIO()
212
+ >>> f = StringIO()
205
213
  >>> frontmatter.dump(post, f)
206
- >>> print(f.getvalue().decode('utf-8'))
214
+ >>> print(f.getvalue())
207
215
  ---
208
216
  layout: post
209
217
  title: Hello, world!
@@ -214,11 +222,11 @@ def dump(
214
222
 
215
223
  .. testcode::
216
224
 
217
- from io import BytesIO
225
+ from io import StringIO
218
226
  post = frontmatter.load('tests/yaml/hello-world.txt')
219
- f = BytesIO()
227
+ f = StringIO()
220
228
  frontmatter.dump(post, f)
221
- print(f.getvalue().decode('utf-8'))
229
+ print(f.getvalue())
222
230
 
223
231
  .. testoutput::
224
232
 
@@ -231,13 +239,16 @@ def dump(
231
239
 
232
240
  """
233
241
  content = dumps(post, handler, **kwargs)
234
- if hasattr(fd, "write"):
235
- fd.write(content.encode(encoding))
242
+ if is_writable(fd):
243
+ fd.write(content)
236
244
 
237
- else:
238
- with codecs.open(fd, "w", encoding) as f:
245
+ elif can_open(fd):
246
+ with open(fd, "w", encoding=encoding) as f:
239
247
  f.write(content)
240
248
 
249
+ else:
250
+ raise ValueError(f"Cannot open filename using type {type(fd)}")
251
+
241
252
 
242
253
  def dumps(post: Post, handler: BaseHandler | None = None, **kwargs: object) -> str:
243
254
  """
@@ -278,6 +289,7 @@ def dumps(post: Post, handler: BaseHandler | None = None, **kwargs: object) -> s
278
289
  if handler is None:
279
290
  handler = getattr(post, "handler", None) or YAMLHandler()
280
291
 
292
+ assert handler is not None
281
293
  return handler.format(post, **kwargs)
282
294
 
283
295
 
@@ -8,8 +8,8 @@ By default, ``frontmatter`` reads and writes YAML metadata. But maybe
8
8
  you don't like YAML. Maybe enjoy writing metadata in JSON, or TOML, or
9
9
  some other exotic markup not yet invented. For this, there are handlers.
10
10
 
11
- This module includes handlers for YAML, JSON and TOML, as well as a
12
- :py:class:`BaseHandler <frontmatter.default_handlers.BaseHandler>` that
11
+ This module includes handlers for YAML, JSON and TOML, as well as a
12
+ :py:class:`BaseHandler <frontmatter.default_handlers.BaseHandler>` that
13
13
  outlines the basic API and can be subclassed to deal with new formats.
14
14
 
15
15
  **Note**: The TOML handler is only available if the `toml <https://pypi.org/project/toml/>`_
@@ -32,10 +32,10 @@ A handler needs to do four things:
32
32
 
33
33
  An example:
34
34
 
35
- Calling :py:func:`frontmatter.load <frontmatter.load>` (or :py:func:`loads <frontmatter.loads>`)
36
- with the ``handler`` argument tells frontmatter which handler to use.
37
- The handler instance gets saved as an attribute on the returned post
38
- object. By default, calling :py:func:`frontmatter.dumps <frontmatter.dumps>`
35
+ Calling :py:func:`frontmatter.load <frontmatter.load>` (or :py:func:`loads <frontmatter.loads>`)
36
+ with the ``handler`` argument tells frontmatter which handler to use.
37
+ The handler instance gets saved as an attribute on the returned post
38
+ object. By default, calling :py:func:`frontmatter.dumps <frontmatter.dumps>`
39
39
  on the post will use the attached handler.
40
40
 
41
41
 
@@ -67,7 +67,7 @@ on the post will use the attached handler.
67
67
  <BLANKLINE>
68
68
  And this shouldn't break.
69
69
 
70
- Passing a new handler to :py:func:`frontmatter.dumps <frontmatter.dumps>`
70
+ Passing a new handler to :py:func:`frontmatter.dumps <frontmatter.dumps>`
71
71
  (or :py:func:`dump <frontmatter.dump>`) changes the export format:
72
72
 
73
73
  ::
@@ -283,6 +283,7 @@ class JSONHandler(BaseHandler):
283
283
  END_DELIMITER = ""
284
284
 
285
285
  def split(self, text: str) -> tuple[str, str]:
286
+ assert self.FM_BOUNDARY is not None
286
287
  _, fm, content = self.FM_BOUNDARY.split(text, 2)
287
288
  return "{" + fm + "}", content
288
289
 
@@ -298,7 +299,7 @@ class JSONHandler(BaseHandler):
298
299
 
299
300
  if toml:
300
301
 
301
- class TOMLHandler(BaseHandler):
302
+ class TOMLHandler(BaseHandler): # pyright: ignore
302
303
  """
303
304
  Load and export TOML metadata.
304
305
 
frontmatter/py.typed ADDED
@@ -0,0 +1 @@
1
+ # Marker file for PEP 561. This package uses inline types.
frontmatter/util.py CHANGED
@@ -2,16 +2,29 @@
2
2
  """
3
3
  Utilities for handling unicode and other repetitive bits
4
4
  """
5
- from typing import AnyStr
5
+ from os import PathLike
6
+ from typing import TypeGuard, TextIO
6
7
 
7
8
 
8
- def u(text: AnyStr, encoding: str = "utf-8") -> str:
9
+ def is_readable(fd: object) -> TypeGuard[TextIO]:
10
+ return callable(getattr(fd, "read", None))
11
+
12
+
13
+ def is_writable(fd: object) -> TypeGuard[TextIO]:
14
+ return callable(getattr(fd, "write", None))
15
+
16
+
17
+ def can_open(fd: object) -> TypeGuard[str | PathLike[str]]:
18
+ return isinstance(fd, str) or isinstance(fd, PathLike)
19
+
20
+
21
+ def u(text: str | bytes, encoding: str = "utf-8") -> str:
9
22
  "Return unicode text, no matter what"
10
23
 
11
24
  if isinstance(text, bytes):
12
25
  text_str: str = text.decode(encoding)
13
26
  else:
14
- text_str = text
27
+ text_str = str(text)
15
28
 
16
29
  # it's already unicode
17
30
  text_str = text_str.replace("\r\n", "\n")
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: python-frontmatter
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Parse and manage posts with YAML (or other) frontmatter
5
5
  Home-page: https://github.com/eyeseast/python-frontmatter
6
6
  Author: Chris Amico
@@ -12,23 +12,35 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Natural Language :: English
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.8
16
15
  Classifier: Programming Language :: Python :: 3.9
17
16
  Classifier: Programming Language :: Python :: 3.10
18
17
  Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  Requires-Dist: PyYAML
23
- Provides-Extra: docs
24
- Requires-Dist: sphinx ; extra == 'docs'
25
23
  Provides-Extra: test
26
- Requires-Dist: pytest ; extra == 'test'
27
- Requires-Dist: toml ; extra == 'test'
28
- Requires-Dist: pyaml ; extra == 'test'
29
- Requires-Dist: mypy ; extra == 'test'
30
- Requires-Dist: types-PyYAML ; extra == 'test'
31
- Requires-Dist: types-toml ; extra == 'test'
24
+ Requires-Dist: pytest; extra == "test"
25
+ Requires-Dist: toml; extra == "test"
26
+ Requires-Dist: pyaml; extra == "test"
27
+ Requires-Dist: mypy; extra == "test"
28
+ Requires-Dist: types-PyYAML; extra == "test"
29
+ Requires-Dist: types-toml; extra == "test"
30
+ Provides-Extra: docs
31
+ Requires-Dist: sphinx; extra == "docs"
32
+ Dynamic: author
33
+ Dynamic: author-email
34
+ Dynamic: classifier
35
+ Dynamic: description
36
+ Dynamic: description-content-type
37
+ Dynamic: home-page
38
+ Dynamic: keywords
39
+ Dynamic: license
40
+ Dynamic: license-file
41
+ Dynamic: provides-extra
42
+ Dynamic: requires-dist
43
+ Dynamic: summary
32
44
 
33
45
  # Python Frontmatter
34
46
 
@@ -142,10 +154,10 @@ Well, hello there, world.
142
154
  Or write to a file (or file-like object):
143
155
 
144
156
  ```python
145
- >>> from io import BytesIO
146
- >>> f = BytesIO()
157
+ >>> from io import StringIO
158
+ >>> f = StringIO()
147
159
  >>> frontmatter.dump(post, f)
148
- >>> print(f.getvalue().decode('utf-8')) # doctest: +NORMALIZE_WHITESPACE
160
+ >>> print(f.getvalue()) # doctest: +NORMALIZE_WHITESPACE
149
161
  ---
150
162
  excerpt: tl;dr
151
163
  layout: post
@@ -0,0 +1,10 @@
1
+ frontmatter/__init__.py,sha256=PtzuMdiQeiEq3r6FLgzDjT1pxkphlSt40VKtL-jUokY,9177
2
+ frontmatter/conftest.py,sha256=LxRFalFdwQA2RtSYqCSB7V7THG7rHt5zZmMEzJlf0DA,220
3
+ frontmatter/default_handlers.py,sha256=ucN_cv4nnPfRevZDZYbtio-r1aITcUZI4qBCKXWOAxA,9717
4
+ frontmatter/py.typed,sha256=DtCsIDq6KOv2NOEdQjTbeMWJKRh6ZEL2E-6Mf1RLeMA,59
5
+ frontmatter/util.py,sha256=utw8plpS0Ld6CJkY02k_VpT1vfiVm5ai1npsKR5Hius,784
6
+ python_frontmatter-1.2.0.dist-info/licenses/LICENSE,sha256=LmiF34zbpRujrv40WY5ohW8bJGjR5YxxlhQAS4b7Q9U,1077
7
+ python_frontmatter-1.2.0.dist-info/METADATA,sha256=Rn5Q4VXKA6eZx-lhGd8eeklpUBMoqjTFpknGs0mkD5k,4370
8
+ python_frontmatter-1.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
9
+ python_frontmatter-1.2.0.dist-info/top_level.txt,sha256=o5ywgaMiFjjXFwNlYulDy2RAulwnLsZCmYQn0jy7w7M,12
10
+ python_frontmatter-1.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: setuptools (82.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,9 +0,0 @@
1
- frontmatter/__init__.py,sha256=wL0nd60OKonncekh0qpxqX7PjquQscdF7ADyQurygJc,8809
2
- frontmatter/conftest.py,sha256=LxRFalFdwQA2RtSYqCSB7V7THG7rHt5zZmMEzJlf0DA,220
3
- frontmatter/default_handlers.py,sha256=kO4QMSqvUWAUOcKC2sCmVA0SX2aZRXrhlckFCqZttkg,9661
4
- frontmatter/util.py,sha256=hlyw6r54QvGdsln0EcXUHh51eQqYRnONy4P6fuemo6M,419
5
- python_frontmatter-1.1.0.dist-info/LICENSE,sha256=LmiF34zbpRujrv40WY5ohW8bJGjR5YxxlhQAS4b7Q9U,1077
6
- python_frontmatter-1.1.0.dist-info/METADATA,sha256=FKyXVYaz4nPtlhZxEDx0WcDqFlcajLbGLVCZ-jzHOZA,4137
7
- python_frontmatter-1.1.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
8
- python_frontmatter-1.1.0.dist-info/top_level.txt,sha256=o5ywgaMiFjjXFwNlYulDy2RAulwnLsZCmYQn0jy7w7M,12
9
- python_frontmatter-1.1.0.dist-info/RECORD,,