python-frontmatter 1.1.0__tar.gz → 1.2.0__tar.gz

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.
Files changed (44) hide show
  1. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/MANIFEST.in +1 -0
  2. {python-frontmatter-1.1.0/python_frontmatter.egg-info → python_frontmatter-1.2.0}/PKG-INFO +27 -7
  3. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/README.md +3 -3
  4. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/frontmatter/__init__.py +35 -23
  5. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/frontmatter/default_handlers.py +9 -8
  6. python_frontmatter-1.2.0/frontmatter/py.typed +1 -0
  7. python_frontmatter-1.2.0/frontmatter/util.py +31 -0
  8. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0/python_frontmatter.egg-info}/PKG-INFO +27 -7
  9. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/python_frontmatter.egg-info/SOURCES.txt +1 -0
  10. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/setup.py +3 -2
  11. python-frontmatter-1.1.0/frontmatter/util.py +0 -18
  12. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/LICENSE +0 -0
  13. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/frontmatter/conftest.py +0 -0
  14. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/python_frontmatter.egg-info/dependency_links.txt +0 -0
  15. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/python_frontmatter.egg-info/not-zip-safe +0 -0
  16. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/python_frontmatter.egg-info/requires.txt +0 -0
  17. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/python_frontmatter.egg-info/top_level.txt +0 -0
  18. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/setup.cfg +0 -0
  19. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/empty/empty-frontmatter.result.json +0 -0
  20. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/empty/empty-frontmatter.txt +0 -0
  21. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/empty/no-frontmatter.result.json +0 -0
  22. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/empty/no-frontmatter.txt +0 -0
  23. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/json/hello-json.md +0 -0
  24. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/json/hello-json.result.json +0 -0
  25. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/stub_tests.py +0 -0
  26. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/test_docs.py +0 -0
  27. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/test_files.py +0 -0
  28. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/toml/hello-toml.md +0 -0
  29. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/toml/hello-toml.result.json +0 -0
  30. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/unit_test.py +0 -0
  31. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/chinese.result.json +0 -0
  32. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/chinese.txt +0 -0
  33. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/extra-dash.result.json +0 -0
  34. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/extra-dash.txt +0 -0
  35. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/extra-space.result.json +0 -0
  36. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/extra-space.txt +0 -0
  37. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/hello-markdown.md +0 -0
  38. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/hello-markdown.result.json +0 -0
  39. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/hello-world.result.json +0 -0
  40. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/hello-world.txt +0 -0
  41. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/network-diagrams.md +0 -0
  42. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/network-diagrams.result.json +0 -0
  43. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/unpretty.md +0 -0
  44. {python-frontmatter-1.1.0 → python_frontmatter-1.2.0}/tests/yaml/unpretty.result.json +0 -0
@@ -1,5 +1,6 @@
1
1
  include LICENSE
2
2
  include README.md
3
+ include frontmatter/py.typed
3
4
 
4
5
  recursive-include tests *
5
6
 
@@ -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,15 +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
+ License-File: LICENSE
22
+ Requires-Dist: PyYAML
21
23
  Provides-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"
22
30
  Provides-Extra: docs
23
- License-File: LICENSE
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
24
44
 
25
45
  # Python Frontmatter
26
46
 
@@ -134,10 +154,10 @@ Well, hello there, world.
134
154
  Or write to a file (or file-like object):
135
155
 
136
156
  ```python
137
- >>> from io import BytesIO
138
- >>> f = BytesIO()
157
+ >>> from io import StringIO
158
+ >>> f = StringIO()
139
159
  >>> frontmatter.dump(post, f)
140
- >>> print(f.getvalue().decode('utf-8')) # doctest: +NORMALIZE_WHITESPACE
160
+ >>> print(f.getvalue()) # doctest: +NORMALIZE_WHITESPACE
141
161
  ---
142
162
  excerpt: tl;dr
143
163
  layout: post
@@ -110,10 +110,10 @@ Well, hello there, world.
110
110
  Or write to a file (or file-like object):
111
111
 
112
112
  ```python
113
- >>> from io import BytesIO
114
- >>> f = BytesIO()
113
+ >>> from io import StringIO
114
+ >>> f = StringIO()
115
115
  >>> frontmatter.dump(post, f)
116
- >>> print(f.getvalue().decode('utf-8')) # doctest: +NORMALIZE_WHITESPACE
116
+ >>> print(f.getvalue()) # doctest: +NORMALIZE_WHITESPACE
117
117
  ---
118
118
  excerpt: tl;dr
119
119
  layout: post
@@ -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
 
@@ -0,0 +1 @@
1
+ # Marker file for PEP 561. This package uses inline types.
@@ -0,0 +1,31 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Utilities for handling unicode and other repetitive bits
4
+ """
5
+ from os import PathLike
6
+ from typing import TypeGuard, TextIO
7
+
8
+
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:
22
+ "Return unicode text, no matter what"
23
+
24
+ if isinstance(text, bytes):
25
+ text_str: str = text.decode(encoding)
26
+ else:
27
+ text_str = str(text)
28
+
29
+ # it's already unicode
30
+ text_str = text_str.replace("\r\n", "\n")
31
+ return text_str
@@ -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,15 +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
+ License-File: LICENSE
22
+ Requires-Dist: PyYAML
21
23
  Provides-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"
22
30
  Provides-Extra: docs
23
- License-File: LICENSE
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
24
44
 
25
45
  # Python Frontmatter
26
46
 
@@ -134,10 +154,10 @@ Well, hello there, world.
134
154
  Or write to a file (or file-like object):
135
155
 
136
156
  ```python
137
- >>> from io import BytesIO
138
- >>> f = BytesIO()
157
+ >>> from io import StringIO
158
+ >>> f = StringIO()
139
159
  >>> frontmatter.dump(post, f)
140
- >>> print(f.getvalue().decode('utf-8')) # doctest: +NORMALIZE_WHITESPACE
160
+ >>> print(f.getvalue()) # doctest: +NORMALIZE_WHITESPACE
141
161
  ---
142
162
  excerpt: tl;dr
143
163
  layout: post
@@ -5,6 +5,7 @@ setup.py
5
5
  frontmatter/__init__.py
6
6
  frontmatter/conftest.py
7
7
  frontmatter/default_handlers.py
8
+ frontmatter/py.typed
8
9
  frontmatter/util.py
9
10
  python_frontmatter.egg-info/PKG-INFO
10
11
  python_frontmatter.egg-info/SOURCES.txt
@@ -11,7 +11,7 @@ with open("README.md") as f:
11
11
  readme = f.read()
12
12
 
13
13
 
14
- VERSION = "1.1.0"
14
+ VERSION = "1.2.0"
15
15
 
16
16
 
17
17
  setup(
@@ -24,6 +24,7 @@ setup(
24
24
  author_email="eyeseast@gmail.com",
25
25
  url="https://github.com/eyeseast/python-frontmatter",
26
26
  packages=["frontmatter"],
27
+ package_data={"frontmatter": ["py.typed"]},
27
28
  include_package_data=True,
28
29
  install_requires=["PyYAML"],
29
30
  extras_require={
@@ -40,11 +41,11 @@ setup(
40
41
  "License :: OSI Approved :: MIT License",
41
42
  "Natural Language :: English",
42
43
  "Programming Language :: Python :: 3",
43
- "Programming Language :: Python :: 3.8",
44
44
  "Programming Language :: Python :: 3.9",
45
45
  "Programming Language :: Python :: 3.10",
46
46
  "Programming Language :: Python :: 3.11",
47
47
  "Programming Language :: Python :: 3.12",
48
+ "Programming Language :: Python :: 3.13",
48
49
  ],
49
50
  test_suite="test",
50
51
  )
@@ -1,18 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Utilities for handling unicode and other repetitive bits
4
- """
5
- from typing import AnyStr
6
-
7
-
8
- def u(text: AnyStr, encoding: str = "utf-8") -> str:
9
- "Return unicode text, no matter what"
10
-
11
- if isinstance(text, bytes):
12
- text_str: str = text.decode(encoding)
13
- else:
14
- text_str = text
15
-
16
- # it's already unicode
17
- text_str = text_str.replace("\r\n", "\n")
18
- return text_str