Overview
Comment:support for #console-scripts: in setup+flit
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: b5dc7df53f864588e11faa07c9815146e123aae82e8ac017ba76beafcf7b483f
User & Date: mario on 2022-10-26 16:50:11
Other Links: manifest | tags
Context
2022-10-27
05:34
lots pylint fixes, add documentation check-in: 9c3d295916 user: mario tags: trunk
2022-10-26
16:50
support for #console-scripts: in setup+flit check-in: b5dc7df53f user: mario tags: trunk
14:00
add warning and missing pluginconf. references check-in: 85f6aa649a user: mario tags: trunk
Changes

Modified README.md from [afd1caefa1] to [d0883b96e6].

104
105
106
107
108
109
110

111
112
113
114
115
116
117
#### get_data( filename= )

Is mostly an alias for pkgutil.get_data(). Abstracts usage within PYZ packages a little.

#### argparse_map()

Provides a simpler way to specify ugly argparse definitions. And allows to amass options from plugins.


# GUI

There's a Tkinter/PySimpleGUI variant of the option dialog from
[streamtuner2](https://fossil.include-once.org/streamtuner2/) included:
![](https://fossil.include-once.org/streamtuner2/raw/ba3d43061948b97087a38b45f015c7736843a631?m=image/png)








>







104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#### get_data( filename= )

Is mostly an alias for pkgutil.get_data(). Abstracts usage within PYZ packages a little.

#### argparse_map()

Provides a simpler way to specify ugly argparse definitions. And allows to amass options from plugins.


# GUI

There's a Tkinter/PySimpleGUI variant of the option dialog from
[streamtuner2](https://fossil.include-once.org/streamtuner2/) included:
![](https://fossil.include-once.org/streamtuner2/raw/ba3d43061948b97087a38b45f015c7736843a631?m=image/png)

166
167
168
169
170
171
172
















173
174
175
176

177
178
179
180
181
182
183
automatic lookup. The non-standard PMD field `# classifiers: x11, python`
can be used to lookup trove categories (crude search on select topics).
All other `setup(fields=…)` are passed on to distutils/setuptools as is.
-- Btw, [setupmeta](https://pypi.org/project/setupmeta/) is an even more
versatile wrapper with sensible defaults and source scanning.


















# other modules

 * DependencyValidation is now in `pluginconf.depends`
 * `pluginconf.flit` is meant as alternative to setup.



#### Caveats

 * It’s mostly just an excerpt from streamtuner2.
 * Might need customization prior use.
 * The GUI implmentation is fairly simplistic.







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|


<
>







167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192

193
194
195
196
197
198
199
200
automatic lookup. The non-standard PMD field `# classifiers: x11, python`
can be used to lookup trove categories (crude search on select topics).
All other `setup(fields=…)` are passed on to distutils/setuptools as is.
-- Btw, [setupmeta](https://pypi.org/project/setupmeta/) is an even more
versatile wrapper with sensible defaults and source scanning.


# flit wrapper

Alternatively, there's `pluginconf.flit` to utilize pyproject.toml for
building packages, while sourcing meta data from the primary package file.

     [build-system]
     requires = ["flit_core", "pluginconf"]
     build-backend = "pluginconf.flit"

     [project]
     name = "projectname"

It can be invoked via `python -m pluginconf.flit build` or even
`flit-pluginconf build`. It's not very robust however.


## other modules

 * DependencyValidation is now in `pluginconf.depends`

 * argparse_map() might also end up in a separate module.


#### Caveats

 * It’s mostly just an excerpt from streamtuner2.
 * Might need customization prior use.
 * The GUI implmentation is fairly simplistic.

Modified pluginconf/__init__.py from [4d5e4ffe7f] to [a0794eef80].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

17
18
19
20
21
22
23
# encoding: utf-8
# api: python
##type: extract
# category: config
# title: Plugin configuration
# description: Read meta data, pyz/package contents, module locating
# version: 0.7.7
# state: stable
# classifiers: documentation
# license: PD
# priority: core
# docs: https://fossil.include-once.org/pluginspec/
# url: http://fossil.include-once.org/streamtuner2/wiki/plugin+meta+data
# config: -
# format: off
# pylint: disable=invalid-name

#
# Provides plugin lookup and meta data extraction utility functions.
# It's used to abstract module+option management in applications.
# For consolidating internal use and external/tool accessibility.
#
# The key:value format is language-agnostic. It's basically YAML in
# a topmost script comment. For Python only # hash comments though.






|









>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# encoding: utf-8
# api: python
##type: extract
# category: config
# title: Plugin configuration
# description: Read meta data, pyz/package contents, module locating
# version: 0.7.8
# state: stable
# classifiers: documentation
# license: PD
# priority: core
# docs: https://fossil.include-once.org/pluginspec/
# url: http://fossil.include-once.org/streamtuner2/wiki/plugin+meta+data
# config: -
# format: off
# pylint: disable=invalid-name
# console-scripts: flit-pluginconf=pluginconf.flit:main
#
# Provides plugin lookup and meta data extraction utility functions.
# It's used to abstract module+option management in applications.
# For consolidating internal use and external/tool accessibility.
#
# The key:value format is language-agnostic. It's basically YAML in
# a topmost script comment. For Python only # hash comments though.

Modified pluginconf/flit.py from [ff6747a2ac] to [7b6634f0e5].

1
2
3
4
5
6
7
8
9
10
11
12
13
14


15
16
17
18
19
20
21
22
23




24
25
26
27
28
29
30
31
32
33
34
35
36
37
38




39

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

58
59
60
61
62
63
64

65


66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

120
121
122
123

124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174

175
176
177
178
179
# encoding: utf-8
# api: pep517
# title: flit backend
# description: wraps flit_core.buildapi
# version: 0.1
# depends: python:flit (>=3.0, <4.0)
# license: BSD-3-Clause
# priority: extra
# src: ~/.local/lib/python3.8/site-packages/flit_core/
#
# This is supposed to become an alternative to pluginconf.setup,
# using flit as pep517 build backend. But adding automagic field
# lookup of course.
# 


#
# [build-system]
# requires = ["flit_core", "pluginconf"]
# build-backend = "pluginconf.flit"
#
# [project]
# name = "foobar"
# dynamic = ["version", "description"]
#




#

import os
import re
import os.path
import pathlib
import functools

import pluginconf
import pluginconf.setup

import flit_core.common


#-- patch functions






def patch_flit_config(path):
    """ read_flit_config() with preset dynamic fields """
    d = tomllib.loads(path.read_text('utf-8'))
    
    # make fields dynamic
    if not "dynamic" in d["project"]:
        d["project"]["dynamic"] = []
    for dyn in ['description', 'version']: 
        if dyn in d["project"]:
            del d["project"][dyn]
        if not dyn in d["project"]["dynamic"]:
            d["project"]["dynamic"].append(dyn)
    print(d)

    # turn it into LoadedConfig
    return prep_toml_config(d, path)

# override make_metadata

def patch_metadata(module, ini_info):
    meta = {
        "name": module.name,
        "provides": [module.name]
    }
    meta.update(ini_info.metadata)
    meta.update(

        pmd_meta(filename=module.file)


    )
    return flit_core.common.Metadata(meta)

# inject
flit_core.common.make_metadata = patch_metadata


#-- map plugin meta to flit Metadata

def pmd_meta(filename):
    """ enjoin PMD fields with flit meta data """
    pmd  = pluginconf.plugin_meta(filename=filename)
    
    meta = dict(
        summary = pmd.get("description"),
        version = pmd.get("version"),
        home_page = pmd.get("url"),
        author = pmd.get("author"),  # should split this into mail and name
        author_email = None,
        maintainer = None,
        maintainer_email = None,
        license = pmd.get("license"),
        keywords = pmd.get("keywords"),
        download_url = None,
        requires_python = pluginconf.setup._python_requires(pmd).get("python_requires", ">= 2.7"),
        platform = (),
        supported_platform = (),
        classifiers = list(pluginconf.setup._classifiers(pmd))
                    + pluginconf.setup._trove_license(pmd)
                    + pluginconf.setup._trove_status(pmd),
        provides = (),
        requires = pluginconf.setup._install_requires(pmd).get("install_requires") or (),
        obsoletes = (),
        project_urls = [f"{k}, {v}" for k,v in pluginconf.setup._project_urls(pmd).items()],
        provides_dist = (),
        requires_dist = pluginconf.setup._install_requires(pmd).get("install_requires") or (),
        obsoletes_dist = (),
        requires_external = (),
        provides_extra = (),
    )
    meta.update({k[5:]: v for k,v in pluginconf.setup._plugin_doc(pmd).items()})
    meta.update({k[5:]: v for k,v in pluginconf.setup._get_readme().items()})

    return meta


# 'metadata', 'module', 'referenced_files', 'reqs_by_extra',
# 'sdist_exclude_patterns', 'sdist_include_patterns']
#
# ini.metadata =
#   {'name': 'basename', 'requires_dist': []}
#
# ini.module =
#   basename

#
# ini.dynamic_metadata =
#   {'version', 'description'}
#

# ini.__dict__ =
# {
# 'module': 'pluginconf',
# 'metadata': {
#   'name': 'pluginconf',
#   'version': '1.0',
#   'summary': '...',
#   'requires_dist': []
# },
# 'reqs_by_extra': {},
# 'entrypoints': {},
# 'referenced_files': [],
# 'sdist_include_patterns': [],
# 'sdist_exclude_patterns': [],
# 'dynamic_metadata': [],
# 'data_directory': None
# }

#module = Module(ini.module, pathlib.Path.cwd())
#print(module.__dict__)
#print(module.file)
# {'name': 'pluginconf', 'path':
# PosixPath('/home/mario/projects/pluginconf/pluginconf'), 'is_package':
# True, 'prefix': '', 'source_dir':
# PosixPath('/home/mario/projects/pluginconf')}

#MD = patch_metadata(module, ini)
#print(MD.__dict__)


import flit_core.buildapi

flit_core.buildapi.prepare_metadata_for_build_wheel("./build/")



#-- buildapi
#
# These need to be late imports;
# else they'll bind the original helper functions
from flit_core.buildapi import (
    get_requires_for_build_wheel,
    get_requires_for_build_sdist,
    get_requires_for_build_editable,
    prepare_metadata_for_build_wheel,
    prepare_metadata_for_build_editable,
    build_wheel,
    build_editable,
    build_sdist,
)


import flit

# as invocation point
def main(argv=None):
    return flit.main(argv)




|








|
>
>

|
|
|

|
|
|

>
>
>
>


|

<
<



|




|
>
>
>
>

>
|

















>
|






>
|
>
>



<
<
<
<
|
<
|

<
<









|

|
|

|
|
|

|

|

|




<
<

|
|
|
<
<
<
|
<
<
|
<
>
|
|
<
<
>
|
|
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
|
<
<
<
<
<
<
<
|
<
|
|
|
<
|
<




<
<
<
|
|









>
|

<
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81




82

83
84


85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111


112
113
114
115



116


117

118
119
120


121
122
123








124






125







126

127
128
129

130

131
132
133
134



135
136
137
138
139
140
141
142
143
144
145
146
147
148

149
150
# encoding: utf-8
# api: pep517
# title: flit backend
# description: wraps flit_core.buildapi
# version: 0.2
# depends: python:flit (>=3.0, <4.0)
# license: BSD-3-Clause
# priority: extra
# src: ~/.local/lib/python3.8/site-packages/flit_core/
#
# This is supposed to become an alternative to pluginconf.setup,
# using flit as pep517 build backend. But adding automagic field
# lookup of course.
#
# It can be invoked per `flit-pluginconfig build` and requires
# a `pyproject.toml` like:
#
#       [build-system]
#       requires = ["flit_core", "pluginconf"]
#       build-backend = "pluginconf.flit"
#
#       [project]
#       name = "foobar"
#       #dynamic = ["version", "description"]
#
# Injecting attributes between ini reading and parameter collection
# turned out easier than expanding on flit_core.buildapi functions.
# And lastly, this just chains to flit.main() to handle setup and
# build steps.
#

import sys
import re


import functools

import pluginconf
import pluginconf.setup as psetup

import flit_core.common


#-- patchy patch
def inject(where):
    def wrapped(func):
        setattr(where, func.__name__, func)
    return wrapped

@inject(flit_core.common)
def read_flit_config(path):
    """ read_flit_config() with preset dynamic fields """
    d = tomllib.loads(path.read_text('utf-8'))
    
    # make fields dynamic
    if not "dynamic" in d["project"]:
        d["project"]["dynamic"] = []
    for dyn in ['description', 'version']: 
        if dyn in d["project"]:
            del d["project"][dyn]
        if not dyn in d["project"]["dynamic"]:
            d["project"]["dynamic"].append(dyn)
    print(d)

    # turn it into LoadedConfig
    return prep_toml_config(d, path)

# override make_metadata
@inject(flit_core.common)
def make_metadata(module, ini_info):
    meta = {
        "name": module.name,
        "provides": [module.name]
    }
    meta.update(ini_info.metadata)
    meta.update(
        pmd_meta(
            pluginconf.plugin_meta(filename=module.file),
            ini_info
        )
    )
    return flit_core.common.Metadata(meta)





# map plugin meta to flit Metadata

def pmd_meta(pmd, ini):
    """ enjoin PMD fields with flit meta data """


    meta = dict(
        summary = pmd.get("description"),
        version = pmd.get("version"),
        home_page = pmd.get("url"),
        author = pmd.get("author"),  # should split this into mail and name
        author_email = None,
        maintainer = None,
        maintainer_email = None,
        license = pmd.get("license"),
        keywords = psetup._keywords(pmd),
        download_url = None,
        requires_python = psetup._python_requires(pmd).get("python_requires", ">= 2.7"),
        platform = pmd.get("architecture"),
        supported_platform = (),
        classifiers = list(psetup._classifiers(pmd))
                    + psetup._trove_license(pmd)
                    + psetup._trove_status(pmd),
        provides = (),
        requires = psetup._install_requires(pmd).get("install_requires") or (),
        obsoletes = (),
        project_urls = [f"{k}, {v}" for k,v in psetup._project_urls(pmd).items()],
        provides_dist = (),
        requires_dist = psetup._install_requires(pmd).get("install_requires") or (),
        obsoletes_dist = (),
        requires_external = (),
        provides_extra = (),
    )



    # comment/readme
    for docs in psetup._plugin_doc(pmd), psetup._get_readme():
        if docs["long_description"]:



            meta.update({


                k[5:]: v for k,v in docs.items()

            })

    # entry_points are in ini file


    for section, entries in psetup._entry_points(pmd).items():
        ini.entrypoints[section] = ini.entrypoints.get(section, {})
        for e in entries:








            ini.entrypoints[section].update(






                dict(re.findall("(.+)=(.+)", e))







            )

    print(ini.entrypoints)
    
    # strip empty entries

    return {k: v for k, v in meta.items() if v}




#-- buildapi



from flit_core.buildapi import (     # These have to be late imports; else they'll
    get_requires_for_build_wheel,    # bind with the original buildapi functions.
    get_requires_for_build_sdist,
    get_requires_for_build_editable,
    prepare_metadata_for_build_wheel,
    prepare_metadata_for_build_editable,
    build_wheel,
    build_editable,
    build_sdist,
)

#-- invocation point
from flit import main


if __name__ == "__main__":
    main(sys.argv)

Modified pluginconf/setup.py from [9ccd666569] to [786122b150].

155
156
157
158
159
160
161









162
163
164
165

166
167
168
169
170
171
172
    return []

def _datafiles_man():
    """ data_files= """
    for man in glob.glob("man*/*.[12345678]"):
        section = man[-1]
        yield ("man/man"+section, [man],)










def _keywords(pmd):
    """ keywords= """
    return pmd.get("keywords") or pmd.get("category")



def setup(debug=0, **kwargs):
    """
    Wrapper around `setuptools.setup()` which adds some defaults
    and plugin meta data import, with two shortcut params:
    







>
>
>
>
>
>
>
>
>




>







155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
    return []

def _datafiles_man():
    """ data_files= """
    for man in glob.glob("man*/*.[12345678]"):
        section = man[-1]
        yield ("man/man"+section, [man],)

def _entry_points(pmd):
    """ collect console-scripts: """
    params = {}
    for field in ["console_scripts", "gui_scripts"]:
        if not pmd.get(field):
            continue
        params[field] = params.get(field, []) + re.findall("(\w+[^,;\s]+=\w+[^,;\s]+)", pmd[field])
    return params

def _keywords(pmd):
    """ keywords= """
    return pmd.get("keywords") or pmd.get("category")



def setup(debug=0, **kwargs):
    """
    Wrapper around `setuptools.setup()` which adds some defaults
    and plugin meta data import, with two shortcut params:
    
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
            kwargs[k] = v

    # package name
    if "name" not in kwargs and kwargs.get("packages"):
        kwargs["name"] = kwargs["packages"][0]

    # read README
    if not "long_description" in kwargs:
        kwargs.update(_get_readme())

    # search name= package if no fn= given
    if kwargs.get("filename"):
        kwargs["fn"] = kwargs["filename"]
        del kwargs["filename"]
    if not kwargs.get("fn") and kwargs.get("name"):







|







204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
            kwargs[k] = v

    # package name
    if "name" not in kwargs and kwargs.get("packages"):
        kwargs["name"] = kwargs["packages"][0]

    # read README
    if re.match("^$|^[@./]*README.{0,5}$", kwargs.get("long_description", "")):
        kwargs.update(_get_readme())

    # search name= package if no fn= given
    if kwargs.get("filename"):
        kwargs["fn"] = kwargs["filename"]
        del kwargs["filename"]
    if not kwargs.get("fn") and kwargs.get("name"):
242
243
244
245
246
247
248



249
250
251
252
253
254
255

    # keywords=
    if not "keywords" in kwargs:
        kwargs["keywords"] = _keywords(pmd)
    
    # automatic inclusions
    kwargs["data_files"] = kwargs.get("data_files", []) + list(_datafiles_man())




    # classifiers=
    # license:
    if pmd.get("license") and not any(re.match("License ::", l) for l in kwargs["classifiers"]):
        kwargs["classifiers"].extend(_trove_license(pmd))
    # state:
    if pmd.get("state", pmd.get("status")) and not any(re.match("Development Status ::", l) for l in kwargs["classifiers"]):







>
>
>







252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268

    # keywords=
    if not "keywords" in kwargs:
        kwargs["keywords"] = _keywords(pmd)
    
    # automatic inclusions
    kwargs["data_files"] = kwargs.get("data_files", []) + list(_datafiles_man())
    # entry points
    for section, entries in _entry_points(pmd).items():
        kwargs["entry_points"][section] = kwargs["entry_points"].get(section, []) + entries

    # classifiers=
    # license:
    if pmd.get("license") and not any(re.match("License ::", l) for l in kwargs["classifiers"]):
        kwargs["classifiers"].extend(_trove_license(pmd))
    # state:
    if pmd.get("state", pmd.get("status")) and not any(re.match("Development Status ::", l) for l in kwargs["classifiers"]):

Modified setup.py from [f210898d14] to [44fd264fd9].

14
15
16
17
18
19
20
21
22





23
24

#from os import system
#system("pandoc -f markdown -t rst README.md  > README.rst")


pluginconf.setup.setup(
    fn="pluginconf/__init__.py",
    long_description="README.md"
    #author_email="m..@include-once.org",





)








|

>
>
>
>
>


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

#from os import system
#system("pandoc -f markdown -t rst README.md  > README.rst")


pluginconf.setup.setup(
    fn="pluginconf/__init__.py",
    long_description="README.md",
    #author_email="m..@include-once.org",
#    entry_points={
#        "console_scripts": [
#            "flit-pluginconf=pluginconf.flit:main",
#        ]
#    },
)