plain.dev 0.18.1__tar.gz → 0.19.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plain_dev-0.18.1 → plain_dev-0.19.0}/LICENSE +14 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/PKG-INFO +1 -2
- plain_dev-0.19.0/plain/dev/__init__.py +4 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/cli.py +32 -4
- plain_dev-0.19.0/plain/dev/debug.py +41 -0
- plain_dev-0.19.0/plain/dev/entrypoints.py +17 -0
- plain_dev-0.19.0/plain/dev/pdb.py +129 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/pyproject.toml +2 -3
- {plain_dev-0.18.1 → plain_dev-0.19.0}/uv.lock +1 -12
- plain_dev-0.18.1/plain/dev/__init__.py +0 -5
- plain_dev-0.18.1/plain/dev/debug.py +0 -12
- plain_dev-0.18.1/plain/dev/entrypoints.py +0 -10
- {plain_dev-0.18.1 → plain_dev-0.19.0}/.gitignore +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/README.md +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/README.md +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/contribute/__init__.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/contribute/cli.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/db/__init__.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/db/cli.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/db/container.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/default_settings.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/gunicorn_logging.json +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/mkcert.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/pid.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/poncho/__init__.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/poncho/color.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/poncho/compat.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/poncho/manager.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/poncho/printer.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/poncho/process.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/precommit/__init__.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/precommit/cli.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/requests.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/services.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/templates/dev/requests.html +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/urls.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/utils.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/plain/dev/views.py +0 -0
- {plain_dev-0.18.1 → plain_dev-0.19.0}/tests/settings.py +0 -0
@@ -51,3 +51,17 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
51
51
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
52
52
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
53
53
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
54
|
+
|
55
|
+
|
56
|
+
## This package contains code forked from github.com/ionelmc/python-remote-pdb
|
57
|
+
|
58
|
+
BSD 2-Clause License
|
59
|
+
|
60
|
+
Copyright (c) 2014-2022, Ionel Cristian Mărieș. All rights reserved.
|
61
|
+
|
62
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
63
|
+
|
64
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
65
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
66
|
+
|
67
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -1,13 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: plain.dev
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.19.0
|
4
4
|
Summary: Local development tools for Plain.
|
5
5
|
Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
|
6
6
|
License-Expression: BSD-3-Clause
|
7
7
|
License-File: LICENSE
|
8
8
|
Requires-Python: >=3.11
|
9
9
|
Requires-Dist: click>=8.0.0
|
10
|
-
Requires-Dist: debugpy~=1.6.3
|
11
10
|
Requires-Dist: gunicorn>20
|
12
11
|
Requires-Dist: plain<1.0.0
|
13
12
|
Requires-Dist: psycopg[binary]~=3.2.2
|
@@ -4,6 +4,7 @@ import os
|
|
4
4
|
import platform
|
5
5
|
import subprocess
|
6
6
|
import sys
|
7
|
+
import time
|
7
8
|
import tomllib
|
8
9
|
from importlib.metadata import entry_points
|
9
10
|
from importlib.util import find_spec
|
@@ -65,6 +66,32 @@ def cli(ctx, port, hostname, log_level):
|
|
65
66
|
sys.exit(returncode)
|
66
67
|
|
67
68
|
|
69
|
+
@cli.command()
|
70
|
+
def debug():
|
71
|
+
"""Connect to the remote debugger"""
|
72
|
+
|
73
|
+
def _connect():
|
74
|
+
if subprocess.run(["which", "nc"], capture_output=True).returncode == 0:
|
75
|
+
return subprocess.run(["nc", "-C", "localhost", "4444"])
|
76
|
+
else:
|
77
|
+
raise OSError("nc not found")
|
78
|
+
|
79
|
+
result = _connect()
|
80
|
+
|
81
|
+
# Try again once without a message
|
82
|
+
if result.returncode == 1:
|
83
|
+
time.sleep(1)
|
84
|
+
result = _connect()
|
85
|
+
|
86
|
+
# Keep trying...
|
87
|
+
while result.returncode == 1:
|
88
|
+
click.secho(
|
89
|
+
"Failed to connect. Make sure remote pdb is ready. Retrying...", fg="red"
|
90
|
+
)
|
91
|
+
result = _connect()
|
92
|
+
time.sleep(1)
|
93
|
+
|
94
|
+
|
68
95
|
@cli.command()
|
69
96
|
def services():
|
70
97
|
"""Start additional services defined in pyproject.toml"""
|
@@ -103,6 +130,7 @@ class Dev:
|
|
103
130
|
|
104
131
|
self.plain_env = {
|
105
132
|
"PYTHONUNBUFFERED": "true",
|
133
|
+
"PLAIN_DEV": "true",
|
106
134
|
"PLAIN_LOG_LEVEL": self.log_level.upper(),
|
107
135
|
"APP_LOG_LEVEL": self.log_level.upper(),
|
108
136
|
**os.environ,
|
@@ -145,13 +173,13 @@ class Dev:
|
|
145
173
|
status_bar = Columns(
|
146
174
|
[
|
147
175
|
Text.from_markup(
|
148
|
-
f"[bold]Tunnel
|
176
|
+
f"[bold]Tunnel[/bold] [underline][link={self.tunnel_url}]{self.tunnel_url}[/link][/underline]"
|
149
177
|
),
|
150
178
|
Text.from_markup(
|
151
|
-
f"[dim][bold]
|
179
|
+
f"[dim][bold]Server[/bold] [link={self.url}]{self.url}[/link][/dim]"
|
152
180
|
),
|
153
181
|
Text.from_markup(
|
154
|
-
"[dim]
|
182
|
+
"[dim][bold]Ctrl+C[/bold] to stop[/dim]",
|
155
183
|
justify="right",
|
156
184
|
),
|
157
185
|
],
|
@@ -161,7 +189,7 @@ class Dev:
|
|
161
189
|
status_bar = Columns(
|
162
190
|
[
|
163
191
|
Text.from_markup(
|
164
|
-
f"[bold]
|
192
|
+
f"[bold]Server[/bold] [underline][link={self.url}]{self.url}[/link][/underline]"
|
165
193
|
),
|
166
194
|
Text.from_markup(
|
167
195
|
"[dim][bold]Ctrl+C[/bold] to stop[/dim]", justify="right"
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import os
|
2
|
+
import platform
|
3
|
+
import subprocess
|
4
|
+
import sys
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
from . import pdb
|
8
|
+
|
9
|
+
|
10
|
+
def set_breakpoint_hook():
|
11
|
+
"""
|
12
|
+
If a `plain dev` process is running, set a
|
13
|
+
breakpoint hook to trigger a remote debugger.
|
14
|
+
"""
|
15
|
+
|
16
|
+
if not os.environ.get("PLAIN_DEV"):
|
17
|
+
# Only want to set the breakpoint hook if
|
18
|
+
# we're in a process managed by `plain dev`
|
19
|
+
return
|
20
|
+
|
21
|
+
def _breakpoint():
|
22
|
+
system = platform.system()
|
23
|
+
|
24
|
+
if system == "Darwin":
|
25
|
+
pwd = Path.cwd()
|
26
|
+
script = f"""
|
27
|
+
tell application "Terminal"
|
28
|
+
activate
|
29
|
+
do script "cd {pwd} && plain dev debug"
|
30
|
+
end tell
|
31
|
+
"""
|
32
|
+
subprocess.run(["osascript", "-e", script])
|
33
|
+
else:
|
34
|
+
raise OSError("Unsupported operating system")
|
35
|
+
|
36
|
+
pdb.set_trace(
|
37
|
+
# Make sure the debugger starts outside of this
|
38
|
+
frame=sys._getframe().f_back,
|
39
|
+
)
|
40
|
+
|
41
|
+
sys.breakpointhook = _breakpoint
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
from dotenv import load_dotenv
|
4
|
+
|
5
|
+
from .debug import set_breakpoint_hook
|
6
|
+
|
7
|
+
|
8
|
+
def setup():
|
9
|
+
# Try to set a new breakpoint() hook
|
10
|
+
# so we can connect to pdb remotely.
|
11
|
+
set_breakpoint_hook()
|
12
|
+
|
13
|
+
# Load environment variables from .env file
|
14
|
+
if plain_env := os.environ.get("PLAIN_ENV", ""):
|
15
|
+
load_dotenv(f".env.{plain_env}")
|
16
|
+
else:
|
17
|
+
load_dotenv(".env")
|
@@ -0,0 +1,129 @@
|
|
1
|
+
import errno
|
2
|
+
import logging
|
3
|
+
import re
|
4
|
+
import socket
|
5
|
+
import sys
|
6
|
+
from pdb import Pdb
|
7
|
+
|
8
|
+
log = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
|
11
|
+
def cry(message, stderr=sys.__stderr__):
|
12
|
+
log.critical(message)
|
13
|
+
print(message, file=stderr)
|
14
|
+
stderr.flush()
|
15
|
+
|
16
|
+
|
17
|
+
class LF2CRLF_FileWrapper:
|
18
|
+
def __init__(self, connection):
|
19
|
+
self.connection = connection
|
20
|
+
self.stream = fh = connection.makefile("rw")
|
21
|
+
self.read = fh.read
|
22
|
+
self.readline = fh.readline
|
23
|
+
self.readlines = fh.readlines
|
24
|
+
self.close = fh.close
|
25
|
+
self.flush = fh.flush
|
26
|
+
self.fileno = fh.fileno
|
27
|
+
if hasattr(fh, "encoding"):
|
28
|
+
self._send = lambda data: connection.sendall(data.encode(fh.encoding))
|
29
|
+
else:
|
30
|
+
self._send = connection.sendall
|
31
|
+
|
32
|
+
@property
|
33
|
+
def encoding(self):
|
34
|
+
return self.stream.encoding
|
35
|
+
|
36
|
+
def __iter__(self):
|
37
|
+
return self.stream.__iter__()
|
38
|
+
|
39
|
+
def write(self, data, nl_rex=re.compile("\r?\n")):
|
40
|
+
data = nl_rex.sub("\r\n", data)
|
41
|
+
self._send(data)
|
42
|
+
|
43
|
+
def writelines(self, lines, nl_rex=re.compile("\r?\n")):
|
44
|
+
for line in lines:
|
45
|
+
self.write(line, nl_rex)
|
46
|
+
|
47
|
+
|
48
|
+
class DevPdb(Pdb):
|
49
|
+
"""
|
50
|
+
This will run pdb as a ephemeral telnet service. Once you connect no one
|
51
|
+
else can connect. On construction this object will block execution till a
|
52
|
+
client has connected.
|
53
|
+
|
54
|
+
Based on https://github.com/tamentis/rpdb I think ...
|
55
|
+
|
56
|
+
To use this::
|
57
|
+
|
58
|
+
DevPdb(host='0.0.0.0', port=4444).set_trace()
|
59
|
+
|
60
|
+
Then run: telnet 127.0.0.1 4444
|
61
|
+
"""
|
62
|
+
|
63
|
+
active_instance = None
|
64
|
+
|
65
|
+
def __init__(self, host, port, patch_stdstreams=False, quiet=False):
|
66
|
+
self._quiet = quiet
|
67
|
+
listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
68
|
+
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
|
69
|
+
listen_socket.bind((host, port))
|
70
|
+
if not self._quiet:
|
71
|
+
cry(
|
72
|
+
"DevPdb session open at {}:{}, waiting for connection ...".format(
|
73
|
+
*listen_socket.getsockname()
|
74
|
+
)
|
75
|
+
)
|
76
|
+
listen_socket.listen(1)
|
77
|
+
connection, address = listen_socket.accept()
|
78
|
+
if not self._quiet:
|
79
|
+
cry(f"DevPdb accepted connection from {repr(address)}.")
|
80
|
+
self.handle = LF2CRLF_FileWrapper(connection)
|
81
|
+
Pdb.__init__(self, completekey="tab", stdin=self.handle, stdout=self.handle)
|
82
|
+
self.backup = []
|
83
|
+
if patch_stdstreams:
|
84
|
+
for name in (
|
85
|
+
"stderr",
|
86
|
+
"stdout",
|
87
|
+
"__stderr__",
|
88
|
+
"__stdout__",
|
89
|
+
"stdin",
|
90
|
+
"__stdin__",
|
91
|
+
):
|
92
|
+
self.backup.append((name, getattr(sys, name)))
|
93
|
+
setattr(sys, name, self.handle)
|
94
|
+
DevPdb.active_instance = self
|
95
|
+
|
96
|
+
def __restore(self):
|
97
|
+
if self.backup and not self._quiet:
|
98
|
+
cry(f"Restoring streams: {self.backup} ...")
|
99
|
+
for name, fh in self.backup:
|
100
|
+
setattr(sys, name, fh)
|
101
|
+
self.handle.close()
|
102
|
+
DevPdb.active_instance = None
|
103
|
+
|
104
|
+
def do_quit(self, arg):
|
105
|
+
self.__restore()
|
106
|
+
return Pdb.do_quit(self, arg)
|
107
|
+
|
108
|
+
do_q = do_exit = do_quit
|
109
|
+
|
110
|
+
def set_trace(self, frame=None):
|
111
|
+
if frame is None:
|
112
|
+
frame = sys._getframe().f_back
|
113
|
+
try:
|
114
|
+
Pdb.set_trace(self, frame)
|
115
|
+
except OSError as exc:
|
116
|
+
if exc.errno != errno.ECONNRESET:
|
117
|
+
raise
|
118
|
+
|
119
|
+
|
120
|
+
def set_trace(
|
121
|
+
frame=None, host="127.0.0.1", port=4444, patch_stdstreams=False, quiet=False
|
122
|
+
):
|
123
|
+
"""
|
124
|
+
Opens a remote PDB over a host:port.
|
125
|
+
"""
|
126
|
+
devpdb = DevPdb(
|
127
|
+
host=host, port=port, patch_stdstreams=patch_stdstreams, quiet=quiet
|
128
|
+
)
|
129
|
+
devpdb.set_trace(frame=frame)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "plain.dev"
|
3
|
-
version = "0.
|
3
|
+
version = "0.19.0"
|
4
4
|
description = "Local development tools for Plain."
|
5
5
|
authors = [{name = "Dave Gaeddert", email = "dave.gaeddert@dropseed.dev"}]
|
6
6
|
license = "BSD-3-Clause"
|
@@ -9,7 +9,6 @@ requires-python = ">=3.11"
|
|
9
9
|
dependencies = [
|
10
10
|
"plain<1.0.0",
|
11
11
|
"click>=8.0.0",
|
12
|
-
"debugpy~=1.6.3",
|
13
12
|
"python-dotenv~=1.0.0",
|
14
13
|
"gunicorn>20",
|
15
14
|
"requests>=2.0.0",
|
@@ -24,7 +23,7 @@ dependencies = [
|
|
24
23
|
"contrib" = "plain.dev.contribute:cli"
|
25
24
|
|
26
25
|
[project.entry-points."plain.setup"]
|
27
|
-
"dev-
|
26
|
+
"dev-setup" = "plain.dev.entrypoints:setup"
|
28
27
|
|
29
28
|
[tool.hatch.build.targets.wheel]
|
30
29
|
packages = ["plain"]
|
@@ -79,15 +79,6 @@ wheels = [
|
|
79
79
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
80
80
|
]
|
81
81
|
|
82
|
-
[[package]]
|
83
|
-
name = "debugpy"
|
84
|
-
version = "1.6.7.post1"
|
85
|
-
source = { registry = "https://pypi.org/simple" }
|
86
|
-
sdist = { url = "https://files.pythonhosted.org/packages/0b/c8/b6978e56c917c2fe5096cd83d81bab8ac08ceb1f9a4b9373ad5b066a3265/debugpy-1.6.7.post1.zip", hash = "sha256:fe87ec0182ef624855d05e6ed7e0b7cb1359d2ffa2a925f8ec2d22e98b75d0ca", size = 4664667 }
|
87
|
-
wheels = [
|
88
|
-
{ url = "https://files.pythonhosted.org/packages/81/72/cb3a101717d81ab1f97ee38a409be9740e7256bc25074e48fd634055261f/debugpy-1.6.7.post1-py2.py3-none-any.whl", hash = "sha256:1093a5c541af079c13ac8c70ab8b24d1d35c8cacb676306cf11e57f699c02926", size = 4872783 },
|
89
|
-
]
|
90
|
-
|
91
82
|
[[package]]
|
92
83
|
name = "gunicorn"
|
93
84
|
version = "23.0.0"
|
@@ -214,11 +205,10 @@ wheels = [
|
|
214
205
|
|
215
206
|
[[package]]
|
216
207
|
name = "plain-dev"
|
217
|
-
version = "0.
|
208
|
+
version = "0.19.0"
|
218
209
|
source = { editable = "." }
|
219
210
|
dependencies = [
|
220
211
|
{ name = "click" },
|
221
|
-
{ name = "debugpy" },
|
222
212
|
{ name = "gunicorn" },
|
223
213
|
{ name = "plain" },
|
224
214
|
{ name = "psycopg", extra = ["binary"] },
|
@@ -230,7 +220,6 @@ dependencies = [
|
|
230
220
|
[package.metadata]
|
231
221
|
requires-dist = [
|
232
222
|
{ name = "click", specifier = ">=8.0.0" },
|
233
|
-
{ name = "debugpy", specifier = "~=1.6.3" },
|
234
223
|
{ name = "gunicorn", specifier = ">20" },
|
235
224
|
{ name = "plain", specifier = "<1.0.0" },
|
236
225
|
{ name = "psycopg", extras = ["binary"], specifier = "~=3.2.2" },
|
@@ -1,12 +0,0 @@
|
|
1
|
-
import debugpy
|
2
|
-
|
3
|
-
|
4
|
-
def attach(endpoint=("localhost", 5678)):
|
5
|
-
if debugpy.is_client_connected():
|
6
|
-
print("Debugger already attached")
|
7
|
-
return
|
8
|
-
|
9
|
-
debugpy.listen(endpoint)
|
10
|
-
print("Waiting for debugger to attach...")
|
11
|
-
debugpy.wait_for_client()
|
12
|
-
print("Debugger attached!")
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|