~hristoast/hristoast

hristoast/site/emacs-python-my-setup-2018.html -rw-r--r-- 9.4 KiB
c910679bHristos N. Triantafillou Still need pystache 15 hours ago
                                                                                
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
<h1 id="title">Emacs & Python: My Setup 2018</h1>

<div id="dates">
  <span>Posted: <time id="post-date">2018-11-27</time> | Updated: <time>2019-11-08</time></span>
</div>

<p id="post-excerpt">
  Earlier this year I did an expansion to my entries on MPD, sort of a state of my setup piece, and  I've decided to do the same thing for my Emacs and Python setups as well.  If you write Python code and use Emacs, or are interested in either, read on!
</p>

<div id="toc"></div>

<h3>The system: Void Linux</h3>

<p>
  My operating system environment is <a href="https://voidlinux.org/">Void Linux</a> and has been for over four years now.  At the OS-level, I've installed just a few Python 3 packages to get started:
</p>

<pre><code class="language-sh">python3             # Gives me the python interpreter
python3-pip         # Allows installation of packages
python3-setuptools  # Wanted by pip</pre></code>

<h3>Pip packages: local style</h3>

<p>
  Personally, I don't love installing python packages I need for development through the system package manager.  It's definitely appropriate for libraries needed by other system packages and things like that, but for my local dev environment I do something else.
</p>

<p>
  For my login account, "hristos", I've added <code>$HOME/.local/bin</code> to my <code>$PATH</code>.  Additionally, when I want to install a package via <code>pip</code>, I invoke it like this:
</p>

<pre><code class="language-sh">$ pip install --user ansible beets django httpie pyflakes flake8 ipython jedi selenium uWSGI</pre></code>

<p>
  This <code>--user</code> flag installs the package to <code>$HOME/.local</code> so now all I need to do is configure my editor to look there for Python libraries.  A nice benefit of doing things this way is you don't need superuser access, and you aren't messing with the system python in any way.
</p>

<p>
  Anyways, in the manner seen above, you would install at minimum these packages: <code>pyflakes</code>, <code>flake8</code>, and <code>jedi</code>.
</p>

<h3>The editor: Emacs</h3>

<p>
  Emacs being my editor of choice, I'll be using various plugins to get the functionality to where I want it.  This includes:
</p>

<ul>
  <li>Code completion</li>
  <li>Expanding libraries I've installed locally</li>
  <li>Code navigation; Shift+left click on a symbol and be taken to its definition</li>
  <li>Code documentation; Shift+right click on a symbol and a PyDoc window will open.</li>
  <li>Support for Django HTML templates - not essential for everyone, but why not!</li>
</ul>

<h4>Code completion + local libraries</h4>

<p>
  Completion is enabled with two primary packages: Company mode and <a href="https://git.sr.ht/~hristoast/dot-emacs/tree/713364dc3ee4ba7e559ca85c559f5d6772d74d11/init.el#L302">company-jedi</a>.  Company mode itself comes with Emacs, but follow the link to see an example of installing company-jedi with the excellent use-package.
</p>

<p>
  But this isn't enough to enable completion for libraries you install with pip.  To do this we need  to tell Jedi about our local python libraries under <code>$HOME/.local/lib</code>, the below snippet is <a href="https://git.sr.ht/~hristoast/dot-emacs/tree/713364dc3ee4ba7e559ca85c559f5d6772d74d11/init.el#L523">how this looks in my init.el at the time of this writing</a>:
</p>

<pre><code class="language-lisp">(defun use-system-python3 ()
    "Use the system python3."
    (interactive)
    (maybe-stop-jedi-server)
    (defvar python-check-command)
    (defvar python-shell-interpreter)
    (setq
       python-check-command "pyflakes"
       python-shell-interpreter "python3"
       flycheck-python-flake8-executable "flake8"
       jedi:environment-virtualenv (list "python -m venv")
       jedi:environment-root (concat dot-emacs "/.py/system3")
       jedi:server-args
       '("--sys-path" "/usr/lib/python3.6/site-packages"
         "--sys-path" "~/.local/lib/python3.6/site-packages"))
      (if (not (file-exists-p
                (concat jedi:environment-root
                        "/lib/python3.6/site-packages/jediepcserver.py")))
          (jedi:install-server)))</pre></code>

<p>The above function is ran as a python-mode hook and enables several things:</p>

<ul>
  <li>If a Jedi server is running, it's stopped (via another function)</li>
  <li>Check commands are specified</li>
  <li>A path to a virtualenv is specified (this is required by Jedi, even if you don't use it.)</li>
  <li>I tell Jedi to include my local Python library paths under <code>$HOME/.local/lib</code> in it's list of python paths so it will autocomplete and check those.</li>
  <li>Finally, if it's not already there, the Jedi server is installed to it's virtualenv.</li>
</ul>

<p>You may notice that absolute paths are not used for the various binaries I've set, and that's deliberately.  The idea is, let <code>$PATH</code> do it's job and find the right thing.  This lets me use locally installed binaries ahead of anything that may be installed at the system-level.</p>

<h4>Code navigation</h4>

<p>It's often useful to go to the definition of a particular symbol to learn more about it and whatnot.  <a href="https://git.sr.ht/~hristoast/dot-emacs/tree/713364dc3ee4ba7e559ca85c559f5d6772d74d11/init.el#L500">The below function</a> is mapped to Shift+left click to make this easy:</p>

<pre><code class="language-lisp">  (defun goto-definition-at-point (event)
    "Move the point to the clicked position
     and jedi:goto-definition the thing at point."
    (interactive "e")
    (let ((es (event-start event)))
      (select-window (posn-window es))
      (goto-char (posn-point es))
      (jedi:goto-definition)))</pre></code>

<p>My use-package entry for python-mode then looks like <a href="https://git.sr.ht/~hristoast/dot-emacs/tree/713364dc3ee4ba7e559ca85c559f5d6772d74d11/init.el#L492">this</a>:</p>

<pre><code class="language-lisp">(use-package python-mode
  :bind
  ("&lt;S-down-mouse-1&gt;" . goto-definition-at-point)
  ... Truncated ...</pre></code>

<h4>Code documentation</h4>

<p>In the spirit of empowering Emacs to give me everything I want, <a href="">the below function</a>, bound to shift+right click, will open a buffer with the PyDoc for the given symbol:</p>

<pre><code class="language-lisp">  (defun quick-pydoc (event)
    "Move the point to the clicked position
     and pydoc the thing at point."
    (interactive "e")
    (let ((es (event-start event)))
      (select-window (posn-window es))
      (goto-char (posn-point es))
      (pydoc-at-point)))</pre></code>

<p>And again, my use-package for python mode looks like this:</p>

<pre><code class="language-lisp">(use-package python-mode
  :bind
  ("&lt;S-down-mouse-1&gt;" . goto-definition-at-point)
  ("&lt;S-down-mouse-3&gt;" . quick-pydoc)
  ... Truncated ...</pre></code>

<h4>Support for Django HTML templates</h4>

<p>To enable auto-expanding of special HTML template characters like <code>{{</code> and <code>{%</code> I use the excellent <a href="https://git.sr.ht/~hristoast/dot-emacs/tree/713364dc3ee4ba7e559ca85c559f5d6772d74d11/init.el#L723-L738">web-mode</a>.  But installing web-mode is not enough, and if you use smartparens <a href="https://git.sr.ht/~hristoast/dot-emacs/tree/713364dc3ee4ba7e559ca85c559f5d6772d74d11/init.el#L682-L685">some additional configuration is needed</a>.</p>

<p>Because not every HTML file I edit is a Django template, I use a project-local <code>.dir-locals.el</code> file and set the following inside:</p>

<pre><code class="language-lisp">((web-mode (eval . (web-mode-set-engine "django"))))</pre></code>

<p>This goes in the root of my project and automatically sets the web-mode engine to "django" for all html files in the project's sub-folders.</p>

<h3>The Service</h3>

<p>I won't go into much detail here, see <a href="/emacs-daemon-runit-service/">this entry</a> for more, but the final part of my setup involves an Emacs daemon runit service.</p>

<p>In my runit service file for Emacs, I set a few environment variables to ensure Emacs has the same <code>$PATH</code> as my user, and other things.  Anything specific about one's environment needs to be set here since runit doesn't pass much of an environment to services (see <a href="http://smarden.org/runit/benefits.html">here</a> for more information on that.)  Below is what my service file looks like:</p>

<pre><code class="language-sh">#!/bin/sh
export EMACS_GO=true
export HOME=/home/hristos
export PATH=$HOME/.local/bin:$PATH
cd $HOME
exec chpst -u hristos:hristos /usr/bin/emacs --fg-daemon=hristos-emacsd 2>&1</code></pre>

<p>Nothing special here, just setting up the environment as needed.</p>

<h3>Bonus!</h3>

<p>There's many more awesome tidbits that come along with this setup -- to see things like python-specific keybindings: with a python-mode buffer open, run <code>M-x RET describe-mode RET</code> and Emacs will show you some of those details and much more.</p>

<h3>Conclusion</h3>

<p>The tools described above help me be a more productive Python programmer.  It's not a comprehensive writeup of my entire workflow, just the main parts that make writing Python more awesome.</p>

<p>One could take this a step further and utilize packages like <a href="https://github.com/fgallina/python-django.el">python-django.el</a> which provide much deeper project integration.  I personally use a different pattern for this but it's a nice way to manage a project when you want to keep everything within Emacs.</p>

<p>In the future, I may do a more in depth entry on my Django workflow, or other more specific things.  Until then, happy hacking!</p>