Merge branch 'develop' into feature/pricelist
This commit is contained in:
		
						commit
						ff1a0544f8
					
				| 
						 | 
					@ -9,7 +9,7 @@ from flaschengeist import _module_path, logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Default config:
 | 
					# Default config:
 | 
				
			||||||
config = {"DATABASE": {"port": 3306}}
 | 
					config = {"DATABASE": {"engine": "mysql", "port": 3306}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def update_dict(d, u):
 | 
					def update_dict(d, u):
 | 
				
			||||||
| 
						 | 
					@ -65,17 +65,35 @@ def configure_app(app, test_config=None):
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        app.config["SECRET_KEY"] = config["FLASCHENGEIST"]["secret_key"]
 | 
					        app.config["SECRET_KEY"] = config["FLASCHENGEIST"]["secret_key"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if test_config is None:
 | 
					    if test_config is not None:
 | 
				
			||||||
        app.config["SQLALCHEMY_DATABASE_URI"] = "mysql{driver}://{user}:{passwd}@{host}:{port}/{database}".format(
 | 
					        config["DATABASE"]["engine"] = "sqlite"
 | 
				
			||||||
            driver="+pymysql" if os.name == "nt" else "",
 | 
					
 | 
				
			||||||
 | 
					    if config["DATABASE"]["engine"] == "mysql":
 | 
				
			||||||
 | 
					        engine = "mysql"
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # Try mysqlclient first
 | 
				
			||||||
 | 
					            from MySQLdb import _mysql
 | 
				
			||||||
 | 
					        except ModuleNotFoundError:
 | 
				
			||||||
 | 
					            engine += "+pymysql"
 | 
				
			||||||
 | 
					        options = "?charset=utf8mb4"
 | 
				
			||||||
 | 
					    elif config["DATABASE"]["engine"] == "postgres":
 | 
				
			||||||
 | 
					        engine = "postgresql+psycopg2"
 | 
				
			||||||
 | 
					        options = "?client_encoding=utf8"
 | 
				
			||||||
 | 
					    elif config["DATABASE"]["engine"] == "sqlite":
 | 
				
			||||||
 | 
					        engine = "sqlite"
 | 
				
			||||||
 | 
					        options = ""
 | 
				
			||||||
 | 
					        host = ""
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        logger.error(f"Invalid database engine configured. >{config['DATABASE']['engine']}< is unknown")
 | 
				
			||||||
 | 
					        raise Exception
 | 
				
			||||||
 | 
					    if config["DATABASE"]["engine"] in ["mysql", "postgresql"]:
 | 
				
			||||||
 | 
					        host = "{user}:{password}@{host}:{port}".format(
 | 
				
			||||||
            user=config["DATABASE"]["user"],
 | 
					            user=config["DATABASE"]["user"],
 | 
				
			||||||
            passwd=config["DATABASE"]["password"],
 | 
					            password=config["DATABASE"]["password"],
 | 
				
			||||||
            host=config["DATABASE"]["host"],
 | 
					            host=config["DATABASE"]["host"],
 | 
				
			||||||
            database=config["DATABASE"]["database"],
 | 
					 | 
				
			||||||
            port=config["DATABASE"]["port"],
 | 
					            port=config["DATABASE"]["port"],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    else:
 | 
					    app.config["SQLALCHEMY_DATABASE_URI"] = f"{engine}://{host}/{config['DATABASE']['database']}{options}"
 | 
				
			||||||
        app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite+pysqlite://{config['DATABASE']['file_path']}"
 | 
					 | 
				
			||||||
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
 | 
					    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if "root" in config["FLASCHENGEIST"]:
 | 
					    if "root" in config["FLASCHENGEIST"]:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@ secret_key = "V3ryS3cr3t"
 | 
				
			||||||
level = "WARNING"
 | 
					level = "WARNING"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[DATABASE]
 | 
					[DATABASE]
 | 
				
			||||||
 | 
					# engine = "mysql" (default)
 | 
				
			||||||
# user = "user"
 | 
					# user = "user"
 | 
				
			||||||
# host = "127.0.0.1"
 | 
					# host = "127.0.0.1"
 | 
				
			||||||
# password = "password"
 | 
					# password = "password"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ import sys
 | 
				
			||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from sqlalchemy import BigInteger
 | 
					from sqlalchemy import BigInteger
 | 
				
			||||||
from sqlalchemy.dialects import mysql
 | 
					from sqlalchemy.dialects import mysql, sqlite
 | 
				
			||||||
from sqlalchemy.types import DateTime, TypeDecorator
 | 
					from sqlalchemy.types import DateTime, TypeDecorator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,7 @@ class ModelSerializeMixin:
 | 
				
			||||||
class Serial(TypeDecorator):
 | 
					class Serial(TypeDecorator):
 | 
				
			||||||
    """Same as MariaDB Serial used for IDs"""
 | 
					    """Same as MariaDB Serial used for IDs"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    impl = BigInteger().with_variant(mysql.BIGINT(unsigned=True), "mysql")
 | 
					    impl = BigInteger().with_variant(mysql.BIGINT(unsigned=True), "mysql").with_variant(sqlite.INTEGER, "sqlite")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UtcDateTime(TypeDecorator):
 | 
					class UtcDateTime(TypeDecorator):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -567,6 +567,7 @@ def get_columns(userid, current_session):
 | 
				
			||||||
        userController.persist()
 | 
					        userController.persist()
 | 
				
			||||||
        return no_content()
 | 
					        return no_content()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@PriceListPlugin.blueprint.route("/users/<userid>/pricecalc_columns_order", methods=["GET", "PUT"])
 | 
					@PriceListPlugin.blueprint.route("/users/<userid>/pricecalc_columns_order", methods=["GET", "PUT"])
 | 
				
			||||||
@login_required()
 | 
					@login_required()
 | 
				
			||||||
def get_columns_order(userid, current_session):
 | 
					def get_columns_order(userid, current_session):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -231,3 +231,21 @@ def notifications(current_session):
 | 
				
			||||||
def remove_notifications(nid, current_session):
 | 
					def remove_notifications(nid, current_session):
 | 
				
			||||||
    userController.delete_notification(nid, current_session.user_)
 | 
					    userController.delete_notification(nid, current_session.user_)
 | 
				
			||||||
    return no_content()
 | 
					    return no_content()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@UsersPlugin.blueprint.route("/users/<userid>/shortcuts", methods=["GET", "PUT"])
 | 
				
			||||||
 | 
					@login_required()
 | 
				
			||||||
 | 
					def shortcuts(userid, current_session):
 | 
				
			||||||
 | 
					    if userid != current_session.user_.userid:
 | 
				
			||||||
 | 
					        raise Forbidden
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user = userController.get_user(userid)
 | 
				
			||||||
 | 
					    if request.method == "GET":
 | 
				
			||||||
 | 
					        return jsonify(user.get_attribute("users_link_shortcuts", []))
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        data = request.get_json()
 | 
				
			||||||
 | 
					        if not isinstance(data, list) or not all(isinstance(n, dict) for n in data):
 | 
				
			||||||
 | 
					            raise BadRequest
 | 
				
			||||||
 | 
					        user.set_attribute("users_link_shortcuts", data)
 | 
				
			||||||
 | 
					        userController.persist()
 | 
				
			||||||
 | 
					        return no_content()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										114
									
								
								readme.md
								
								
								
								
							
							
						
						
									
										114
									
								
								readme.md
								
								
								
								
							| 
						 | 
					@ -1,9 +1,16 @@
 | 
				
			||||||
# Flaschengeist
 | 
					# Flaschengeist
 | 
				
			||||||
 | 
					This is the backend of the Flaschengeist.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Installation
 | 
					## Installation
 | 
				
			||||||
### Requirements
 | 
					### Requirements
 | 
				
			||||||
- mysql or mariadb
 | 
					- `mysql` or `mariadb`
 | 
				
			||||||
- python 3.6+
 | 
					    - maybe `libmariadb` development files[1]
 | 
				
			||||||
 | 
					- python 3.7+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[1] By default Flaschengeist uses mysql as database backend, if you are on Windows Flaschengeist uses `PyMySQL`, but on
 | 
				
			||||||
 | 
					Linux / Mac the faster `mysqlclient` is used, if it is not already installed installing from pypi requires the
 | 
				
			||||||
 | 
					development files for `libmariadb` to be present on your system.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Install python files
 | 
					### Install python files
 | 
				
			||||||
    pip3 install --user .
 | 
					    pip3 install --user .
 | 
				
			||||||
or with ldap support
 | 
					or with ldap support
 | 
				
			||||||
| 
						 | 
					@ -30,9 +37,21 @@ Configuration is done within the a `flaschengeist.toml`file, you can copy the on
 | 
				
			||||||
1. `~/.config/`
 | 
					1. `~/.config/`
 | 
				
			||||||
2. A custom path and set environment variable `FLASCHENGEIST_CONF`
 | 
					2. A custom path and set environment variable `FLASCHENGEIST_CONF`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Change at least the database parameters!
 | 
					Uncomment and change at least all the database parameters!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Database installation
 | 
					### Database installation
 | 
				
			||||||
 | 
					The user needs to have full permissions to the database.
 | 
				
			||||||
 | 
					If not you need to create user and database manually do (or similar on Windows):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        echo "CREATE DATABASE flaschengeist;"
 | 
				
			||||||
 | 
					        echo "CREATE USER 'flaschengeist'@'localhost' IDENTIFIED BY 'flaschengeist';"
 | 
				
			||||||
 | 
					        echo "GRANT ALL PRIVILEGES ON 'flaschengeist'.* TO 'flaschengeist'@'localhost';"
 | 
				
			||||||
 | 
					        echo "FLUSH PRIVILEGES;"
 | 
				
			||||||
 | 
					    ) | sudo mysql
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Then you can install the database tables and initial entries:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    run_flaschengeist install
 | 
					    run_flaschengeist install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Run
 | 
					### Run
 | 
				
			||||||
| 
						 | 
					@ -41,6 +60,8 @@ or with debug messages:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    run_flaschengeist run --debug
 | 
					    run_flaschengeist run --debug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This will run the backend on http://localhost:5000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Tests
 | 
					## Tests
 | 
				
			||||||
    $ pip install '.[test]'
 | 
					    $ pip install '.[test]'
 | 
				
			||||||
    $ pytest
 | 
					    $ pytest
 | 
				
			||||||
| 
						 | 
					@ -53,89 +74,4 @@ Or with html output (open `htmlcov/index.html` in a browser):
 | 
				
			||||||
    $ coverage html
 | 
					    $ coverage html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Development
 | 
					## Development
 | 
				
			||||||
### Code Style
 | 
					Please refer to our [development wiki](https://flaschengeist.dev/Flaschengeist/flaschengeist/wiki/Development).
 | 
				
			||||||
We enforce you to use PEP 8 code style with a line length of 120 as used by Black.
 | 
					 | 
				
			||||||
See also [Black Code Style](https://github.com/psf/black/blob/master/docs/the_black_code_style.md).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### Code formatting
 | 
					 | 
				
			||||||
We use [Black](https://github.com/psf/black) as the code formatter.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Installation:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pip install black
 | 
					 | 
				
			||||||
Usage:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    black -l 120 DIRECTORY_OR_FILE
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Misc
 | 
					 | 
				
			||||||
#### Git blame
 | 
					 | 
				
			||||||
When using `git blame` use this to ignore the code formatting commits:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    $ git blame FILE.py --ignore-revs-file .git-blame-ignore-revs
 | 
					 | 
				
			||||||
Or if you just want to use `git blame`, configure git like this:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    $ git config blame.ignoreRevsFile .git-blame-ignore-revs
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### Ignore changes on config
 | 
					 | 
				
			||||||
    git update-index --assume-unchanged flaschengeist/flaschengeist.toml
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Plugin Development
 | 
					 | 
				
			||||||
### File Structure
 | 
					 | 
				
			||||||
    flaschengeist-example-plugin
 | 
					 | 
				
			||||||
     |> __init__.py
 | 
					 | 
				
			||||||
     |> model.py
 | 
					 | 
				
			||||||
     |> setup.py
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Files
 | 
					 | 
				
			||||||
#### \_\_init\_\_.py
 | 
					 | 
				
			||||||
    from flask import Blueprint
 | 
					 | 
				
			||||||
    from flaschengeist.modules import Plugin
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    example_bp = Blueprint("example", __name__, url_prefix="/example")
 | 
					 | 
				
			||||||
    permissions = ["example_hello"]
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    class PluginExample(Plugin):
 | 
					 | 
				
			||||||
        def __init__(self, conf):
 | 
					 | 
				
			||||||
            super().__init__(blueprint=example_bp, permissions=permissions)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def install(self):
 | 
					 | 
				
			||||||
            from flaschengeist.system.database import db
 | 
					 | 
				
			||||||
            import .model
 | 
					 | 
				
			||||||
            db.create_all()
 | 
					 | 
				
			||||||
            db.session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @example_bp.route("/hello", methods=['GET'])
 | 
					 | 
				
			||||||
    @login_required(roles=['example_hello'])
 | 
					 | 
				
			||||||
    def __hello(id, **kwargs):
 | 
					 | 
				
			||||||
        return "Hello"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### model.py
 | 
					 | 
				
			||||||
Optional, only needed if you need your own models (database)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    from flaschengeist.system.database import db
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    class ExampleModel(db.Model):
 | 
					 | 
				
			||||||
        """Example Model"""
 | 
					 | 
				
			||||||
        __tablename__ = 'example'
 | 
					 | 
				
			||||||
        id = db.Column(db.Integer, primary_key=True)
 | 
					 | 
				
			||||||
        description = db.Column(db.String(240))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### setup.py
 | 
					 | 
				
			||||||
    from setuptools import setup, find_packages
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    setup(
 | 
					 | 
				
			||||||
        name="flaschengeist-example-plugin",
 | 
					 | 
				
			||||||
        version="0.0.0-dev",
 | 
					 | 
				
			||||||
        packages=find_packages(),
 | 
					 | 
				
			||||||
        install_requires=[
 | 
					 | 
				
			||||||
            "flaschengeist >= 2",
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        entry_points={
 | 
					 | 
				
			||||||
            "flaschengeist.plugin": [
 | 
					 | 
				
			||||||
                "example = flaschengeist-example-plugin:ExampleModel"
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,7 @@ class PrefixMiddleware(object):
 | 
				
			||||||
    def __call__(self, environ, start_response):
 | 
					    def __call__(self, environ, start_response):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if environ["PATH_INFO"].startswith(self.prefix):
 | 
					        if environ["PATH_INFO"].startswith(self.prefix):
 | 
				
			||||||
            environ["PATH_INFO"] = environ["PATH_INFO"][len(self.prefix):]
 | 
					            environ["PATH_INFO"] = environ["PATH_INFO"][len(self.prefix) :]
 | 
				
			||||||
            environ["SCRIPT_NAME"] = self.prefix
 | 
					            environ["SCRIPT_NAME"] = self.prefix
 | 
				
			||||||
            return self.app(environ, start_response)
 | 
					            return self.app(environ, start_response)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
| 
						 | 
					@ -83,19 +83,19 @@ class InterfaceGenerator:
 | 
				
			||||||
        import typing
 | 
					        import typing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
                inspect.ismodule(module[1])
 | 
					            inspect.ismodule(module[1])
 | 
				
			||||||
                and module[1].__name__.startswith(self.basename)
 | 
					            and module[1].__name__.startswith(self.basename)
 | 
				
			||||||
                and module[1].__name__ not in self.known
 | 
					            and module[1].__name__ not in self.known
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            self.known.append(module[1].__name__)
 | 
					            self.known.append(module[1].__name__)
 | 
				
			||||||
            for cls in inspect.getmembers(module[1], lambda x: inspect.isclass(x) or inspect.ismodule(x)):
 | 
					            for cls in inspect.getmembers(module[1], lambda x: inspect.isclass(x) or inspect.ismodule(x)):
 | 
				
			||||||
                self.walker(cls)
 | 
					                self.walker(cls)
 | 
				
			||||||
        elif (
 | 
					        elif (
 | 
				
			||||||
                inspect.isclass(module[1])
 | 
					            inspect.isclass(module[1])
 | 
				
			||||||
                and module[1].__module__.startswith(self.basename)
 | 
					            and module[1].__module__.startswith(self.basename)
 | 
				
			||||||
                and module[0] not in self.classes
 | 
					            and module[0] not in self.classes
 | 
				
			||||||
                and not module[0].startswith("_")
 | 
					            and not module[0].startswith("_")
 | 
				
			||||||
                and hasattr(module[1], "__annotations__")
 | 
					            and hasattr(module[1], "__annotations__")
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            self.this_type = module[0]
 | 
					            self.this_type = module[0]
 | 
				
			||||||
            print("\n\n" + module[0] + "\n")
 | 
					            print("\n\n" + module[0] + "\n")
 | 
				
			||||||
| 
						 | 
					@ -156,12 +156,14 @@ def export(arguments):
 | 
				
			||||||
    app = create_app()
 | 
					    app = create_app()
 | 
				
			||||||
    with app.app_context():
 | 
					    with app.app_context():
 | 
				
			||||||
        gen = InterfaceGenerator(arguments.namespace, arguments.file)
 | 
					        gen = InterfaceGenerator(arguments.namespace, arguments.file)
 | 
				
			||||||
        gen.run(models)
 | 
					        if not arguments.no_core:
 | 
				
			||||||
        if arguments.plugins:
 | 
					            gen.run(models)
 | 
				
			||||||
 | 
					        if arguments.plugins is not None:
 | 
				
			||||||
            for entry_point in pkg_resources.iter_entry_points("flaschengeist.plugin"):
 | 
					            for entry_point in pkg_resources.iter_entry_points("flaschengeist.plugin"):
 | 
				
			||||||
                plg = entry_point.load()
 | 
					                if len(arguments.plugins) == 0 or entry_point.name in arguments.plugins:
 | 
				
			||||||
                if hasattr(plg, "models") and plg.models is not None:
 | 
					                    plg = entry_point.load()
 | 
				
			||||||
                    gen.run(plg.models)
 | 
					                    if hasattr(plg, "models") and plg.models is not None:
 | 
				
			||||||
 | 
					                        gen.run(plg.models)
 | 
				
			||||||
        gen.write()
 | 
					        gen.write()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -183,7 +185,12 @@ if __name__ == "__main__":
 | 
				
			||||||
    parser_export.set_defaults(func=export)
 | 
					    parser_export.set_defaults(func=export)
 | 
				
			||||||
    parser_export.add_argument("--file", help="Filename where to save", default="flaschengeist.d.ts")
 | 
					    parser_export.add_argument("--file", help="Filename where to save", default="flaschengeist.d.ts")
 | 
				
			||||||
    parser_export.add_argument("--namespace", help="Namespace of declarations", default="FG")
 | 
					    parser_export.add_argument("--namespace", help="Namespace of declarations", default="FG")
 | 
				
			||||||
    parser_export.add_argument("--plugins", help="Also export plugins", action="store_true")
 | 
					    parser_export.add_argument(
 | 
				
			||||||
 | 
					        "--no-core",
 | 
				
			||||||
 | 
					        help="Do not export core declarations (only useful in conjunction with --plugins)",
 | 
				
			||||||
 | 
					        action="store_true",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    parser_export.add_argument("--plugins", help="Also export plugins (none means all)", nargs="*")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    args = parser.parse_args()
 | 
					    args = parser.parse_args()
 | 
				
			||||||
    args.func(args)
 | 
					    args.func(args)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue