Repository: kivy/kivy

Issue:
Simply loading kivy has side-effects, writes logs to console and filesystem.
### Versions

* Python: any
* OS: any
* Kivy: any
* Kivy installation method: pip

### Description

Simply importing `kivy` and nothing else emits 250 bytes of logging to `stdout` and creates a dozen files or directories in the file system.  

This breaks my system for some invocations that emit JSON, and is undesirable in other cases.

I can suppress all logging with the [`KIVY_NO_CONSOLELOG`](https://kivy.org/doc/stable/guide/environment.html#configuration) environment variable, but that's worse, because all ERRORs and WARNINGs will be eliminated.

Writing entries in the users `~/.kivy/config.ini` seems like a really bad idea, because "if everyone else did it..." - even though I'm pretty sure that most of our users have no other `kivy` applications, yet.

My current plan is this.  Before `kivy` is loaded anywhere in my code, I change `os.environ['KIVY_HOME']` to refer to my application's folder, and then write my own `config.ini` to turn up the log level.

This is fragile as the correct functioning of my code will depend on the order that modules happen to be loaded, and I have multiple main programs.

----

Just _loading_ a library should not have any side effects like disk or console output.  

By comparison, the other 20+ Python libraries I use don't log at all or create any files on the file system just by being loaded - here's a subset of those libraries which covers all sorts of areas in Python and many of which you likely already have on your system.

    import (bibliopixel, cv2, flake8, flask, jedi, mido, numpy, pytest,
            requests, six, tornado, websocket, zmq)



### Code and Logs

```python

import kivy
```



Discussion:
We know. It was decision that was there from the beginning with pymt, but we never been able to change it. Import in kivy are active not passive, and yes it is a bad design.

It may be nice to be able to change any kivy configuration token via environment (like KIVY_CONFIG_LOG_LEVEL=warning) or something like that.

Just for the reference, if we don't make the library active at import:
- when do we load the configuration?
- how do we handle logs that can be generated by any modules you import?
- how do we handle initialization required for others modules to work? (the most known is any GL call require windows, but sometimes you can start writing GL-related code, or instanciate widget, without any window created. This was an issue as people didn't understood why it was crashing, and so now if the window is not created before a widget is instanciated, we create it to prevent crash. etc.)
- how do we design it in a way that the user doesn't need to call "init()" or stuff like that ?
- how do we do in a back compatible way?

In a way, we are closer to Panda3D. 

@tito for what it's worth, one way to do this is on first resource access (window or image or font) but of course that may need be a lot of code revamp to make that happen
Ah, hope the above didn't come off as too negative - I managed to whip
together a nice UI in a really short time with kivy, and then was
frustrated by being unable to get past this tiny issue!

I am in fact leading towards late loading in a single place.  The program
in question allows a lot of other sorts of heavy resources (like a flask
instance) that don't get loaded until a very specific point ("right before
start") and treating kivy that way is perfectly reasonable and won't take
much code.


On Sun, Mar 10, 2019 at 5:55 PM JonasT <notifications@github.com> wrote:

> @tito <https://github.com/tito> for what it's worth, one way to do this
> is on first resource access (window or image or font) but of course that
> may need be a lot of code revamp to make that happen
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/kivy/kivy/issues/6190#issuecomment-471323338>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AAPdspSFognWQ7_KZavitMJuFy2ru58Bks5vVTkOgaJpZM4bmvcA>
> .
>


-- 
     /t

PGP Key: https://flowcrypt.com/pub/tom.ritchford@gmail.com
*https://tom.ritchford.com <https://tom.ritchford.com>*
*https://tom.swirly.com <https://tom.swirly.com>*


Resolving patch:
diff --git a/kivy/__init__.py b/kivy/__init__.py
--- a/kivy/__init__.py
+++ b/kivy/__init__.py
@@ -288,7 +288,7 @@ def kivy_usage():
     environ['KIVY_DOC'] = '1'
 if 'sphinx-build' in sys.argv[0]:
     environ['KIVY_DOC_INCLUDE'] = '1'
-if any('nosetests' in arg for arg in sys.argv):
+if any(('nosetests' in arg or 'pytest' in arg) for arg in sys.argv):
     environ['KIVY_UNITTEST'] = '1'
 if any('pyinstaller' in arg.lower() for arg in sys.argv):
     environ['KIVY_PACKAGING'] = '1'
diff --git a/kivy/config.py b/kivy/config.py
--- a/kivy/config.py
+++ b/kivy/config.py
@@ -27,6 +27,8 @@
 restart the app for the changes to take effect. Note that this approach will
 effect all Kivy apps system wide.
 
+Please note that no underscores (`_`) are allowed in the section name.
+
 Usage of the Config object
 --------------------------
 
@@ -49,6 +51,31 @@
     converted from ascii to unicode only when needed. The method get() returns
     utf-8 strings.
 
+Changing configuration with environment variables
+-------------------------------------------------
+
+Since 1.11.0, it is now possible to change the configuration using
+environment variables. They take precedence on the loaded config.ini.
+The format is::
+
+    KCFG_<section>_<key> = <value>
+
+For example:
+
+    KCFG_GRAPHICS_FULLSCREEN=auto ...
+    KCFG_KIVY_LOG_LEVEL=warning ...
+
+Or in your file before any kivy import:
+
+    import os
+    os.environ["KCFG_KIVY_LOG_LEVEL"] = "warning"
+
+If you don't want to map any environment variables, you can disable
+the behavior::
+
+    os.environ["KIVY_NO_ENV_CONFIG"] = "1"
+
+
 .. _configuration-tokens:
 
 Available configuration tokens
@@ -514,6 +541,7 @@ def getdefaultint(self, section, option, defaultvalue):
     def adddefaultsection(self, section):
         '''Add a section if the section is missing.
         '''
+        assert("_" not in section)
         if self.has_section(section):
             return
         self.add_section(section)
@@ -874,3 +902,40 @@ def name(self, value):
             Config.write()
         except Exception as e:
             Logger.exception('Core: Error while saving default config file')
+
+    # Load configuration from env
+    if 'KIVY_NO_ENV_CONFIG' not in environ:
+        for key, value in environ.items():
+            if not key.startswith("KCFG_"):
+                continue
+            try:
+                _, section, name = key.split("_", 2)
+            except ValueError:
+                Logger.warning((
+                    "Config: Environ `{}` invalid format, "
+                    "must be KCFG_section_name").format(key))
+                continue
+
+            # extract and check section
+            section = section.lower()
+            if not Config.has_section(section):
+                Logger.warning(
+                    "Config: Environ `{}`: unknown section `{}`".format(
+                        key, section))
+                continue
+
+            # extract and check the option name
+            name = name.lower()
+            sections_to_check = {
+                "kivy", "graphics", "widgets", "postproc", "network"}
+            if (section in sections_to_check and
+                    not Config.has_option(section, name)):
+                Logger.warning((
+                    "Config: Environ `{}` unknown `{}` "
+                    "option in `{}` section.").format(
+                        key, name, section))
+                # we don't avoid to set an unknown option, because maybe
+                # an external modules or widgets (in garden?) may want to
+                # save its own configuration here.
+
+            Config.set(section, name, value)


Test patch: