I'm trying pants for the first time, on a pretty s...
# general
a
I'm trying pants for the first time, on a pretty simple flask app (just one
app.py
module, plus some static assets), I've gone through the getting started docs, but can't get it to run the app. It looks like it's starting the initial process correctly, flask (werkzeug) comes up and says it's going to listen on whatever port, and tries to start the child serving process, but that one fails to import flask. It seems like the child process created by werkzeug isn't running with the same environment the parent process is using?
h
Hi! Sorry for the trouble. Are you running the flask app with
./pants run
?
a
yeah
h
I recall having this exact issue with the Django dev server, due to how it forks child processes, so I need to remember how I fixed it
a
BUILD
Copy code
pex_binary(
    name="app",
    entry_point="app.py",
)

python_requirements(
    name="requirements.txt0",
)

python_sources(
    name="root",
)
h
Let me go into my mind palace for a sec
šŸ’” 1
a
haha thanks
h
Your cmd line is
./pants run path/to/package:app
?
a
furrows brow wait it started working
% ./pants run :app
h
Ah that BUILD file is in the root
a
yeah, I don't have a src/python directory, this was previously a really simple thing with just a virtualenv and an app module
h
That should be fine
a
./pants run app.py
also works
h
Yep
a
welp
h
OK, so now I'm either confused that it works or confused that it didn't before
a
same
Copy code
% ./pants run :app
17:35:07.95 [INFO] Completed: Building 2 requirements for app.pex from the 3rdparty/python/default.lock resolve: flask, flask-assets
17:35:08.58 [INFO] Completed: Building local_dists.pex
 * Serving Flask app 'demo' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on all addresses (0.0.0.0)
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on <http://127.0.0.1:5001>
 * Running on <http://192.168.69.100:5001> (Press CTRL+C to quit)
 * Restarting with stat
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/leif/git/demo/app.py", line 8, in <module>
    from flask import Flask, render_template
ModuleNotFoundError: No module named 'flask'
that's what it looked like before
h
Right
so... race condition? šŸ¤·ā€ā™‚ļø
a
not sure
I thought it started working after I activated my old venv but then I deactivated it and it's still working
h
Not directly related, but FYI since I'm finding things in the mind palace: You can set
restartable=True
on the
pex_binary
and then the server will reload automatically on edits
šŸ‘šŸ» 1
Including edits to any dependencies: python sources, requirements, resources, etc.
a
ok well, that was going to be my next question once we solved this: how to manage other resources like static assets, particularly ones that require another tool as a build step (tailwind css, in particular)
h
That
Restarting with stat
line looks suspicious, is that what Flask prints when the server restarts due to its own change detection?
Anyway, if the problem rears up again, let us know here!
a
I think that's it telling me which mechanism it's going to use to watch files for hot reloading
h
Got it
a
the other option would be inotify, I believe
h
Got it, so with
restartable=True
Pants will do its own hot reloading, which is more robust than Flask/Django's alone can be, because it knows about all your deps and how to build them from the original sources. E.g., tailwind css in your case
šŸ‘šŸ» 1
a
I'm guessing it's probably this https://www.pantsbuild.org/docs/plugins-codegen
h
Pants supports a lot of code generators out of the box, but tailwind css is not among them
however it shouldn't be too hard to add, and we'd love the contribution if you're up for it! We can guide
āž• 1
Yeah, it's basically codegen
a
I'll start with that and see how far I get
thanks!
h
I'd need to do like 20 minutes of reading about tailwind
h
In the meantime to writing a codegen plugin, another option is to manually generate the files and then load them with a
resources
target
h
True, that could be helpful for getting things running initially, but then you don't get the hot reloading
a
this project is an opportunity for me to learn about pants, so I do want to try to do everything inside the build system
šŸ‘ 1
(like, my goal is to explore pants, not to ship my app; I've already got it shipped as much as I need it)
šŸ‘ 1
h
With codgen, generally the things you'd look at are: A) installing the tailwind CLI (I suggest ignoring that at first and just assuming it's available on the PATH, then tackling it later) B) modeling the input targets, C) modeling the output sources, D) Writing the code that turns B) into C)
There are a ton of examples in the repo, e.g., for protobuf and thrift
a
anyway, about tailwind, I don't understand it super well yet, a friend recommended I try it. It seems like it's just a collection of css classes like
text-white
font-bold
bg-blue-500
so that instead of writing css files, you just do everything in html, and apply the classes you want but there's a build step that takes a bit of tailwind config, plus a simple css template-ish file, and generates a plain css file to bundle in assets
great, thanks šŸ™‚
h
And feel free to ask any question on #development at any time!
šŸ™šŸ» 1
h
(I suggest ignoring that at first and just assuming it's available on the PATH, then tackling it later)
Specifically, use an absolute path like
/usr/bin/tailwind
in the
argv=
for a
Process()
h
For the record, there are no bad questions, only incomplete documentation...
šŸ’Æ 1
a
it's a pypi package, so I think that part should be easy
šŸ‘ 1
h
Oh I thought it was an NPM thing
if it's PyPI that is definitely easy
a
er, it is, but there's pytailwindcss which doesn't require npm stuff
h
even better
a
yep!
ok, interesting, I had been using https://webassets.readthedocs.io/en/latest/index.html, which I can configure to run the tailwind generator (and detect when it needs to), but that more happens during app startup, and it expects to be able to write outputs to static/dist/
I think for a truly pantsy build I do want to write a pants plugin so it happens before packing stuff into the pex
āž• 1
it doesn't appear to explain: ā€¢ where many of the names are imported from (e.g. Get, Process, ProcessResult) ā€¢ where the name
request.protocol_target
comes from ā€¢ what digests are at all, or what a snapshot is
h
Agreed. We want to rewrite the plugin docs. It will help to read over the Target API and Rules API chapters first to understand more how plugins work, including what a
Process
is
a
yeah, I'm working through those now
jumping to "add codegen" first was the wrong move
h
But bump that I generally recommend getting things working first w/ manual generation before doing a plugin -- the learning curve can be high on plugins (which we very much want to improve through better docs + better error messages)
a
I think I do have that
ok, I've read the rules api stuff, and am pretty confused about the digest api, so I think I'm about ready to give up and go read a book, but I'll try to describe what I think is the biggest challenge here, in case it's helpful product feedback
tailwind has a config file,
tailwind.config.js
, mine looks like this
Copy code
module.exports = {
  content: [
    './templates/**/*.html',
  ],
  theme: {
    colors: {
      running: {
        500: '#9C59D1',
        700: '#A773D1',
      },
      stopped: {
        500: '#FCF434',
        700: '#FCF897',
      },
    },
    extend: {},
  },
  plugins: [],
}
what tailwind does with that
content
array is interesting: it scans all your html to figure out which class names you're actually using, to make sure it only omits the definitions you're going to use, to reduce asset size
which means,
tailwind.config.js
, and all the files matching that glob pattern, need to be inputs to the rule
which means, at best, in the python rule definition you could strip off
module.exports =
, try to parse the rest as json (I guess hope there aren't any comments), and find the other files to include that way; at worst, you might have to evaluate the config with a javascript interpreter to figure it out
the corresponding way to teach flask-assets about tailwind is considerably simpler:
Copy code
class TailwindCSS(ExternalTool):

    name = "tailwindcss"
    options = {
        "tailwindcss": ("binary", "TAILWINDCSS_BIN"),
        "minify": "TAILWINDCSS_MINIFY",
    }

    def input(self, in_, out, source_path, output_path, **kwargs):
        command = self.parse_binary(self.tailwindcss or "tailwindcss")
        command.extend(["--input", "{input}"])
        command.extend(["--output", "{output}"])
        if self.minify:
            command.append("--minify")
        self.subprocess(command, out, in_)

    def output(self, in_, out, **kwargs):
        out.write(in_.read())


register_filter(TailwindCSS)


app = Flask("demo")

assets = Environment(app)
assets.config["tailwindcss_minify"] = True
css = Bundle(
    "src/main.css",
    filters="tailwindcss",
    depends=["tailwind.config.js", "templates/**/*.html"],
    output="dist/main.css",
)
naturally it's not going to be as good as pants at tracking what needs to be rebuilt, but it gets pretty close and is far more accessible as a "plugin" author
not sure if that's helpful or not, but happy to discuss more if y'all want
anyway, happy weekend all, and thanks for the help earlier
h
Thanks for the info! Yeah, the plugin API is not as well documented as we'd like and improving that is part of our upcoming overall docs improvement project. Right now one of the more accessible ways of writing a plugin is cargo-culting from existing ones, which is not ideal
But it does seem like with the right documentation and/or prior knowledge, this is pretty amenable to being a plugin. It has the classic shape of a codegen plugin.
For example, that
content
array could be used to do dependency inference
BTW one thing you might be able to do more easily is hack something together with `experimental_shell_command`: https://www.pantsbuild.org/docs/run-shell-commands#the-experimental_shell_command-target