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)
_50f813c0 – Lines(L)
_efbdf781 – FileList(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
_65aba5ea – Owner, repo
_846e8d38 – Project type
_bf0c525a – SolveIT 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.
set_api
def set_api(
api:ghapi.core.GhApi | None
):
Set the default GhApi instance for all Project instances.
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.
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.
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.
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.
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 withp = Project.new().Both return a
Projectready to use.
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.
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).
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 |
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.
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.
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.
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.
Modify
nbs/_quarto.ymlto use alight|darktheme pair.theme: light: cosmo dark: [cosmo, dark.scss]Create
nbs/dark.scssto produce a dark themed nbdev style, with readable font colors for code blocks.
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), afterGit(collisions are rare, hopefully), if/when people want more GH API access.
Alternatively, we’d build morepjmethods to handle the most common use-cases (like PRs, reviews, merge, etc.) right from a project’s dialog.better general outputs
- better messages for
Projectmethods. - 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.
- better messages for
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.
pyfiles 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.cfgfiles 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:
- run fn to make cell with file content (type raw, note, code; depending on file format)
- edit cell (optionally dup it)
- run fn to take content from CELL ABOVE and write to file
- optional “backup” arg to copy first to
old.filebefore writing it - optional output of result (run first fn to make cell below)
- optional “backup” arg to copy first to
- 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)
- either an