first commit
This commit is contained in:
parent
be42928458
commit
44b095cc06
488
LICENSE.md
Normal file
488
LICENSE.md
Normal file
|
@ -0,0 +1,488 @@
|
|||
PyVenv
|
||||
Copyright Zoey Mae 2022
|
||||
|
||||
COOPERATIVE NON-VIOLENT PUBLIC LICENSE v7+
|
||||
|
||||
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS
|
||||
COOPERATIVE NON-VIOLENT PUBLIC LICENSE (\"LICENSE\"). THE WORK IS
|
||||
PROTECTED BY COPYRIGHT AND ALL OTHER APPLICABLE LAWS. ANY USE OF THE
|
||||
WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS
|
||||
PROHIBITED. BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED IN THIS
|
||||
LICENSE, YOU AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE.TO THE
|
||||
EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR
|
||||
GRANTS YOU THE RIGHTS CONTAINED HERE IN AS CONSIDERATION FOR ACCEPTING
|
||||
THE TERMS AND CONDITIONS OF THIS LICENSE AND FOR AGREEING TO BE BOUND BY
|
||||
THE TERMS AND CONDITIONS OF THIS LICENSE.
|
||||
|
||||
# Definitions
|
||||
|
||||
An Act of War is any action of one country against any group either with
|
||||
an intention to provoke a conflict or an action that occurs during a
|
||||
declared war or during armed conflict between military forces of any
|
||||
origin. This includes but is not limited to enforcing sanctions or
|
||||
sieges, supplying armed forces, or profiting from the manufacture of
|
||||
tools or weaponry used in military conflict.
|
||||
|
||||
An Adaptation is a work based upon the Work, or upon the Work and other
|
||||
pre-existing works, such as a translation, adaptation, derivative work,
|
||||
arrangement of music or other alterations of a literary or artistic
|
||||
work, or phonogram or performance and includes cinematographic
|
||||
adaptations or any other form in which the Work may be recast,
|
||||
transformed, or adapted including in any form recognizably derived from
|
||||
the original, except that a work that constitutes a Collection will not
|
||||
be considered an Adaptation for the purpose of this License. For the
|
||||
avoidance of doubt, where the Work is a musical work, performance or
|
||||
phonogram, the synchronization of the Work in timed-relation with a
|
||||
moving image (\"synching\") will be considered an Adaptation for the
|
||||
purpose of this License. In addition, where the Work is designed to
|
||||
output a neural network the output of the neural network will be
|
||||
considered an Adaptation for the purpose of this license.
|
||||
|
||||
Bodily Harm is any physical hurt or injury to a person that interferes
|
||||
with the health or comfort of the person and that is more than merely
|
||||
transient or trifling in nature.
|
||||
|
||||
Distribute is to make available to the public the original and copies of
|
||||
the Work or Adaptation, as appropriate, through sale, gift or any other
|
||||
transfer of possession or ownership.
|
||||
|
||||
Incarceration is Confinement in a jail, prison, or any other place where
|
||||
individuals of any kind are held against either their will or (if their
|
||||
will cannot be determined) the will of their legal guardian or
|
||||
guardians. In the case of a conflict between the will of the individual
|
||||
and the will of their legal guardian or guardians, the will of the
|
||||
individual will take precedence.
|
||||
|
||||
Licensor is The individual, individuals, entity, or entities that
|
||||
offer(s) the Work under the terms of this License
|
||||
|
||||
Original Author is in the case of a literary or artistic work, the
|
||||
individual, individuals, entity or entities who created the Work or if
|
||||
no individual or entity can be identified, the publisher; and in
|
||||
addition
|
||||
|
||||
- in the case of a performance the actors, singers, musicians,
|
||||
dancers, and other persons who act, sing, deliver, declaim, play in,
|
||||
interpret or otherwise perform literary or artistic works or
|
||||
expressions of folklore;
|
||||
|
||||
- in the case of a phonogram the producer being the person or legal
|
||||
entity who first fixes the sounds of a performance or other sounds;
|
||||
and,
|
||||
|
||||
- in the case of broadcasts, the organization that transmits the
|
||||
broadcast.
|
||||
|
||||
Work is the literary and/or artistic work offered under the terms of
|
||||
this License including without limitation any production in the
|
||||
literary, scientific and artistic domain, whatever may be the mode or
|
||||
form of its expression including digital form, such as a book, pamphlet
|
||||
and other writing; a lecture, address, sermon or other work of the same
|
||||
nature; a dramatic or dramatico-musical work; a choreographic work or
|
||||
entertainment in dumb show; a musical composition with or without words;
|
||||
a cinematographic work to which are assimilated works expressed by a
|
||||
process analogous to cinematography; a work of drawing, painting,
|
||||
architecture, sculpture, engraving or lithography; a photographic work
|
||||
to which are assimilated works expressed by a process analogous to
|
||||
photography; a work of applied art; an illustration, map, plan, sketch
|
||||
or three-dimensional work relative to geography, topography,
|
||||
architecture or science; a performance; a broadcast; a phonogram; a
|
||||
compilation of data to the extent it is protected as a copyrightable
|
||||
work; or a work performed by a variety or circus performer to the extent
|
||||
it is not otherwise considered a literary or artistic work.
|
||||
|
||||
You means an individual or entity exercising rights under this License
|
||||
who has not previously violated the terms of this License with respect
|
||||
to the Work, or who has received express permission from the Licensor to
|
||||
exercise rights under this License despite a previous violation.
|
||||
|
||||
Publicly Perform means to perform public recitations of the Work and to
|
||||
communicate to the public those public recitations, by any means or
|
||||
process, including by wire or wireless means or public digital
|
||||
performances; to make available to the public Works in such a way that
|
||||
members of the public may access these Works from a place and at a place
|
||||
individually chosen by them; to perform the Work to the public by any
|
||||
means or process and the communication to the public of the performances
|
||||
of the Work, including by public digital performance; to broadcast and
|
||||
rebroadcast the Work by any means including signs, sounds or images.
|
||||
|
||||
Reproduce is to make copies of the Work by any means including without
|
||||
limitation by sound or visual recordings and the right of fixation and
|
||||
reproducing fixations of the Work, including storage of a protected
|
||||
performance or phonogram in digital form or other electronic medium.
|
||||
|
||||
Software is any digital Work which, through use of a third-party piece
|
||||
of Software or through the direct usage of itself on a computer system,
|
||||
the memory of the computer is modified dynamically or semi-dynamically.
|
||||
\"Software\", secondly, processes or interprets information.
|
||||
|
||||
Source Code is Any digital Work which, through use of a third-party
|
||||
piece of Software or through the direct usage of itself on a computer
|
||||
system, the memory of the computer is modified dynamically or
|
||||
semi-dynamically. \"Software\", secondly, processes or interprets
|
||||
information.
|
||||
|
||||
Surveilling is the use of the Work to either overtly or covertly observe
|
||||
and record persons and or their activities.
|
||||
|
||||
A Network Service is the use of a piece of Software to interpret or
|
||||
modify information that is subsequently and directly served to users
|
||||
over the Internet.
|
||||
|
||||
To Discriminate is the use of a piece of Software to interpret or modify
|
||||
information that is subsequently and directly served to users over the
|
||||
Internet.
|
||||
|
||||
Hate Speech is Communication or any form of expression which is solely
|
||||
for the purpose of expressing hatred for some group or advocating a form
|
||||
of Discrimination between humans.
|
||||
|
||||
Coercion is leveraging of the threat of force or use of force to
|
||||
intimidate a person in order to gain compliance, or to offer large
|
||||
incentives which aim to entice a person to act against their will.
|
||||
|
||||
# Fair Dealing Rights
|
||||
|
||||
Nothing in this License is intended to reduce, limit, or restrict any
|
||||
uses free from copyright or rights arising from limitations or
|
||||
exceptions that are provided for in connection with the copyright
|
||||
protection under copyright law or other applicable laws.
|
||||
|
||||
# License Grant
|
||||
|
||||
Subject to the terms and conditions of this License, Licensor hereby
|
||||
grants You a worldwide, royalty-free, non-exclusive, perpetual (for the
|
||||
duration of the applicable copyright) license to exercise the rights in
|
||||
the Work as stated below:
|
||||
|
||||
To Reproduce the Work, to incorporate the Work into one or more
|
||||
Collections, and to Reproduce the Work as incorporated in the
|
||||
Collections
|
||||
|
||||
To create and Reproduce Adaptations provided that any such Adaptation,
|
||||
including any translation in any medium, takes reasonable steps to
|
||||
clearly label, demarcate or otherwise identify that changes were made to
|
||||
the original Work. For example, a translation could be marked \"The
|
||||
original work was translated from English to Spanish,\" or a
|
||||
modification could indicate \"The original work has been modified.\"
|
||||
|
||||
To Distribute and Publicly Perform the Work including as incorporated in
|
||||
Collections.
|
||||
|
||||
To Distribute and Publicly Perform Adaptations. The above rights may be
|
||||
exercised in all media and formats whether now known or hereafter
|
||||
devised. The above rights include the right to make such modifications
|
||||
as are technically necessary to exercise the rights in other media and
|
||||
formats. This License constitutes the entire agreement between the
|
||||
parties with respect to the Work licensed here. There are no
|
||||
understandings, agreements or representations with respect to the Work
|
||||
not specified here. Licensor shall not be bound by any additional
|
||||
provisions that may appear in any communication from You. This License
|
||||
may not be modified without the mutual written agreement of the Licensor
|
||||
and You. All rights not expressly granted by Licensor are hereby
|
||||
reserved, including but not limited to the rights set forth in
|
||||
Non-waivable Compulsory License Schemes, Waivable Compulsory License
|
||||
Schemes, and Voluntary License Schemes in the restrictions.
|
||||
|
||||
# Restrictions
|
||||
|
||||
The license granted in the license grant above is expressly made subject
|
||||
to and limited by the following restrictions:
|
||||
|
||||
You may Distribute or Publicly Perform the Work only under the terms of
|
||||
this License. You must include a copy of, or the Uniform Resource
|
||||
Identifier (URI) for, this License with every copy of the Work You
|
||||
Distribute or Publicly Perform. You may not offer or impose any terms on
|
||||
the Work that restrict the terms of this License or the ability of the
|
||||
recipient of the Work to exercise the rights granted to that recipient
|
||||
under the terms of the License. You may not sublicense the Work. You
|
||||
must keep intact all notices that refer to this License and to the
|
||||
disclaimer of warranties with every copy of the Work You Distribute or
|
||||
Publicly Perform. When You Distribute or Publicly Perform the Work, You
|
||||
may not impose any effective technological measures on the Work that
|
||||
restrict the ability of a recipient of the Work from You to exercise the
|
||||
rights granted to that recipient under the terms of the License. This
|
||||
Section applies to the Work as incorporated in a Collection, but this
|
||||
does not require the Collection apart from the Work itself to be made
|
||||
subject to the terms of this License. If You create a Collection, upon
|
||||
notice from any Licensor You must, to the extent practicable, remove
|
||||
from the Collection any credit as requested. If You create an
|
||||
Adaptation, upon notice from any Licensor You must, to the extent
|
||||
practicable, remove from the Adaptation any credit as requested.
|
||||
|
||||
## Commercial Restrictions
|
||||
|
||||
You may not exercise any of the rights granted to You in the above
|
||||
section in any manner that is primarily intended for or directed toward
|
||||
commercial advantage or private monetary compensation unless you meet
|
||||
the following requirements.
|
||||
|
||||
i. You are a worker-owned business or worker-owned collective.
|
||||
|
||||
ii. after tax, all financial gain, surplus, profits and benefits
|
||||
produced by the business or collective are distributed among the
|
||||
worker-owners unless a set amount is to be allocated towards
|
||||
community projects as decided by a previously-established consensus
|
||||
agreement between the worker-owners where all worker-owners agreed.
|
||||
|
||||
iii. You are not using such rights on behalf of a business other than
|
||||
those specified in (i) or (ii) above, nor are using such rights as
|
||||
a proxy on behalf of a business with the intent to circumvent the
|
||||
aforementioned restrictions on such a business.
|
||||
|
||||
The exchange of the Work for other copyrighted works by means of digital
|
||||
file-sharing or otherwise shall not be considered to be intended for or
|
||||
directed toward commercial advantage or private monetary compensation,
|
||||
provided there is no payment of any monetary compensation in connection
|
||||
with the exchange of copyrighted works.
|
||||
|
||||
If the Work meets the definition of Software, You may exercise the
|
||||
rights granted in the license grant only if You provide a copy of the
|
||||
corresponding Source Code from which the Work was derived in digital
|
||||
form, or You provide a URI for the corresponding Source Code of the
|
||||
Work, to any recipients upon request.
|
||||
|
||||
If the Work is used as or for a Network Service, You may exercise the
|
||||
rights granted in the license grant only if You provide a copy of the
|
||||
corresponding Source Code from which the Work was derived in digital
|
||||
form, or You provide a URI for the corresponding Source Code to the
|
||||
Work, to any recipients of the data served or modified by the Web
|
||||
Service.
|
||||
|
||||
Any use by a business that is privately owned and managed, and that
|
||||
seeks to generate profit from the labor of employees paid by salary or
|
||||
other wages, is not permitted under this license.
|
||||
|
||||
##
|
||||
|
||||
You may exercise the rights granted in the license grant for any
|
||||
purposes only if:
|
||||
|
||||
i. You do not use the Work for the purpose of inflicting Bodily Harm on
|
||||
human beings (subject to criminal prosecution or otherwise) outside
|
||||
of providing medical aid or undergoing a voluntary procedure under
|
||||
no form of Coercion.
|
||||
|
||||
ii. You do not use the Work for the purpose of Surveilling or tracking
|
||||
individuals for financial gain.
|
||||
|
||||
iii. You do not use the Work in an Act of War.
|
||||
|
||||
iv. You do not use the Work for the purpose of supporting or profiting
|
||||
from an Act of War.
|
||||
|
||||
v. You do not use the Work for the purpose of Incarceration.
|
||||
|
||||
vi. You do not use the Work for the purpose of extracting, processing,
|
||||
or refining, oil, gas, or coal. Or to in any other way to
|
||||
deliberately pollute the environment as a byproduct of manufacturing
|
||||
or irresponsible disposal of hazardous materials.
|
||||
|
||||
vii. You do not use the Work for the purpose of expediting,
|
||||
coordinating, or facilitating paid work undertaken by individuals
|
||||
under the age of 12 years.
|
||||
|
||||
viii. You do not use the Work to either Discriminate or spread Hate
|
||||
Speech on the basis of sex, sexual orientation, gender identity,
|
||||
race, age, disability, color, national origin, religion, caste, or
|
||||
lower economic status.
|
||||
|
||||
##
|
||||
|
||||
If You Distribute, or Publicly Perform the Work or any Adaptations or
|
||||
Collections, You must, unless a request has been made by any Licensor to
|
||||
remove credit from a Collection or Adaptation, keep intact all copyright
|
||||
notices for the Work and provide, reasonable to the medium or means You
|
||||
are utilizing:
|
||||
|
||||
i. the name of the Original Author (or pseudonym, if applicable) if
|
||||
supplied, and/or if the Original Author and/or Licensor designate
|
||||
another party or parties (e.g., a sponsor institute, publishing
|
||||
entity, journal) for attribution (\"Attribution Parties\") in
|
||||
Licensor\'s copyright notice, terms of service or by other
|
||||
reasonable means, the name of such party or parties;
|
||||
|
||||
ii. the title of the Work if supplied;
|
||||
|
||||
iii. to the extent reasonably practicable, the URI, if any, that
|
||||
Licensor to be associated with the Work, unless such URI does not
|
||||
refer to the copyright notice or licensing information for the
|
||||
Work; and,
|
||||
|
||||
iv. in the case of an Adaptation, a credit identifying the use of the
|
||||
Work in the Adaptation (e.g., \"French translation of the Work by
|
||||
Original Author,\" or \"Screenplay based on original Work by
|
||||
Original Author\").
|
||||
|
||||
If any Licensor has sent notice to request removing credit, You must, to
|
||||
the extent practicable, remove any credit as requested. The credit
|
||||
required by this Section may be implemented in any reasonable manner;
|
||||
provided, however, that in the case of an Adaptation or Collection, at a
|
||||
minimum such credit will appear, if a credit for all contributing
|
||||
authors of the Adaptation or Collection appears, then as part of these
|
||||
credits and in a manner at least as prominent as the credits for the
|
||||
other contributing authors. For the avoidance of doubt, You may only use
|
||||
the credit required by this Section for the purpose of attribution in
|
||||
the manner set out above and, by exercising Your rights under this
|
||||
License, You may not implicitly or explicitly assert or imply any
|
||||
connection with, sponsorship or endorsement by the Original Author,
|
||||
Licensor and/or Attribution Parties, as appropriate, of You or Your use
|
||||
of the Work, without the separate, express prior written permission of
|
||||
the Original Author, Licensor and/or Attribution Parties.
|
||||
|
||||
Non-waivable Compulsory License Schemes. In those jurisdictions in which
|
||||
the right to collect royalties through any statutory or compulsory
|
||||
licensing scheme cannot be waived, the Licensor reserves the exclusive
|
||||
right to collect such royalties for any exercise by You of the rights
|
||||
granted under this License
|
||||
|
||||
Waivable Compulsory License Schemes. In those jurisdictions in which the
|
||||
right to collect royalties through any statutory or compulsory licensing
|
||||
scheme can be waived, the Licensor reserves the exclusive right to
|
||||
collect such royalties for any exercise by You of the rights granted
|
||||
under this License if Your exercise of such rights is for a purpose or
|
||||
use which is otherwise than noncommercial as permitted under Commercial
|
||||
Restrictions and otherwise waives the right to collect royalties through
|
||||
any statutory or compulsory licensing scheme.
|
||||
|
||||
Voluntary License Schemes. The Licensor reserves the right to collect
|
||||
royalties, whether individually or, in the event that the Licensor is a
|
||||
member of a collecting society that administers voluntary licensing
|
||||
schemes, via that society, from any exercise by You of the rights
|
||||
granted under this License that is for a purpose or use which is
|
||||
otherwise than noncommercial as permitted under the license grant.
|
||||
|
||||
Except as otherwise agreed in writing by the Licensor or as may be
|
||||
otherwise permitted by applicable law, if You Reproduce, Distribute or
|
||||
Publicly Perform the Work either by itself or as part of any Adaptations
|
||||
or Collections, You must not distort, mutilate, modify or take other
|
||||
derogatory action in relation to the Work which would be prejudicial to
|
||||
the Original Author\'s honor or reputation. Licensor agrees that in
|
||||
those jurisdictions (e.g. Japan), in which any exercise of the right
|
||||
granted in the license grant of this License (the right to make
|
||||
Adaptations) would be deemed to be a distortion, mutilation,
|
||||
modification or other derogatory action prejudicial to the Original
|
||||
Author\'s honor and reputation, the Licensor will waive or not assert,
|
||||
as appropriate, this Section, to the fullest extent permitted by the
|
||||
applicable national law, to enable You to reasonably exercise Your right
|
||||
under the license grant of this License (right to make Adaptations) but
|
||||
not otherwise.
|
||||
|
||||
Do not make any legal claim against anyone accusing the Work, with or
|
||||
without changes, alone or with other works, of infringing any patent
|
||||
claim.
|
||||
|
||||
# Representations Warranties and Disclaimer
|
||||
|
||||
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
|
||||
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
|
||||
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
|
||||
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
|
||||
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
|
||||
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE
|
||||
EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
|
||||
|
||||
# Limitation on Liability
|
||||
|
||||
EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL
|
||||
LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF
|
||||
THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED
|
||||
OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
# Termination
|
||||
|
||||
This License and the rights granted hereunder will terminate
|
||||
automatically upon any breach by You of the terms of this License.
|
||||
Individuals or entities who have received Adaptations or Collections
|
||||
from You under this License, however, will not have their licenses
|
||||
terminated provided such individuals or entities remain in full
|
||||
compliance with those licenses. The Sections on definitions, fair
|
||||
dealing rights, representations, warranties, and disclaimer, limitation
|
||||
on liability, termination, and revised license versions will survive any
|
||||
termination of this License.
|
||||
|
||||
Subject to the above terms and conditions, the license granted here is
|
||||
perpetual (for the duration of the applicable copyright in the Work).
|
||||
Notwithstanding the above, Licensor reserves the right to release the
|
||||
Work under different license terms or to stop distributing the Work at
|
||||
any time; provided, however that any such election will not serve to
|
||||
withdraw this License (or any other license that has been, or is
|
||||
required to be, granted under the terms of this License), and this
|
||||
License will continue in full force and effect unless terminated as
|
||||
stated above.
|
||||
|
||||
# Revised License Versions
|
||||
|
||||
This License may receive future revisions in the original spirit of the
|
||||
license intended to strengthen This License. Each version of This
|
||||
License has an incrementing version number.
|
||||
|
||||
Unless otherwise specified like in the below subsection The Licensor has
|
||||
only granted this current version of This License for The Work. In this
|
||||
case future revisions do not apply.
|
||||
|
||||
The Licensor may specify that the latest available revision of This
|
||||
License be used for The Work by either explicitly writing so or by
|
||||
suffixing the License URI with a \"+\" symbol.
|
||||
|
||||
The Licensor may specify that The Work is also available under the terms
|
||||
of This License\'s current revision as well as specific future
|
||||
revisions. The Licensor may do this by writing it explicitly or
|
||||
suffixing the License URI with any additional version numbers each
|
||||
separated by a comma.
|
||||
|
||||
# Miscellaneous
|
||||
|
||||
Each time You Distribute or Publicly Perform the Work or a Collection,
|
||||
the Licensor offers to the recipient a license to the Work on the same
|
||||
terms and conditions as the license granted to You under this License.
|
||||
|
||||
Each time You Distribute or Publicly Perform an Adaptation, Licensor
|
||||
offers to the recipient a license to the original Work on the same terms
|
||||
and conditions as the license granted to You under this License.
|
||||
|
||||
If the Work is classified as Software, each time You Distribute or
|
||||
Publicly Perform an Adaptation, Licensor offers to the recipient a copy
|
||||
and/or URI of the corresponding Source Code on the same terms and
|
||||
conditions as the license granted to You under this License.
|
||||
|
||||
If the Work is used as a Network Service, each time You Distribute or
|
||||
Publicly Perform an Adaptation, or serve data derived from the Software,
|
||||
the Licensor offers to any recipients of the data a copy and/or URI of
|
||||
the corresponding Source Code on the same terms and conditions as the
|
||||
license granted to You under this License.
|
||||
|
||||
If any provision of this License is invalid or unenforceable under
|
||||
applicable law, it shall not affect the validity or enforceability of
|
||||
the remainder of the terms of this License, and without further action
|
||||
by the parties to this agreement, such provision shall be reformed to
|
||||
the minimum extent necessary to make such provision valid and
|
||||
enforceable.
|
||||
|
||||
No term or provision of this License shall be deemed waived and no
|
||||
breach consented to unless such waiver or consent shall be in writing
|
||||
and signed by the party to be charged with such waiver or consent.
|
||||
|
||||
This License constitutes the entire agreement between the parties with
|
||||
respect to the Work licensed here. There are no understandings,
|
||||
agreements or representations with respect to the Work not specified
|
||||
here. Licensor shall not be bound by any additional provisions that may
|
||||
appear in any communication from You. This License may not be modified
|
||||
without the mutual written agreement of the Licensor and You.
|
||||
|
||||
The rights granted under, and the subject matter referenced, in this
|
||||
License were drafted utilizing the terminology of the Berne Convention
|
||||
for the Protection of Literary and Artistic Works (as amended on
|
||||
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
|
||||
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and
|
||||
the Universal Copyright Convention (as revised on July 24, 1971). These
|
||||
rights and subject matter take effect in the relevant jurisdiction in
|
||||
which the License terms are sought to be enforced according to the
|
||||
corresponding provisions of the implementation of those treaty
|
||||
provisions in the applicable national law. If the standard suite of
|
||||
rights granted under applicable copyright law includes additional rights
|
||||
not granted under this License, such additional rights are deemed to be
|
||||
included in the License; this License is not intended to restrict the
|
||||
license of any rights under applicable law.
|
6
pyproject.toml
Normal file
6
pyproject.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[build-system]
|
||||
requires = [
|
||||
"setuptools >= 38.3.0",
|
||||
"wheel"
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
2
pyvenv/__init__.py
Normal file
2
pyvenv/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
__software__ = 'PyVenv'
|
||||
__version__ = '0.1'
|
10
pyvenv/__main__.py
Normal file
10
pyvenv/__main__.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import sys
|
||||
|
||||
from .cli import main
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
sys.exit()
|
373
pyvenv/cli.py
Normal file
373
pyvenv/cli.py
Normal file
|
@ -0,0 +1,373 @@
|
|||
import click
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
from pathlib import Path
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from . import (
|
||||
__version__ as version,
|
||||
__software__ as software
|
||||
)
|
||||
|
||||
from .misc import Module
|
||||
from .env import PythonEnvironment
|
||||
from .watcher import VenvWatcher
|
||||
|
||||
|
||||
context_settings = dict(
|
||||
show_default = True,
|
||||
ignore_unknown_options = True,
|
||||
allow_extra_args = True
|
||||
)
|
||||
|
||||
|
||||
@click.group('cli', context_settings=context_settings)
|
||||
@click.option('--venv', help='The name of the venv to work with.', default=Path.cwd().name)
|
||||
@click.version_option(version=version, prog_name=software)
|
||||
@click.pass_context
|
||||
def cli(ctx, venv):
|
||||
ctx.obj = PythonEnvironment(venv)
|
||||
|
||||
|
||||
@cli.command('create')
|
||||
@click.pass_context
|
||||
def cli_create(ctx):
|
||||
'Create a new python venv'
|
||||
|
||||
click.echo(f'Setting up environment: {ctx.obj.name}')
|
||||
|
||||
ctx.obj.create()
|
||||
ctx.obj.save_config()
|
||||
|
||||
click.echo('Successfully created environment :3')
|
||||
|
||||
|
||||
@cli.command('destroy')
|
||||
@click.pass_context
|
||||
def cli_destroy(ctx):
|
||||
'Remove a python venv'
|
||||
|
||||
if not click.confirm(f'Are you sure you want to remove the venv at {ctx.obj.path}?'):
|
||||
return
|
||||
|
||||
ctx.obj.destroy()
|
||||
click.echo(f'Destroyed environment: {ctx.obj.name}')
|
||||
|
||||
|
||||
@cli.command('setup')
|
||||
@click.pass_context
|
||||
def cli_setup(ctx):
|
||||
'Install all modules'
|
||||
|
||||
if not ctx.obj.path.exists():
|
||||
ctx.obj.create()
|
||||
|
||||
ctx.obj.install_all_modules()
|
||||
|
||||
|
||||
@cli.command('run', context_settings=context_settings)
|
||||
@click.argument('command', nargs=-1)
|
||||
@click.pass_context
|
||||
def cli_run(ctx, command):
|
||||
'Run a command or module in the venv'
|
||||
|
||||
if len(command) == 0:
|
||||
return click.echo('Script or module name not provided')
|
||||
|
||||
cmd = ' '.join(command)
|
||||
print(cmd)
|
||||
|
||||
if Path(command[0]).is_file():
|
||||
ctx.obj.run_script(cmd)
|
||||
|
||||
else:
|
||||
ctx.obj.run_module(cmd)
|
||||
|
||||
|
||||
@cli.command('import')
|
||||
@click.option('--file', '-f', 'filename', help='The requirements file to import', default='requirements.txt')
|
||||
@click.pass_context
|
||||
def cli_import(ctx, filename):
|
||||
'Import module list from requirements.txt'
|
||||
|
||||
## This won't read a req file with the contents of "." yet
|
||||
with ctx.obj.workdir.joinpath(filename).open('r') as fd:
|
||||
for line in fd.readlines:
|
||||
mod = Module.new_from_string(line)
|
||||
ctx.obj.modules[mod.name] = mod
|
||||
|
||||
ctx.obj.save_config()
|
||||
|
||||
click.echo(f'Imported requirements file: {filename}')
|
||||
|
||||
|
||||
@cli.command('export')
|
||||
@click.option('--file', '-f', 'filename', help='The requirements file to import', default='requirements.txt')
|
||||
@click.pass_context
|
||||
def cli_export(ctx, filename):
|
||||
'Export module list to requirements.txt'
|
||||
|
||||
with ctx.obj.workdir.joinpath(filename).open('w') as fd:
|
||||
for module in ctx.obj.modules.values():
|
||||
fd.write(module.compile() + '\n')
|
||||
|
||||
click.echo(f'Exported requirements file: {filename}')
|
||||
|
||||
|
||||
@cli.command('edit')
|
||||
@click.option('--editor', '-e', help='The text editor to use for config editing', default=os.environ.get('EDITOR'))
|
||||
@click.pass_context
|
||||
def cli_edit(ctx, editor):
|
||||
'Edit pyvenv.cfg file in current directory'
|
||||
|
||||
click.edit(editor=editor, require_save=True, filename=ctx.obj.cfgpath)
|
||||
|
||||
|
||||
@cli.command('fix')
|
||||
@click.pass_context
|
||||
def cli_fix(ctx):
|
||||
'Fetch the version number for any modules without a specifed version'
|
||||
|
||||
for mod in ctx.obj.modules.values():
|
||||
if mod.version:
|
||||
continue
|
||||
|
||||
mod.version = ctx.obj.module_version(mod.name)
|
||||
|
||||
ctx.obj.save_config()
|
||||
|
||||
|
||||
@cli.command('list')
|
||||
@click.pass_context
|
||||
def cli_list(ctx):
|
||||
'List modules to be used in this venv'
|
||||
|
||||
for module in ctx.obj.modules.values():
|
||||
installed_version = ctx.obj.module_version(module.name)
|
||||
text = module.name
|
||||
|
||||
if module.version:
|
||||
text += f'=={module.version}'
|
||||
|
||||
if not installed_version:
|
||||
text += f' (not installed)'
|
||||
|
||||
elif module.version != installed_version:
|
||||
text += f' (installed: {installed_version})'
|
||||
|
||||
click.echo(text)
|
||||
|
||||
|
||||
@cli.command('add')
|
||||
@click.argument('module', nargs=-1)
|
||||
@click.pass_context
|
||||
def cli_add(ctx, module):
|
||||
'Add a module to the list'
|
||||
|
||||
module = ' '.join(module)
|
||||
mod = Module.new_from_string(module)
|
||||
|
||||
ctx.obj.modules[mod.name] = mod
|
||||
ctx.obj.save_config()
|
||||
|
||||
click.echo(f'Added module: {mod.name}')
|
||||
|
||||
|
||||
@cli.command('remove')
|
||||
@click.argument('module', nargs=-1)
|
||||
@click.pass_context
|
||||
def cli_remove(ctx, module):
|
||||
'Remove a module from the list'
|
||||
|
||||
module = ' '.join(module)
|
||||
mod = Module.new_from_string(module)
|
||||
|
||||
try:
|
||||
del ctx.obj.modules[mod.name]
|
||||
ctx.obj.save_config()
|
||||
|
||||
except KeyError:
|
||||
click.echo(f'Module was never installed: {mod.name}')
|
||||
|
||||
|
||||
@cli.command('install')
|
||||
@click.argument('module', nargs=-1)
|
||||
@click.pass_context
|
||||
def cli_install(ctx, module):
|
||||
'Install a module and add it to the list'
|
||||
|
||||
module = ' '.join(module)
|
||||
ctx.obj.add_module(module)
|
||||
|
||||
|
||||
@cli.command('uninstall')
|
||||
@click.argument('module', nargs=-1)
|
||||
@click.pass_context
|
||||
def cli_uninstall(ctx, module):
|
||||
'Uninstall a module and remove it from the list'
|
||||
|
||||
module = ' '.join(module)
|
||||
ctx.obj.remove_module(module)
|
||||
|
||||
|
||||
|
||||
@cli.group('watcher')
|
||||
@click.pass_context
|
||||
def cli_watcher(ctx):
|
||||
'Manage the process watcher'
|
||||
|
||||
|
||||
@cli_watcher.command('get')
|
||||
@click.argument('key', required=False)
|
||||
@click.pass_context
|
||||
def cli_watcher_get(ctx, key=None):
|
||||
'Check the value of a watcher config option'
|
||||
|
||||
if key:
|
||||
if key not in ctx.obj.watcher:
|
||||
click.echo(f'Invalid watcher config option: {key}')
|
||||
return
|
||||
|
||||
items = [(key, ctx.obj.watcher[key])]
|
||||
|
||||
else:
|
||||
items = ctx.obj.watcher.items()
|
||||
|
||||
for key, value in items:
|
||||
if isinstance(value, list):
|
||||
value = ', '.join(value)
|
||||
|
||||
click.echo(f'{key.ljust(14)} = {value}')
|
||||
|
||||
|
||||
@cli_watcher.command('set')
|
||||
@click.argument('key')
|
||||
@click.argument('value', nargs=-1, required=False)
|
||||
@click.pass_context
|
||||
def cli_watcher_set(ctx, key, value=()):
|
||||
'Set a watcher config option'
|
||||
|
||||
if key not in ['command', 'path']:
|
||||
click.echo(f'Invalid watcher config option: {key}')
|
||||
return
|
||||
|
||||
value = ' '.join(value)
|
||||
|
||||
try:
|
||||
value = ctx.obj.watcher_set_config(key, value)
|
||||
ctx.obj.save_config()
|
||||
|
||||
except KeyError:
|
||||
click.echo(f'Invalid watcher config option: {key}')
|
||||
return
|
||||
|
||||
click.echo(f'Set watcher config option: {key} = {value}')
|
||||
|
||||
|
||||
@cli_watcher.command('append')
|
||||
@click.argument('key')
|
||||
@click.argument('values', nargs=-1, required=True)
|
||||
@click.pass_context
|
||||
def cli_watcher_append(ctx, key, values):
|
||||
'Append values onto list options'
|
||||
|
||||
if key not in ['ext', 'ignore_dirs', 'ignore_files']:
|
||||
click.echo(f'Invalid watcher config option: {key}')
|
||||
return
|
||||
|
||||
if not len(values):
|
||||
click.echo(f'Please provide at least one value')
|
||||
return
|
||||
|
||||
for value in values:
|
||||
if value not in ctx.obj.watcher[key]:
|
||||
ctx.obj.watcher[key].append(value)
|
||||
|
||||
ctx.obj.save_config()
|
||||
|
||||
value_string = ', '.join(ctx.obj.watcher[key])
|
||||
click.echo(f'Set watcher config option: {key} = {value_string}')
|
||||
|
||||
|
||||
@cli_watcher.command('append')
|
||||
@click.argument('key')
|
||||
@click.argument('values', nargs=-1, required=True)
|
||||
@click.pass_context
|
||||
def cli_watcher_append(ctx, key, values):
|
||||
'Append values to list options'
|
||||
|
||||
if key not in ['ext', 'ignore_dirs', 'ignore_files']:
|
||||
click.echo(f'Invalid watcher config option: {key}')
|
||||
return
|
||||
|
||||
if not len(values):
|
||||
click.echo(f'Please provide at least one value')
|
||||
return
|
||||
|
||||
for value in values:
|
||||
if value not in ctx.obj.watcher[key]:
|
||||
ctx.obj.watcher[key].append(value)
|
||||
|
||||
ctx.obj.save_config()
|
||||
|
||||
value_string = ', '.join(ctx.obj.watcher[key])
|
||||
click.echo(f'Set watcher config option: {key} = {value_string}')
|
||||
|
||||
|
||||
@cli_watcher.command('remove')
|
||||
@click.argument('key')
|
||||
@click.argument('values', nargs=-1, required=True)
|
||||
@click.pass_context
|
||||
def cli_watcher_remove(ctx, key, values):
|
||||
'Remove values from list options'
|
||||
|
||||
if key not in ['ext', 'ignore_dirs', 'ignore_files']:
|
||||
click.echo(f'Invalid watcher config option: {key}')
|
||||
return
|
||||
|
||||
if not len(values):
|
||||
click.echo(f'Please provide at least one value')
|
||||
return
|
||||
|
||||
for value in values:
|
||||
try:
|
||||
ctx.obj.watcher[key].remove(value)
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
ctx.obj.save_config()
|
||||
|
||||
value_string = ', '.join(ctx.obj.watcher[key])
|
||||
click.echo(f'Set watcher config option: {key} = {value_string}')
|
||||
|
||||
|
||||
@cli_watcher.command('run')
|
||||
@click.pass_context
|
||||
def cli_watcher_run(ctx):
|
||||
'Watch the directory for changes and restart the specified program'
|
||||
|
||||
handler = VenvWatcher(ctx.obj)
|
||||
|
||||
watcher = Observer()
|
||||
watcher.schedule(handler, str(ctx.obj.watcher.path), recursive=True)
|
||||
watcher.start()
|
||||
|
||||
handler.run_proc()
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(0.1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
handler.kill_proc()
|
||||
watcher.stop()
|
||||
watcher.join()
|
||||
|
||||
|
||||
def main():
|
||||
cli(prog_name='pyvenv')
|
210
pyvenv/env.py
Executable file
210
pyvenv/env.py
Executable file
|
@ -0,0 +1,210 @@
|
|||
import json
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from .misc import DotDict, Module, Process
|
||||
|
||||
|
||||
watcher_defaults = DotDict(
|
||||
path = '.',
|
||||
command = None,
|
||||
ext = ['py', 'pyx', 'pyc'],
|
||||
ignore_dirs = ['build', 'config', 'data'],
|
||||
ignore_files = ['reload.py', 'test.py', 'pyvenv.py']
|
||||
)
|
||||
|
||||
|
||||
class PythonEnvironment:
|
||||
cfgpath = Path('pyvenv.json').resolve()
|
||||
envroot = Path.home().joinpath('.local/share/python-env')
|
||||
workdir = Path.cwd()
|
||||
|
||||
|
||||
def __init__(self, name, config_file=None, envroot=None):
|
||||
self.name = name
|
||||
self.modules = DotDict()
|
||||
self.watcher = DotDict(
|
||||
path = '.',
|
||||
command = name,
|
||||
ext = ['py', 'pyx', 'pyc'],
|
||||
ignore_dirs = ['build', 'config', 'data'],
|
||||
ignore_files = ['reload.py', 'test.py', 'pyvenv.py']
|
||||
)
|
||||
|
||||
if config_file:
|
||||
self.cfgpath = Path(config_file)
|
||||
|
||||
if envroot:
|
||||
self.envroot = Path(envroot)
|
||||
|
||||
self.load_config()
|
||||
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self.envroot.joinpath(self.name)
|
||||
|
||||
|
||||
@property
|
||||
def executable(self):
|
||||
return self.path.joinpath('bin/python')
|
||||
|
||||
|
||||
def create(self):
|
||||
Process(f'{sys.executable} -m venv --upgrade-deps {self.path}')
|
||||
|
||||
if not self.executable.exists():
|
||||
raise FileNotFoundError(f'Failed to create venv: {self.name}')
|
||||
|
||||
self.install_module('wheel')
|
||||
self.install_all_modules()
|
||||
|
||||
|
||||
def destroy(self):
|
||||
if self.path:
|
||||
shutil.rmtree(self.path)
|
||||
|
||||
|
||||
def run_python(self, command, *args, **kwargs):
|
||||
if not self.executable.exists():
|
||||
raise FileNotFoundError('Cannot find python executable. Was the environment created?')
|
||||
|
||||
return Process(f'{self.executable} {command}', *args, **kwargs)
|
||||
|
||||
|
||||
def run_script(self, script):
|
||||
return self.run_python(script)
|
||||
|
||||
|
||||
def run_module(self, module, *args):
|
||||
text = f'-m {module}'
|
||||
|
||||
for arg in args:
|
||||
text += f' {arg}'
|
||||
|
||||
return self.run_python(text)
|
||||
|
||||
|
||||
def install_module(self, requirement, update=False):
|
||||
if update:
|
||||
return self.run_module('pip', f'install -U {requirement}')
|
||||
|
||||
else:
|
||||
return self.run_module('pip', f'install {requirement}')
|
||||
|
||||
|
||||
def uninstall_module(self, name):
|
||||
return self.run_module('pip', f'uninstall -y {name}')
|
||||
|
||||
|
||||
def install_all_modules(self):
|
||||
for module in self.modules.values():
|
||||
installed_version = self.module_version(module.name)
|
||||
|
||||
if not installed_version:
|
||||
self.install_module(module.compile())
|
||||
|
||||
elif module.version and module.version != installed_version:
|
||||
self.install_module(module.compile())
|
||||
|
||||
|
||||
def add_module(self, name, version=None, options=[], url=None):
|
||||
if not version and not options and not url:
|
||||
module = Module.new_from_string(name)
|
||||
|
||||
else:
|
||||
module = Module(name, version, options, url)
|
||||
|
||||
if not self.module_version(module.name):
|
||||
self.install_module(module.compile())
|
||||
|
||||
if not module.version:
|
||||
module.version = self.module_version(module.name)
|
||||
|
||||
self.modules[module.name] = module
|
||||
self.save_config()
|
||||
|
||||
|
||||
def remove_module(self, name):
|
||||
if not self.modules.get(name):
|
||||
raise KeyError(f'module "{name}" not added to module list')
|
||||
|
||||
del self.modules[name]
|
||||
|
||||
self.uninstall_module(name)
|
||||
self.save_config()
|
||||
|
||||
|
||||
def module_version(self, name):
|
||||
exec_string = f'from importlib.metadata import distribution; print(distribution("{name}").version)'
|
||||
|
||||
proc = Process(f'{self.executable} -c \'{exec_string}\'', capture_output=True)
|
||||
return proc.stdout.strip()
|
||||
|
||||
|
||||
def create_script(self):
|
||||
script = Path('python')
|
||||
|
||||
if script.is_symlink():
|
||||
return False
|
||||
|
||||
with Path('python').open('w') as fd:
|
||||
fd.write(f'#!/bin/sh\n')
|
||||
fd.write(f'{self.executable} $@')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def create_symlink(self):
|
||||
symlink = Path('python')
|
||||
|
||||
if symlink.exists():
|
||||
symlink.unlink()
|
||||
|
||||
symlink.symlink_to(self.envroot.joinpath('bin/python'))
|
||||
return True
|
||||
|
||||
|
||||
def watcher_set_config(self, key, value=None):
|
||||
if key not in self.watcher:
|
||||
raise KeyError(f'Invalid watcher config option: {key}')
|
||||
|
||||
if not value:
|
||||
self.watcher[key] = watcher_defaults[key]
|
||||
|
||||
else:
|
||||
self.watcher[key] = value
|
||||
|
||||
return self.watcher[key]
|
||||
|
||||
|
||||
def save_config(self):
|
||||
config = dict(
|
||||
name = self.name,
|
||||
modules = self.modules,
|
||||
watcher = self.watcher
|
||||
)
|
||||
|
||||
with self.cfgpath.open('w') as fd:
|
||||
json.dump(config, fd, indent='\t')
|
||||
|
||||
|
||||
def load_config(self):
|
||||
try:
|
||||
with self.cfgpath.open('r') as fd:
|
||||
config = json.load(fd)
|
||||
|
||||
except FileNotFoundError:
|
||||
return
|
||||
|
||||
self.name = config['name']
|
||||
|
||||
for name, info in config.get('modules', {}).items():
|
||||
self.modules[name] = Module(name, **info)
|
||||
|
||||
for key, value in config.get('watcher', {}).items():
|
||||
if key in self.watcher:
|
||||
self.watcher[key] = value
|
||||
|
283
pyvenv/misc.py
Normal file
283
pyvenv/misc.py
Normal file
|
@ -0,0 +1,283 @@
|
|||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import pkg_resources as pkg
|
||||
|
||||
|
||||
def run_command(command, *args, **kwargs):
|
||||
assert isinstance(command, (str, tuple, list))
|
||||
|
||||
if 'check' not in kwargs:
|
||||
kwargs['check'] = True
|
||||
|
||||
if 'capture_output' not in kwargs:
|
||||
kwargs['capture_output'] = True
|
||||
|
||||
if 'text' not in kwargs:
|
||||
kwargs['text'] = True
|
||||
|
||||
return subprocess.run(shlex.split(command) if isinstance(command, str) else command, *args, **kwargs)
|
||||
|
||||
|
||||
def signal_handler(func, *args, original_args=False, **kwargs):
|
||||
if func:
|
||||
if original_args:
|
||||
handler = lambda signum, frame: func(signum, frame, *args, **kwargs)
|
||||
|
||||
else:
|
||||
handler = lambda *_: func(*args, **kwargs)
|
||||
|
||||
else:
|
||||
handler = signal.SIG_DFL
|
||||
|
||||
signal.signal(signal.SIGHUP, handler)
|
||||
signal.signal(signal.SIGINT, handler)
|
||||
signal.signal(signal.SIGQUIT, handler)
|
||||
signal.signal(signal.SIGTERM, handler)
|
||||
|
||||
|
||||
class DotDict(dict):
|
||||
def __setitem__(self, key, value):
|
||||
if type(value) == dict:
|
||||
value = DotDict(value)
|
||||
|
||||
super().__setitem__(key, value)
|
||||
|
||||
|
||||
def __getattr__(self, key):
|
||||
return self[key]
|
||||
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key.startswith('_'):
|
||||
return super().__setattr__(key, value)
|
||||
|
||||
self[key] = value
|
||||
|
||||
|
||||
class Module(DotDict):
|
||||
__slots__ = ['version', 'options', 'url']
|
||||
|
||||
def __init__(self, name, version=None, options=[], url=None):
|
||||
super().__init__(
|
||||
version = version,
|
||||
options = options,
|
||||
url = url
|
||||
)
|
||||
|
||||
self._name = name
|
||||
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
|
||||
@name.setter
|
||||
def name(self, value):
|
||||
self._name = value
|
||||
|
||||
|
||||
@classmethod
|
||||
def new_from_string(cls, string):
|
||||
try:
|
||||
module = list(pkg.parse_requirements(string))[0]
|
||||
except IndexError:
|
||||
return
|
||||
|
||||
try: version = module.specs[0][1]
|
||||
except IndexError: version = None
|
||||
|
||||
return cls(
|
||||
module.key,
|
||||
version,
|
||||
list(module.extras),
|
||||
module.url
|
||||
)
|
||||
|
||||
|
||||
def compile(self, with_version=True):
|
||||
text = self.name
|
||||
|
||||
if self.options:
|
||||
optline = ','.join(self.options)
|
||||
text += f'[{optline}]'
|
||||
|
||||
if self.url:
|
||||
text += f' @ {self.url}'
|
||||
|
||||
elif self.version and with_version:
|
||||
text += f'=={self.version}'
|
||||
|
||||
return text
|
||||
|
||||
|
||||
class Process:
|
||||
def __init__(self, command, *args, run=True, **kwargs):
|
||||
if isinstance(command, str):
|
||||
self.command = shlex.split(command)
|
||||
|
||||
elif isinstance(command, (tuple, list)):
|
||||
self.command = command
|
||||
|
||||
else:
|
||||
raise TypeError(f'Command must be a str, tuple, or list, not {type(command).__name__}')
|
||||
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self._proc = None
|
||||
|
||||
if run:
|
||||
self.run()
|
||||
|
||||
|
||||
def __enter__(self):
|
||||
self.run()
|
||||
return self
|
||||
|
||||
|
||||
def __exit__(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
@property
|
||||
def exitcode(self):
|
||||
return self._proc.returncode
|
||||
|
||||
|
||||
@property
|
||||
def stdout(self):
|
||||
try:
|
||||
return self._proc.stdout.decode('utf-8')
|
||||
|
||||
except AttributeError:
|
||||
return
|
||||
|
||||
|
||||
@property
|
||||
def stderr(self):
|
||||
try:
|
||||
return self._proc.stdout.decode('utf-8')
|
||||
|
||||
except AttributeError:
|
||||
return
|
||||
|
||||
|
||||
def readlines(self, pipe='stdout', text=True):
|
||||
if pipe not in ['stdout', 'stderr']:
|
||||
raise ValueError(f'Can only read "stdout" or "stderr", not {pipe}')
|
||||
|
||||
if not self._proc:
|
||||
return
|
||||
|
||||
for line in getattr(self, pipe).splitlines():
|
||||
yield line
|
||||
|
||||
|
||||
def run(self):
|
||||
if self._proc:
|
||||
return
|
||||
|
||||
self._proc = subprocess.run(self.command, *self.args, **self.kwargs)
|
||||
|
||||
|
||||
class ProcessHandler:
|
||||
def __init__(self, command, *args, **kwargs):
|
||||
self.proc = None
|
||||
self.command = shlex.split(command)
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
if 'shell' not in self.kwargs:
|
||||
self.kwargs['shell'] = True
|
||||
|
||||
self._end_proc = False
|
||||
|
||||
|
||||
def __enter__(self):
|
||||
self.start()
|
||||
return self
|
||||
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self.stop()
|
||||
|
||||
|
||||
@property
|
||||
def exitcode(self):
|
||||
try: return self.proc.poll()
|
||||
except AttributeError: 0
|
||||
|
||||
|
||||
@property
|
||||
def stdout(self):
|
||||
return self.proc.stdout
|
||||
|
||||
|
||||
@property
|
||||
def stderr(self):
|
||||
return self.proc.stderr
|
||||
|
||||
|
||||
def start(self):
|
||||
if self.proc:
|
||||
return
|
||||
|
||||
signal_handler(self.stop)
|
||||
self._end_proc = False
|
||||
self.proc = subprocess.Popen(self.command, *self.args, **self.kwargs)
|
||||
|
||||
|
||||
def run(self):
|
||||
if not self.proc:
|
||||
self.start()
|
||||
|
||||
while True:
|
||||
if self.exitcode != None or self._end_proc:
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
return self.exitcode
|
||||
|
||||
|
||||
def stop(self):
|
||||
if not self.proc:
|
||||
return
|
||||
|
||||
self._end_proc = True
|
||||
self.proc.terminate()
|
||||
|
||||
count = 0
|
||||
|
||||
while self.exitcode == None:
|
||||
count += 1
|
||||
time.sleep(0.1)
|
||||
|
||||
if count >= 10:
|
||||
self.proc.kill()
|
||||
break
|
||||
|
||||
signal_handler(None)
|
||||
self.proc = None
|
||||
|
||||
|
||||
def read(self, pipe='stdout', text=True):
|
||||
if pipe.lower() not in ['stdout', 'stderr']:
|
||||
raise ValueError(f'Can only read "stdout" or "stderr", not {pipe}')
|
||||
|
||||
if not self.proc:
|
||||
return
|
||||
|
||||
data = self.proc.stdout.read() if pipe == 'stdout' else self.proc.stderr.read()
|
||||
|
||||
if text:
|
||||
try:
|
||||
return data.decode('utf-8').strip()
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
return data
|
86
pyvenv/watcher.py
Normal file
86
pyvenv/watcher.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
import logging
|
||||
import shlex
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
format = '%(levelname)s: %(message)s'
|
||||
)
|
||||
|
||||
|
||||
class VenvWatcher(FileSystemEventHandler):
|
||||
#patterns = [f'*.{ext}' for ext in config.watch_ext]
|
||||
|
||||
proc = None
|
||||
last_restart = None
|
||||
|
||||
|
||||
def __init__(self, env):
|
||||
super().__init__()
|
||||
|
||||
self.env = env
|
||||
|
||||
|
||||
def on_any_event(self, event):
|
||||
if event.event_type not in ['modified', 'created', 'deleted']:
|
||||
logging.debug(f'Ignored event: {event.event_type}')
|
||||
return
|
||||
|
||||
path = Path(event.src_path)
|
||||
directory = str(path.parent).replace(str(self.env.watcher.path), '')
|
||||
|
||||
if not set(self.env.watcher.ignore_dirs).intersection(directory.split('/')):
|
||||
for filename in self.env.watcher.ignore_files:
|
||||
if path.name == filename:
|
||||
logging.debug(f'Ignored file name: {filename}')
|
||||
return
|
||||
|
||||
self.run_proc(restart=True)
|
||||
|
||||
|
||||
def kill_proc(self):
|
||||
if self.proc.poll() != None:
|
||||
return
|
||||
|
||||
logging.info(f'Terminating process {self.proc.pid}')
|
||||
self.proc.terminate()
|
||||
sec = 0.0
|
||||
|
||||
while self.proc.poll() == None:
|
||||
time.sleep(0.1)
|
||||
sec += 0.1
|
||||
|
||||
if sec >= 5:
|
||||
logging.error('Failed to terminate. Killing process...')
|
||||
self.proc.kill()
|
||||
break
|
||||
|
||||
logging.info('Process terminated')
|
||||
|
||||
|
||||
def run_proc(self, restart=False):
|
||||
timestamp = datetime.timestamp(datetime.now())
|
||||
self.last_restart = timestamp if not self.last_restart else 0
|
||||
|
||||
if restart == True and self.proc.pid != '':
|
||||
if timestamp - 3 < self.last_restart:
|
||||
logging.debug('Process restarted less than 3 sec ago')
|
||||
return
|
||||
|
||||
self.kill_proc()
|
||||
|
||||
command = shlex.split(self.env.watcher.command)
|
||||
|
||||
if Path(command[0]).is_file():
|
||||
self.proc = subprocess.Popen([self.env.executable, *command])
|
||||
|
||||
else:
|
||||
self.proc = subprocess.Popen([self.env.executable, '-m', *command])
|
||||
|
||||
self.last_restart = timestamp
|
||||
logging.info(f'Started process with PID {self.proc.pid}')
|
32
setup.cfg
Normal file
32
setup.cfg
Normal file
|
@ -0,0 +1,32 @@
|
|||
[metadata]
|
||||
name = PyVenv
|
||||
version = 0.1
|
||||
description = Python vnv manager
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown; charset=UTF-8
|
||||
url = https://git.barkshark.xyz/izaliamae/pyvenv
|
||||
license = CNPL7+
|
||||
license_file = LICENSE.md
|
||||
classifiers =
|
||||
Environment :: Console
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
project_urls =
|
||||
Bug Tracker = https://git.barkshark.xyz/izaliamae/pyvenv/issues
|
||||
Documentation = https://git.barkshark.xyz/izaliamae/pyvenv/wiki
|
||||
Source Code = https://git.barkshark.xyz/izaliamae/pyvenv
|
||||
|
||||
[options]
|
||||
zip_safe = True
|
||||
packages = pyvenv
|
||||
python_requires = >=3.6
|
||||
install_requires =
|
||||
setuptools
|
||||
click >= 8.0.0
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
pyvenv = pyvenv.cli:main
|
Loading…
Reference in a new issue