Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-jupyter-server-fileid for
openSUSE:Factory checked in at 2023-04-24 22:31:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-jupyter-server-fileid (Old)
and /work/SRC/openSUSE:Factory/.python-jupyter-server-fileid.new.1533
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jupyter-server-fileid"
Mon Apr 24 22:31:01 2023 rev:2 rq:1082311 version:0.9.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-jupyter-server-fileid/python-jupyter-server-fileid.changes
2023-03-24 15:20:45.726994630 +0100
+++
/work/SRC/openSUSE:Factory/.python-jupyter-server-fileid.new.1533/python-jupyter-server-fileid.changes
2023-04-24 22:31:04.779462994 +0200
@@ -1,0 +2,16 @@
+Sun Apr 23 17:38:40 UTC 2023 - Ben Greiner <[email protected]>
+
+- Update to 0.9.0
+ * add Jupyter Releaser workflows #65 (@dlqqq)
+ * Fix behavior for OOB move followed by IB move #63 (@dlqqq)
+- Release 0.8.0
+ * Add db_journal_mode trait to FileIdManager classes #61
+ (@kevin-bates)
+- Release 0.7.0
+ * remove mtime fallback #47 (@dlqqq)
+ * Make ArbitraryFileIdManager filesystem-agnostic and fix Windows
+ CI #46 (@kevin-bates)
+ * Relax jupyter_events dependency requirement #57 (@akchinSTC)
+ * Update check-jsonschema usage to latest style #50 (@sirosen)
+
+-------------------------------------------------------------------
Old:
----
jupyter_server_fileid-0.6.0.tar.gz
New:
----
jupyter_server_fileid-0.9.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-jupyter-server-fileid.spec ++++++
--- /var/tmp/diff_new_pack.0xGB44/_old 2023-04-24 22:31:05.359466441 +0200
+++ /var/tmp/diff_new_pack.0xGB44/_new 2023-04-24 22:31:05.359466441 +0200
@@ -17,14 +17,15 @@
%define plainpython3dist python3dist
-%define distversion 0.6
+%define pyversion 0.9.0
+%define distversion 0.9
%if 0%{?suse_version} > 1500
%bcond_without libalternatives
%else
%bcond_with libalternatives
%endif
Name: python-jupyter-server-fileid
-Version: 0.6.0
+Version: %{pyversion}
Release: 0
Summary: File IDs for documents in a running Jupyter Server
License: BSD-3-Clause
@@ -36,9 +37,9 @@
BuildRequires: fdupes
BuildRequires: jupyter-rpm-macros
BuildRequires: python-rpm-macros
-Requires: (python-jupyter-events >= 0.5.0 with python-jupyter-events < 1)
-Requires: (python-jupyter-server >= 1.15 with python-jupyter-server < 3)
Requires: jupyter-server-fileid = %{version}
+Requires: python-jupyter-events >= 0.5.0
+Requires: (python-jupyter-server >= 1.15 with python-jupyter-server < 3)
Recommends: python-click
Provides: python-jupyter_server_fileid = %{version}-%{release}
BuildArch: noarch
@@ -47,10 +48,10 @@
Requires: alts
%else
Requires(post): update-alternatives
-Requires(postun): update-alternatives
+Requires(postun):update-alternatives
%endif
# SECTION test requirements
-BuildRequires: %{python_module jupyter-events >= 0.5.0 with
%python-jupyter-events < 1}
+BuildRequires: %{python_module jupyter-events >= 0.5.0}
BuildRequires: %{python_module jupyter-server-test >= 1.15 with
%python-jupyter-server-test < 3}
BuildRequires: %{python_module pytest}
BuildRequires: %{python_module traitlets}
@@ -83,7 +84,9 @@
%python_expand %fdupes %{buildroot}%{$python_sitelib}
%check
-%pytest
+# flaky on obs
+donttest="test_get_path_oob_move_nested"
+%pytest -k "not ($donttest)"
%pre
%python_libalternatives_reset_alternative jupyter-fileid
++++++ jupyter_server_fileid-0.6.0.tar.gz -> jupyter_server_fileid-0.9.0.tar.gz
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server_fileid-0.6.0/.github/workflows/prep-release.yml
new/jupyter_server_fileid-0.9.0/.github/workflows/prep-release.yml
--- old/jupyter_server_fileid-0.6.0/.github/workflows/prep-release.yml
1970-01-01 01:00:00.000000000 +0100
+++ new/jupyter_server_fileid-0.9.0/.github/workflows/prep-release.yml
2020-02-02 01:00:00.000000000 +0100
@@ -0,0 +1,41 @@
+name: "Step 1: Prep Release"
+on:
+ workflow_dispatch:
+ inputs:
+ version_spec:
+ description: "New Version Specifier"
+ default: "next"
+ required: false
+ branch:
+ description: "The branch to target"
+ required: false
+ post_version_spec:
+ description: "Post Version Specifier"
+ required: false
+ since:
+ description: "Use PRs with activity since this date or git reference"
+ required: false
+ since_last_stable:
+ description: "Use PRs with activity since the last stable git tag"
+ required: false
+ type: boolean
+jobs:
+ prep_release:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+
+ - name: Prep Release
+ id: prep-release
+ uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2
+ with:
+ token: ${{ secrets.ADMIN_GITHUB_TOKEN }}
+ version_spec: ${{ github.event.inputs.version_spec }}
+ post_version_spec: ${{ github.event.inputs.post_version_spec }}
+ branch: ${{ github.event.inputs.branch }}
+ since: ${{ github.event.inputs.since }}
+ since_last_stable: ${{ github.event.inputs.since_last_stable }}
+
+ - name: "** Next Step **"
+ run: |
+ echo "Optional): Review Draft Release: ${{
steps.prep-release.outputs.release_url }}"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server_fileid-0.6.0/.github/workflows/publish-release.yml
new/jupyter_server_fileid-0.9.0/.github/workflows/publish-release.yml
--- old/jupyter_server_fileid-0.6.0/.github/workflows/publish-release.yml
1970-01-01 01:00:00.000000000 +0100
+++ new/jupyter_server_fileid-0.9.0/.github/workflows/publish-release.yml
2020-02-02 01:00:00.000000000 +0100
@@ -0,0 +1,52 @@
+name: "Step 2: Publish Release"
+on:
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: "The target branch"
+ required: false
+ release_url:
+ description: "The URL of the draft GitHub release"
+ required: false
+ steps_to_skip:
+ description: "Comma separated list of steps to skip"
+ required: false
+
+jobs:
+ publish_release:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+
+ - name: Populate Release
+ id: populate-release
+ uses:
jupyter-server/jupyter_releaser/.github/actions/populate-release@v2
+ with:
+ token: ${{ secrets.ADMIN_GITHUB_TOKEN }}
+ branch: ${{ github.event.inputs.branch }}
+ release_url: ${{ github.event.inputs.release_url }}
+ steps_to_skip: ${{ github.event.inputs.steps_to_skip }}
+
+ - name: Finalize Release
+ id: finalize-release
+ env:
+ PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
+ PYPI_TOKEN_MAP: ${{ secrets.PYPI_TOKEN_MAP }}
+ TWINE_USERNAME: __token__
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ uses:
jupyter-server/jupyter-releaser/.github/actions/finalize-release@v2
+ with:
+ token: ${{ secrets.ADMIN_GITHUB_TOKEN }}
+ release_url: ${{ steps.populate-release.outputs.release_url }}
+
+ - name: "** Next Step **"
+ if: ${{ success() }}
+ run: |
+ echo "Verify the final release"
+ echo ${{ steps.finalize-release.outputs.release_url }}
+
+ - name: "** Failure Message **"
+ if: ${{ failure() }}
+ run: |
+ echo "Failed to Publish the Draft Release Url:"
+ echo ${{ steps.populate-release.outputs.release_url }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server_fileid-0.6.0/.github/workflows/test-python.yml
new/jupyter_server_fileid-0.9.0/.github/workflows/test-python.yml
--- old/jupyter_server_fileid-0.6.0/.github/workflows/test-python.yml
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server_fileid-0.9.0/.github/workflows/test-python.yml
2020-02-02 01:00:00.000000000 +0100
@@ -19,12 +19,12 @@
matrix:
os:
- ubuntu-latest
- # - windows-latest
+ - windows-latest
- macos-latest
python-version: ["3.7", "3.10"]
include:
- # - os: windows-latest
- # python-version: "3.9"
+ - os: windows-latest
+ python-version: "3.9"
# - os: ubuntu-latest
# python-version: "pypy-3.8"
- os: macos-latest
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server_fileid-0.6.0/.pre-commit-config.yaml
new/jupyter_server_fileid-0.9.0/.pre-commit-config.yaml
--- old/jupyter_server_fileid-0.6.0/.pre-commit-config.yaml 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server_fileid-0.9.0/.pre-commit-config.yaml 2020-02-02
01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.3.0
+ rev: v4.4.0
hooks:
- id: end-of-file-fixer
- id: check-case-conflict
@@ -16,43 +16,39 @@
- id: trailing-whitespace
- repo: https://github.com/psf/black
- rev: 22.10.0
+ rev: 23.1.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
- rev: 5.10.1
+ rev: 5.12.0
hooks:
- id: isort
args: ["--profile", "black"]
files: \.py$
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.982
+ rev: v1.0.0
hooks:
- id: mypy
additional_dependencies: [types-requests]
- repo: https://github.com/asottile/pyupgrade
- rev: v3.1.0
+ rev: v3.3.1
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/john-hen/Flake8-pyproject
- rev: 1.1.0.post0
+ rev: 1.2.2
hooks:
- id: Flake8-pyproject
alias: flake8
additional_dependencies:
["flake8-bugbear==22.6.22", "flake8-implicit-str-concat==0.2.0"]
- - repo: https://github.com/sirosen/check-jsonschema
- rev: 0.18.4
+ - repo: https://github.com/python-jsonschema/check-jsonschema
+ rev: 0.21.0
hooks:
- - id: check-jsonschema
- name: "Check GitHub Workflows"
- files: ^\.github/workflows/
- types: [yaml]
- args: ["--schemafile", "https://json.schemastore.org/github-workflow"]
+ - id: check-github-workflows
stages: [manual]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server_fileid-0.6.0/CHANGELOG.md
new/jupyter_server_fileid-0.9.0/CHANGELOG.md
--- old/jupyter_server_fileid-0.6.0/CHANGELOG.md 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server_fileid-0.9.0/CHANGELOG.md 2020-02-02
01:00:00.000000000 +0100
@@ -2,6 +2,64 @@
<!-- <START NEW CHANGELOG ENTRY> -->
+## 0.9.0
+
+([Full
Changelog](https://github.com/jupyter-server/jupyter_server_fileid/compare/v0.8.0...505806162b4df60b4cbb461cfec1266b81df32ce))
+
+### Enhancements made
+
+- add Jupyter Releaser workflows
[#65](https://github.com/jupyter-server/jupyter_server_fileid/pull/65)
([@dlqqq](https://github.com/dlqqq))
+- Fix behavior for OOB move followed by IB move
[#63](https://github.com/jupyter-server/jupyter_server_fileid/pull/63)
([@dlqqq](https://github.com/dlqqq))
+
+### Contributors to this release
+
+([GitHub contributors page for this
release](https://github.com/jupyter-server/jupyter_server_fileid/graphs/contributors?from=2023-02-23&to=2023-04-09&type=c))
+
+[@dleen](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Adleen+updated%3A2023-02-23..2023-04-09&type=Issues)
|
[@dlqqq](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Adlqqq+updated%3A2023-02-23..2023-04-09&type=Issues)
+
+<!-- <END NEW CHANGELOG ENTRY> -->
+
+## 0.8.0
+
+([Full
Changelog](https://github.com/jupyter-server/jupyter_server_fileid/compare/v0.7.0...542ccebfcf7713a81a4f2fbd07e8227573c3a282))
+
+### Enhancements made
+
+- Add db_journal_mode trait to FileIdManager classes
[#61](https://github.com/jupyter-server/jupyter_server_fileid/pull/61)
([@kevin-bates](https://github.com/kevin-bates))
+
+### Contributors to this release
+
+([GitHub contributors page for this
release](https://github.com/jupyter-server/jupyter_server_fileid/graphs/contributors?from=2023-02-16&to=2023-02-23&type=c))
+
+[@codecov](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Acodecov+updated%3A2023-02-16..2023-02-23&type=Issues)
|
[@kevin-bates](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Akevin-bates+updated%3A2023-02-16..2023-02-23&type=Issues)
+
+## 0.7.0
+
+([Full
Changelog](https://github.com/jupyter-server/jupyter_server_fileid/compare/v0.6.0...f42d481b072f8c1a961ad8dc6c2b3ab35a6d0777))
+
+### Enhancements made
+
+- remove mtime fallback
[#47](https://github.com/jupyter-server/jupyter_server_fileid/pull/47)
([@dlqqq](https://github.com/dlqqq))
+- Make ArbitraryFileIdManager filesystem-agnostic and fix Windows CI
[#46](https://github.com/jupyter-server/jupyter_server_fileid/pull/46)
([@kevin-bates](https://github.com/kevin-bates))
+
+### Bugs fixed
+
+- Relax jupyter_events dependency requirement
[#57](https://github.com/jupyter-server/jupyter_server_fileid/pull/57)
([@akchinSTC](https://github.com/akchinSTC))
+- Fix project URL
[#55](https://github.com/jupyter-server/jupyter_server_fileid/pull/55)
([@frenzymadness](https://github.com/frenzymadness))
+- Make ArbitraryFileIdManager filesystem-agnostic and fix Windows CI
[#46](https://github.com/jupyter-server/jupyter_server_fileid/pull/46)
([@kevin-bates](https://github.com/kevin-bates))
+
+### Maintenance and upkeep improvements
+
+### Other merged PRs
+
+- Update check-jsonschema usage to latest style
[#50](https://github.com/jupyter-server/jupyter_server_fileid/pull/50)
([@sirosen](https://github.com/sirosen))
+
+### Contributors to this release
+
+([GitHub contributors page for this
release](https://github.com/jupyter-server/jupyter_server_fileid/graphs/contributors?from=2022-10-28&to=2023-02-16&type=c))
+
+[@akchinSTC](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3AakchinSTC+updated%3A2022-10-28..2023-02-16&type=Issues)
|
[@codecov](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Acodecov+updated%3A2022-10-28..2023-02-16&type=Issues)
|
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Acodecov-commenter+updated%3A2022-10-28..2023-02-16&type=Issues)
|
[@dlqqq](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Adlqqq+updated%3A2022-10-28..2023-02-16&type=Issues)
|
[@frenzymadness](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Afrenzymadness+updated%3A2022-10-28..2023-02-16&type=Issues)
|
[@kevin-bates](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Akevin-bates+updated%3A2022-10-28..2023-02-16&type=Issues)
| [@pre-commit-ci](https://github.com/search?q=
repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Apre-commit-ci+updated%3A2022-10-28..2023-02-16&type=Issues)
|
[@sirosen](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Asirosen+updated%3A2022-10-28..2023-02-16&type=Issues)
|
[@welcome](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Awelcome+updated%3A2022-10-28..2023-02-16&type=Issues)
+
## 0.6.0
([Full
Changelog](https://github.com/jupyter-server/jupyter_server_fileid/compare/v0.5.0...328d893ff2323f20925e036e57eb62f302fa94e2))
@@ -22,8 +80,6 @@
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Acodecov-commenter+updated%3A2022-10-25..2022-10-28&type=Issues)
|
[@dlqqq](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Adlqqq+updated%3A2022-10-25..2022-10-28&type=Issues)
|
[@kevin-bates](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Akevin-bates+updated%3A2022-10-25..2022-10-28&type=Issues)
|
[@welcome](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server_fileid+involves%3Awelcome+updated%3A2022-10-25..2022-10-28&type=Issues)
-<!-- <END NEW CHANGELOG ENTRY> -->
-
## 0.5.0
([Full
Changelog](https://github.com/jupyter-server/jupyter_server_fileid/compare/v0.4.2...d968097b42f7b4d21fd851bd69c23a34098e675a))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server_fileid-0.6.0/PKG-INFO
new/jupyter_server_fileid-0.9.0/PKG-INFO
--- old/jupyter_server_fileid-0.6.0/PKG-INFO 2020-02-02 01:00:00.000000000
+0100
+++ new/jupyter_server_fileid-0.9.0/PKG-INFO 2020-02-02 01:00:00.000000000
+0100
@@ -1,7 +1,7 @@
Metadata-Version: 2.1
Name: jupyter_server_fileid
-Version: 0.6.0
-Project-URL: Home, https://github.com/github_username/jupyter_server_fileid
+Version: 0.9.0
+Project-URL: Home, https://github.com/jupyter-server/jupyter_server_fileid
Author-email: "David L. Qiu" <[email protected]>
License: BSD 3-Clause License
@@ -43,7 +43,7 @@
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Requires-Python: >=3.7
-Requires-Dist: jupyter-events~=0.5.0
+Requires-Dist: jupyter-events>=0.5.0
Requires-Dist: jupyter-server<3,>=1.15
Provides-Extra: cli
Requires-Dist: click; extra == 'cli'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server_fileid-0.6.0/jupyter_server_fileid/__init__.py
new/jupyter_server_fileid-0.9.0/jupyter_server_fileid/__init__.py
--- old/jupyter_server_fileid-0.6.0/jupyter_server_fileid/__init__.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server_fileid-0.9.0/jupyter_server_fileid/__init__.py
2020-02-02 01:00:00.000000000 +0100
@@ -1,7 +1,7 @@
"""A Jupyter Server extension providing an implementation of the File ID
service."""
from .extension import FileIdExtension
-__version__ = "0.6.0"
+__version__ = "0.9.0"
def _jupyter_server_extension_points():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server_fileid-0.6.0/jupyter_server_fileid/extension.py
new/jupyter_server_fileid-0.9.0/jupyter_server_fileid/extension.py
--- old/jupyter_server_fileid-0.6.0/jupyter_server_fileid/extension.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server_fileid-0.9.0/jupyter_server_fileid/extension.py
2020-02-02 01:00:00.000000000 +0100
@@ -6,7 +6,6 @@
class FileIdExtension(ExtensionApp):
-
name = "jupyter_server_fileid"
file_id_manager_class = Type(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server_fileid-0.6.0/jupyter_server_fileid/manager.py
new/jupyter_server_fileid-0.9.0/jupyter_server_fileid/manager.py
--- old/jupyter_server_fileid-0.6.0/jupyter_server_fileid/manager.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server_fileid-0.9.0/jupyter_server_fileid/manager.py
2020-02-02 01:00:00.000000000 +0100
@@ -1,4 +1,5 @@
import os
+import posixpath
import sqlite3
import stat
import time
@@ -7,7 +8,7 @@
from typing import Any, Callable, Dict, Optional
from jupyter_core.paths import jupyter_data_dir
-from traitlets import TraitError, Unicode, validate
+from traitlets import TraitError, Unicode, default, validate
from traitlets.config.configurable import LoggingConfigurable
@@ -74,67 +75,71 @@
)
return proposal["value"]
+ JOURNAL_MODES = ["DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF"]
+ db_journal_mode = Unicode(
+ help=(
+ f"The journal mode setting for the SQLite database. Must be one
of {JOURNAL_MODES}."
+ ),
+ config=True,
+ )
+
+ @validate("db_journal_mode")
+ def _validate_db_journal_mode(self, proposal):
+ candidate_value = proposal["value"]
+ if candidate_value is None or candidate_value.upper() not in
self.JOURNAL_MODES:
+ raise TraitError(
+ f"db_journal_mode ('{candidate_value}') must be one of
{self.JOURNAL_MODES}."
+ )
+ return candidate_value.upper()
+
@staticmethod
def _uuid() -> str:
return str(uuid.uuid4())
+ @abstractmethod
def _normalize_path(self, path: str) -> str:
- """Accepts an API path and returns a filesystem path, i.e. one prefixed
- by root_dir and uses os.path.sep."""
- # use commonprefix instead of commonpath, since root_dir may not be a
- # absolute POSIX path.
- if os.path.commonprefix([self.root_dir, path]) != self.root_dir:
- path = os.path.join(self.root_dir, path)
-
- return path
+ """Accepts an API path and returns a "persistable" path, i.e. one
prefixed
+ by root_dir that can then be persisted in a format relative to the
implementation."""
+ pass
+ @abstractmethod
def _from_normalized_path(self, path: Optional[str]) -> Optional[str]:
- """Accepts a filesystem path and returns an API path, i.e. one relative
+ """Accepts a "persistable" path and returns an API path, i.e. one
relative
to root_dir and uses forward slashes as the path separator. Returns
`None` if the given path is None or is not relative to root_dir."""
- if path is None:
- return None
-
- if os.path.commonprefix([self.root_dir, path]) != self.root_dir:
- return None
-
- relpath = os.path.relpath(path, self.root_dir)
- # always use forward slashes to delimit children
- relpath = relpath.replace(os.path.sep, "/")
-
- return relpath
+ pass
- def _move_recursive(self, old_path: str, new_path: str, sep: str =
os.path.sep) -> None:
+ def _move_recursive(self, old_path: str, new_path: str, path_mgr: Any =
os.path) -> None:
"""Move all children of a given directory at `old_path` to a new
directory at `new_path`, delimited by `sep`."""
- old_path_glob = old_path + sep + "*"
+ old_path_glob = old_path + path_mgr.sep + "*"
records = self.con.execute(
"SELECT id, path FROM Files WHERE path GLOB ?", (old_path_glob,)
).fetchall()
for record in records:
id, old_recpath = record
- new_recpath = os.path.join(new_path, os.path.relpath(old_recpath,
start=old_path))
+ new_recpath = path_mgr.join(new_path,
path_mgr.relpath(old_recpath, start=old_path))
self.con.execute("UPDATE Files SET path = ? WHERE id = ?",
(new_recpath, id))
- def _copy_recursive(self, from_path: str, to_path: str, sep: str =
os.path.sep) -> None:
+ def _copy_recursive(self, from_path: str, to_path: str, path_mgr: Any =
os.path) -> None:
"""Copy all children of a given directory at `from_path` to a new
directory at `to_path`, delimited by `sep`."""
- from_path_glob = from_path + sep + "*"
+ from_path_glob = from_path + path_mgr.sep + "*"
records = self.con.execute(
"SELECT path FROM Files WHERE path GLOB ?", (from_path_glob,)
).fetchall()
for record in records:
(from_recpath,) = record
- to_recpath = os.path.join(to_path, os.path.relpath(from_recpath,
start=from_path))
+ to_recpath = path_mgr.join(to_path, path_mgr.relpath(from_recpath,
start=from_path))
self.con.execute(
"INSERT INTO Files (id, path) VALUES (?, ?)", (self._uuid(),
to_recpath)
)
- def _delete_recursive(self, path: str, sep: str = os.path.sep) -> None:
+ def _delete_recursive(self, path: str, path_mgr: Any = os.path) -> None:
"""Delete all children of a given directory, delimited by `sep`."""
- path_glob = path + sep + "*"
+ path_glob = path + path_mgr.sep + "*"
self.con.execute("DELETE FROM Files WHERE path GLOB ?", (path_glob,))
@abstractmethod
@@ -232,6 +237,19 @@
Server 2.
"""
+ @validate("root_dir")
+ def _validate_root_dir(self, proposal):
+ # Convert root_dir to an api path, since that's essentially what we
persist.
+ if proposal["value"] is None:
+ return ""
+
+ normalized_content_root = self._normalize_separators(proposal["value"])
+ return normalized_content_root
+
+ @default("db_journal_mode")
+ def _default_db_journal_mode(self):
+ return "DELETE"
+
def __init__(self, *args, **kwargs):
# pass args and kwargs to parent Configurable
super().__init__(*args, **kwargs)
@@ -242,9 +260,11 @@
self.log.info(f"ArbitraryFileIdManager : Configured database path:
{self.db_path}")
self.con = sqlite3.connect(self.db_path)
self.log.info("ArbitraryFileIdManager : Successfully connected to
database file.")
- self.log.info("ArbitraryFileIdManager : Creating File ID tables and
indices.")
- # do not allow reads to block writes. required when using multiple
processes
- self.con.execute("PRAGMA journal_mode = WAL")
+ self.log.info(
+ f"ArbitraryFileIdManager : Creating File ID tables and indices
with "
+ f"journal_mode = {self.db_journal_mode}"
+ )
+ self.con.execute(f"PRAGMA journal_mode = {self.db_journal_mode}")
self.con.execute(
"CREATE TABLE IF NOT EXISTS Files("
"id TEXT PRIMARY KEY NOT NULL, "
@@ -254,6 +274,41 @@
self.con.execute("CREATE INDEX IF NOT EXISTS ix_Files_path ON Files
(path)")
self.con.commit()
+ @staticmethod
+ def _normalize_separators(path):
+ """Replaces backslashes with forward slashes, removing adjacent
slashes."""
+
+ parts = path.strip("\\").split("\\")
+ return "/".join(parts)
+
+ def _normalize_path(self, path):
+ """Accepts an API path and returns a "persistable" path, i.e. one
prefixed
+ by root_dir that can then be persisted in a format relative to the
implementation."""
+ # use commonprefix instead of commonpath, since root_dir may not be a
+ # absolute POSIX path.
+
+ # norm_root_dir = self._normalize_separators(self.root_dir)
+ path = self._normalize_separators(path)
+ if posixpath.commonprefix([self.root_dir, path]) != self.root_dir:
+ path = posixpath.join(self.root_dir, path)
+
+ return path
+
+ def _from_normalized_path(self, path: Optional[str]) -> Optional[str]:
+ """Accepts a "persistable" path and returns an API path, i.e. one
relative
+ to root_dir and uses forward slashes as the path separator. Returns
+ `None` if the given path is None or is not relative to root_dir."""
+ if path is None:
+ return None
+
+ # Convert root_dir to an api path, since that's essentially what we
persist.
+ # norm_root_dir = self._normalize_separators(self.root_dir)
+ if posixpath.commonprefix([self.root_dir, path]) != self.root_dir:
+ return None
+
+ relpath = posixpath.relpath(path, self.root_dir)
+ return relpath
+
def _create(self, path: str) -> str:
path = self._normalize_path(path)
id = self._uuid()
@@ -291,7 +346,7 @@
if id:
self.con.execute("UPDATE Files SET path = ? WHERE path = ?",
(new_path, old_path))
- self._move_recursive(old_path, new_path, "/")
+ self._move_recursive(old_path, new_path, posixpath)
else:
id = self._create(new_path)
@@ -303,7 +358,7 @@
to_path = self._normalize_path(to_path)
id = self._create(to_path)
- self._copy_recursive(from_path, to_path, "/")
+ self._copy_recursive(from_path, to_path, posixpath)
self.con.commit()
return id
@@ -312,7 +367,7 @@
path = self._normalize_path(path)
self.con.execute("DELETE FROM Files WHERE path = ?", (path,))
- self._delete_recursive(path, "/")
+ self._delete_recursive(path, posixpath)
self.con.commit()
@@ -362,6 +417,10 @@
)
return proposal["value"]
+ @default("db_journal_mode")
+ def _default_db_journal_mode(self):
+ return "WAL"
+
def __init__(self, *args, **kwargs):
# pass args and kwargs to parent Configurable
super().__init__(*args, **kwargs)
@@ -373,9 +432,11 @@
self.log.info(f"LocalFileIdManager : Configured database path:
{self.db_path}")
self.con = sqlite3.connect(self.db_path)
self.log.info("LocalFileIdManager : Successfully connected to database
file.")
- self.log.info("LocalFileIdManager : Creating File ID tables and
indices.")
- # do not allow reads to block writes. required when using multiple
processes
- self.con.execute("PRAGMA journal_mode = WAL")
+ self.log.info(
+ f"LocalFileIdManager : Creating File ID tables and indices with "
+ f"journal_mode = {self.db_journal_mode}"
+ )
+ self.con.execute(f"PRAGMA journal_mode = {self.db_journal_mode}")
self.con.execute(
"CREATE TABLE IF NOT EXISTS Files("
"id TEXT PRIMARY KEY NOT NULL, "
@@ -395,12 +456,32 @@
self.con.commit()
def _normalize_path(self, path):
- path = super()._normalize_path(path)
+ """Accepts an API path and returns a filesystem path, i.e. one
prefixed by root_dir."""
+ if os.path.commonprefix([self.root_dir, path]) != self.root_dir:
+ path = os.path.join(self.root_dir, path)
+
path = os.path.normcase(path)
path = os.path.normpath(path)
-
return path
+ def _from_normalized_path(self, path: Optional[str]) -> Optional[str]:
+ """Accepts a "persisted" filesystem path and returns an API path, i.e.
+ one relative to root_dir and uses forward slashes as the path
separator.
+ Returns `None` if the given path is None or is not relative to
root_dir.
+ """
+ if path is None:
+ return None
+
+ norm_root_dir = os.path.normcase(self.root_dir)
+ if os.path.commonprefix([norm_root_dir, path]) != norm_root_dir:
+ return None
+
+ relpath = os.path.relpath(path, norm_root_dir)
+ # always use forward slashes to delimit children
+ relpath = relpath.replace(os.path.sep, "/")
+
+ return relpath
+
def _index_all(self):
"""Recursively indexes all directories under the server root."""
self._index_dir_recursively(self.root_dir, self._stat(self.root_dir))
@@ -495,23 +576,6 @@
scan_iter.close()
- def _check_timestamps(self, stat_info):
- """Returns True if the timestamps of a file match those recorded in the
- Files table. Returns False otherwise."""
-
- src = self.con.execute(
- "SELECT crtime, mtime FROM Files WHERE ino = ?", (stat_info.ino,)
- ).fetchone()
-
- # if no record with matching ino, then return None
- if not src:
- return False
-
- src_crtime, src_mtime = src
- src_timestamp = src_crtime if src_crtime is not None else src_mtime
- dst_timestamp = stat_info.crtime if stat_info.crtime is not None else
stat_info.mtime
- return src_timestamp == dst_timestamp
-
def _sync_file(self, path, stat_info):
"""
Syncs the file at `path` with the Files table by detecting whether the
@@ -545,16 +609,16 @@
return None
src = self.con.execute(
- "SELECT id, path FROM Files WHERE ino = ?", (stat_info.ino,)
+ "SELECT id, path, crtime FROM Files WHERE ino = ?",
(stat_info.ino,)
).fetchone()
# if ino is not in database, return None
if src is None:
return None
- id, old_path = src
+ id, old_path, crtime = src
# if timestamps don't match, delete existing record and return None
- if not self._check_timestamps(stat_info):
+ if crtime != stat_info.crtime:
self.con.execute("DELETE FROM Files WHERE id = ?", (id,))
return None
@@ -699,38 +763,29 @@
prior to calling `get_path()`.
"""
# optimistic approach: first check to see if path was not yet moved
- row = self.con.execute("SELECT path, ino FROM Files WHERE id = ?",
(id,)).fetchone()
-
- # if file ID does not exist, return None
- if not row:
- return None
-
- path, ino = row
- stat_info = self._stat(path)
-
- if stat_info and ino == stat_info.ino and
self._check_timestamps(stat_info):
- # if file already exists at path and the ino and timestamps match,
- # then return the correct path immediately (best case)
- return self._from_normalized_path(path)
-
- # otherwise, try again after calling _sync_all() to sync the Files
table to the file tree
- self._sync_all()
- row = self.con.execute("SELECT path, ino FROM Files WHERE id = ?",
(id,)).fetchone()
- # file ID already guaranteed to exist from previous check
- path, ino = row
+ for retry in [True, False]:
+ row = self.con.execute(
+ "SELECT path, ino, crtime FROM Files WHERE id = ?", (id,)
+ ).fetchone()
+
+ # if file ID does not exist, return None
+ if not row:
+ return None
- # if file no longer exists at path, return None
- stat_info = self._stat(path)
- if stat_info is None:
- return None
+ path, ino, crtime = row
+ stat_info = self._stat(path)
- # if inode numbers or timestamps of the file and record don't match,
- # return None
- if ino != stat_info.ino or not self._check_timestamps(stat_info):
- return None
+ if stat_info and ino == stat_info.ino and crtime ==
stat_info.crtime:
+ # if file already exists at path and the ino and timestamps
match,
+ # then return the correct path immediately (best case)
+ return self._from_normalized_path(path)
+
+ # otherwise, try again after calling _sync_all() to sync the Files
table to the file tree
+ if retry:
+ self._sync_all()
- # finally, convert the path to a relative one and return it
- return self._from_normalized_path(path)
+ # If we're here, the retry didn't work.
+ return None
@log(
lambda self, old_path, new_path: f"Updating index following move from
{old_path} to {new_path}.",
@@ -747,23 +802,18 @@
if stat_info is None:
return None
- if stat_info.is_dir:
- self._move_recursive(old_path, new_path)
-
- # attempt to fetch ID associated with old path
- # we avoid using get_id() here since that will always return None as
file no longer exists at old path
- row = self.con.execute("SELECT id FROM Files WHERE path = ?",
(old_path,)).fetchone()
- if row is None:
+ # sync the file and see if it was already indexed
+ #
+ # originally this method did not call _sync_file() for performance
+ # reasons, but this is needed to handle an edge case:
+ # https://github.com/jupyter-server/jupyter_server_fileid/issues/62
+ id = self._sync_file(new_path, stat_info)
+ if id is None:
# if no existing record, create a new one
id = self._create(new_path, stat_info)
- self.con.commit()
- return id
- else:
- # update existing record with new path and stat info
- id = row[0]
- self._update(id, stat_info, new_path)
- self.con.commit()
- return id
+
+ self.con.commit()
+ return id
def _copy_recursive(self, from_path: str, to_path: str, _: str = "") ->
None:
"""Copy all children of a given directory at `from_path` to a new
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server_fileid-0.6.0/pyproject.toml
new/jupyter_server_fileid-0.9.0/pyproject.toml
--- old/jupyter_server_fileid-0.6.0/pyproject.toml 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server_fileid-0.9.0/pyproject.toml 2020-02-02
01:00:00.000000000 +0100
@@ -19,7 +19,10 @@
"Programming Language :: Python :: 3.10",
"Framework :: Jupyter",
]
-dependencies = ["jupyter_server>=1.15, <3", "jupyter_events~=0.5.0"]
+dependencies = [
+ "jupyter_server>=1.15, <3",
+ "jupyter_events>=0.5.0"
+]
[project.optional-dependencies]
test = [
@@ -39,7 +42,7 @@
file="LICENSE"
[project.urls]
-Home = "https://github.com/github_username/jupyter_server_fileid"
+Home = "https://github.com/jupyter-server/jupyter_server_fileid"
[tool.hatch.build.targets.wheel.shared-data]
"jupyter-config" = "etc/jupyter"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server_fileid-0.6.0/tests/test_manager.py
new/jupyter_server_fileid-0.9.0/tests/test_manager.py
--- old/jupyter_server_fileid-0.6.0/tests/test_manager.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server_fileid-0.9.0/tests/test_manager.py 2020-02-02
01:00:00.000000000 +0100
@@ -1,11 +1,17 @@
import ntpath
import os
+import posixpath
+import sys
from unittest.mock import patch
import pytest
from traitlets import TraitError
-from jupyter_server_fileid.manager import ArbitraryFileIdManager,
LocalFileIdManager
+from jupyter_server_fileid.manager import (
+ ArbitraryFileIdManager,
+ BaseFileIdManager,
+ LocalFileIdManager,
+)
@pytest.fixture
@@ -19,7 +25,8 @@
def test_path_child(test_path, fs_helpers):
path = os.path.join(test_path, "child")
fs_helpers.touch(path)
- return path
+ # return api-style path
+ return posixpath.join(test_path, "child")
@pytest.fixture
@@ -34,14 +41,16 @@
def old_path_child(old_path, fs_helpers):
path = os.path.join(old_path, "child")
fs_helpers.touch(path, dir=True)
- return path
+ # return api-style path
+ return posixpath.join(old_path, "child")
@pytest.fixture
def old_path_grandchild(old_path_child, fs_helpers):
path = os.path.join(old_path_child, "grandchild")
fs_helpers.touch(path)
- return path
+ # return api-style path
+ return posixpath.join(old_path_child, "grandchild")
@pytest.fixture
@@ -52,18 +61,43 @@
@pytest.fixture
def new_path_child(new_path):
- return os.path.join(new_path, "child")
+ return posixpath.join(new_path, "child")
@pytest.fixture
def new_path_grandchild(new_path_child):
- return os.path.join(new_path_child, "grandchild")
+ return posixpath.join(new_path_child, "grandchild")
-def get_id_nosync(fid_manager, path):
- if not os.path.isabs(path):
+def _normalize_path_local(fid_manager, path):
+ if os.path.commonprefix([fid_manager.root_dir, path]) !=
fid_manager.root_dir:
path = os.path.join(fid_manager.root_dir, path)
+ path = os.path.normcase(path)
+ path = os.path.normpath(path)
+ return path
+
+
+def _normalize_separators(path):
+ parts = path.strip("\\").split("\\")
+ return "/".join(parts)
+
+
+def _normalize_path_arbitrary(fid_manager, path):
+ if posixpath.commonprefix([fid_manager.root_dir, path]) !=
fid_manager.root_dir:
+ path = posixpath.join(fid_manager.root_dir, path)
+
+ path = _normalize_separators(path)
+ return path
+
+
+def get_id_nosync(fid_manager, path):
+ # We need to first put the path into a form the fileId manager
implementation will for persistence.
+ if isinstance(fid_manager, LocalFileIdManager):
+ path = _normalize_path_local(fid_manager, path)
+ else:
+ path = _normalize_path_arbitrary(fid_manager, path)
+
row = fid_manager.con.execute("SELECT id FROM Files WHERE path = ?",
(path,)).fetchone()
return row and row[0]
@@ -75,7 +109,25 @@
if path is None:
return None
- return os.path.relpath(path, fid_manager.root_dir)
+ return os.path.relpath(path, os.path.normcase(fid_manager.root_dir))
+
+
+def normalize_path(fid_manager: BaseFileIdManager, path: str) -> str:
+ """Normalize path or case based on operating system and FileIdManager
instance.
+
+ When testing instances of LocalFileIdManager, we need to normalize the
+ case relative to the OS when comparing results of get_path() since Windows
+ is case-insensitive.
+
+ When testing instances of ArbitraryFileIdManager, we need to normalize the
+ path, regardless of OS, when comparing results of get_path() since this
fileID
+ manager must be filesystem agnostic.
+ """
+ if isinstance(fid_manager, LocalFileIdManager):
+ path = os.path.normcase(path)
+
+ parts = path.strip("\\").split("\\")
+ return "/".join(parts)
def test_validates_root_dir(fid_db_path):
@@ -86,7 +138,7 @@
afm = ArbitraryFileIdManager(root_dir=root_dir, db_path=fid_db_path)
assert afm.root_dir == root_dir
afm2 = ArbitraryFileIdManager(root_dir=None, db_path=fid_db_path)
- assert afm2.root_dir is None
+ assert afm2.root_dir == ""
def test_validates_db_path(jp_root_dir, any_fid_manager_class):
@@ -134,6 +186,10 @@
assert id == any_fid_manager.index(test_path)
[email protected](
+ sys.version_info < (3, 8) and sys.platform.startswith("win"),
+ reason="symbolic links on Windows Python 3.7 not behaving like 3.8+",
+)
def test_index_symlink(fid_manager, test_path):
link_path = os.path.join(fid_manager.root_dir, "link_path")
os.symlink(os.path.join(fid_manager.root_dir, test_path), link_path)
@@ -153,6 +209,10 @@
assert fid_manager.index(new_path) == id
+crtime_support = os.name == "nt" or hasattr(os.stat_result, "st_birthtime")
+
+
[email protected](not crtime_support, reason="Requires crtime support.")
def test_index_after_deleting_dir_in_same_path(fid_manager, test_path,
fs_helpers):
old_id = fid_manager.index(test_path)
@@ -165,6 +225,7 @@
assert fid_manager.get_path(new_id) == test_path
[email protected](not crtime_support, reason="Requires crtime support.")
def test_index_after_deleting_regfile_in_same_path(fid_manager,
test_path_child, fs_helpers):
old_id = fid_manager.index(test_path_child)
@@ -285,10 +346,6 @@
assert path == arbitrary_fid_manager.get_path(id)
-@patch("os.path.sep", new="\\")
-@patch("os.path.relpath", new=ntpath.relpath)
-@patch("os.path.normpath", new=ntpath.normpath)
-@patch("os.path.join", new=ntpath.join)
def test_get_path_returns_api_path(jp_root_dir, fid_db_path):
"""Tests whether get_path() method always returns an API path, i.e. one
relative to the server root and one delimited by forward slashes (even if
@@ -369,7 +426,7 @@
fs_helpers.move(old_path, new_path)
fs_helpers.move(old_test_path, new_test_path)
- assert fid_manager.get_path(id) == new_test_path
+ assert fid_manager.get_path(id) == normalize_path(fid_manager,
new_test_path)
# move file into directory within an indexed-but-moved directory
@@ -388,7 +445,7 @@
fs_helpers.move(old_path, new_path)
fs_helpers.move(old_test_path, new_test_path)
- assert fid_manager.get_path(id) == new_test_path
+ assert fid_manager.get_path(id) == normalize_path(fid_manager,
new_test_path)
def test_move_unindexed(any_fid_manager, old_path, new_path, fs_helpers):
@@ -413,16 +470,38 @@
assert any_fid_manager.get_path(old_id) == new_path
-# test for disjoint move handling
-# disjoint move: any out-of-band move that does not preserve stat info
-def test_disjoint_move_indexed(any_fid_manager, old_path, new_path,
fs_helpers):
- old_id = any_fid_manager.index(old_path)
+def test_oob_ib_move(fid_manager, test_path, fs_helpers):
+ """
+ Test an out-of-band move followed by an in-band move.
+ """
+ new_path_1 = "path1"
+ new_path_2 = "path2"
+ id = fid_manager.index(test_path)
- fs_helpers.delete(old_path)
- fs_helpers.touch(new_path, dir=True)
- new_id = any_fid_manager.move(old_path, new_path)
+ # out-of-band
+ fs_helpers.move(test_path, new_path_1)
+ # in-band
+ fs_helpers.move(new_path_1, new_path_2)
+ fid_manager.move(new_path_1, new_path_2)
+
+ assert id == fid_manager.get_id(new_path_2)
- assert old_id == new_id
+
+def test_ib_oob_move(fid_manager, test_path, fs_helpers):
+ """
+ Test an in-band move followed by an out-of-band move.
+ """
+ new_path_1 = "path1"
+ new_path_2 = "path2"
+ id = fid_manager.index(test_path)
+
+ # in-band
+ fs_helpers.move(test_path, new_path_1)
+ fid_manager.move(test_path, new_path_1)
+ # out-of-band
+ fs_helpers.move(new_path_1, new_path_2)
+
+ assert id == fid_manager.get_id(new_path_2)
def test_move_recursive(
@@ -508,3 +587,29 @@
any_fid_manager.save(test_path)
assert any_fid_manager.get_id(test_path) == id
+
+
[email protected](
+ "db_journal_mode", ["invalid", None, "DELETE", "TRUNCATE", "PERSIST",
"MEMORY", "WAL", "OFF"]
+)
+def test_db_journal_mode(any_fid_manager_class, fid_db_path, jp_root_dir,
db_journal_mode):
+ if db_journal_mode == "invalid": # test invalid
+ with pytest.raises(TraitError, match=" must be one of "):
+ any_fid_manager_class(
+ db_path=fid_db_path, root_dir=str(jp_root_dir),
db_journal_mode=db_journal_mode
+ )
+ else:
+ if not db_journal_mode: # test correct defaults
+ expected_journal_mode = (
+ "WAL" if any_fid_manager_class.__name__ ==
"LocalFileIdManager" else "DELETE"
+ )
+ fid_manager = any_fid_manager_class(db_path=fid_db_path,
root_dir=str(jp_root_dir))
+ else: # test any valid value
+ expected_journal_mode = db_journal_mode
+ fid_manager = any_fid_manager_class(
+ db_path=fid_db_path, root_dir=str(jp_root_dir),
db_journal_mode=db_journal_mode
+ )
+
+ cursor = fid_manager.con.execute("PRAGMA journal_mode")
+ actual_journal_mode = cursor.fetchone()
+ assert actual_journal_mode[0].upper() == expected_journal_mode