Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implementing script metadata and DAG sorting mechanism #13944

Merged
merged 8 commits into from
Nov 20, 2023

Conversation

wfjsw
Copy link
Contributor

@wfjsw wfjsw commented Nov 11, 2023

Description

NOTE: This PR directly conflicts with #13943

BREAKING: graphlib is a Python 3.9 thing. It may breaks on Python 3.8.

  • Implemented an extension metadata system with configparser
  • In the extension metadata file, extensions are allowed to
    • Specify a canonical name, which will be used to uniquely define an extension and to prevent an extension from being load twice. This is optional. The default value is the directory name.
    • Specify that the extension or a specific file Requires another extension or a file of the same type. The extension/script will be disabled if the requirement does not meet.
    • Specify that a specific file wants to load After another extension or a single file of the same type.
    • Specify that a specific file wants to load Before another extension or a single file of the same type.
  • When a circular dependency is detected, it will collapse into filesystem order.

When specifying individual script's Requires, Before and After, use either:

  • Extension's canonical name, or
  • <extension_canonical_name>/<script_file_name> (note the directory name is not included)

I have not tested this yet. Will do it later.

This system is built upon the implication that script files has unique name. Please let me know if this is not the case.

Example sd_webui_metadata.ini metadata.ini:

[Extension]
Name=adetailer
Requires=sd-webui-controlnet sd-webui-animatediff

[scripts]
Requires=sd-webui-controlnet sd-webui-animatediff
Before=sd-webui-controlnet

[scripts/!adetailer.py]
Requires=sd-webui-controlnet sd-webui-animatediff
Before=sd-webui-controlnet
After=sd-webui-animatediff/animatediff.py

[javascript]
After=ui.js

[javascript/canvas.js]
After=localization.js

Checklist:

@wfjsw wfjsw requested a review from AUTOMATIC1111 as a code owner November 11, 2023 09:51
@w-e-w
Copy link
Collaborator

w-e-w commented Nov 11, 2023

This system is built upon the implication that script files has unique name. Please let me know if this is not the case.

sadly that is Faaaaaaaar from the case

examples of extensions using generic names

https://github.com/aria1th/webui-model-uploader/tree/main/scripts
https://github.com/SignalFlagZ/sd-webui-civbrowser/tree/mod/scripts
https://github.com/aigc-apps/sd-webui-EasyPhoto/tree/main/scripts
https://github.com/Scholar01/sd-webui-mov2mov/tree/master/scripts
https://github.com/AlpacaInTheNight/PromptsBrowser/tree/main/scripts
https://github.com/Chaest/sd-webui-sc-loader/tree/master/scripts
https://github.com/wywywywy/sd-webui-depth-lib/tree/main/scripts
https://github.com/bit9labs/sd-masonry/tree/master/scripts
https://github.com/SpenserCai/sd-webui-discord-ex/tree/main/scripts
https://github.com/sinano1107/sd-webui-auto-tweet/tree/main/scripts
https://github.com/NON906/sd-webui-chatgpt/tree/main/scripts
https://github.com/AUTOMATIC1111/stable-diffusion-webui-wildcards/tree/master/scripts
https://github.com/pk5ls20/sd-webui-controlnet-fastload/tree/main/scripts
https://github.com/SpenserCai/sd-webui-deoldify/tree/main/scripts
https://github.com/numz/sd-wav2lip-uhq/tree/main/scripts
https://github.com/thisjam/sd-webui-oldsix-prompt/tree/main/scripts
https://github.com/wcde/sd-webui-refiner/tree/main/scripts
https://github.com/glucauze/sd-webui-faceswaplab/tree/main/scripts
https://github.com/viyiviyi/prompts-filter/tree/master/scripts
https://github.com/LucasMali/sd-history-slider/tree/main/scripts
https://github.com/feynlee/latent-upscale/tree/main/scripts
https://github.com/OpenTalker/SadTalker/tree/main/scripts
https://github.com/AbdullahAlfaraj/Auto-Photoshop-StableDiffusion-Plugin/tree/master/scripts
https://github.com/antfu/sd-webui-qrcode-toolkit/tree/main/scripts
https://github.com/lobehub/sd-webui-lobe-theme/tree/main/scripts
https://github.com/EnsignMK/danbooru-prompt/tree/main/scripts
https://github.com/h43lb1t0/SD-WebUI-BatchCheckpointPrompt/tree/main/scripts
https://github.com/jtydhr88/sd-webui-txt-img-to-3d-model/tree/master/scripts
https://github.com/Uminosachi/sd-webui-inpaint-anything/tree/main/scripts
https://github.com/limithit/Mask2Background/tree/main/scripts

this is not all of them

yes in lots of cases these are files that are not meant to be imported by others extensions
but there's also lots of instances of people using main.py as the name of the script file

@wfjsw
Copy link
Contributor Author

wfjsw commented Nov 11, 2023

I use this condition because afaik scripts will step on each other anyway if names collide (or this has been fixed already? I dunno). If this is not fixed I'd rather make the error explicit. Or I can add some scope here.

@w-e-w
Copy link
Collaborator

w-e-w commented Nov 11, 2023

Tl;DR is that duplicate name will prevent a later scripts from importing the correct script


currently if there is a duplicate script file

every one of those files will be executed (so far no problem)

the problem comes when a script tries to "import" a file that has duplicate names from another file
when importing it will always import the first one thats loaded


example: as I have 2 ext
ext_1-script_1.py and ext_1-script_2.py
script_2 imports script_1

ext_2-script_1.py and ext_2-script_2.py
script_2 imports script_1

execute:
ext_1-script_1.py ok
ext_1-script_2.py ok import script_1.py found ext_1-script_1.py ok

ext_2-script_1.py ok
ext_2-script_2.py ok import script_1.py found ext_1-script_1.py NOT_OK


but if later script does not import the script with duplicate name then all is fine

ext_2-script_1.py and script_2.py
script_2 imports script_1

ext_2-script_1.py and ext_2-script_2.py

execute:

ext_1-script_1.py ok
ext_1-script_2.py ok import script_1.py found ext_1-script_1.py ok

ext_2-script_1.py ok
ext_2-script_2.py ok

@w-e-w
Copy link
Collaborator

w-e-w commented Nov 11, 2023

just realized that we already have a good candidate to be used as the unique identifier name for an extension
currently in the index every extension is listed under a unique file-name.json
https://github.com/AUTOMATIC1111/stable-diffusion-webui-extensions/tree/extensions/extensions

@wfjsw
Copy link
Contributor Author

wfjsw commented Nov 11, 2023

But it is not possible to match a canonical name between an already installed extension and this file.

@w-e-w
Copy link
Collaborator

w-e-w commented Nov 11, 2023

But it is not possible to match a canonical name between an already installed extension and this file.

the extension install directly cannot be used as a means of identification

even though by default an extension is installed using the repo name
the name can be modified by the user
and this is not including situations that the extension repo name is just bad see Catppuccin Theme

I've mentioned in past I wish to make the install directory unique possibly by adding a Owner sufix, but the idea hasn't coming to fruition because some extension uses bad method of importing libraries form other extension

currently I do not believe there is a definitive method of identifying an extension
we need an extension to declare its identity

and it also doesn't really matter that pre-existing extension cannot be identified
if extension isn't updated with metadata the benefit of this system is already half gone
as it will be basically guesswork to generate the proper load order

eventually extension will be updated with the metadata, so it shouldn't be an issue after a while

if it is really necessary, it is possible for us to implement a system that will automatically assign a identifier by matching the git url

after the load order system is fully setup extension will start updateing and providing the metadata


I still prefer json over ini
json allows the storage of more complex data types where as ini is only strings and need manual conversion

you mentioned fearing that people might json file, the user should not edit this file this file should be only edited by the developer
if any configuration needs to be done it should be done via tha UI an information is stored in settings
if a corruption does happen the error will be immediately apparent, as a message can be shown in the UI and can be fix by the developer before publishing

in my opinion json is more versatile as it supports different data types
it is also easy to read what indentation is used
I do have plans using metadat for other use not just for load order

@wfjsw wfjsw marked this pull request as draft November 11, 2023 17:13
@wfjsw wfjsw marked this pull request as ready for review November 11, 2023 17:58
@wfjsw
Copy link
Contributor Author

wfjsw commented Nov 11, 2023

Some observation:

  1. Topological sort would put any file that has a dependency relation to the bottom of the list.
[Extension]
Name=adetailer

[scripts]
After=sd-webui-controlnet/controlnet.py
Before=sd-webui-controlnet/external_code.py

image

@AUTOMATIC1111
Copy link
Owner

AUTOMATIC1111 commented Nov 19, 2023

Do we have any users with 3.8 (probably some google colab-like services)?

The syntax for requirements as it's written seems to make it impossible to use spaces in names.

I would prefer a more simple name for the metadata file, like metadata.ini. Is ini even a good choice for a file like that?

Is it possible to just move files that have dep requirement in the order of loaded scripts to their correct location, without putting them all at the end?

Also I'd rather emit warnings and still attempt to load extensions with missing dependencies rather than forbid it outright.

@wfjsw
Copy link
Contributor Author

wfjsw commented Nov 19, 2023

  1. Colab upgraded to Python 3.10 already. but we could always opt to try and catch a ImportError and do fallback here.

  2. Why'd we want spaces in name? This is an internal identifier rather than a display name and currently it's already hard to use spaces given the git repo name restriction. If display name is needed there could be a new field.

  3. I'm fine with that. Given ini is capable of handling strings, boolean and numbers I don't know anything else would happened to be needed here.

  4. It is possible to imply a default After relation based on the directory names but I don't know if it will accidentally create loops. Also it would cause ambiguities.

  5. I guess the extension would crash anyway if they decided to assume the required extension is there and import it... but I'm fine with this.

@w-e-w
Copy link
Collaborator

w-e-w commented Nov 19, 2023

I would prefer a more simple name for the metadata file, like metadata.ini

I would argue against using a common filename like metadata
as there's a chance of the filename name is already used for other purposes


Is ini even a good choice for a file like that?

ini is capable of handling strings

I also don't prefer ini, even it's support handles the data types we need, ini it's not really convenient to have structures
such as list or dict
there's the possibility of extending the metadata for other use in the future, so having the format more structure is also beneficial


Also I'd rather emit warnings and still attempt to load extensions with missing dependencies rather than forbid it outright.

I guess the extension would crash anyway if they decided to assume the required extension is there and import it... but I'm fine with this.

I agree on this point
in some cases certain extensions would have extra functions when other extensions is installed, but still would work without them
is like a "soft" requirement, naturally for these extra function to work this extension would have to be loaded after their dependencies, but they also have error handling when those extensions do not exist

I'm not sure maybe this already works but just not specify require only after

@wfjsw
Copy link
Contributor Author

wfjsw commented Nov 19, 2023

I'm not sure maybe this already works but just not specify require only after

Requires and After are independent and none of it implies the other.

@w-e-w
Copy link
Collaborator

w-e-w commented Nov 19, 2023

I'm not sure maybe this already works but just not specify require only after

Requires and After are independent and none of it implies the other.

nice

@wfjsw
Copy link
Contributor Author

wfjsw commented Nov 19, 2023

(Some of the design actually comes from systemd.service and that's the main reason I choose ini.)

@w-e-w
Copy link
Collaborator

w-e-w commented Nov 19, 2023

I would prefer a more simple name for the metadata file, like metadata.ini

I would argue against using a common filename like metadata
as there's a chance of the filename name is already used for other purposes


example of how it might look using json

  • fill path
{
    "extension": {
        "name": "adetailer",
        "requires": ["sd-webui-controlnet", "sd-webui-animatediff"]
    },
    "scripts": {
        "requires": ["sd-webui-controlnet", "sd-webui-animatediff"],
        "before": ["sd-webui-controlnet"]
    },
    "scripts/!adetailer.py": {
        "requires": ["sd-webui-controlnet", "sd-webui-animatediff"],
        "before": ["sd-webui-controlnet"],
        "after": ["sd-webui-animatediff/animatediff.py"]
    },
    "javascript": {
        "after": ["ui.js"]
    },
    "javascript/canvas.js": {
        "after": "localization.js"
    }
}
  • nested
{
    "name": "adetailer",
    "requires": ["sd-webui-controlnet", "sd-webui-animatediff"],
    "scripts": {
        "!adetailer.py" : {
            "requires": ["sd-webui-controlnet", "sd-webui-animatediff"],
            "before": ["sd-webui-controlnet"],
            "after": ["sd-webui-animatediff/animatediff.py"]
        }
    },
    "javascript": {
        "canvas.js": {
            "after": "localization.js"   <---when there's only one then list [] is not required
        }
    }
}

@AUTOMATIC1111 AUTOMATIC1111 merged commit 73a0b4b into AUTOMATIC1111:dev Nov 20, 2023
3 checks passed
@AUTOMATIC1111
Copy link
Owner

I changed a lot in implementation to use classes with named fields instead of dictionaries, use a custom sorter that doesn't change the order as much and ignores cyclic reference errors (which I see as a preferable solution: if one extension adds some messed up order, that shouldn't break nice ordering of all other extensions), and eliminated some duplicate code.

@w-e-w w-e-w mentioned this pull request Dec 4, 2023
@w-e-w w-e-w mentioned this pull request Dec 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants