Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": false
},
{
"name": "Debug Unit Test",
"type": "python",
"type": "debugpy",
"request": "launch",
"justMyCode": false,
"program": "${file}",
Expand Down
5,282 changes: 3,659 additions & 1,623 deletions poetry.lock

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ include = []
exclude = ["**/tests/**", "**/*-test", "**/samples.py"]

[tool.poetry.dependencies]
python = "^3.8"
mpremote = "^1.22.0"
python = "^3.9"
mpremote = "1.25.0"
mpflash = "^1.26.0"
loguru = "^0.7.2"
ipympl = "^0.9.3"

Expand Down Expand Up @@ -104,4 +105,7 @@ output = "coverage/coverage.xml"
output = "coverage/coverage.json"

[tool.coverage.lcov]
output = "coverage/coverage.lcov"
output = "coverage/coverage.lcov"

[tool.poetry.scripts]
micropython-magic = "micropython_magic.cli:main"
18 changes: 18 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,24 @@ In order to automatically load the magic on startup, you can add the following t
'micropython_magic'
]
```

Alternatively, the package provides a small console script to create an IPython startup file that will load `micropython_magic` automatically for a given profile. After you have installed the package you can run:

```bash
# install then enable (one-liner)
# pip install -U micropython-magic
micropython-magic enable

# or when using poetry
poetry run micropython-magic enable

# to remove the startup file
micropython-magic disable
```

By default the command writes `00-micropython_magic.py` into `~/.ipython/profile_default/startup/` but you can change the profile or filename using `--profile` and `--filename` or force an overwrite using `--force`.


## Configuration options

Configuration can be done via the `%config` magic
Expand Down
162 changes: 162 additions & 0 deletions src/micropython_magic/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"""CLI helpers for micropython-magic

This module provides a tiny console script to create an IPython startup
file that loads the `micropython_magic` extension automatically and
to remove it again on request.
"""
from __future__ import annotations

import argparse
import os
from typing import Optional, Tuple

STARTUP_TEMPLATE = """# Auto-generated by micropython-magic
try:
from IPython import get_ipython

ip = get_ipython()
if ip is not None:
ip.run_line_magic("load_ext", "micropython_magic")
except Exception:
# best-effort: do not raise on import/startup
pass
"""


def _locate_ipython_dir(ipython_dir: Optional[str]) -> str:
if ipython_dir:
return ipython_dir
try:
from IPython.paths import get_ipython_dir


return get_ipython_dir()
except Exception:
return os.path.join(os.path.expanduser("~"), ".ipython")


def _startup_path(profile: str, filename: str, ipython_dir: Optional[str]) -> str:
ip_dir = _locate_ipython_dir(ipython_dir)
return os.path.join(ip_dir, profile, "startup", filename)


def enable_startup(
profile: str = "profile_default",
filename: str = "00-micropython_magic.py",
force: bool = False,
ipython_dir: Optional[str] = None,
) -> Tuple[str, bool]:
"""Create an IPython startup file to load `micropython_magic`.

Returns (path, created) where `created` is True when the file was created
or overwritten.
"""
file_path = _startup_path(profile, filename, ipython_dir)
startup_dir = os.path.dirname(file_path)
os.makedirs(startup_dir, exist_ok=True)

if os.path.exists(file_path):
try:
with open(file_path, "r", encoding="utf-8") as f:
existing = f.read()
except Exception:
existing = ""

if "micropython_magic" in existing and not force:
# nothing to do
return file_path, False

if not force:
raise FileExistsError(
f"Startup file {file_path} already exists. Use --force to overwrite."
)

# write the startup file
with open(file_path, "w", encoding="utf-8") as f:
f.write(STARTUP_TEMPLATE)

return file_path, True


def disable_startup(
profile: str = "profile_default",
filename: str = "00-micropython_magic.py",
ipython_dir: Optional[str] = None,
) -> Tuple[str, bool]:
"""Remove the IPython startup file if it was created by this tool.

Returns (path, removed). If the file exists but does not contain the
auto-generated marker nothing is removed and removed is False.
"""
file_path = _startup_path(profile, filename, ipython_dir)

if not os.path.exists(file_path):
return file_path, False

try:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
except Exception:
# cannot read the file: do not remove
return file_path, False

marker = "Auto-generated by micropython-magic"
if marker in content or "ip.run_line_magic(\"load_ext\", \"micropython_magic\")" in content:
try:
os.remove(file_path)
return file_path, True
except Exception as e:
raise OSError(f"Failed to remove startup file {file_path}: {e}")

return file_path, False


def main() -> None:
parser = argparse.ArgumentParser(prog="micropython-magic")
subparsers = parser.add_subparsers(dest="command", required=True)

# enable subcommand
p_enable = subparsers.add_parser("enable", help="create an IPython startup file that loads micropython_magic")
p_enable.add_argument("--profile", default="profile_default", help="IPython profile to use")
p_enable.add_argument("--filename", default="00-micropython_magic.py", help="startup filename")
p_enable.add_argument("--force", action="store_true", help="overwrite existing file")
p_enable.add_argument("--ipython-dir", dest="ipython_dir", help="override IPython dir (for testing)")

# disable subcommand
p_disable = subparsers.add_parser("disable", help="remove the startup file created by enable")
p_disable.add_argument("--profile", default="profile_default", help="IPython profile to use")
p_disable.add_argument("--filename", default="00-micropython_magic.py", help="startup filename")
p_disable.add_argument("--ipython-dir", dest="ipython_dir", help="override IPython dir (for testing)")
p_disable.add_argument("-y", "--yes", action="store_true", help="do not prompt for confirmation")

args = parser.parse_args()

if args.command == "enable":
try:
path, created = enable_startup(
profile=args.profile, filename=args.filename, force=args.force, ipython_dir=args.ipython_dir
)
except FileExistsError as e:
print(str(e))
raise SystemExit(1)
except Exception as e: # pragma: no cover - defensive error reporting
print(f"Failed to create startup file: {e}")
raise SystemExit(1)

if created:
print(f"Created startup file: {path}")
else:
print(f"Startup already contains micropython_magic: {path} (no change)")

elif args.command == "disable":
path, removed = disable_startup(profile=args.profile, filename=args.filename, ipython_dir=args.ipython_dir)
if not removed:
print(f"No auto-generated startup file found at: {path}")
raise SystemExit(1)

# if we are here the file has been removed
print(f"Removed startup file: {path}")


if __name__ == "__main__":
main()
Loading