- We like Trey Hunner's Style Guide.
- Recommend you watch Trey's presentation at PyCon 2017.
- ArjanCode's talk on functions.
- Brandon Rhodes talk on clean architecture.
Those talks, in our view, provide a good overview of better practices to push toward.
- We try to abide by strong recommendations in PEP 8
- We use ruff for automated style checks
- We use mypy to check typing
Instead of listing all the possible rules, here are the most important rules for contributing to UPSTAGE:
- Prefer testing the emptiness of iterables using truthiness:
if some_list
, notif len(some_list) > 0
- Do not use truthiness for zero value checking, though:
if x%2==0
, e.g. - Test explicitly for
is None
when applicable. - Use comprehensions.
- Use
enumerate
for iterations that also need an index. Usezip
instead if you were going to use that index to match another iterable. - Inline
... if ... else ...
are preferred if they fit. - Do not use
var=[]
orvar={}
as a default argument (see number 3).
Infinite while
loops often need to exist in simulation code such as UPSTAGE.
We prefer to signal an infinite loop with:
while True:
...
while taking care to test for all exit conditions. An infinite loop should likely not exist if the feature isn't yielding to the SimPy event loop.
Typically if we find we are using a non-infinite while
loop, we'll consider whether we could either:
- Rewrite the loop as a
for
loop - Create a generator function that hides the
while
loop and loop over the generator with afor
loop
It's preferable to use longer variable and function names that make it clear what is happening.
A function with a name like get_actors_from_area()
is preferred to area_actors()
, for example.
Similarly, get_actors_from_hill_areas()
is preferred to get_actors_from_areas(area_type="hill")
.
An alternative would be to use Enum
values as an input, rather than strings or booleans, to express the possible values a function or method could take. get_actors_from_area(AREAS.HILL)
is expressive and explicit, and autocomplete/mypy will help the user avoid bugs.
If your proposed feature will hand data to the user, rather than returning a tuple:
def some_function() -> tuple[float,...]:
return a, b, c
If it makes sense, return an object that contains information about what you are returning:
@dataclass
class Measurement:
time: float
distance: float
probability: float
def other_function() -> Measurement:
return Measurement(a, b, c)
While docstrings should have this information regardless, it's often helpful to let tab-completion point the user to what they are looking for.
For UPSTAGE, we prefer to avoid dependencies on libraries that are not built-in. SimPy doesn't require extra, and we don't want UPSTAGE to require more, either.
Docstrings must follow the google
documentation style guide.
More information can be found here.
Docstring code is not tested (see Testing
below), but please provide enough information to show how to use a feature.
The project uses ruff
to check for style in code and docstrings. Please review the output of the style report in the pipeline before submitting a pull request.
The ruff
report is not a guarantee that the style of the code and the documentation abides by the rules of this style guide, or consistency with other UPSTAGE features, and we may ask for small changes to unify the style.
Typing is checked through mypy
. In many cases, Any
is fine, especially when passing through user values from a simulation. If possible, keeping the types simple (dataclasses help!) is preferred.
Testing is done using pytest
. Fixtures and parametrization are preferred, if possible, but not required.
Docstrings are not tested. This is because more than a few lines of code are generally required to set up and run any UPSTAGE feature, which would make docstrings unreadably long. Write tests for your feature that match the docstring as close as possible. We find that writing a test, then including the test in the Sphinx documentation is a reasonable balance for demonstrating and verifying UPSTAGE features.