fps_file_id 0.1.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,349 @@
1
+
2
+ # Created by https://www.toptal.com/developers/gitignore/api/git,linux,macos,python,windows,pycharm,jupyternotebook,vscode
3
+ # Edit at https://www.toptal.com/developers/gitignore?templates=git,linux,macos,python,windows,pycharm,jupyternotebook,vscode
4
+
5
+ .fps_cli_args.toml
6
+
7
+ ### Git ###
8
+ # Created by git for backups. To disable backups in Git:
9
+ # $ git config --global mergetool.keepBackup false
10
+ *.orig
11
+
12
+ # Created by git when using merge tools for conflicts
13
+ *.BACKUP.*
14
+ *.BASE.*
15
+ *.LOCAL.*
16
+ *.REMOTE.*
17
+ *_BACKUP_*.txt
18
+ *_BASE_*.txt
19
+ *_LOCAL_*.txt
20
+ *_REMOTE_*.txt
21
+
22
+ #!! ERROR: jupyternotebook is undefined. Use list command to see defined gitignore types !!#
23
+
24
+ ### Linux ###
25
+ *~
26
+
27
+ # temporary files which can be created if a process still has a handle open of a deleted file
28
+ .fuse_hidden*
29
+
30
+ # KDE directory preferences
31
+ .directory
32
+
33
+ # Linux trash folder which might appear on any partition or disk
34
+ .Trash-*
35
+
36
+ # .nfs files are created when an open file is removed but is still being accessed
37
+ .nfs*
38
+
39
+ ### macOS ###
40
+ # General
41
+ .DS_Store
42
+ .AppleDouble
43
+ .LSOverride
44
+
45
+ # Icon must end with two \r
46
+ Icon
47
+
48
+
49
+ # Thumbnails
50
+ ._*
51
+
52
+ # Files that might appear in the root of a volume
53
+ .DocumentRevisions-V100
54
+ .fseventsd
55
+ .Spotlight-V100
56
+ .TemporaryItems
57
+ .Trashes
58
+ .VolumeIcon.icns
59
+ .com.apple.timemachine.donotpresent
60
+
61
+ # Directories potentially created on remote AFP share
62
+ .AppleDB
63
+ .AppleDesktop
64
+ Network Trash Folder
65
+ Temporary Items
66
+ .apdisk
67
+
68
+ ### PyCharm ###
69
+ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
70
+ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
71
+
72
+ # User-specific stuff
73
+ .idea/**/workspace.xml
74
+ .idea/**/tasks.xml
75
+ .idea/**/usage.statistics.xml
76
+ .idea/**/dictionaries
77
+ .idea/**/shelf
78
+
79
+ # AWS User-specific
80
+ .idea/**/aws.xml
81
+
82
+ # Generated files
83
+ .idea/**/contentModel.xml
84
+
85
+ # Sensitive or high-churn files
86
+ .idea/**/dataSources/
87
+ .idea/**/dataSources.ids
88
+ .idea/**/dataSources.local.xml
89
+ .idea/**/sqlDataSources.xml
90
+ .idea/**/dynamic.xml
91
+ .idea/**/uiDesigner.xml
92
+ .idea/**/dbnavigator.xml
93
+
94
+ # Gradle
95
+ .idea/**/gradle.xml
96
+ .idea/**/libraries
97
+
98
+ # Gradle and Maven with auto-import
99
+ # When using Gradle or Maven with auto-import, you should exclude module files,
100
+ # since they will be recreated, and may cause churn. Uncomment if using
101
+ # auto-import.
102
+ # .idea/artifacts
103
+ # .idea/compiler.xml
104
+ # .idea/jarRepositories.xml
105
+ # .idea/modules.xml
106
+ # .idea/*.iml
107
+ # .idea/modules
108
+ # *.iml
109
+ # *.ipr
110
+
111
+ # CMake
112
+ cmake-build-*/
113
+
114
+ # Mongo Explorer plugin
115
+ .idea/**/mongoSettings.xml
116
+
117
+ # File-based project format
118
+ *.iws
119
+
120
+ # IntelliJ
121
+ out/
122
+
123
+ # mpeltonen/sbt-idea plugin
124
+ .idea_modules/
125
+
126
+ # JIRA plugin
127
+ atlassian-ide-plugin.xml
128
+
129
+ # Cursive Clojure plugin
130
+ .idea/replstate.xml
131
+
132
+ # Crashlytics plugin (for Android Studio and IntelliJ)
133
+ com_crashlytics_export_strings.xml
134
+ crashlytics.properties
135
+ crashlytics-build.properties
136
+ fabric.properties
137
+
138
+ # Editor-based Rest Client
139
+ .idea/httpRequests
140
+
141
+ # Android studio 3.1+ serialized cache file
142
+ .idea/caches/build_file_checksums.ser
143
+
144
+ ### PyCharm Patch ###
145
+ # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
146
+
147
+ # *.iml
148
+ # modules.xml
149
+ # .idea/misc.xml
150
+ # *.ipr
151
+
152
+ # Sonarlint plugin
153
+ # https://plugins.jetbrains.com/plugin/7973-sonarlint
154
+ .idea/**/sonarlint/
155
+
156
+ # SonarQube Plugin
157
+ # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
158
+ .idea/**/sonarIssues.xml
159
+
160
+ # Markdown Navigator plugin
161
+ # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
162
+ .idea/**/markdown-navigator.xml
163
+ .idea/**/markdown-navigator-enh.xml
164
+ .idea/**/markdown-navigator/
165
+
166
+ # Cache file creation bug
167
+ # See https://youtrack.jetbrains.com/issue/JBR-2257
168
+ .idea/$CACHE_FILE$
169
+
170
+ # CodeStream plugin
171
+ # https://plugins.jetbrains.com/plugin/12206-codestream
172
+ .idea/codestream.xml
173
+
174
+ ### Python ###
175
+ # Byte-compiled / optimized / DLL files
176
+ __pycache__/
177
+ *.py[cod]
178
+ *$py.class
179
+
180
+ # C extensions
181
+ *.so
182
+
183
+ # Distribution / packaging
184
+ .Python
185
+ build/
186
+ develop-eggs/
187
+ dist/
188
+ downloads/
189
+ eggs/
190
+ .eggs/
191
+ lib/
192
+ lib64/
193
+ parts/
194
+ sdist/
195
+ var/
196
+ wheels/
197
+ share/python-wheels/
198
+ *.egg-info/
199
+ .installed.cfg
200
+ *.egg
201
+ MANIFEST
202
+
203
+ # PyInstaller
204
+ # Usually these files are written by a python script from a template
205
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
206
+ *.manifest
207
+ *.spec
208
+
209
+ # Installer logs
210
+ pip-log.txt
211
+ pip-delete-this-directory.txt
212
+
213
+ # Unit test / coverage reports
214
+ htmlcov/
215
+ .tox/
216
+ .nox/
217
+ .coverage
218
+ .coverage.*
219
+ .cache
220
+ nosetests.xml
221
+ coverage.xml
222
+ *.cover
223
+ *.py,cover
224
+ .hypothesis/
225
+ .pytest_cache/
226
+ cover/
227
+
228
+ # Translations
229
+ *.mo
230
+ *.pot
231
+
232
+ # Django stuff:
233
+ *.log
234
+ local_settings.py
235
+ db.sqlite3
236
+ db.sqlite3-journal
237
+
238
+ # Flask stuff:
239
+ instance/
240
+ .webassets-cache
241
+
242
+ # Scrapy stuff:
243
+ .scrapy
244
+
245
+ # Sphinx documentation
246
+ docs/_build/
247
+
248
+ # PyBuilder
249
+ .pybuilder/
250
+ target/
251
+
252
+ # Jupyter Notebook
253
+ .ipynb_checkpoints
254
+
255
+ # IPython
256
+ profile_default/
257
+ ipython_config.py
258
+
259
+ # pyenv
260
+ # For a library or package, you might want to ignore these files since the code is
261
+ # intended to run in multiple environments; otherwise, check them in:
262
+ # .python-version
263
+
264
+ # pipenv
265
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
266
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
267
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
268
+ # install all needed dependencies.
269
+ #Pipfile.lock
270
+
271
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
272
+ __pypackages__/
273
+
274
+ # Celery stuff
275
+ celerybeat-schedule
276
+ celerybeat.pid
277
+
278
+ # SageMath parsed files
279
+ *.sage.py
280
+
281
+ # Environments
282
+ .env
283
+ .venv
284
+ env/
285
+ venv/
286
+ ENV/
287
+ env.bak/
288
+ venv.bak/
289
+
290
+ # Spyder project settings
291
+ .spyderproject
292
+ .spyproject
293
+
294
+ # Rope project settings
295
+ .ropeproject
296
+
297
+ # mkdocs documentation
298
+ /site
299
+
300
+ # mypy
301
+ .mypy_cache/
302
+ .dmypy.json
303
+ dmypy.json
304
+
305
+ # Pyre type checker
306
+ .pyre/
307
+
308
+ # pytype static type analyzer
309
+ .pytype/
310
+
311
+ # Cython debug symbols
312
+ cython_debug/
313
+
314
+ #!! ERROR: vscode is undefined. Use list command to see defined gitignore types !!#
315
+
316
+ ### Windows ###
317
+ # Windows thumbnail cache files
318
+ Thumbs.db
319
+ Thumbs.db:encryptable
320
+ ehthumbs.db
321
+ ehthumbs_vista.db
322
+
323
+ # Dump file
324
+ *.stackdump
325
+
326
+ # Folder config file
327
+ [Dd]esktop.ini
328
+
329
+ # Recycle Bin used on file shares
330
+ $RECYCLE.BIN/
331
+
332
+ # Windows Installer files
333
+ *.cab
334
+ *.msi
335
+ *.msix
336
+ *.msm
337
+ *.msp
338
+
339
+ # Windows shortcuts
340
+ *.lnk
341
+
342
+ # End of https://www.toptal.com/developers/gitignore/api/git,linux,macos,python,windows,pycharm,jupyternotebook,vscode
343
+
344
+ .jupyter_ystore.db
345
+ .jupyter_ystore.db-journal
346
+ fps_cli_args.toml
347
+
348
+ # pixi environments
349
+ .pixi
@@ -0,0 +1,59 @@
1
+ # Licensing terms
2
+
3
+ This project is licensed under the terms of the Modified BSD License
4
+ (also known as New or Revised or 3-Clause BSD), as follows:
5
+
6
+ - Copyright (c) 2025-, Jupyter Development Team
7
+
8
+ All rights reserved.
9
+
10
+ Redistribution and use in source and binary forms, with or without
11
+ modification, are permitted provided that the following conditions are met:
12
+
13
+ Redistributions of source code must retain the above copyright notice, this
14
+ list of conditions and the following disclaimer.
15
+
16
+ Redistributions in binary form must reproduce the above copyright notice, this
17
+ list of conditions and the following disclaimer in the documentation and/or
18
+ other materials provided with the distribution.
19
+
20
+ Neither the name of the Jupyter Development Team nor the names of its
21
+ contributors may be used to endorse or promote products derived from this
22
+ software without specific prior written permission.
23
+
24
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
25
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
28
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+
35
+ ## About the Jupyter Development Team
36
+
37
+ The Jupyter Development Team is the set of all contributors to the Jupyter project.
38
+ This includes all of the Jupyter subprojects.
39
+
40
+ The core team that coordinates development on GitHub can be found here:
41
+ https://github.com/jupyter/.
42
+
43
+ ## Our Copyright Policy
44
+
45
+ Jupyter uses a shared copyright model. Each contributor maintains copyright
46
+ over their contributions to Jupyter. But, it is important to note that these
47
+ contributions are typically only changes to the repositories. Thus, the Jupyter
48
+ source code, in its entirety is not the copyright of any single person or
49
+ institution. Instead, it is the collective copyright of the entire Jupyter
50
+ Development Team. If individual contributors want to maintain a record of what
51
+ changes/contributions they have specific copyright on, they should indicate
52
+ their copyright in the commit message of the change, when they commit the
53
+ change to one of the Jupyter repositories.
54
+
55
+ With this in mind, the following banner should be used in any source code file
56
+ to indicate the copyright and license terms:
57
+
58
+ # Copyright (c) Jupyter Development Team.
59
+ # Distributed under the terms of the Modified BSD License.
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.4
2
+ Name: fps_file_id
3
+ Version: 0.1.0
4
+ Summary: An FPS plugin for the file ID API
5
+ Project-URL: Homepage, https://jupyter.org
6
+ Author-email: Jupyter Development Team <jupyter@googlegroups.com>
7
+ License: BSD 3-Clause License
8
+ License-File: COPYING.md
9
+ Keywords: fastapi,jupyter,plugins,server
10
+ Requires-Python: >=3.9
11
+ Requires-Dist: anyio<5,>=3.6.2
12
+ Requires-Dist: jupyverse-api<0.10.0,>=0.9.0
13
+ Requires-Dist: sqlite-anyio<0.3.0,>=0.2.0
14
+ Requires-Dist: watchfiles<2,>=1.0.4
15
+ Description-Content-Type: text/markdown
16
+
17
+ # fps-contents
18
+
19
+ An FPS plugin for the file ID API.
@@ -0,0 +1,3 @@
1
+ # fps-contents
2
+
3
+ An FPS plugin for the file ID API.
@@ -0,0 +1,6 @@
1
+ import importlib.metadata
2
+
3
+ try:
4
+ __version__ = importlib.metadata.version("fps_file_id")
5
+ except importlib.metadata.PackageNotFoundError:
6
+ __version__ = "unknown"
@@ -0,0 +1,229 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import sqlite3
5
+ from uuid import uuid4
6
+
7
+ import structlog
8
+ from anyio import Event, Lock, Path
9
+ from sqlite_anyio import connect
10
+ from watchfiles import Change, awatch
11
+
12
+ from jupyverse_api.file_id import FileId
13
+
14
+ logger = structlog.get_logger()
15
+ watchfiles_logger = logging.getLogger("watchfiles")
16
+ watchfiles_logger.setLevel(logging.WARNING)
17
+
18
+
19
+ class Watcher:
20
+ def __init__(self, path: str) -> None:
21
+ self.path = path
22
+ self._event = Event()
23
+
24
+ def __aiter__(self):
25
+ return self
26
+
27
+ async def __anext__(self):
28
+ await self._event.wait()
29
+ self._event = Event()
30
+ return self._change
31
+
32
+ def notify(self, change):
33
+ self._change = change
34
+ self._event.set()
35
+
36
+
37
+ class _FileId(FileId):
38
+ db_path: str
39
+ initialized: Event
40
+ watchers: dict[str, list[Watcher]]
41
+ lock: Lock
42
+
43
+ def __init__(self, db_path: str = ".fileid.db"):
44
+ self.db_path = db_path
45
+ self.initialized = Event()
46
+ self.watchers = {}
47
+ self.stop_event = Event()
48
+ self.lock = Lock()
49
+
50
+ async def start(self) -> None:
51
+ self._db = await connect(self.db_path)
52
+ try:
53
+ await self.watch_files()
54
+ except sqlite3.ProgrammingError:
55
+ pass
56
+
57
+ async def stop(self) -> None:
58
+ await self._db.close()
59
+ self.stop_event.set()
60
+
61
+ async def get_id(self, path: str) -> str | None:
62
+ await self.initialized.wait()
63
+ async with self.lock:
64
+ cursor = await self._db.cursor()
65
+ await cursor.execute("SELECT id FROM fileids WHERE path = ?", (path,))
66
+ for (idx,) in await cursor.fetchall():
67
+ return idx
68
+ return None
69
+
70
+ async def get_path(self, idx: str) -> str | None:
71
+ await self.initialized.wait()
72
+ async with self.lock:
73
+ cursor = await self._db.cursor()
74
+ await cursor.execute("SELECT path FROM fileids WHERE id = ?", (idx,))
75
+ for (path,) in await cursor.fetchall():
76
+ return path
77
+ return None
78
+
79
+ async def index(self, path: str) -> str | None:
80
+ await self.initialized.wait()
81
+ async with self.lock:
82
+ apath = Path(path)
83
+ if not await apath.exists():
84
+ return None
85
+
86
+ idx = uuid4().hex
87
+ mtime = (await apath.stat()).st_mtime
88
+ cursor = await self._db.cursor()
89
+ await cursor.execute("INSERT INTO fileids VALUES (?, ?, ?)", (idx, path, mtime))
90
+ await self._db.commit()
91
+ return idx
92
+
93
+ async def watch_files(self):
94
+ async with self.lock:
95
+ cursor = await self._db.cursor()
96
+ await cursor.execute("DROP TABLE IF EXISTS fileids")
97
+ await cursor.execute(
98
+ "CREATE TABLE fileids "
99
+ "(id TEXT PRIMARY KEY, path TEXT NOT NULL UNIQUE, mtime REAL NOT NULL)"
100
+ )
101
+ await self._db.commit()
102
+
103
+ # index files
104
+ async with self.lock:
105
+ cursor = await self._db.cursor()
106
+ async for path in Path().rglob("*"):
107
+ idx = uuid4().hex
108
+ try:
109
+ mtime = (await path.stat()).st_mtime
110
+ except FileNotFoundError:
111
+ pass
112
+ else:
113
+ await cursor.execute(
114
+ "INSERT INTO fileids VALUES (?, ?, ?)", (idx, str(path), mtime)
115
+ )
116
+ await self._db.commit()
117
+ self.initialized.set()
118
+
119
+ async for changes in awatch(".", stop_event=self.stop_event):
120
+ async with self.lock:
121
+ deleted_paths = set()
122
+ added_paths = set()
123
+ cursor = await self._db.cursor()
124
+ for change, changed_path in changes:
125
+ # get relative path
126
+ changed_path = Path(changed_path).relative_to(await Path().absolute())
127
+ changed_path_str = str(changed_path)
128
+
129
+ if change == Change.deleted:
130
+ logger.debug("File was deleted", path=changed_path_str)
131
+ await cursor.execute(
132
+ "SELECT COUNT(*) FROM fileids WHERE path = ?", (changed_path_str,)
133
+ )
134
+ if not (await cursor.fetchone())[0]:
135
+ # path is not indexed, ignore
136
+ logger.debug(
137
+ "File is not indexed, ignoring",
138
+ path=changed_path_str,
139
+ )
140
+ continue
141
+ # path is indexed
142
+ await maybe_rename(
143
+ self._db, changed_path_str, deleted_paths, added_paths, False
144
+ )
145
+ elif change == Change.added:
146
+ logger.debug("File was added", path=changed_path_str)
147
+ await maybe_rename(
148
+ self._db, changed_path_str, added_paths, deleted_paths, True
149
+ )
150
+ elif change == Change.modified:
151
+ logger.debug("File was modified", path=changed_path_str)
152
+ if changed_path_str == self.db_path:
153
+ continue
154
+ await cursor.execute(
155
+ "SELECT COUNT(*) FROM fileids WHERE path = ?", (changed_path_str,)
156
+ )
157
+ if not (await cursor.fetchone())[0]:
158
+ # path is not indexed, ignore
159
+ logger.debug(
160
+ "File is not indexed, ignoring",
161
+ path=changed_path_str,
162
+ )
163
+ continue
164
+ mtime = (await changed_path.stat()).st_mtime
165
+ await cursor.execute(
166
+ "UPDATE fileids SET mtime = ? WHERE path = ?",
167
+ (mtime, changed_path_str),
168
+ )
169
+
170
+ for path in deleted_paths - added_paths:
171
+ logger.debug("Unindexing file", path=path)
172
+ await cursor.execute("DELETE FROM fileids WHERE path = ?", (path,))
173
+ await self._db.commit()
174
+
175
+ for change in changes:
176
+ changed_path = change[1]
177
+ # get relative path
178
+ relative_changed_path = str(Path(changed_path).relative_to(await Path().absolute()))
179
+ relative_change = (change[0], relative_changed_path)
180
+ for watcher in self.watchers.get(relative_changed_path, []):
181
+ watcher.notify(relative_change)
182
+
183
+ def watch(self, path: str) -> Watcher:
184
+ watcher = Watcher(path)
185
+ self.watchers.setdefault(path, []).append(watcher)
186
+ return watcher
187
+
188
+ def unwatch(self, path: str, watcher: Watcher):
189
+ self.watchers[path].remove(watcher)
190
+
191
+
192
+ async def get_mtime(path, db) -> float | None:
193
+ if db:
194
+ cursor = await db.cursor()
195
+ await cursor.execute("SELECT mtime FROM fileids WHERE path = ?", (path,))
196
+ for (mtime,) in await cursor.fetchall():
197
+ return mtime
198
+ # deleted file is not in database, shouldn't happen
199
+ return None
200
+ try:
201
+ mtime = (await Path(path).stat()).st_mtime
202
+ except FileNotFoundError:
203
+ return None
204
+ return mtime
205
+
206
+
207
+ async def maybe_rename(
208
+ db, changed_path: str, changed_paths: set[str], other_paths: set[str], is_added_path: bool
209
+ ) -> None:
210
+ # check if the same file was added/deleted, this would be a rename
211
+ db_or_fs1, db_or_fs2 = db, None
212
+ if is_added_path:
213
+ db_or_fs1, db_or_fs2 = db_or_fs2, db_or_fs1
214
+ mtime1 = await get_mtime(changed_path, db_or_fs1)
215
+ if mtime1 is None:
216
+ return
217
+ for other_path in other_paths:
218
+ mtime2 = await get_mtime(other_path, db_or_fs2)
219
+ if mtime1 == mtime2:
220
+ # same files, according to modification times
221
+ path1, path2 = changed_path, other_path
222
+ if is_added_path:
223
+ path1, path2 = path2, path1
224
+ logger.debug("File was renamed", from_path=path1, to_path=path2)
225
+ cursor = await db.cursor()
226
+ await cursor.execute("UPDATE fileids SET path = ? WHERE path = ?", (path2, path1))
227
+ other_paths.remove(other_path)
228
+ return
229
+ changed_paths.add(changed_path)
@@ -0,0 +1,16 @@
1
+ from anyio import create_task_group
2
+ from fps import Module
3
+
4
+ from jupyverse_api.file_id import FileId
5
+
6
+ from .file_id import _FileId
7
+
8
+
9
+ class FileIdModule(Module):
10
+ async def prepare(self) -> None:
11
+ self.file_id = _FileId()
12
+
13
+ async with create_task_group() as tg:
14
+ tg.start_soon(self.file_id.start)
15
+ self.put(self.file_id, FileId, teardown_callback=self.file_id.stop)
16
+ self.done()
File without changes
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = [ "hatchling",]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "fps_file_id"
7
+ description = "An FPS plugin for the file ID API"
8
+ keywords = ["jupyter", "server", "fastapi", "plugins"]
9
+ requires-python = ">=3.9"
10
+ dependencies = [
11
+ "watchfiles >=1.0.4,<2",
12
+ "sqlite-anyio >=0.2.0,<0.3.0",
13
+ "anyio>=3.6.2,<5",
14
+ "jupyverse-api >=0.9.0,<0.10.0",
15
+ ]
16
+ version = "0.1.0"
17
+ [[project.authors]]
18
+ name = "Jupyter Development Team"
19
+ email = "jupyter@googlegroups.com"
20
+
21
+ [project.readme]
22
+ file = "README.md"
23
+ content-type = "text/markdown"
24
+
25
+ [project.license]
26
+ text = "BSD 3-Clause License"
27
+
28
+ [project.urls]
29
+ Homepage = "https://jupyter.org"
30
+
31
+ [project.entry-points]
32
+ "fps.modules" = {file_id = "fps_file_id.main:FileIdModule"}
33
+ "jupyverse.modules" = {file_id = "fps_file_id.main:FileIdModule"}