patternpythonMinor
"Drone Racer" now shipping in your local area
Viewed 0 times
localyourdronenowareashippingracer
Problem
This question is a follow-up of this one; where I present a software to plan and manage drone races. I might come with a new question about the SQL part in a few days as well.
The code can still be browsed on GitHub.
Since then, on top of the changes in documentation that I made in my own answer, I included the
On top of the previous ones, I'm a little bit concerned about the layout of my project since I basically put everything in a single directory:
The program invocation now looks like one of the following:
The code became:
droneracer.py
```
"""Drone Racer is a project primarily developed for the DroneFest
organized as part of the FabLab Festival 2015. Its aim is to provide
an all-in-one interface for races organizers to:
- create different events for drones competition;
- register contestants and their associated drones;
- classify drones into categories;
- create several routes with their own set of rules for each event;
- setup and monitor races on a designated route;
- gather statistics on races for drivers, event or kind of route.
To reduce the overhead of having extraneous services for database
access, Drone Racer makes use of the python's built-in sqlite module.
It uses it to store informations on the contestants, the drones, the
different type of routes and the races leaderboards.
Additionally, setup, updates & leaderboard for each race can be sent
to a RESTful API for the audience.
"""
import os
from argparse import ArgumentParser
from drone_racer.i18n import translations
import drone_racer
_, _N = translations('cli')
The code can still be browsed on GitHub.
Since then, on top of the changes in documentation that I made in my own answer, I included the
gettext module and changed the CLI to include sub-commands.On top of the previous ones, I'm a little bit concerned about the layout of my project since I basically put everything in a single directory:
+ droneracer.py
+ drone_racer
+ fr
| + LC_MESSAGES
| + cli.po
| + cli.mo
| + utils.po
| + utils.mo
+ __init__.py
+ console.py
+ i18n.py
+ rest.py
+ sql.py
+ threads.py
+ ui.pyThe program invocation now looks like one of the following:
python droneracer.py --fancy-title xbee /dev/ttyUSB0
python droneracer.py udp --port 3487
python droneracer.py --fancy-title
The code became:
droneracer.py
```
"""Drone Racer is a project primarily developed for the DroneFest
organized as part of the FabLab Festival 2015. Its aim is to provide
an all-in-one interface for races organizers to:
- create different events for drones competition;
- register contestants and their associated drones;
- classify drones into categories;
- create several routes with their own set of rules for each event;
- setup and monitor races on a designated route;
- gather statistics on races for drivers, event or kind of route.
To reduce the overhead of having extraneous services for database
access, Drone Racer makes use of the python's built-in sqlite module.
It uses it to store informations on the contestants, the drones, the
different type of routes and the races leaderboards.
Additionally, setup, updates & leaderboard for each race can be sent
to a RESTful API for the audience.
"""
import os
from argparse import ArgumentParser
from drone_racer.i18n import translations
import drone_racer
_, _N = translations('cli')
Solution
i18n
This is just me, but I've always found the
i18n.py
cli.py
I understand that it isn't for everyone, but I've always preferred that the original strings be less embedded in the source code, and instead be in the translating engine. I actually prefer it even more if they aren't in the source code at all, but rather in some JSON or XML file (or whatever your preferred format is). By putting all of the strings into the translating engine instead, you make it much easier to change where and how the strings and their translations are stored and received, and you don't have to change the source of any of the other files.
If you don't like that they're instances, you can do some hackery for class-level properties - I've never found a satisfying way to implement that where the properties aren't read-only, but in this context you want them to be read-only (win-win).
Reader classes
Subclassing
Your implementation of your reader classes make me a little uncomfortable. From the docs
No other methods (except for the constructor) should be overridden in a subclass. In other words, only override the
Now, currently, I don't see anything in them that actually overrides anything in the
It seems that you don't actually need your readers to subclass
Signalling
Instead of using a boolean to determine if the reader should continue, use the appropriate concurrency primitive, in this case
I don't know what sort of machines you're running this on, but if there's any chance that
Appropriate errors
Likewise, in
Typo?
Should this say
The whole
This is just me, but I've always found the
_ and _N aspect of gettext a little hard to read and somewhat too tightly coupled. I've always preferred something like this:i18n.py
import os.path
import gettext
def _translations(domain):
locales_dir = os.path.abspath(os.path.dirname(__file__))
translation = gettext.translation(domain, locales_dir)
return translation.gettext, translation.ngettext
class _BaseTranslator(object):
@staticmethod
def _translations(domain):
locales_dir = os.path.abspath(os.path.dirname(__file__))
translation = gettext.translation(domain, locales_dir)
return translation.gettext, translation.ngettext
def __init__(self, domain):
self.domain = domain
self._, self._N = self._translations(domain)
class _CliTranslations(_BaseTranslator):
def __init__(self, domain='cli'):
super().__init__(domain)
@property
def description(self):
return self._('"Drone Racer"\'s Graphical User Interface')
# etc
util_translations = _UtilTranslations('util')
cli_translations = _CliTranslations('cli')cli.py
from i18n import cli_translations as cli
parser = ArgumentParser(description=cli.description)I understand that it isn't for everyone, but I've always preferred that the original strings be less embedded in the source code, and instead be in the translating engine. I actually prefer it even more if they aren't in the source code at all, but rather in some JSON or XML file (or whatever your preferred format is). By putting all of the strings into the translating engine instead, you make it much easier to change where and how the strings and their translations are stored and received, and you don't have to change the source of any of the other files.
If you don't like that they're instances, you can do some hackery for class-level properties - I've never found a satisfying way to implement that where the properties aren't read-only, but in this context you want them to be read-only (win-win).
Reader classes
Subclassing
ThreadYour implementation of your reader classes make me a little uncomfortable. From the docs
No other methods (except for the constructor) should be overridden in a subclass. In other words, only override the
__init__() and run() methods of this class.Now, currently, I don't see anything in them that actually overrides anything in the
Thread class, however I would be mindful of this in case a future Python version changes it (tbh I don't see that happening, but it's worth being considerate of).It seems that you don't actually need your readers to subclass
Thread - what about making each of them callable objects instead, and then set them as the targets of the threads? Then you'll avoid any of these potential issues, and avoid having to muck around with Thread too much.Signalling
Instead of using a boolean to determine if the reader should continue, use the appropriate concurrency primitive, in this case
threading.Event.dummy_threadingI don't know what sort of machines you're running this on, but if there's any chance that
_thread won't be available you should have something like try:
import threading
except ImportError:
import dummy_threading as threadingAppropriate errors
_process_value seems like it should do something if the drone id is less than 0 - probably emit some warning for the user so they know that they, or someone else, has done something wrong. The only reason to just return is if you either don't think it'll ever happen, or if ignoring it is fine. It seems that it would probably be indicative of some larger problem though, one that the operator/officiator/someone should be aware of.Likewise, in
run you ignore TypeErrors - why? Again, unless they can be safely ignored and wouldn't be either unusual or unexpected you should at least give some sort of warning that something unusual might be going on.Typo?
Should this say
_BeeReaderMixin? Or are you trying to incorporate BaseReader? Some combination thereof?self._cls = type('XBeeReader', (_BaseReaderMixin, base_cls), {})The whole
XBeeReader seems like it could be incorporated into a metaclass and then used that way instead of how you have implemented it, but metaclasses aren't my forte.Code Snippets
import os.path
import gettext
def _translations(domain):
locales_dir = os.path.abspath(os.path.dirname(__file__))
translation = gettext.translation(domain, locales_dir)
return translation.gettext, translation.ngettext
class _BaseTranslator(object):
@staticmethod
def _translations(domain):
locales_dir = os.path.abspath(os.path.dirname(__file__))
translation = gettext.translation(domain, locales_dir)
return translation.gettext, translation.ngettext
def __init__(self, domain):
self.domain = domain
self._, self._N = self._translations(domain)
class _CliTranslations(_BaseTranslator):
def __init__(self, domain='cli'):
super().__init__(domain)
@property
def description(self):
return self._('"Drone Racer"\'s Graphical User Interface')
# etc
util_translations = _UtilTranslations('util')
cli_translations = _CliTranslations('cli')from i18n import cli_translations as cli
parser = ArgumentParser(description=cli.description)try:
import threading
except ImportError:
import dummy_threading as threadingself._cls = type('XBeeReader', (_BaseReaderMixin, base_cls), {})Context
StackExchange Code Review Q#116169, answer score: 5
Revisions (0)
No revisions yet.