python-jsonstore 1.3.0__tar.gz → 2.0.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.
@@ -0,0 +1,4 @@
1
+ [paths]
2
+ source =
3
+ .
4
+ */site-packages
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: github-actions
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ groups:
8
+ github-actions:
9
+ patterns:
10
+ - "*"
@@ -0,0 +1,180 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+ pull_request:
9
+ workflow_call:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ defaults:
15
+ run:
16
+ shell: bash
17
+
18
+ jobs:
19
+ build:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - name: Check out repository and version tags
23
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
+ with:
25
+ fetch-depth: 0
26
+ persist-credentials: false
27
+
28
+ - name: Set up Python
29
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
30
+ with:
31
+ python-version: "3.15"
32
+
33
+ - name: Build and check distributions
34
+ run: |
35
+ python -m pip install --upgrade pip build twine
36
+ python -m build --sdist --wheel
37
+ python -m twine check --strict dist/*
38
+
39
+ - name: Upload distributions
40
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
41
+ with:
42
+ name: distributions
43
+ path: dist/*
44
+ if-no-files-found: error
45
+
46
+ tests:
47
+ needs: build
48
+ runs-on: ${{ matrix.os }}
49
+ strategy:
50
+ fail-fast: false
51
+ matrix:
52
+ os:
53
+ - ubuntu-latest
54
+ - windows-latest
55
+ python-version:
56
+ - "3.9"
57
+ - "3.10"
58
+ - "3.11"
59
+ - "3.12"
60
+ - "3.13"
61
+ - "3.14"
62
+ - "3.15"
63
+ - "pypy3.11"
64
+ exclude:
65
+ - os: windows-latest
66
+ python-version: "pypy3.11"
67
+ steps:
68
+ - name: Check out tests
69
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
70
+ with:
71
+ persist-credentials: false
72
+
73
+ - name: Set up Python ${{ matrix.python-version }}
74
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
75
+ with:
76
+ python-version: ${{ matrix.python-version }}
77
+
78
+ - name: Download distributions
79
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
80
+ with:
81
+ name: distributions
82
+ path: dist
83
+
84
+ - name: Install wheel and test dependencies
85
+ run: |
86
+ python -m pip install --upgrade pip
87
+ python -m pip install --no-cache-dir dist/*.whl pytest pytest-cov
88
+ mkdir -p "$RUNNER_TEMP/installed-tests"
89
+ cp test_jsonstore.py "$RUNNER_TEMP/installed-tests/"
90
+
91
+ - name: Test installed wheel outside the checkout
92
+ working-directory: ${{ runner.temp }}/installed-tests
93
+ run: |
94
+ python -I -c 'import jsonstore; print(jsonstore.__file__)'
95
+ python -I -m pytest -vv --cov=jsonstore --cov-report= --junitxml=test-results.xml
96
+
97
+ - name: Map installed coverage to repository paths
98
+ run: |
99
+ python -m coverage combine "$RUNNER_TEMP/installed-tests/.coverage"
100
+ python -m coverage xml -o "$RUNNER_TEMP/installed-tests/coverage.xml"
101
+
102
+ - name: Build and install wheel from source distribution
103
+ run: |
104
+ python -m pip uninstall --yes python-jsonstore
105
+ python -m pip install --no-cache-dir dist/*.tar.gz
106
+
107
+ - name: Test installed source distribution outside the checkout
108
+ working-directory: ${{ runner.temp }}/installed-tests
109
+ run: |
110
+ python -I -c 'import jsonstore; print(jsonstore.__file__)'
111
+ python -I -m pytest -vv
112
+
113
+ - name: Upload test reports
114
+ if: always() && matrix.python-version == '3.15'
115
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
116
+ with:
117
+ name: test-reports-${{ matrix.os }}
118
+ path: |
119
+ ${{ runner.temp }}/installed-tests/coverage.xml
120
+ ${{ runner.temp }}/installed-tests/test-results.xml
121
+ if-no-files-found: warn
122
+
123
+ reports:
124
+ if: ${{ !cancelled() && github.event_name != 'release' }}
125
+ needs: tests
126
+ runs-on: ubuntu-latest
127
+ steps:
128
+ - name: Check whether Codecov is configured
129
+ id: codecov
130
+ env:
131
+ ENABLED: ${{ secrets.CODECOV_TOKEN != '' }}
132
+ run: echo "enabled=$ENABLED" >> "$GITHUB_OUTPUT"
133
+
134
+ - name: Check out repository for report paths
135
+ if: steps.codecov.outputs.enabled == 'true'
136
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
137
+ with:
138
+ persist-credentials: false
139
+
140
+ - name: Download test reports
141
+ id: reports
142
+ continue-on-error: ${{ needs.tests.result != 'success' }}
143
+ if: steps.codecov.outputs.enabled == 'true'
144
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
145
+ with:
146
+ pattern: test-reports-*
147
+ path: reports
148
+
149
+ - name: Find available test reports
150
+ id: files
151
+ if: steps.codecov.outputs.enabled == 'true'
152
+ run: |
153
+ python3 - <<'PYTHON'
154
+ import glob
155
+ import os
156
+
157
+ with open(os.environ["GITHUB_OUTPUT"], "a") as output:
158
+ for name, pattern in (("coverage", "reports/*/coverage.xml"),
159
+ ("tests", "reports/*/test-results.xml")):
160
+ output.write(f"{name}={','.join(sorted(glob.glob(pattern)))}\n")
161
+ PYTHON
162
+
163
+ - name: Upload coverage to Codecov
164
+ if: steps.files.outputs.coverage != ''
165
+ uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
166
+ with:
167
+ token: ${{ secrets.CODECOV_TOKEN }}
168
+ files: ${{ steps.files.outputs.coverage }}
169
+ disable_search: true
170
+ fail_ci_if_error: true
171
+
172
+ - name: Upload test results to Codecov
173
+ if: steps.files.outputs.tests != ''
174
+ uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
175
+ with:
176
+ token: ${{ secrets.CODECOV_TOKEN }}
177
+ files: ${{ steps.files.outputs.tests }}
178
+ report_type: test_results
179
+ disable_search: true
180
+ fail_ci_if_error: true
@@ -0,0 +1,31 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ validate:
12
+ uses: ./.github/workflows/ci.yml
13
+
14
+ publish:
15
+ needs: validate
16
+ runs-on: ubuntu-latest
17
+ environment:
18
+ name: pypi
19
+ url: https://pypi.org/p/python-jsonstore
20
+ permissions:
21
+ id-token: write
22
+ steps:
23
+ - name: Download validated distributions
24
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
25
+ with:
26
+ name: distributions
27
+ path: dist
28
+
29
+ # Keep publishing in this workflow for the existing PyPI Trusted Publisher.
30
+ - name: Publish to PyPI
31
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-jsonstore
3
+ Version: 2.0.0
4
+ Summary: Map JSON files onto Python attributes for simple configuration storage
5
+ Author-email: Oliver Bristow <github+pypi@oliverbristow.co.uk>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Code0x58/python-jsonstore/
8
+ Keywords: json,key,value,store
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
20
+ Classifier: Programming Language :: Python :: Implementation :: CPython
21
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
22
+ Classifier: Topic :: Database
23
+ Classifier: Topic :: Software Development
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/x-rst
26
+ License-File: LICENCE.md
27
+ Dynamic: license-file
28
+
29
+ |PyPI| |Build Status| |Coverage Report|
30
+
31
+ jsonstore
32
+ =========
33
+
34
+ ``JsonStore`` maps a JSON object's keys to Python attributes. Use it to
35
+ load and save configuration containing nested dictionaries and lists.
36
+
37
+ Requires Python 3.9 or newer. Tests run on CPython 3.9–3.15 and PyPy 3.11.
38
+
39
+ Examples
40
+ --------
41
+
42
+ Install with ``pip install python-jsonstore``.
43
+
44
+ Basics
45
+ ~~~~~~
46
+
47
+ .. code:: python
48
+
49
+ from jsonstore import JsonStore
50
+
51
+ # Assignments and deletions are saved automatically.
52
+ store = JsonStore("config.json")
53
+ store.message = "hello"
54
+ store.options = {"enabled": True, "greeting": "💐"}
55
+ store.numbers = [1, 2, 3]
56
+ assert "message" in store
57
+ del store.message
58
+
59
+ # Dot-separated keys traverse dictionaries.
60
+ store["options.enabled"] = False
61
+ assert store["options", "enabled"] is False
62
+
63
+ # Tuple or list paths also accept list indices and slices.
64
+ assert store["numbers", -1] == 3
65
+ assert store[["numbers", 1]] == 2
66
+ del store["numbers", :2]
67
+ assert store.numbers == [3]
68
+
69
+ # A one-element tuple addresses a literal key containing a dot.
70
+ store[("literal.key",)] = "value"
71
+
72
+ Reads and assignments use deep copies. To update a stored collection,
73
+ assign the modified value back; ``+=`` also does this:
74
+
75
+ .. code:: python
76
+
77
+ store.numbers.append(4)
78
+ assert store.numbers == [3]
79
+ store.numbers += [4, 5]
80
+ assert store.numbers == [3, 4, 5]
81
+
82
+ Use item access for keys that conflict with ``JsonStore`` attributes.
83
+ ``indent`` controls JSON formatting and defaults to 2; use ``None`` for
84
+ compact output. ``auto_commit=False`` defers saving until a transaction
85
+ commits.
86
+
87
+ Transactions
88
+ ~~~~~~~~~~~~
89
+
90
+ Use ``with store:`` to group changes into a transaction. Transactions may
91
+ be nested; only successful exit from the outermost transaction saves the
92
+ file. An exception in a transaction restores its previous state, including
93
+ when saving fails.
94
+
95
+ .. code:: python
96
+
97
+ from jsonstore import JsonStore
98
+
99
+ with JsonStore("config.json", indent=None, auto_commit=False) as store:
100
+ store.value = 1
101
+ assert JsonStore("config.json").value == 1
102
+
103
+ try:
104
+ with store:
105
+ store.value = 2
106
+ with store:
107
+ store.value = 3
108
+ raise ValueError("cancel the transaction")
109
+ except ValueError:
110
+ pass
111
+ assert store.value == 1
112
+ assert JsonStore("config.json").value == 1
113
+
114
+ Transactions provide in-memory rollback, with no locking or coordination
115
+ between writers. Use one writer per file; separate stores do not reload
116
+ each other's changes. Atomic replacement does not guarantee durability
117
+ across a power failure.
118
+
119
+ .. |Build Status| image:: https://github.com/Code0x58/python-jsonstore/actions/workflows/ci.yml/badge.svg
120
+ :target: https://github.com/Code0x58/python-jsonstore/actions/workflows/ci.yml
121
+ .. |PyPI| image:: https://img.shields.io/pypi/v/python-jsonstore.svg
122
+ :target: https://pypi.org/project/python-jsonstore/
123
+ .. |Coverage Report| image:: https://codecov.io/gh/Code0x58/python-jsonstore/branch/master/graph/badge.svg
124
+ :target: https://codecov.io/gh/Code0x58/python-jsonstore
@@ -0,0 +1,96 @@
1
+ |PyPI| |Build Status| |Coverage Report|
2
+
3
+ jsonstore
4
+ =========
5
+
6
+ ``JsonStore`` maps a JSON object's keys to Python attributes. Use it to
7
+ load and save configuration containing nested dictionaries and lists.
8
+
9
+ Requires Python 3.9 or newer. Tests run on CPython 3.9–3.15 and PyPy 3.11.
10
+
11
+ Examples
12
+ --------
13
+
14
+ Install with ``pip install python-jsonstore``.
15
+
16
+ Basics
17
+ ~~~~~~
18
+
19
+ .. code:: python
20
+
21
+ from jsonstore import JsonStore
22
+
23
+ # Assignments and deletions are saved automatically.
24
+ store = JsonStore("config.json")
25
+ store.message = "hello"
26
+ store.options = {"enabled": True, "greeting": "💐"}
27
+ store.numbers = [1, 2, 3]
28
+ assert "message" in store
29
+ del store.message
30
+
31
+ # Dot-separated keys traverse dictionaries.
32
+ store["options.enabled"] = False
33
+ assert store["options", "enabled"] is False
34
+
35
+ # Tuple or list paths also accept list indices and slices.
36
+ assert store["numbers", -1] == 3
37
+ assert store[["numbers", 1]] == 2
38
+ del store["numbers", :2]
39
+ assert store.numbers == [3]
40
+
41
+ # A one-element tuple addresses a literal key containing a dot.
42
+ store[("literal.key",)] = "value"
43
+
44
+ Reads and assignments use deep copies. To update a stored collection,
45
+ assign the modified value back; ``+=`` also does this:
46
+
47
+ .. code:: python
48
+
49
+ store.numbers.append(4)
50
+ assert store.numbers == [3]
51
+ store.numbers += [4, 5]
52
+ assert store.numbers == [3, 4, 5]
53
+
54
+ Use item access for keys that conflict with ``JsonStore`` attributes.
55
+ ``indent`` controls JSON formatting and defaults to 2; use ``None`` for
56
+ compact output. ``auto_commit=False`` defers saving until a transaction
57
+ commits.
58
+
59
+ Transactions
60
+ ~~~~~~~~~~~~
61
+
62
+ Use ``with store:`` to group changes into a transaction. Transactions may
63
+ be nested; only successful exit from the outermost transaction saves the
64
+ file. An exception in a transaction restores its previous state, including
65
+ when saving fails.
66
+
67
+ .. code:: python
68
+
69
+ from jsonstore import JsonStore
70
+
71
+ with JsonStore("config.json", indent=None, auto_commit=False) as store:
72
+ store.value = 1
73
+ assert JsonStore("config.json").value == 1
74
+
75
+ try:
76
+ with store:
77
+ store.value = 2
78
+ with store:
79
+ store.value = 3
80
+ raise ValueError("cancel the transaction")
81
+ except ValueError:
82
+ pass
83
+ assert store.value == 1
84
+ assert JsonStore("config.json").value == 1
85
+
86
+ Transactions provide in-memory rollback, with no locking or coordination
87
+ between writers. Use one writer per file; separate stores do not reload
88
+ each other's changes. Atomic replacement does not guarantee durability
89
+ across a power failure.
90
+
91
+ .. |Build Status| image:: https://github.com/Code0x58/python-jsonstore/actions/workflows/ci.yml/badge.svg
92
+ :target: https://github.com/Code0x58/python-jsonstore/actions/workflows/ci.yml
93
+ .. |PyPI| image:: https://img.shields.io/pypi/v/python-jsonstore.svg
94
+ :target: https://pypi.org/project/python-jsonstore/
95
+ .. |Coverage Report| image:: https://codecov.io/gh/Code0x58/python-jsonstore/branch/master/graph/badge.svg
96
+ :target: https://codecov.io/gh/Code0x58/python-jsonstore
@@ -5,19 +5,16 @@ Provides a Python class that maps values to/from a JSON file
5
5
  """
6
6
  from __future__ import absolute_import
7
7
  import json
8
- import os.path
9
- import sys
8
+ import os
9
+ import stat
10
10
  from collections import OrderedDict
11
11
  from copy import deepcopy
12
+ from tempfile import mkstemp
12
13
 
13
14
  __all__ = ["JsonStore"]
14
15
 
15
16
  STRING_TYPES = (str,)
16
- INT_TYPES = (int,)
17
- if sys.version_info < (3,):
18
- STRING_TYPES += (unicode,)
19
- INT_TYPES += (long,)
20
- VALUE_TYPES = (bool, int, float, type(None)) + INT_TYPES
17
+ VALUE_TYPES = (bool, int, float, type(None))
21
18
 
22
19
 
23
20
  class JsonStore(object):
@@ -34,7 +31,11 @@ class JsonStore(object):
34
31
  if any(args):
35
32
  self.__dict__["_data"] = previous_state
36
33
  elif not self.__dict__["_states"]:
37
- self._save()
34
+ try:
35
+ self._save()
36
+ except BaseException:
37
+ self.__dict__["_data"] = previous_state
38
+ raise
38
39
 
39
40
  def _do_auto_commit(self):
40
41
  if self._auto_commit and not self.__dict__["_states"]:
@@ -56,25 +57,39 @@ class JsonStore(object):
56
57
  self.__dict__["_data"] = data
57
58
 
58
59
  def _save(self):
59
- temp = self._path + "~"
60
- with open(temp, "wb") as store:
61
- output = json.dumps(self._data, indent=self._indent)
62
- store.write(output.encode("utf-8"))
63
-
64
- if sys.version_info >= (3, 3):
60
+ output = json.dumps(self._data, indent=self._indent).encode("utf-8")
61
+ try:
62
+ mode = stat.S_IMODE(os.stat(self._path).st_mode)
63
+ except FileNotFoundError:
64
+ mode = None
65
+ descriptor, temp = mkstemp(
66
+ prefix="." + os.path.basename(self._path) + ".",
67
+ suffix=".tmp",
68
+ dir=os.path.dirname(os.path.abspath(self._path)),
69
+ )
70
+ try:
71
+ with os.fdopen(descriptor, "wb") as store:
72
+ store.write(output)
73
+ if mode is not None:
74
+ os.chmod(temp, mode)
65
75
  os.replace(temp, self._path)
66
- elif os.name == "windows":
67
- os.remove(self._path)
68
- os.rename(temp, self._path)
69
- else:
70
- os.rename(temp, self._path)
76
+ finally:
77
+ try:
78
+ os.unlink(temp)
79
+ except FileNotFoundError:
80
+ pass
81
+ except PermissionError:
82
+ if os.name != "nt":
83
+ raise
84
+ os.chmod(temp, stat.S_IWRITE)
85
+ os.unlink(temp)
71
86
 
72
87
  def __init__(self, path, indent=2, auto_commit=True):
73
88
  self.__dict__.update(
74
89
  {
75
90
  "_auto_commit": auto_commit,
76
91
  "_data": None,
77
- "_path": path,
92
+ "_path": os.fsdecode(path),
78
93
  "_indent": indent,
79
94
  "_states": [],
80
95
  }
@@ -93,25 +108,26 @@ class JsonStore(object):
93
108
  Raise an exception if the object is not suitable for assignment.
94
109
 
95
110
  """
96
- # pylint: disable=unicode-builtin,long-builtin
97
- if isinstance(obj, (dict, list)):
111
+ if isinstance(obj, (dict, list, tuple)):
98
112
  if parents is None:
99
- parents = []
100
- elif any(o is obj for o in parents):
101
- raise ValueError("Cycle detected in list/dictionary")
102
- parents.append(obj)
103
-
104
- if isinstance(obj, dict):
105
- for k, v in obj.items():
106
- if not cls._valid_string(k):
107
- # this is necessary because of the JSON serialisation
108
- raise TypeError("a dict has non-string keys")
109
- cls._verify_object(v, parents)
110
- elif isinstance(obj, (list, tuple)):
111
- for o in obj:
112
- cls._verify_object(o, parents)
113
- else:
114
- return cls._valid_value(obj)
113
+ parents = set()
114
+ identity = id(obj)
115
+ if identity in parents:
116
+ raise ValueError("Cycle detected in list/dictionary/tuple")
117
+ parents.add(identity)
118
+ try:
119
+ if isinstance(obj, dict):
120
+ for key, value in obj.items():
121
+ if not cls._valid_string(key):
122
+ raise TypeError("a dict has non-string keys")
123
+ cls._verify_object(value, parents)
124
+ else:
125
+ for value in obj:
126
+ cls._verify_object(value, parents)
127
+ finally:
128
+ parents.remove(identity)
129
+ elif not cls._valid_value(obj):
130
+ raise TypeError("unsupported JSON value type: %s" % type(obj).__name__)
115
131
 
116
132
  @classmethod
117
133
  def _valid_value(cls, value):
@@ -143,11 +159,14 @@ class JsonStore(object):
143
159
 
144
160
  def __setattr__(self, attr, value):
145
161
  self._verify_object(value)
146
- self._data[attr] = deepcopy(value)
162
+ value = deepcopy(value)
163
+ self._verify_object(value)
164
+ self._data[attr] = value
147
165
  self._do_auto_commit()
148
166
 
149
167
  def __delattr__(self, attr):
150
168
  del self._data[attr]
169
+ self._do_auto_commit()
151
170
 
152
171
  def __get_obj(self, steps):
153
172
  """Returns the object which is under the given path."""
@@ -168,11 +187,13 @@ class JsonStore(object):
168
187
  steps = self._canonical_key(key)
169
188
  path, step = steps[:-1], steps[-1]
170
189
  self._verify_object(value)
190
+ value = deepcopy(value)
191
+ self._verify_object(value)
171
192
  container = self.__get_obj(path)
172
193
  if isinstance(container, dict) and not self._valid_string(step):
173
194
  raise TypeError("%s is a dict and %s is not a string" % (path, step))
174
195
  try:
175
- container[step] = deepcopy(value)
196
+ container[step] = value
176
197
  except (IndexError, TypeError) as e:
177
198
  raise type(e)("unable to set %s from %s: %s" % (step, path, e))
178
199
  self._do_auto_commit()
@@ -190,6 +211,7 @@ class JsonStore(object):
190
211
  del obj[step]
191
212
  except (KeyError, IndexError, TypeError) as e:
192
213
  raise type(e)("unable to delete %s from %s: %s" % (step, path, e))
214
+ self._do_auto_commit()
193
215
 
194
216
  def __contains__(self, key):
195
217
  steps = self._canonical_key(key)
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3", "setuptools_scm>=7"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "python-jsonstore"
7
+ description = "Map JSON files onto Python attributes for simple configuration storage"
8
+ readme = "README.rst"
9
+ requires-python = ">=3.9"
10
+ license = "MIT"
11
+ license-files = ["LICENCE.md"]
12
+ authors = [
13
+ {name = "Oliver Bristow", email = "github+pypi@oliverbristow.co.uk"},
14
+ ]
15
+ keywords = ["json", "key", "value", "store"]
16
+ classifiers = [
17
+ "Development Status :: 5 - Production/Stable",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Programming Language :: Python :: 3.15",
28
+ "Programming Language :: Python :: Implementation :: CPython",
29
+ "Programming Language :: Python :: Implementation :: PyPy",
30
+ "Topic :: Database",
31
+ "Topic :: Software Development",
32
+ ]
33
+ dependencies = []
34
+ dynamic = ["version"]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/Code0x58/python-jsonstore/"
38
+
39
+ [tool.setuptools]
40
+ py-modules = ["jsonstore"]
41
+
42
+ [tool.setuptools_scm]