Developer Reference

Contributing

Contributions are welcome, and they are greatly appreciated!

Every little bit helps, and credit will always be given (if so desired).

How to Contribute

First, please respect the Code of Conduct and each other.

Then, decide how you’d like to contribute:

Report Bugs

Report bugs at https://github.com/hotoffthehamster/dob/issues.

When reporting a bug, please include:

  • Your operating system name and version, and the Python version you are using.
  • Any details about your local setup that might be helpful for troubleshooting.
  • Detailed steps to reproduce the bug.

Fix Bugs

Look through the GitHub issues for anything tagged with “bug”. Pick one, assign yourself to it, and work on the issue.

Implement Features

Look through the GitHub issues for anything tagged with “feature”. Pick one, assign yourself to it, and work on the issue.

Write Documentation

If you find documentation out of date, missing, or confusing, please help us to improve it.

This includes the official user documentation, the README, and the inline docstrings that generate the API documentation (per PEP 257 and Google Python Style Guide).

We also appreciate reference from blog posts, articles, and other projects.

Submit Feedback

The best way to send feedback is to file an issue at https://github.com/hotoffthehamster/dob/issues.

See above for reporting bugs.

If you are proposing a feature:

  • Explain in detail how the feature should work.
  • Unless you expect to work on the code yourself, your chances of having a feature implemented are greater if you keep the scope focused and narrow such that it’s as simple as possible for a developer to work on. That, or a monetary contribution speaks volumes.
  • Remember that this is a volunteer-driven project, and that contributions are welcome! You are encouraged to fork the project, hack away, and submit pull requests for new features (and bug fixes).
  • Above all else, please be considerate and kind. A few nice words can go a long way!

Please also feel free to email the author directly if you have any other questions or concerns. Response times may vary depending on season.

Getting Started

Ready to contribute? Here’s how to set up dob for local development.

  1. Dob is (for better and for worse) split across a number of projects. Fork each of these projects on GitHub:

  2. Clone your forks locally.

    For instance, open a terminal, change to a directory you’d like to develop from, and then run the commands:

    GH_URL=git@github.com:<your_user>
    for repo in dob dob-viewer dob-prompt dob-bright nark; do
      git clone ${GH_URL}/${repo}.git
    done
    
  3. Prepare a Python virtual instance, or virtualenv.

    First, ensure that you have virtualenvwrapper installed.

  4. Next, set up a virtual environment for local development. Assuming you’re still in the base directory where you cloned all the projects, change into the dob/ directory and make the virtual environment:

    $ cd dob/
    $ mkvirtualenv -a $(pwd) dob
    (dob) $
    

    Note: We use the -a option so that cdproject changes directories to the dob/ directory when we’re in the virtual environment.

    Note: You can also specify the version of Python if you wish, e.g., add the option --python=/usr/bin/python3.8.

  5. Now, prepare a special requirements file so you can run 1 command to install all five projects, rather than having to run the same command 5 times from five different directories.

    Copy the example file included in the project and rename it.

    From the base dob/ directory, run:

    (dob) $ cp requirements/ultra-editable.pip.example requirements/ultra-editable.pip
    

    If you’re following the instructions here, you should not need to edit the new file. But you might want to peak inside the file anyway to see how it works.

  6. Finally, we’re ready to install dob for local development!

    From the base dob/ directory, run:

    (dob) $ make develop
    

    And that’s it! The make task will install dependencies from PyPI, as well as all the projects you sourced locally.

    • Hint: As usual, run workon to activate the virtual environment, and deactivate to leave it. E.g.,:

      # Load the Python virtual instance.
      $ workon dob
      (dob) $
      
      # Do your work.
      (dob) $ ...
      
      # Finish up.
      (dob) $ deactivate
      $
      
  7. Now that your local development environment is setup, you can do some real work!

    However, before starting any work on a new feature or bug fix, make sure your proving branch is up to date with the official branch:

    (dob) $ cdproject
    (dob) $ git remote add upstream git@github.com:hotoffthehamster/dob.git
    (dob) $ git fetch upstream
    (dob) $ git checkout proving
    (dob) $ git merge --ff-only upstream/proving
    (dob) $ git push origin HEAD  # For good measure.
    

    And also do the same for each of the other local projects.

    (If you’re concerned that managing multiple repositories is a chore, you’re right! Which is why I like to use myrepos and Oh, My Repos! to be able to run the same operation against multiple repositories using a single command.)

  8. Now that you’ve updated your local code to the freshest upstream sources, create a branch for local development. If you are working on an known issue, you may want to reference the Issue number in the branch name, e.g.,:

    $ git checkout -b feature/ISSUE-123-name-of-your-issue
    

    In any case, create a new branch, and start editing code.

  9. Do your work and make one or more sane, concise commits:

    $ git add -p
    $ git commit -m "<Category>: <Short description of changes.>
    
    - <Longer description, if necessary.>"
    

    IMPORTANT: Please make each commit as small and sane as possible.

    Follow these guidelines:

    • Each commit should generally focus on one thing, and one thing only, and that thing should be clearly described in the first line of the commit message.

    • Please use a one-word categorical prefix (see below) to make it easy for someone reading the git log to understand the breadth of your changes.

    • If you move or refactor code, the move or refactor should be captured in a single commit with no other code changes.

      E.g., if you want to enhance a function, but you find that you need to refactor it to make it easier to hack on, first refactor the function – without adding any new code or making any other changes – and then make a commit, using the Refactor: prefix. Next, add your new code, and then make a second commit for the new feature/enhancement.

    • Following are some examples of acceptable commit message prefixes:

      • Feature: Added new feature.
      • Bugfix: Fixed problem doing something.
      • Refactor: Split long function into many.
      • Version: X.Y.Z.
      • Tests: Did something to tests.
      • Docs: Update developer README.
      • Debug: Add trace messages.
      • Developer: Improved developer experience [akin to `Debug:` prefix].
      • Linting: Adjust whitespace.
      • Regression: Oh, boy, when did this get broke?
      • i18n/l10n: Something about words.
      • Feedback: Fix something per PR feedback.

      (You’ll notice that this strategy is similar to gitmoji, but it’s more concise, and less obtuse.)

  10. Throughout development, run tests and the linter – and definitely before you submit a Pull Request.

    dob uses flake8 for linting, pytest for unit testing, and tox for verifying against the many versions of Python.

    You can run all of these tools with one command that should be familiar to seasoned Python developers:

    $ tox
    

    Or you can run the equivalent make task:

    $ make test-all
    

    (which simply executes tox).

    • Note that tox downloads and uses released packages, so you might want to run make test instead (which calls py.test directly) to test against local changes to included packages.

      E.g., if you make changes to nark but have not released them yet, to test dob, you should use make test or py.test, not make test-all or tox.

  11. Rebase and squash your work, if necessary, before submitting a Pull Request.

    E.g., if the linter caught an error, rather than making a new commit with just the linting fix(es), make a temporary commit with the linting fixes, and then “fixup” that commit into the previous commit wherein you originally added the code that didn’t lint.

    (Note: Rebasing is an intermediate Git skill. If you’re unfamiliar, read up elsewhere. But consider a few reminders. First, ensure that you are not rebasing any branch that other developers are also working on (which should not apply to your feature branch, unless you are collaborating with others on that branch, which you are probably not). Second, remember that git rebase --abort can save you from having to resolve any unanticipated or complicated conflicts, should you find yourself faced with rebase conflicts and unsure how to get your work back (abort the rebase and maybe ask someone for help, and try another approach).)

    For example, pretend that I have the following git history:

    $ git log --oneline | head -3
    
    b1c07a4 Regression: Fix some old bug.
    17d1e38 Feature: Add my new feature.
    2e888c3 Bugfix: Oops! Did I do that?
    

    and then I commit a linting fix that should have been included with the second-to-last commit, 17d1e38.

    First, add the linting fix:

    $ git add -A
    

    Then, use the fixup feature to tell Git which commit this belongs in:

    $ git ci --fixup=17d1e38
    

    Next, start a rebase, and use --autosquash:

    $ git rebase --autosquash -i 2e888c3
    

    (Note: Use the SHA1 hash of the commit after the first one you want to be included in the rebase.)

    Git should open your default editor with a file that look like this:

    pick 17d1e38 Feature: Add my new feature.
    fixup f05e080 fixup! Feature: Add my new feature.
    pick b1c07a4 Regression: Fix some old bug.
    

    You’ll notice that Git already reordered the commits and set the commit in question to “fixup”.

    You can just save and close the file and Git will complete the rebase.

    • This is just one example of rebasing, and a rather advanced one at that (I’m not sure how many people use --autosquash, but I love it!).

      Please search online or ask for help if you find yourself struggling to understand and perform rebasing.

  12. Push the changes to your GitHub account.

    After testing and linting, and double-checking that your new feature or bugfix works, and rebasing, and committing your changes, push them to the branch on your GitHub account:

    $ git push origin feature/ISSUE-123-name-of-your-issue
    

    Note: If you pushed your work previosuly and then rebased, you may have to force-push:

    $ git push origin feature/ISSUE-123-name-of-your-issue --force
    
  13. Finally, submit a pull request through the GitHub website.

    Important: Please rebase your code against proving and resolve merge conflicts, so that project maintainers does not have to do so themselves. E.g.,:

    $ git checkout feature/ISSUE-123-name-of-your-issue
    $ git fetch upstream
    $ git rebase upstream/proving
    # Resolve any conflicts...
    $ git push origin HEAD --force
    # And then open the Pull Request.
    

(Ansible users: You might enjoy this Dob-dev setup role: https://github.com/landonb/zoidy_dob-dev.)

Pull Request Guidelines

Before you submit a pull request, check that it meets these guidelines:

  1. Update docs.
    • Use docstrings to document new functions, and use (hopefully concise) inline comments as appropriate.
    • Document broader concepts and capture API changes and additions in the user documentation.
  2. Include tests.
    • If a pull request adds new classes or methods, they should be tested, either implicitly, because they’re already called by an existing test. Or they should be tested explicitly, because you added new tests for them.
    • We strive for test coverage in the high-90s (it’s too tedious to hit all branches and get 100%), but we do not enforce it. Please provide tests that provide majority coverage of your new code (you can ignore or consider error handling branches less important to cover, but all branches would still be good to test!).
      • Note that, as of early 2020, existing test coverage is no where near 100%, so take this guideline with a grain of salt. If existing code coverage improves, the core developers will have more standing to demand the same of contributed code.
  3. Commit sensibly.
    • Each commit should be succinct and singular in focus. Refer to rebasing and squashing, above.
    • Rebase your work atop proving (as mentioned above) before creating the PR, or after making any requested changes.
  4. Run tox.
    • ‘nough said.

Debugging Tips

To run one test or a subset of tests, you can specify a substring expression using the -k option with make test:

$ make test TEST_ARGS="-k NAME_OF_TEST_OR_SUB_MODULE"

The substring will be Python-evaluated. As such, you can test multiple tests using or, e.g., -k 'test_method or test_other'. Or you can exclude tests using not, e.g., -k 'not test_method'.

Note that readline functionality will not work from any breakpoint you encounter under make test. (For example, pressing the Up arrow will print a control character sequence to the terminal, rather than showing the last command you ran.)

  • If you want to interact with the code at runtime, run py.test directly (see next).

If you’d like to break into a debugger when a test fails, run pytest directly and have it start the interactive Python debugger on errors:

$ py.test --pdb tests/

If you’d like a more complete stack trace when a test fails, add verbosity:

$ py.test -v tests/

# Or, better yet, two vees!
$ py.test -vv tests/

If you’d like to run a specific test, use -k, as mentioned above. E.g.,:

$ py.test -k test__repr__no_start_no_end tests/

Put it all together to quickly debug a broken test.

$ py.test --pdb -vv -k <test_name> tests/

You can also set breakpoints in the code with pdb. Simply add a line like this:

import pdb; pdb.set_trace()

For advanced usage, if the code has given up terminal control, e.g., if you set a breakpoint in a Python Prompt Toolkit handler, you can wrest terminal interactivity back with stty:

import os, pdb; os.system("stty sane"); pdb.set_trace()
  • However, if you want to continue after fiddling with stty sane, you need to restore the settings (by calling stty --save first, debugging, and then calling stty again with the saved settings), which is easiest done from within dob using helper methods.

    • From within the Carousel, type the Alt-= key combination to break into the debugger.

    • You can also use the 2 helper methods from within the codebase:

      Controller.pdb_break_enter()
      # Poke around, then `c`ontinue!
      Controller.pdb_break_leave()
      

To test against other Python versions than what is setup in your virtualenv, you can use tox and name an environment with the envlist option:

$ tox -e <ENVIRONMENT>

For instance:

$ tox -e py38

The available environments are declared in tox.ini.

Style Guide

Code style should be readily apparent by reading existing code.

Style Enforcement

The style of new code can be easily and incontrovertibly verified by running various developer tasks.

  1. You can lint the code easily with one command.

    But you have your choice of which one command to run.

    The following three commands are essentially equivalent, and run the code linter:

    # The Makefile lint task:
    $ make lint
    
    # is similar to the tox task:
    $ tox -e flake8
    
    # is just like running flake8:
    $ flake8 setup.py dob/ tests/
    
  2. You can lint the docs easily with one or two commands.

    The inline docstrings used to create the documentation can be verified with the docstrings linter, which returns nonzero on error. (You can also build the docs, but the builder is a little more forgiving and doesn’t complain as much as the linter.)

    # Run the docstrings linter:
    $ tox -e pydocstyle
    
    # Generate the reST docs (peruse the output for errors and warnings):
    $ make docs
    

Note

Not all of this author’s projects adhere that well to docstrings convention, so pep257-compliance is not mandatory. Generally, the module docs still build! Also, this author values tests, coverage, and readable code over spending time fleshing out docstrings (which could be a waste of time during development, as code changes quickly! but then there’s usually “no time” after development, so we often find ourselves with imperfect docstrings littered throughout the code).

As such, feel free to run the pep257 linter, but also feel free not to. It’s noisy.

  1. You can verify import statement order manually.

    Imports are grouped by classification, and then ordered alphabetically within each group.

    The isort tool will automatically fix import statements to conform.

    But isort also commits certain atrocities such as removing comments from setup.cfg and removing trailing file blank lines, the former of which is not easy to work-around, so isort is not a part of the default tox tasks. You must be run isort manually.

    $ tox -e isort
    

    You will likely find that isort makes unintended changes, and you will have to do a selective commit, e.g., git add -p <file>, while reverting other changes, e.g., git checkout -- setup.cfg.

Style Reference

The project style tracks as closely as possible to community conventions, mostly established in 2001 by Python’s creator, Guido van Rossum, and others:

In lieu of PEP 287 – reStructuredText Docstring Format, the project prefers Google-style docstrings, as defined in the Google Python Style Guide:

When building the HTML documentation from the sources, Google-style docstrings are recognized by a Sphinx extension:

  • napoleon: Support for NumPy and Google style docstrings.

Conventional Deviations

The conventions outlined in PEP 8 are enforced by the Flake8 linter, with the following custom rules:

  • Use a maximum line length of 89 characters.

    This accommodates two files side-by-side in an editor on a small laptop screen.

    It also makes code more quickly readable, e.g., think of the width of columns in a newspaper or magazine.

  • Disabled:W391: blank line at end of file”.

    Ending every file with a blank line accommodates the developer jumping their cursor to the bottom of a file in a text editor (say, by pressing <Ctrl-End>) and knowing the cursor will always land in the same column (rather than landing at the end of some arbitrary-length line).

  • Disabled:W503: line break before binary operator”.

    This produces, IMO, more readable code.

    For instance, write this:

    if (some_thing
        and another
        and another_thing):
    

    or write this:

    if (
      some_thing
      and another
      and another_thing
    ):
    

    but do not write this:

    if (some_thing and
        another and
        another_thing):
    
  • Disabled:W605: invalid escape sequence”.

    This rules incorrectly fires on some regex expression, such as \d{2}, thus, shunned.

There are some unwritten rules (because there are unenforceable by the existing linters, by way of not being features), including:

  • Keep methods small and focused.

    Use function-level scoping to break up a long method into many smaller pieces.

    When you use lots of smaller methods rather than one larger method, it has the side effect of forcing you to better document the code, by forcing you to consider and assign method names to each function.

    While this project does not need to be strict about method length – in Ruby, for instance, the RuboCop linter enforces a maximum method length of 10 lines, by default – it’s a good idea to strive for shorter methods, and it’s not all that difficult to do, once you develop your own tricks.

    (For instance, one could write a long function at first, and then break it up into smaller, more coherent pieces, selecting multiple lines of code at once, hitting <Tab> to indent the code one stop, then adding def lines to each grouping of code and assigning descriptive method names.)

  • Prefer single quotes over double quotes. (This is a loose rule).

    In other programming languages, like Bash and Ruby, double-quoted strings are interpolated, but single-quoted strings are not. This affects whether or not certain characters need to be escaped with a delimiter. And it can cause unintended consequences, e.g., a developer uses double quotation marks but forgets to escape characters within the string.

    One rule we could enforce is to use double quotes for human-readable strings, and to use single quotes for all other strings. But human- readable strings should already be encased in the localization method, e.g., _("I'm human-readable!"), so this demarcation has little additional utility.

    So do what feels right in the moment. Oftentimes, using single quotes is easiest, because the developer can avoid the Shift key and type the quotation mark with one finger.

  • Use a single underscore prefix to indicate private functions and methods.

    E.g.,: def _my_private_method(): ....

  • Python 2 compatibility has been retired.

    These conventions are no longer necessary (and were removed from the code):

    • Declare the encoding at the top of every file: -*- coding: utf-8 -*-
    • Use absolute_import and unicode_literals from the __future__ package.
    • Use six.text_type to cast a string (to Unicode).

Of Readability

Concerning Writing Tests, Docstrings, Comments, and Documentation:

  • Strive to write code that is self-documenting.

    Use expressive variable and methods names (and use long names, if they need to be).

    Break long functions into a collection of shorter methods. This will inherently document how the long function works if you give each smaller unit of work a descriptive method name.

    Use well-named, intermediate variables to make code more readable, rather than writing a long one-liner. By naming intermediate values, you will provide inherent documentation to the code.

  • Prefer tests and coverage over docstrings and documentation.

    You are encouraged to spend your time writing self-documenting code, and to develop tests that are illustrative of the usage of the new code, rather than worrying about writing docstrings and documentation, which can be tedious and time consuming to write (and to read! if you made it this far, dear reader!). Written documentation is also likely to become outdated quickly, as new code is added and old code is changed, and documents lie in the dust. (Which is not to say that docstrings have no utility! Just that docstrings are essentially worthless if what you documented has no test coverage, say.)

Just run tox

Save for running isort (see above), you can run all linter and test tasks with one 3-letter command:

$ tox

Once this command is passing, you should be good to commit (or rebase) your work and to submit a Pull Request.

Contributor Covenant Code of Conduct

Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.

Our Standards

Examples of behavior that contributes to creating a positive environment include:

  • Using welcoming and inclusive language.
  • Being respectful of differing viewpoints and experiences.
  • Gracefully accepting constructive criticism.
  • Focusing on what is best for the community.
  • Showing empathy towards other community members.

Examples of unacceptable behavior by participants include:

  • The use of sexualized language or imagery and unwelcome sexual attention or advances.
  • Trolling, insulting/derogatory comments, and personal or political attacks.
  • Public or private harassment.
  • Publishing others’ private information, such as a physical or electronic address, without explicit permission.
  • Other conduct which could reasonably be considered inappropriate in a professional setting.

Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hotoffthehamster+dob@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership.

Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct/.

For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq.

API Reference

Following is the API reference, though it’s not well documented. But it’s generated! =)

And note that this is just the dob API reference.

dob

🐹appy 🐹amster 🐹acking!!1