Skip to main content

Design Philosophy

carefree-portable 📦️ want's to achieve the following goals:

  • Serializable: Everything should be able to configure and run with a single JSON file.
  • Extensible: The packaging process should be streamlined, and one can easily add new Block to the system.

Here's the pseudo code of the packaging process:

config = json.load(...)
blocks = [Block(), ...]
for block in blocks:
block.build(config)

And that's it! Since packaging is more of an asset-heavy task instead of a logic-heavy task, these codes should be enough to handle most of the cases.

Here are some pre-defined default blocks, maybe they can illustrate the style of the carefree-portable 📦️ system to some extent (source codes could be found here):

  • PrepareBlock: prepare the workspace, ask user whether to overwrite existing one.
  • PreparePythonBlock: the downloaded embeddalbe python package cannot be used directly, and this block is to handle the hijack processes.
  • DownloadBlock / FetchAssetsBlock: download assets.
  • InstallPythonRequirementsBlock: install python requirements.
  • SetPythonLaunchScriptBlock: set the python launch script, so users can launch the portable package with a single command / with one click.

Block​

API Reference

Every Block should inherit from the IExecuteBlock, and only needs to implement the build method:

from cfport import *

@IExecuteBlock.register("my_fancy_block")
class MyFancyBlock(IExecuteBlock):
def build(self, config: IConfig) -> None:
...

And there are two scenarios where you may want to create a new Block, which will be discussed in the following sections.

Default Block​

These Blocks will be deeply integrated into the carefree-portable 📦️ system, and will be used by default. They often require the IConfig to be modified in order to control their behaviors, and should often satisfy the following constraints:

  • Should be general enough to be used in many cases.
  • Should be able to fully configure with JSON settings.
  • Should have as less side effects as possible.

For example, when I'm working on the Stable Diffusion - Playground v2 example, I find that the app.py from HuggingFace Spaces cannot be used directly, because they often use import spaces & @spaces in the codes, which require the HF's special environment to work properly. So I have to implement a specific Block to make things work, and here's what I've done:

  • Add a huggingface_space_app_file field in the IConfig.
  • Implement a HijackHFSpaceAppBlock to hijack the app.py file, if it is specified in the huggingface_space_app_file field.

Here are the pseudo codes of the Block:

cfport/executer/blocks/hijack.py
import_spaces = "import spaces"
decorate_spaces = "@spaces"

def hijack_hf_space_app(line: str) -> str:
if line.startswith(import_spaces):
# remove the import
return line[len(import_spaces) :]
if line.startswith(decorate_spaces):
# comment out the decorator
return f"# {line}"
return line

@IExecuteBlock.register("hijack_hf_space_app")
class HijackHFSpaceAppBlock(IExecuteBlock):
def build(self, config: IConfig) -> None:
hf_space_app = config.huggingface_space_app_file
# do nothing if `huggingface_space_app_file` is not specified
if hf_space_app is None:
return
workspace = Path(config.workspace)
log(f"hijacking huggingface space app file '{hf_space_app}'")
hijack_file(workspace / hf_space_app, hijack_hf_space_app)

As you can see, this Block will only be executed when the huggingface_space_app_file field is specified, and it will only hijack the specified file. In this case, we solve a general problem (create portable version of HF Spaces) with a JSON configurable Block, and it has no side effects on other parts of the system.

Specific Block​

These Blocks are often used to solve specific problems, and they are supposed to be used in a specific context. They should not touch the IConfig, and often only contain some post-processing business logic.

For example, when I'm working on the Stable Diffusion Web UI example, I found that although A1111 has done most of the works, some small hijacks still need to be made to run it 'out-of-the-box'. So I created a specific Block to hijack the *.bat files, and everything can work 'end-to-end' now.

note

Check the source codes if you are interested.