From e9d1c2ab771bc352124f7c4a742e16f8dd09ff29 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Sun, 28 Sep 2025 15:58:03 +0100 Subject: [PATCH 01/15] Add StataNow19 directory on Windows --- nbs/01_config.ipynb | 3 ++- nbstata/config.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nbs/01_config.ipynb b/nbs/01_config.ipynb index b6df4fd..d03ae84 100644 --- a/nbs/01_config.ipynb +++ b/nbs/01_config.ipynb @@ -99,7 +99,8 @@ "#| export\n", "def _win_find_path(_dir=None):\n", " if _dir is None:\n", - " dirs = [r'C:\\Program Files\\Stata19',\n", + " dirs = [r'C:\\Program Files\\StataNow19',\n", + " r'C:\\Program Files\\Stata19',\n", " r'C:\\Program Files\\Stata18',\n", " r'C:\\Program Files\\Stata17']\n", " else:\n", diff --git a/nbstata/config.py b/nbstata/config.py index bae6e26..dc790cf 100644 --- a/nbstata/config.py +++ b/nbstata/config.py @@ -20,7 +20,8 @@ # %% ../nbs/01_config.ipynb 8 def _win_find_path(_dir=None): if _dir is None: - dirs = [r'C:\Program Files\Stata19', + dirs = [r'C:\Program Files\StataNow19', + r'C:\Program Files\Stata19', r'C:\Program Files\Stata18', r'C:\Program Files\Stata17'] else: From 62a079726452c9b63cdcd8bd1e3096d60f9b8709 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Sun, 28 Sep 2025 16:18:38 +0100 Subject: [PATCH 02/15] On macOS additionally check /Applications/StataNow --- nbs/01_config.ipynb | 24 +++++++++++++----------- nbstata/config.py | 24 +++++++++++++----------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/nbs/01_config.ipynb b/nbs/01_config.ipynb index d03ae84..3f86f08 100644 --- a/nbs/01_config.ipynb +++ b/nbs/01_config.ipynb @@ -156,23 +156,25 @@ "def _mac_find_path(_dir=None):\n", " \"\"\"\n", " Attempt to find Stata path on macOS when not on user's PATH.\n", - " Modified from stata_kernel's original to only location \"Applications/Stata\". \n", + " Modified from stata_kernel's original to \"/Applications/StataNow\" and \"/Applications/Stata\".\n", "\n", " Returns:\n", " (str): Path to Stata. Empty string if not found.\n", " \"\"\"\n", " if _dir is None:\n", - " _dir = '/Applications/Stata'\n", - " path = Path(_dir)\n", - " if not os.path.exists(path):\n", - " return ''\n", + " dirs = [r'/Applications/StataNow',\n", + " r'/Applications/Stata']\n", " else:\n", - " try:\n", - " # find the application with the suffix .app\n", - " # example path: /Applications/Stata/StataMP.app\n", - " return str(next(path.glob(\"Stata*.app\")))\n", - " except StopIteration:\n", - " return ''" + " dirs = [_dir] \n", + " for this_dir in dirs:\n", + " path = Path(this_dir)\n", + " if os.path.exists(path):\n", + " try:\n", + " # find the application with the suffix .app\n", + " # example path: /Applications/Stata/StataMP.app\n", + " return str(next(path.glob(\"Stata*.app\")))\n", + " except StopIteration:\n", + " return ''" ] }, { diff --git a/nbstata/config.py b/nbstata/config.py index dc790cf..0abdf63 100644 --- a/nbstata/config.py +++ b/nbstata/config.py @@ -46,23 +46,25 @@ def _win_find_path(_dir=None): def _mac_find_path(_dir=None): """ Attempt to find Stata path on macOS when not on user's PATH. - Modified from stata_kernel's original to only location "Applications/Stata". + Modified from stata_kernel's original to "/Applications/StataNow" and "/Applications/Stata". Returns: (str): Path to Stata. Empty string if not found. """ if _dir is None: - _dir = '/Applications/Stata' - path = Path(_dir) - if not os.path.exists(path): - return '' + dirs = [r'/Applications/StataNow', + r'/Applications/Stata'] else: - try: - # find the application with the suffix .app - # example path: /Applications/Stata/StataMP.app - return str(next(path.glob("Stata*.app"))) - except StopIteration: - return '' + dirs = [_dir] + for this_dir in dirs: + path = Path(this_dir) + if os.path.exists(path): + try: + # find the application with the suffix .app + # example path: /Applications/Stata/StataMP.app + return str(next(path.glob("Stata*.app"))) + except StopIteration: + return '' # %% ../nbs/01_config.ipynb 12 def _other_find_path(): From b31af9d89e83a1864369013238da53ef303bb5dd Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Tue, 30 Sep 2025 08:53:33 +0100 Subject: [PATCH 03/15] Add possible StataNow18 directory on Windows --- nbs/01_config.ipynb | 1 + nbstata/config.py | 1 + 2 files changed, 2 insertions(+) diff --git a/nbs/01_config.ipynb b/nbs/01_config.ipynb index 3f86f08..5461df2 100644 --- a/nbs/01_config.ipynb +++ b/nbs/01_config.ipynb @@ -101,6 +101,7 @@ " if _dir is None:\n", " dirs = [r'C:\\Program Files\\StataNow19',\n", " r'C:\\Program Files\\Stata19',\n", + " r'C:\\Program Files\\StataNow18',\n", " r'C:\\Program Files\\Stata18',\n", " r'C:\\Program Files\\Stata17']\n", " else:\n", diff --git a/nbstata/config.py b/nbstata/config.py index 0abdf63..3387336 100644 --- a/nbstata/config.py +++ b/nbstata/config.py @@ -22,6 +22,7 @@ def _win_find_path(_dir=None): if _dir is None: dirs = [r'C:\Program Files\StataNow19', r'C:\Program Files\Stata19', + r'C:\Program Files\StataNow18', r'C:\Program Files\Stata18', r'C:\Program Files\Stata17'] else: From c39b4f9e553a3ecb28ff66a21e6f188a17aaf00c Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Tue, 30 Sep 2025 09:00:06 +0100 Subject: [PATCH 04/15] Strip single and double quotes from stata_dir --- nbs/01_config.ipynb | 5 +++-- nbstata/config.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/nbs/01_config.ipynb b/nbs/01_config.ipynb index 5461df2..e2fbe19 100644 --- a/nbs/01_config.ipynb +++ b/nbs/01_config.ipynb @@ -426,12 +426,13 @@ "source": [ "#| export\n", "def set_pystata_path(stata_dir=None):\n", + " stata_dir = stata_dir.strip('\"\\'')\n", " if stata_dir is None:\n", " stata_dir, _ = find_dir_edition()\n", " if not os.path.isdir(stata_dir):\n", - " raise OSError(f'Specified stata_dir, \"{stata_dir}\", is not a valid directory path')\n", + " raise OSError(f'Specified stata_dir, {stata_dir}, is not a valid directory path')\n", " if not os.path.isdir(os.path.join(stata_dir, 'utilities')):\n", - " raise OSError(f'Specified stata_dir, \"{stata_dir}\", is not Stata\\'s installation path')\n", + " raise OSError(f'Specified stata_dir, {stata_dir}, is not Stata\\'s installation path')\n", " sys.path.append(os.path.join(stata_dir, 'utilities'))" ] }, diff --git a/nbstata/config.py b/nbstata/config.py index 3387336..0c31c25 100644 --- a/nbstata/config.py +++ b/nbstata/config.py @@ -114,12 +114,13 @@ def find_edition(stata_dir): # %% ../nbs/01_config.ipynb 25 def set_pystata_path(stata_dir=None): + stata_dir = stata_dir.strip('"\'') if stata_dir is None: stata_dir, _ = find_dir_edition() if not os.path.isdir(stata_dir): - raise OSError(f'Specified stata_dir, "{stata_dir}", is not a valid directory path') + raise OSError(f'Specified stata_dir, {stata_dir}, is not a valid directory path') if not os.path.isdir(os.path.join(stata_dir, 'utilities')): - raise OSError(f'Specified stata_dir, "{stata_dir}", is not Stata\'s installation path') + raise OSError(f'Specified stata_dir, {stata_dir}, is not Stata\'s installation path') sys.path.append(os.path.join(stata_dir, 'utilities')) # %% ../nbs/01_config.ipynb 29 From dabd0881d33077864b02a4d76034efef9fe5ae6b Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Sat, 18 Oct 2025 06:45:24 -0300 Subject: [PATCH 05/15] Add missing space between #| and eval: --- nbs/15_install.ipynb | 2 +- nbstata/install.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nbs/15_install.ipynb b/nbs/15_install.ipynb index ee80ba7..4749242 100644 --- a/nbs/15_install.ipynb +++ b/nbs/15_install.ipynb @@ -322,7 +322,7 @@ "outputs": [], "source": [ "#| export\n", - "#|eval: false\n", + "#| eval: false\n", "if __name__ == \"__main__\" and not IN_NOTEBOOK:\n", " main()" ] diff --git a/nbstata/install.py b/nbstata/install.py index 7e99a58..f148f41 100644 --- a/nbstata/install.py +++ b/nbstata/install.py @@ -138,6 +138,6 @@ def main(argv=None): create_conf_if_needed(conf_path, conf_file_requested=args.conf_file) # %% ../nbs/15_install.ipynb 15 -#|eval: false +#| eval: false if __name__ == "__main__" and not IN_NOTEBOOK: main() From c7027008adcc30e45736c83a7ec15de7b49008b8 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Wed, 20 Aug 2025 11:09:44 +0100 Subject: [PATCH 06/15] Simplify setup.py --- setup.py | 59 +++----------------------------------------------------- 1 file changed, 3 insertions(+), 56 deletions(-) diff --git a/setup.py b/setup.py index 8492a80..8582479 100644 --- a/setup.py +++ b/setup.py @@ -1,56 +1,3 @@ -from pkg_resources import parse_version -from configparser import ConfigParser -import setuptools -assert parse_version(setuptools.__version__)>=parse_version('36.2') - -# note: all settings are in settings.ini; edit there, not here -config = ConfigParser(delimiters=['=']) -config.read('settings.ini') -cfg = config['DEFAULT'] - -cfg_keys = 'version description keywords author author_email'.split() -expected = cfg_keys + "lib_name user branch license status min_python audience language".split() -for o in expected: assert o in cfg, "missing expected setting: {}".format(o) -setup_cfg = {o:cfg[o] for o in cfg_keys} - -licenses = { - 'apache2': ('Apache Software License 2.0','OSI Approved :: Apache Software License'), - 'mit': ('MIT License', 'OSI Approved :: MIT License'), - 'gpl2': ('GNU General Public License v2', 'OSI Approved :: GNU General Public License v2 (GPLv2)'), - 'gpl3': ('GNU General Public License v3', 'OSI Approved :: GNU General Public License v3 (GPLv3)'), - 'bsd3': ('BSD License', 'OSI Approved :: BSD License'), -} -statuses = [ '1 - Planning', '2 - Pre-Alpha', '3 - Alpha', - '4 - Beta', '5 - Production/Stable', '6 - Mature', '7 - Inactive' ] -py_versions = '3.6 3.7 3.8 3.9 3.10'.split() - -requirements = cfg.get('requirements','').split() -if cfg.get('pip_requirements'): requirements += cfg.get('pip_requirements','').split() -min_python = cfg['min_python'] -lic = licenses.get(cfg['license'].lower(), (cfg['license'], None)) -dev_requirements = (cfg.get('dev_requirements') or '').split() - -setuptools.setup( - name = cfg['lib_name'], - license = lic[0], - classifiers = [ - 'Development Status :: ' + statuses[int(cfg['status'])], - 'Intended Audience :: ' + cfg['audience'].title(), - 'Natural Language :: ' + cfg['language'].title(), - ] + ['Programming Language :: Python :: '+o for o in py_versions[py_versions.index(min_python):]] + (['License :: ' + lic[1] ] if lic[1] else []), - url = cfg['git_url'], - packages = setuptools.find_packages(), - include_package_data = True, - install_requires = requirements, - extras_require={ 'dev': dev_requirements }, - setup_requires = ['wheel'], # https://stackoverflow.com/questions/34819221/why-is-python-setup-py-saying-invalid-command-bdist-wheel-on-travis-ci#comment99762029_54833684 - dependency_links = cfg.get('dep_links','').split(), - python_requires = '>=' + cfg['min_python'], - long_description = open('README.md').read(), - long_description_content_type = 'text/markdown', - zip_safe = False, - entry_points = { - 'console_scripts': cfg.get('console_scripts','').split(), - 'nbdev': [f'{cfg.get("lib_path")}={cfg.get("lib_path")}._modidx:d'] - }, - **setup_cfg) +# setup.py (temporary shim to support old workflows) +from setuptools import setup +setup() From 0a8e5093b7f3c1b9b92d97cb3903404df2af50ac Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Sat, 18 Oct 2025 15:34:08 -0300 Subject: [PATCH 07/15] Update settings.ini --- settings.ini | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/settings.ini b/settings.ini index 0ab693b..b5169ce 100644 --- a/settings.ini +++ b/settings.ini @@ -1,8 +1,8 @@ [DEFAULT] repo = nbstata lib_name = nbstata -version = 0.8.3 -min_python = 3.9 +version = 0.8.3.dev0 +min_python = 3.10 license = gpl3 doc_path = _docs lib_path = nbstata @@ -34,5 +34,4 @@ jupyter_hooks = True clean_ids = True clear_all = False cell_number = True -skip_procs = - +skip_procs = From 3de640ad522d55b424d7b3ed374837c707d7f85c Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Sat, 18 Oct 2025 15:37:11 -0300 Subject: [PATCH 08/15] Update version in __init__.py --- nbstata/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nbstata/__init__.py b/nbstata/__init__.py index ac2487a..3f9f54c 100644 --- a/nbstata/__init__.py +++ b/nbstata/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.8.3" +__version__ = "0.8.3.dev0" from .config import launch_stata, set_graph_format from . import misc_utils, config, stata, stata_more, code_utils, noecho, pandas From 9ab15fc95e314db83cf19171d09cb2edb18057af Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Sun, 19 Oct 2025 11:16:21 -0300 Subject: [PATCH 09/15] Create nbstata/_resources.py --- nbstata/_resources.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 nbstata/_resources.py diff --git a/nbstata/_resources.py b/nbstata/_resources.py new file mode 100644 index 0000000..8cb7baf --- /dev/null +++ b/nbstata/_resources.py @@ -0,0 +1,34 @@ +""" +Zip-safe, modern resource helpers for Python 3.10+ using importlib.resources. +Use these in place of pkg_resources.resource_filename / resource_string. +""" +from __future__ import annotations +from contextlib import contextmanager +from importlib.resources import files, as_file +from pathlib import Path +from typing import Iterator + +PACKAGE = "nbstata" + +def read_text(relpath: str, package: str = PACKAGE) -> str: + """Read a packaged text resource.""" + return (files(package) / relpath).read_text() + +def read_bytes(relpath: str, package: str = PACKAGE) -> bytes: + """Read a packaged binary resource.""" + return (files(package) / relpath).read_bytes() + +@contextmanager +def resource_path(relpath: str, package: str = PACKAGE) -> Iterator[Path]: + """ + Context manager yielding a temporary real filesystem Path for a packaged + resource. This mirrors pkg_resources.resource_filename semantics but is + zip-safe and deprecation-proof. + """ + with as_file(files(package) / relpath) as p: + yield p + +def resource_strpath(relpath: str, package: str = PACKAGE) -> str: + """Return a string path for cases that strictly want str over Path.""" + with resource_path(relpath, package) as p: + return str(p) From c0522fe998c83acd935925befb7e954d58efdd14 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Sun, 19 Oct 2025 11:17:32 -0300 Subject: [PATCH 10/15] Create nbstata/_version_helper.py --- nbstata/_version_helper.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 nbstata/_version_helper.py diff --git a/nbstata/_version_helper.py b/nbstata/_version_helper.py new file mode 100644 index 0000000..1666e3e --- /dev/null +++ b/nbstata/_version_helper.py @@ -0,0 +1,16 @@ +""" +Version helpers that avoid distutils and runtime pkg_resources. +""" +from __future__ import annotations +from packaging.version import Version, InvalidVersion + +def version_at_least(v: str, minimum: str) -> bool: + """ + Return True if version string v >= minimum using robust PEP 440 parsing. + Replaces pkg_resources.parse_version / distutils.LooseVersion. + """ + try: + return Version(v) >= Version(minimum) + except InvalidVersion: + # Conservative fallback: treat unknown as not meeting the minimum + return False From 5badf4889b3d01eaf1ba8833b9c5c09f91899c25 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Sun, 19 Oct 2025 11:19:11 -0300 Subject: [PATCH 11/15] Update MANIFEST.in --- MANIFEST.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index 0ed4944..b812a2c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,3 +4,7 @@ include CONTRIBUTING.md include README.md recursive-include nbstata * recursive-exclude * __pycache__ +global-include *.md +global-include *.json +global-include *.txt +# Keep pkg data wheel-friendly alongside include-package-data in pyproject.toml From 5d042e7b30031bcadec04c254471137a493e2703 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Wed, 20 Aug 2025 09:45:15 +0100 Subject: [PATCH 12/15] Remove use of pkg_resources --- nbs/09_magics.ipynb | 4 ++-- nbstata/magics.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nbs/09_magics.ipynb b/nbs/09_magics.ipynb index 2d2cab9..135b344 100644 --- a/nbs/09_magics.ipynb +++ b/nbs/09_magics.ipynb @@ -46,7 +46,7 @@ "from fastcore.basics import patch_to\n", "import re\n", "import urllib\n", - "from pkg_resources import resource_filename\n", + "from nbstata._resources import resource_path\n", "from bs4 import BeautifulSoup as bs\n", "import configparser" ] @@ -161,7 +161,7 @@ " \n", " abbrev_dict = _construct_abbrev_dict()\n", " \n", - " csshelp_default = resource_filename(\n", + " csshelp_default = resource_path(\n", " 'nbstata', 'css/_StataKernelHelpDefault.css'\n", " )\n", "\n", diff --git a/nbstata/magics.py b/nbstata/magics.py index eab985e..b09e80f 100644 --- a/nbstata/magics.py +++ b/nbstata/magics.py @@ -14,7 +14,7 @@ from fastcore.basics import patch_to import re import urllib -from pkg_resources import resource_filename +from ._resources import resource_path from bs4 import BeautifulSoup as bs import configparser @@ -71,7 +71,7 @@ class StataMagics(): abbrev_dict = _construct_abbrev_dict() - csshelp_default = resource_filename( + csshelp_default = resource_path( 'nbstata', 'css/_StataKernelHelpDefault.css' ) From c9ef179431cff262b649b20fae76bf10da08947b Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Mon, 20 Oct 2025 06:59:16 -0300 Subject: [PATCH 13/15] Create pyproject.toml --- pyproject.toml | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a1e0c70 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,55 @@ +[build-system] +requires = ["setuptools>=69", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name="nbstata" +version = "0.8.3.dev0" +description = "A Jupyter kernel for Stata built on pystata" +readme = "README.md" +requires-python=">=3.10" +license = { text = "GPL-3.0-only" } +authors = [{ name = "Tim Huegerich" }] +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)" +] +# Keep runtime deps minimal; pystata is provided by Stata itself. +dependencies = [ + "bs4>=0.0.2", + "fastcore>=1.8.13", + "ipykernel>=6", + "jupyter-client>=7", + "nbclient>=0.10.2", + "nbformat>=5.10.4", + "numpy>=2.2.6", + "packaging>=23", # for Version parsing (instead of distutils/pkg_resources) + "pyyaml>=6.0.2", +] + +[project.scripts] +# Mirrors the documented CLI usage: python -m nbstata.install +nbstata-install = "nbstata.install:main" + +[tool.setuptools] +include-package-data = true +packages = { find = { where = ["."], include = ["nbstata*"] } } + +# If you ship any data files within the package tree, ensure they're included. +# You already have MANIFEST.in; include-package-data keeps wheels aligned. + +[tool.pytest.ini_options] +minversion = "7.0" +addopts = "-q" +testpaths = ["nbs"] + +[dependency-groups] +dev = [ + "nbdev>=2.4.6", +] From d0b3877a709c7da57cf33dc01f9435acdae5b7c2 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Thu, 23 Oct 2025 01:51:47 +0100 Subject: [PATCH 14/15] Ignore UV lock file --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index fbd479f..e52eb46 100644 --- a/.gitignore +++ b/.gitignore @@ -150,3 +150,5 @@ checklink/cookies.txt # Quarto .quarto + +uv.lock From e2ac0d1f062b3fc90128adedae2d56e1754a8b15 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Thu, 23 Oct 2025 01:47:49 +0100 Subject: [PATCH 15/15] Run tests on all supported Python versions --- .github/workflows/test.yaml | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index eede140..fdf5f3f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,9 +1,31 @@ -name: CI -on: [workflow_dispatch, pull_request, push] -permissions: - contents: read +name: Run tests on supported versions of Python + +on: + push: +# branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: jobs: - test: + tests: runs-on: ubuntu-latest - steps: [uses: fastai/workflows/nbdev-ci@master] + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + steps: + - uses: actions/checkout@v5 + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies and run tests + run: | + uv sync --all-extras + + - name: Run tests in uv virtual environment using nbdev + run: | + uv run nbdev_test