core

Project & friends

Table of contents

_fbf3cf89 Module setup
_1d82f33e - Imports
_aa5fa7c7 - GitHub API
_d476a2e5 - Verbosity
_1d1c6a8c - SolveIT domain
_b5d7fcb4 - Output
_2d4d75ba[Result](https://1iis.github.io/pj/core.html#result)
_50f813c0Lines(L)
_efbdf781FileList(Lines)
_0db6a50a Class [Project](https://1iis.github.io/pj/core.html#project)
_b93d1a6a Project.new()
_a17b0188 [Project.sync()](https://1iis.github.io/pj/core.html#project.sync)
_f9c8ed1e [Project.ship()](https://1iis.github.io/pj/core.html#project.ship)
_0ecb7e6e [Project.ls()](https://1iis.github.io/pj/core.html#project.ls)
_e908abfe Helpers
_9d5246cf - Magic methods
_9e719823__getattr__
_fd836d45__dir__
_6c8717a7_repr_markdown_
_480bb662 - Detection
_65aba5eaOwner, repo
_846e8d38Project type
_bf0c525aSolveIT URL
_1c555637 - Version
_671c0239 - GH Pages branch select
_a84a675d - Quarto dark|light
_294a4299 TODO

See also: README, beginner help.

Module setup

Imports

GitHub API

Module-level _default_api: call set_api(GhApi()) once; it’s shared by all Project instances.


source

set_api


def set_api(
    api:ghapi.core.GhApi | None
):

Set the default GhApi instance for all Project instances.


source

get_api


def get_api(
    
)->ghapi.core.GhApi | None:

Get the current default GhApi instance.

Verbosity

Set verbosity once per dialog: 0=quiet, 1=normal (default), 2=verbose.


source

set_verbosity


def set_verbosity(
    v:int
):

SolveIT domain

Cached at import for building file browser URLs.

Output

Result

Methods return a Result instance wrapping the value. - Value is “stdout”, composable. - Display is “stderr” (_repr_markdown_): human status, notebook-friendly. -
To ease composition, Result.__getattr__ forwards to .val.

Result is invisible: Project.new(“foo”).sync() chains naturally.


source

Result


def Result(
    val, ok:bool=True, msg:str=''
):

Initialize self. See help(type(self)) for accurate signature.

Lines(L)

Base class for pretty list output. Inherits from L for composition; _repr_markdown_ joins items as lines.


source

Lines


def Lines(
    items:NoneType=None, rest:VAR_POSITIONAL, use_list:bool=False, match:NoneType=None
):

L subclass with markdown repr that joins lines.

FileList(Lines)

Returned by ls(). Inherits from L so it’s iterable, indexable, and composable (for f in pj.ls(), .filter(), etc.), while _repr_markdown_ provides rich notebook display with SolveIT links for notebooks.


source

FileList


def FileList(
    items, base_path, domain:NoneType=None
):

L subclass with markdown repr that joins lines.

Class Project

Each Project instance represents a repository. Auto-detects owner and repo from the git remote URL. If set, org overrides owner.

[!TIP] Existing projects are loaded by instanciating the class with a path (p = Project('path/to/repo')).
New ones should be created with p = Project.new().

Both return a Project ready to use.


source

Project


def Project(
    path:str | pathlib.Path, api:ghapi.core.GhApi | None=None, org:str | None=None
):

Manage a project’s lifecycle: init, sync, ship.

Project.new()

Creates everything from scratch: GitHub repo via API, local clone, nbdev hooks installed. If nbdev=True, runs scaffolding and applies dark theme. Initial commit, push, and Pages setup.


source

new


def new(
    cls, name:str, # Repository name
    path:str | pathlib.Path | None=None, # Local path (defaults to ./name)
    api:ghapi.core.GhApi | None=None, # GitHub API instance
    org:str | None=None, # Organization (overrides authenticated user)
    desc:str | None=None, # Repository description
    private:bool=True, # Create private repository
    nbdev:bool=False, # Initialize as nbdev project
)->Project:

Create a new GitHub repo, clone it, and return a Project instance.

Project.sync()

nbdev_prepare first for notebooks, then Git add all, commit, pull --rebase, and push.
We use rebase for a clean, linear log (no merge commit) whenever there’s no conflict (e.g. one may edit different files on different hosts, and commit all later).


source

Project.sync


def sync(
    msg:str='sync', # Git commit message
)->Result:

Commit all, pull –rebase, push. Runs nbdev_prepare if nbdev project.

Project.ship()

Full release cycle, auto-detecting strategy by project type. Won’t ship with uncommitted changes unless force=True.

Project Type Version Bump Build & Upload Tag & Release
nbdev nbdev_bump_version nbdev_pypi git tag + ghapi
python manual in pyproject.toml build + twine git tag + ghapi
other n/a n/a git tag + ghapi

source

Project.ship


def ship(
    part:int=2, # Version part to bump: 0=major, 1=minor, 2=patch
    dry_run:bool=False, # Show what would happen without executing
    force:bool=False, # Ship even with uncommitted changes
    pypi:bool=False, # Upload to PyPI
    quiet:bool=False, # Suppress build output
)->Result:

Bump version, build, upload to PyPI, tag, and create GitHub release.

Project.ls()

List repo files with smart defaults. Shortcuts: 'nbs', 'py'; or pass regex. Notebooks get SolveIT links.


source

Project.ls


def ls(
    pattern:str='', # Shortcut ('nbs', 'py') or regex
    exclude:list=None, # Paths to exclude
)->FileList:

List files in repo, optionally filtered. Returns FileList.

Helpers

Magic methods

__getattr__

Forward unknown method calls to self.g (the Git instance), so p.status() is p.g.status() (i.e. git status).

__dir__

Extend tab-completion to include Git methods. Without this, the editor wouldn’t know p.status exists.

_repr_markdown_

Called by Jupyter/SolveIT when displaying an object: return a markdown string showing project info.

Detection

Owner, repo

_parse_remote() extracts (owner, repo) from the git remote URL, handling both HTTPS and SSH formats.

Project type

_detect_type() checks which config files exist: settings.ini means nbdev, pyproject.toml means standard Python, otherwise other.
Used to make property Project.pjtype.

SolveIT URL

Returns the SolveIT file browser URL for this project, or None if not in SolveIT.


source

solveit_url


def solveit_url(
    
):

Version

GH Pages branch select

For nbdev: - _ensure_pages() configures GitHub Pages to deploy from the gh-pages branch: polls for existence (waiting for Actions), then calls the API.
- setup_pages() for manual call.


source

Project.setup_pages


def setup_pages(
    
)->Result:

Manually configure GitHub Pages. Useful for debugging.

Quarto dark|light

Quarto feature: auto-select light or dark theme based on user system settings, and display a manual toggle button.

  1. Modify nbs/_quarto.yml to use a light|dark theme pair.

        theme:
          light: cosmo
          dark: [cosmo, dark.scss]
  2. Create nbs/dark.scss to produce a dark themed nbdev style, with readable font colors for code blocks.


source

Project.dark_theme


def dark_theme(
    
)->Result:

Apply dark mode theme to nbdev docs.

TODO

  • We may extend __getattr__ to forward to the GitHub API (GhApi), after Git (collisions are rare, hopefully), if/when people want more GH API access.
    Alternatively, we’d build more pj methods to handle the most common use-cases (like PRs, reviews, merge, etc.) right from a project’s dialog.

  • better general outputs

    • better messages for Project methods.
    • interactive HTML menu for some of them, with dropdown menus, spoiler folders, info, all the cool stuff.
      • fence that with args, we shouldn’t always force such rich stuff.
  • richer ls() output with links to all the things: source on GH, doc page, SolveIT file editor, Codespaces, VSCode (local), whatever we can do to make it super nice to use.

    • make it optional, with great defaults
    • see user.cfg
  • see if we can use L elsewhere now that we’re loading it! :D

  • think about externalizing the whole display part to its own nb

    • or even its own module, idk. Depends how this generalizes to what, how we want to use it, etc.
  • user.cfg

    • some way to have personal settings for all the things, default values, etc.
    • lets you tell pj what you have, what you do, how, etc.
    • e.g. py files should open in ..., whereas X in Y, etc.
    • see if maybe there are nice online tools to use (e.g. nice renderers for file types like ?ML, cool tools to manipulate stuff)
    • this isn’t pj specific, so we should have instance-wide user.cfg; then overriding additions of all user.cfg files in subdirs (such that each dir inherits from configs between it and root).
    • plug CRAFT and TEMPLATE deploy from central repo/repos (and make that repo, btw!)
  • in-dialog editor! (IDE!!! LOL)

    • either an <iframe> if we can help it (with the SolveIT editor, or whatever else we can do)
    • or a sol-flow:
      1. run fn to make cell with file content (type raw, note, code; depending on file format)
      2. edit cell (optionally dup it)
      3. run fn to take content from CELL ABOVE and write to file
        • optional “backup” arg to copy first to old.file before writing it
        • optional output of result (run first fn to make cell below)
      4. optional:
        • diff before writing
        • diff after writing (if backup)
        • use Markdown ``` to fence and render code properly when not python (alt. to raw)
        • clean up: fence with XML raws, confirm, delete. (user-chosen and never surprising)