Merge branch 'backend-frameworkification' of bss/dr.botzo into master
Merge the (aborted) backend/Discord attempt. See PR #2
This commit is contained in:
commit
cbbf6eb311
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,6 +3,8 @@ build/
|
|||||||
dist/
|
dist/
|
||||||
tags/
|
tags/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
dr.botzo.data
|
dr.botzo.data
|
||||||
dr.botzo.cfg
|
dr.botzo.cfg
|
||||||
localsettings.py
|
localsettings.py
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
doc-warnings: true
|
|
||||||
strictness: high
|
|
||||||
ignore-paths:
|
|
||||||
- migrations
|
|
||||||
ignore-patterns:
|
|
||||||
- \.log$
|
|
||||||
- localsettings.py$
|
|
||||||
- parsetab.py$
|
|
||||||
pylint:
|
|
||||||
enable:
|
|
||||||
- relative-import
|
|
||||||
options:
|
|
||||||
max-line-length: 120
|
|
||||||
good-names: log
|
|
||||||
pep8:
|
|
||||||
options:
|
|
||||||
max-line-length: 120
|
|
||||||
pep257:
|
|
||||||
disable:
|
|
||||||
- D203
|
|
@ -22,7 +22,8 @@ class Countdown(Plugin):
|
|||||||
new_reminder_regex = (r'remind\s+(?P<who>[^\s]+)\s+(?P<when_type>at|in|on)\s+(?P<when>.*?)\s+'
|
new_reminder_regex = (r'remind\s+(?P<who>[^\s]+)\s+(?P<when_type>at|in|on)\s+(?P<when>.*?)\s+'
|
||||||
r'(and\s+every\s+(?P<recurring_period>.*?)\s+)?'
|
r'(and\s+every\s+(?P<recurring_period>.*?)\s+)?'
|
||||||
r'(until\s+(?P<recurring_until>.*?)\s+)?'
|
r'(until\s+(?P<recurring_until>.*?)\s+)?'
|
||||||
r'(to|that|about)\s+(?P<text>.*)')
|
r'(to|that|about)\s+(?P<text>.*?)'
|
||||||
|
r'(?=\s+\("(?P<name>.*)"\)|$)')
|
||||||
|
|
||||||
def __init__(self, bot, connection, event):
|
def __init__(self, bot, connection, event):
|
||||||
"""Initialize some stuff."""
|
"""Initialize some stuff."""
|
||||||
@ -42,7 +43,7 @@ class Countdown(Plugin):
|
|||||||
|
|
||||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!countdown\s+list$',
|
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!countdown\s+list$',
|
||||||
self.handle_item_list, -20)
|
self.handle_item_list, -20)
|
||||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!countdown\s+(\S+)$',
|
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!countdown\s+(.+)$',
|
||||||
self.handle_item_detail, -20)
|
self.handle_item_detail, -20)
|
||||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], self.new_reminder_regex,
|
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], self.new_reminder_regex,
|
||||||
self.handle_new_reminder, -50)
|
self.handle_new_reminder, -50)
|
||||||
@ -99,9 +100,16 @@ class Countdown(Plugin):
|
|||||||
recurring_period = match.group('recurring_period')
|
recurring_period = match.group('recurring_period')
|
||||||
recurring_until = match.group('recurring_until')
|
recurring_until = match.group('recurring_until')
|
||||||
text = match.group('text')
|
text = match.group('text')
|
||||||
|
name = match.group('name')
|
||||||
log.debug("%s / %s / %s", who, when, text)
|
log.debug("%s / %s / %s", who, when, text)
|
||||||
|
|
||||||
item_name = '{0:s}-{1:s}'.format(event.sender_nick, timezone.now().strftime('%s'))
|
if not name:
|
||||||
|
item_name = '{0:s}-{1:s}'.format(event.sender_nick, timezone.now().strftime('%s'))
|
||||||
|
else:
|
||||||
|
if CountdownItem.objects.filter(name=name).count() > 0:
|
||||||
|
self.bot.reply(event, "item with name '{0:s}' already exists".format(name))
|
||||||
|
return 'NO MORE'
|
||||||
|
item_name = name
|
||||||
|
|
||||||
# parse when to send the notification
|
# parse when to send the notification
|
||||||
if when_type == 'in':
|
if when_type == 'in':
|
||||||
|
23
countdown/migrations/0006_auto_20201025_1716.py
Normal file
23
countdown/migrations/0006_auto_20201025_1716.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-10-25 17:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('countdown', '0005_countdownitem_recurring_until'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='countdownitem',
|
||||||
|
name='recurring_period',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='countdownitem',
|
||||||
|
name='reminder_target',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=64),
|
||||||
|
),
|
||||||
|
]
|
@ -1,14 +1,8 @@
|
|||||||
"""Countdown item models."""
|
"""Countdown item models."""
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger('countdown.models')
|
|
||||||
|
|
||||||
|
|
||||||
class CountdownItem(models.Model):
|
class CountdownItem(models.Model):
|
||||||
"""Track points in time."""
|
"""Track points in time."""
|
||||||
|
|
||||||
@ -19,13 +13,13 @@ class CountdownItem(models.Model):
|
|||||||
sent_reminder = models.BooleanField(default=False)
|
sent_reminder = models.BooleanField(default=False)
|
||||||
|
|
||||||
reminder_message = models.TextField(default="")
|
reminder_message = models.TextField(default="")
|
||||||
reminder_target = models.CharField(max_length=64, default='')
|
reminder_target = models.CharField(max_length=64, blank=True, default='')
|
||||||
|
|
||||||
recurring_period = models.CharField(max_length=64, default='')
|
recurring_period = models.CharField(max_length=64, blank=True, default='')
|
||||||
recurring_until = models.DateTimeField(null=True, blank=True, default=None)
|
recurring_until = models.DateTimeField(null=True, blank=True, default=None)
|
||||||
|
|
||||||
created_time = models.DateTimeField(auto_now_add=True)
|
created_time = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""String representation."""
|
"""Summarize object."""
|
||||||
return "{0:s} @ {1:s}".format(self.name, timezone.localtime(self.at_time).strftime('%Y-%m-%d %H:%M:%S %Z'))
|
return "{0:s} @ {1:s}".format(self.name, timezone.localtime(self.at_time).strftime('%Y-%m-%d %H:%M:%S %Z'))
|
||||||
|
14
countdown/serializers.py
Normal file
14
countdown/serializers.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
"""REST serializers for countdown items."""
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from countdown.models import CountdownItem
|
||||||
|
|
||||||
|
|
||||||
|
class CountdownItemSerializer(serializers.ModelSerializer):
|
||||||
|
"""Countdown item serializer for the REST API."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta options."""
|
||||||
|
|
||||||
|
model = CountdownItem
|
||||||
|
fields = '__all__'
|
12
countdown/urls.py
Normal file
12
countdown/urls.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"""URL patterns for the countdown views."""
|
||||||
|
from django.conf.urls import include, url
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
|
from countdown.views import CountdownItemViewSet
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r'items', CountdownItemViewSet)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^api/', include(router.urls)),
|
||||||
|
]
|
16
countdown/views.py
Normal file
16
countdown/views.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""Provide an interface to countdown items."""
|
||||||
|
# from rest_framework.decorators import action
|
||||||
|
# from rest_framework.response import Response
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
|
|
||||||
|
from countdown.models import CountdownItem
|
||||||
|
from countdown.serializers import CountdownItemSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class CountdownItemViewSet(ReadOnlyModelViewSet):
|
||||||
|
"""Provide list and detail actions for countdown items."""
|
||||||
|
|
||||||
|
queryset = CountdownItem.objects.all()
|
||||||
|
serializer_class = CountdownItemSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
@ -14,11 +14,12 @@ logger = logging.getLogger(__name__)
|
|||||||
class Dice(Plugin):
|
class Dice(Plugin):
|
||||||
"""Roll simple or complex dice strings."""
|
"""Roll simple or complex dice strings."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, bot, connection, event):
|
||||||
"""Set up the plugin."""
|
"""Set up the plugin."""
|
||||||
super(Dice, self).__init__()
|
|
||||||
self.roller = DiceRoller()
|
self.roller = DiceRoller()
|
||||||
|
|
||||||
|
super(Dice, self).__init__(bot, connection, event)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Set up the handlers."""
|
"""Set up the handlers."""
|
||||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!roll\s+(.*)$',
|
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!roll\s+(.*)$',
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
"""General/baselite/site-wide URLs."""
|
"""General/baselite/site-wide URLs."""
|
||||||
|
from adminplus.sites import AdminSitePlus
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from adminplus.sites import AdminSitePlus
|
|
||||||
|
|
||||||
admin.site = AdminSitePlus()
|
admin.site = AdminSitePlus()
|
||||||
admin.sites.site = admin.site
|
admin.sites.site = admin.site
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
@ -13,11 +11,13 @@ admin.autodiscover()
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', TemplateView.as_view(template_name='index.html'), name='index'),
|
url(r'^$', TemplateView.as_view(template_name='index.html'), name='index'),
|
||||||
|
|
||||||
|
url(r'^countdown/', include('countdown.urls')),
|
||||||
url(r'^dice/', include('dice.urls')),
|
url(r'^dice/', include('dice.urls')),
|
||||||
url(r'^dispatch/', include('dispatch.urls')),
|
url(r'^dispatch/', include('dispatch.urls')),
|
||||||
url(r'^itemsets/', include('facts.urls')),
|
url(r'^itemsets/', include('facts.urls')),
|
||||||
url(r'^karma/', include('karma.urls')),
|
url(r'^karma/', include('karma.urls')),
|
||||||
url(r'^markov/', include('markov.urls')),
|
url(r'^markov/', include('markov.urls')),
|
||||||
|
url(r'^pi/', include('pi.urls')),
|
||||||
url(r'^races/', include('races.urls')),
|
url(r'^races/', include('races.urls')),
|
||||||
url(r'^weather/', include('weather.urls')),
|
url(r'^weather/', include('weather.urls')),
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import re
|
|||||||
import irc.client
|
import irc.client
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db.models import Count, Sum
|
||||||
|
|
||||||
from ircbot.lib import Plugin
|
from ircbot.lib import Plugin
|
||||||
from karma.models import KarmaKey, KarmaLogEntry
|
from karma.models import KarmaKey, KarmaLogEntry
|
||||||
@ -23,6 +24,9 @@ class Karma(Plugin):
|
|||||||
self.connection.add_global_handler('pubmsg', self.handle_chatter, -20)
|
self.connection.add_global_handler('pubmsg', self.handle_chatter, -20)
|
||||||
self.connection.add_global_handler('privmsg', self.handle_chatter, -20)
|
self.connection.add_global_handler('privmsg', self.handle_chatter, -20)
|
||||||
|
|
||||||
|
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
||||||
|
(r'^!karma\s+keyreport\s+(.*)'),
|
||||||
|
self.handle_keyreport, -20)
|
||||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
||||||
r'^!karma\s+rank\s+(.*)$',
|
r'^!karma\s+rank\s+(.*)$',
|
||||||
self.handle_rank, -20)
|
self.handle_rank, -20)
|
||||||
@ -42,6 +46,7 @@ class Karma(Plugin):
|
|||||||
self.connection.remove_global_handler('pubmsg', self.handle_chatter)
|
self.connection.remove_global_handler('pubmsg', self.handle_chatter)
|
||||||
self.connection.remove_global_handler('privmsg', self.handle_chatter)
|
self.connection.remove_global_handler('privmsg', self.handle_chatter)
|
||||||
|
|
||||||
|
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_keyreport)
|
||||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_rank)
|
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_rank)
|
||||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_report)
|
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_report)
|
||||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_stats)
|
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_stats)
|
||||||
@ -102,6 +107,19 @@ class Karma(Plugin):
|
|||||||
except KarmaKey.DoesNotExist:
|
except KarmaKey.DoesNotExist:
|
||||||
return self.bot.reply(event, "i have not seen any karma for {0:s}".format(match.group(1)))
|
return self.bot.reply(event, "i have not seen any karma for {0:s}".format(match.group(1)))
|
||||||
|
|
||||||
|
def handle_keyreport(self, connection, event, match):
|
||||||
|
"""Provide report on a karma key."""
|
||||||
|
key = match.group(1).lower().rstrip()
|
||||||
|
try:
|
||||||
|
karma_key = KarmaKey.objects.get(key=key)
|
||||||
|
karmaers = KarmaLogEntry.objects.filter(key=karma_key)
|
||||||
|
karmaers = karmaers.values('nickmask').annotate(Sum('delta')).annotate(Count('delta')).order_by('-delta__count')
|
||||||
|
karmaers_list = [f"{irc.client.NickMask(x['nickmask']).nick} ({x['delta__count']}, {'+' if x['delta__sum'] >= 0 else ''}{x['delta__sum']})" for x in karmaers]
|
||||||
|
karmaers_list_str = ", ".join(karmaers_list[:10])
|
||||||
|
return self.bot.reply(event, f"most opinionated on {key}: {karmaers_list_str}")
|
||||||
|
except KarmaKey.DoesNotExist:
|
||||||
|
return self.bot.reply(event, "i have not seen any karma for {0:s}".format(match.group(1)))
|
||||||
|
|
||||||
def handle_report(self, connection, event, match):
|
def handle_report(self, connection, event, match):
|
||||||
"""Provide some karma reports."""
|
"""Provide some karma reports."""
|
||||||
|
|
||||||
|
@ -1,21 +1,14 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
"""Provide pi simulation results to IRC."""
|
||||||
import logging
|
|
||||||
|
|
||||||
from ircbot.lib import Plugin
|
from ircbot.lib import Plugin
|
||||||
from pi.models import PiLog
|
from pi.models import PiLog
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger('pi.ircplugin')
|
|
||||||
|
|
||||||
|
|
||||||
class Pi(Plugin):
|
class Pi(Plugin):
|
||||||
|
|
||||||
"""Use the Monte Carlo method to simulate pi."""
|
"""Use the Monte Carlo method to simulate pi."""
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Set up the handlers."""
|
"""Set up the handlers."""
|
||||||
|
|
||||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!pi$',
|
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!pi$',
|
||||||
self.handle_pi, -20)
|
self.handle_pi, -20)
|
||||||
|
|
||||||
@ -23,17 +16,16 @@ class Pi(Plugin):
|
|||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Tear down handlers."""
|
"""Tear down handlers."""
|
||||||
|
|
||||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_pi)
|
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_pi)
|
||||||
|
|
||||||
super(Pi, self).stop()
|
super(Pi, self).stop()
|
||||||
|
|
||||||
def handle_pi(self, connection, event, match):
|
def handle_pi(self, connection, event, match):
|
||||||
"""Handle the pi command by generating another value and presenting it."""
|
"""Handle the pi command by generating another value and presenting it."""
|
||||||
|
newest, x, y = PiLog.objects.simulate()
|
||||||
newest, x, y, hit = PiLog.objects.simulate()
|
|
||||||
msg = ("({0:.10f}, {1:.10f}) is {2}within the unit circle. π is {5:.10f}. (i:{3:d} p:{4:d})"
|
msg = ("({0:.10f}, {1:.10f}) is {2}within the unit circle. π is {5:.10f}. (i:{3:d} p:{4:d})"
|
||||||
"".format(x, y, "" if hit else "not ", newest.count_inside, newest.count_total, newest.value()))
|
"".format(x, y, "" if newest.hit else "not ", newest.total_count_inside,
|
||||||
|
newest.total_count, newest.value))
|
||||||
|
|
||||||
return self.bot.reply(event, msg)
|
return self.bot.reply(event, msg)
|
||||||
|
|
||||||
|
23
pi/migrations/0003_rename_count_fields.py
Normal file
23
pi/migrations/0003_rename_count_fields.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-10-24 16:27
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pi', '0002_auto_20150521_2204'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='pilog',
|
||||||
|
old_name='count_total',
|
||||||
|
new_name='total_count',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='pilog',
|
||||||
|
old_name='count_inside',
|
||||||
|
new_name='total_count_inside',
|
||||||
|
),
|
||||||
|
]
|
25
pi/migrations/0004_simulation_x_y_logging.py
Normal file
25
pi/migrations/0004_simulation_x_y_logging.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-10-24 16:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pi', '0003_rename_count_fields'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pilog',
|
||||||
|
name='simulation_x',
|
||||||
|
field=models.DecimalField(decimal_places=10, default=-1.0, max_digits=11),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pilog',
|
||||||
|
name='simulation_y',
|
||||||
|
field=models.DecimalField(decimal_places=10, default=-1.0, max_digits=11),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
55
pi/models.py
55
pi/models.py
@ -1,67 +1,70 @@
|
|||||||
"""Karma logging models."""
|
"""Karma logging models."""
|
||||||
|
|
||||||
import logging
|
|
||||||
import math
|
import math
|
||||||
import pytz
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
import pytz
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger('pi.models')
|
|
||||||
|
|
||||||
|
|
||||||
class PiLogManager(models.Manager):
|
class PiLogManager(models.Manager):
|
||||||
|
|
||||||
"""Assemble some queries against PiLog."""
|
"""Assemble some queries against PiLog."""
|
||||||
|
|
||||||
def simulate(self):
|
def simulate(self):
|
||||||
"""Add one more entry to the log, and return it."""
|
"""Add one more entry to the log, and return it."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
latest = self.latest()
|
latest = self.latest()
|
||||||
except PiLog.DoesNotExist:
|
except PiLog.DoesNotExist:
|
||||||
latest = PiLog(count_inside=0, count_total=0)
|
latest = PiLog.objects.create(simulation_x=0.0, simulation_y=0.0,
|
||||||
|
total_count_inside=0, total_count=0)
|
||||||
latest.save()
|
latest.save()
|
||||||
|
|
||||||
inside = latest.count_inside
|
inside = latest.total_count_inside
|
||||||
total = latest.count_total
|
total = latest.total_count
|
||||||
|
|
||||||
x = random.random()
|
x = random.random()
|
||||||
y = random.random()
|
y = random.random()
|
||||||
hit = True if math.hypot(x,y) < 1 else False
|
total += 1
|
||||||
|
if math.hypot(x, y) < 1:
|
||||||
|
inside += 1
|
||||||
|
|
||||||
if hit:
|
newest = PiLog.objects.create(simulation_x=x, simulation_y=y,
|
||||||
newest = PiLog(count_inside=inside+1, count_total=total+1)
|
total_count_inside=inside, total_count=total)
|
||||||
else:
|
|
||||||
newest = PiLog(count_inside=inside, count_total=total+1)
|
|
||||||
newest.save()
|
|
||||||
|
|
||||||
return newest, x, y, hit
|
# TODO: remove the x, y return values, now that we track them in the object
|
||||||
|
return newest, x, y
|
||||||
|
|
||||||
|
|
||||||
class PiLog(models.Model):
|
class PiLog(models.Model):
|
||||||
|
|
||||||
"""Track pi as it is estimated over time."""
|
"""Track pi as it is estimated over time."""
|
||||||
|
|
||||||
count_inside = models.PositiveIntegerField()
|
simulation_x = models.DecimalField(max_digits=11, decimal_places=10)
|
||||||
count_total = models.PositiveIntegerField()
|
simulation_y = models.DecimalField(max_digits=11, decimal_places=10)
|
||||||
|
total_count_inside = models.PositiveIntegerField()
|
||||||
|
total_count = models.PositiveIntegerField()
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
objects = PiLogManager()
|
objects = PiLogManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
"""Options for the PiLog class."""
|
||||||
|
|
||||||
get_latest_by = 'created'
|
get_latest_by = 'created'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""String representation."""
|
"""Provide string representation."""
|
||||||
|
|
||||||
tz = pytz.timezone(settings.TIME_ZONE)
|
tz = pytz.timezone(settings.TIME_ZONE)
|
||||||
return "({0:d}/{1:d}) @ {2:s}".format(self.count_inside, self.count_total,
|
return "({0:d}/{1:d}) @ {2:s}".format(self.total_count_inside, self.total_count,
|
||||||
self.created.astimezone(tz).strftime('%Y-%m-%d %H:%M:%S %Z'))
|
self.created.astimezone(tz).strftime('%Y-%m-%d %H:%M:%S %Z'))
|
||||||
|
|
||||||
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
"""Return this log entry's value of pi."""
|
"""Return this log entry's estimated value of pi."""
|
||||||
|
if self.total_count == 0:
|
||||||
|
return 0.0
|
||||||
|
return 4.0 * int(self.total_count_inside) / int(self.total_count)
|
||||||
|
|
||||||
return 4.0 * int(self.count_inside) / int(self.count_total)
|
@property
|
||||||
|
def hit(self):
|
||||||
|
"""Return if this log entry is inside the unit circle."""
|
||||||
|
return math.hypot(self.simulation_x, self.simulation_y) < 1
|
||||||
|
14
pi/serializers.py
Normal file
14
pi/serializers.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
"""REST serializers for pi simulations."""
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from pi.models import PiLog
|
||||||
|
|
||||||
|
|
||||||
|
class PiLogSerializer(serializers.ModelSerializer):
|
||||||
|
"""Pi simulation log entry serializer for the REST API."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta options."""
|
||||||
|
|
||||||
|
model = PiLog
|
||||||
|
fields = ('id', 'simulation_x', 'simulation_y', 'total_count', 'total_count_inside', 'value', 'hit')
|
12
pi/urls.py
Normal file
12
pi/urls.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"""URL patterns for the pi views."""
|
||||||
|
from django.conf.urls import include, url
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
|
from pi.views import PiLogViewSet
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r'simulations', PiLogViewSet)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^api/', include(router.urls)),
|
||||||
|
]
|
22
pi/views.py
Normal file
22
pi/views.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""Provide pi simulation results."""
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
|
|
||||||
|
from pi.models import PiLog
|
||||||
|
from pi.serializers import PiLogSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class PiLogViewSet(ReadOnlyModelViewSet):
|
||||||
|
"""Provide list and detail actions for pi simulation log entries."""
|
||||||
|
|
||||||
|
queryset = PiLog.objects.all()
|
||||||
|
serializer_class = PiLogSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
@action(detail=False, methods=['post'])
|
||||||
|
def simulate(self, request):
|
||||||
|
"""Run one simulation of the pi estimator."""
|
||||||
|
simulation, _, _ = PiLog.objects.simulate()
|
||||||
|
return Response(self.get_serializer(simulation).data, 201)
|
@ -1,6 +0,0 @@
|
|||||||
-r requirements.in
|
|
||||||
|
|
||||||
logilab-common # prospector thing, i guess
|
|
||||||
pip-tools # pip-compile
|
|
||||||
prospector # code quality
|
|
||||||
versioneer # auto-generate version numbers
|
|
@ -1,68 +0,0 @@
|
|||||||
#
|
|
||||||
# This file is autogenerated by pip-compile
|
|
||||||
# To update, run:
|
|
||||||
#
|
|
||||||
# pip-compile --output-file=requirements-dev.txt requirements-dev.in
|
|
||||||
#
|
|
||||||
astroid==2.2.5 # via pylint, pylint-celery, pylint-flask, requirements-detector
|
|
||||||
certifi==2019.6.16 # via requests
|
|
||||||
chardet==3.0.4 # via requests
|
|
||||||
click==7.0 # via pip-tools
|
|
||||||
django-adminplus==0.5
|
|
||||||
django-bootstrap3==11.0.0
|
|
||||||
django-extensions==2.1.9
|
|
||||||
django-registration-redux==2.6
|
|
||||||
django==2.2.2
|
|
||||||
djangorestframework==3.9.4
|
|
||||||
dodgy==0.1.9 # via prospector
|
|
||||||
future==0.17.1 # via parsedatetime
|
|
||||||
idna==2.8 # via requests
|
|
||||||
inflect==2.1.0 # via jaraco.itertools
|
|
||||||
irc==15.0.6
|
|
||||||
isort==4.3.20 # via pylint
|
|
||||||
jaraco.classes==2.0 # via jaraco.collections
|
|
||||||
jaraco.collections==2.0 # via irc
|
|
||||||
jaraco.functools==2.0 # via irc, jaraco.text, tempora
|
|
||||||
jaraco.itertools==4.4.2 # via irc
|
|
||||||
jaraco.logging==2.0 # via irc
|
|
||||||
jaraco.stream==2.0 # via irc
|
|
||||||
jaraco.text==3.0 # via irc, jaraco.collections
|
|
||||||
lazy-object-proxy==1.4.1 # via astroid
|
|
||||||
logilab-common==1.4.2
|
|
||||||
mccabe==0.6.1 # via prospector, pylint
|
|
||||||
more-itertools==7.0.0 # via irc, jaraco.functools, jaraco.itertools
|
|
||||||
oauthlib==3.0.1 # via requests-oauthlib
|
|
||||||
parsedatetime==2.4
|
|
||||||
pep8-naming==0.4.1 # via prospector
|
|
||||||
pip-tools==4.1.0
|
|
||||||
ply==3.11
|
|
||||||
prospector==1.1.6.4
|
|
||||||
pycodestyle==2.4.0 # via prospector
|
|
||||||
pydocstyle==3.0.0 # via prospector
|
|
||||||
pyflakes==1.6.0 # via prospector
|
|
||||||
pylint-celery==0.3 # via prospector
|
|
||||||
pylint-django==2.0.9 # via prospector
|
|
||||||
pylint-flask==0.6 # via prospector
|
|
||||||
pylint-plugin-utils==0.5 # via prospector, pylint-celery, pylint-django, pylint-flask
|
|
||||||
pylint==2.3.1 # via prospector, pylint-celery, pylint-django, pylint-flask, pylint-plugin-utils
|
|
||||||
python-dateutil==2.8.0
|
|
||||||
python-gitlab==1.9.0
|
|
||||||
python-mpd2==1.0.0
|
|
||||||
pytz==2019.1
|
|
||||||
pyyaml==5.1.1 # via prospector
|
|
||||||
requests-oauthlib==1.2.0 # via twython
|
|
||||||
requests==2.22.0 # via python-gitlab, requests-oauthlib, twython
|
|
||||||
requirements-detector==0.6 # via prospector
|
|
||||||
setoptconf==0.2.0 # via prospector
|
|
||||||
six==1.12.0 # via astroid, django-extensions, irc, jaraco.classes, jaraco.collections, jaraco.itertools, jaraco.logging, jaraco.stream, logilab-common, pip-tools, pydocstyle, python-dateutil, python-gitlab, tempora
|
|
||||||
snowballstemmer==1.2.1 # via pydocstyle
|
|
||||||
sqlparse==0.3.0 # via django
|
|
||||||
tempora==1.14.1 # via irc, jaraco.logging
|
|
||||||
twython==3.7.0
|
|
||||||
typed-ast==1.4.0 # via astroid
|
|
||||||
urllib3==1.25.3 # via requests
|
|
||||||
versioneer==0.18
|
|
||||||
wrapt==1.11.2 # via astroid
|
|
||||||
|
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
|
||||||
# setuptools==41.4.0 # via logilab-common
|
|
@ -1,40 +0,0 @@
|
|||||||
#
|
|
||||||
# This file is autogenerated by pip-compile
|
|
||||||
# To update, run:
|
|
||||||
#
|
|
||||||
# pip-compile --output-file=requirements.txt requirements.in
|
|
||||||
#
|
|
||||||
certifi==2019.6.16 # via requests
|
|
||||||
chardet==3.0.4 # via requests
|
|
||||||
django-adminplus==0.5
|
|
||||||
django-bootstrap3==11.0.0
|
|
||||||
django-extensions==2.1.9
|
|
||||||
django-registration-redux==2.6
|
|
||||||
django==2.2.2
|
|
||||||
djangorestframework==3.9.4
|
|
||||||
future==0.17.1 # via parsedatetime
|
|
||||||
idna==2.8 # via requests
|
|
||||||
inflect==2.1.0 # via jaraco.itertools
|
|
||||||
irc==15.0.6
|
|
||||||
jaraco.classes==2.0 # via jaraco.collections
|
|
||||||
jaraco.collections==2.0 # via irc
|
|
||||||
jaraco.functools==2.0 # via irc, jaraco.text, tempora
|
|
||||||
jaraco.itertools==4.4.2 # via irc
|
|
||||||
jaraco.logging==2.0 # via irc
|
|
||||||
jaraco.stream==2.0 # via irc
|
|
||||||
jaraco.text==3.0 # via irc, jaraco.collections
|
|
||||||
more-itertools==7.0.0 # via irc, jaraco.functools, jaraco.itertools
|
|
||||||
oauthlib==3.0.1 # via requests-oauthlib
|
|
||||||
parsedatetime==2.4
|
|
||||||
ply==3.11
|
|
||||||
python-dateutil==2.8.0
|
|
||||||
python-gitlab==1.9.0
|
|
||||||
python-mpd2==1.0.0
|
|
||||||
pytz==2019.1
|
|
||||||
requests-oauthlib==1.2.0 # via twython
|
|
||||||
requests==2.22.0 # via python-gitlab, requests-oauthlib, twython
|
|
||||||
six==1.12.0 # via django-extensions, irc, jaraco.classes, jaraco.collections, jaraco.itertools, jaraco.logging, jaraco.stream, python-dateutil, python-gitlab, tempora
|
|
||||||
sqlparse==0.3.0 # via django
|
|
||||||
tempora==1.14.1 # via irc, jaraco.logging
|
|
||||||
twython==3.7.0
|
|
||||||
urllib3==1.25.3 # via requests
|
|
25
requirements/requirements-dev.in
Normal file
25
requirements/requirements-dev.in
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
-r requirements.in
|
||||||
|
|
||||||
|
# testing runner, test reporting, packages used during testing (e.g. requests-mock), etc.
|
||||||
|
pytest
|
||||||
|
pytest-cov
|
||||||
|
pytest-django
|
||||||
|
|
||||||
|
# linting and other static code analysis
|
||||||
|
bandit
|
||||||
|
dlint
|
||||||
|
flake8 # flake8 and plugins, for local dev linting in vim
|
||||||
|
flake8-blind-except
|
||||||
|
flake8-builtins
|
||||||
|
flake8-docstrings
|
||||||
|
flake8-executable
|
||||||
|
flake8-fixme
|
||||||
|
flake8-isort
|
||||||
|
flake8-logging-format
|
||||||
|
flake8-mutable
|
||||||
|
|
||||||
|
# maintenance utilities and tox
|
||||||
|
pip-tools # pip-compile
|
||||||
|
tox # CI stuff
|
||||||
|
tox-wheel # build wheels in tox
|
||||||
|
versioneer # automatic version numbering
|
92
requirements/requirements-dev.txt
Normal file
92
requirements/requirements-dev.txt
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile
|
||||||
|
# To update, run:
|
||||||
|
#
|
||||||
|
# pip-compile --output-file=requirements/requirements-dev.txt requirements/requirements-dev.in
|
||||||
|
#
|
||||||
|
appdirs==1.4.4 # via virtualenv
|
||||||
|
asgiref==3.2.10 # via django
|
||||||
|
attrs==20.2.0 # via pytest
|
||||||
|
bandit==1.6.2 # via -r requirements/requirements-dev.in
|
||||||
|
certifi==2020.6.20 # via requests
|
||||||
|
chardet==3.0.4 # via requests
|
||||||
|
click==7.1.2 # via pip-tools
|
||||||
|
coverage==5.3 # via pytest-cov
|
||||||
|
distlib==0.3.1 # via virtualenv
|
||||||
|
django-adminplus==0.5 # via -r requirements/requirements.in
|
||||||
|
django-bootstrap3==14.2.0 # via -r requirements/requirements.in
|
||||||
|
django-extensions==3.0.9 # via -r requirements/requirements.in
|
||||||
|
django-registration-redux==2.8 # via -r requirements/requirements.in
|
||||||
|
django==3.1.2 # via -r requirements/requirements.in, django-bootstrap3, djangorestframework
|
||||||
|
djangorestframework==3.12.1 # via -r requirements/requirements.in
|
||||||
|
dlint==0.10.3 # via -r requirements/requirements-dev.in
|
||||||
|
filelock==3.0.12 # via tox, virtualenv
|
||||||
|
flake8-blind-except==0.1.1 # via -r requirements/requirements-dev.in
|
||||||
|
flake8-builtins==1.5.3 # via -r requirements/requirements-dev.in
|
||||||
|
flake8-docstrings==1.5.0 # via -r requirements/requirements-dev.in
|
||||||
|
flake8-executable==2.0.4 # via -r requirements/requirements-dev.in
|
||||||
|
flake8-fixme==1.1.1 # via -r requirements/requirements-dev.in
|
||||||
|
flake8-isort==4.0.0 # via -r requirements/requirements-dev.in
|
||||||
|
flake8-logging-format==0.6.0 # via -r requirements/requirements-dev.in
|
||||||
|
flake8-mutable==1.2.0 # via -r requirements/requirements-dev.in
|
||||||
|
flake8==3.8.4 # via -r requirements/requirements-dev.in, dlint, flake8-builtins, flake8-docstrings, flake8-executable, flake8-isort, flake8-mutable
|
||||||
|
gitdb==4.0.5 # via gitpython
|
||||||
|
gitpython==3.1.11 # via bandit
|
||||||
|
idna==2.10 # via requests
|
||||||
|
importlib-metadata==1.7.0 # via django-bootstrap3, flake8, inflect, pluggy, pytest, stevedore, tox, virtualenv
|
||||||
|
importlib-resources==3.1.1 # via jaraco.text, virtualenv
|
||||||
|
inflect==4.1.0 # via jaraco.itertools
|
||||||
|
iniconfig==1.1.1 # via pytest
|
||||||
|
irc==15.0.6 # via -r requirements/requirements.in
|
||||||
|
isort==5.6.4 # via flake8-isort
|
||||||
|
jaraco.classes==3.1.0 # via jaraco.collections
|
||||||
|
jaraco.collections==3.0.0 # via irc
|
||||||
|
jaraco.functools==3.0.1 # via irc, jaraco.text, tempora
|
||||||
|
jaraco.itertools==5.0.0 # via irc
|
||||||
|
jaraco.logging==3.0.0 # via irc
|
||||||
|
jaraco.stream==3.0.0 # via irc
|
||||||
|
jaraco.text==3.2.0 # via irc, jaraco.collections
|
||||||
|
mccabe==0.6.1 # via flake8
|
||||||
|
more-itertools==8.5.0 # via irc, jaraco.classes, jaraco.functools, jaraco.itertools
|
||||||
|
oauthlib==3.1.0 # via requests-oauthlib
|
||||||
|
packaging==20.4 # via pytest, tox
|
||||||
|
parsedatetime==2.6 # via -r requirements/requirements.in
|
||||||
|
pbr==5.5.1 # via stevedore
|
||||||
|
pip-tools==5.3.1 # via -r requirements/requirements-dev.in
|
||||||
|
pluggy==0.13.1 # via pytest, tox
|
||||||
|
ply==3.11 # via -r requirements/requirements.in
|
||||||
|
py==1.9.0 # via pytest, tox
|
||||||
|
pycodestyle==2.6.0 # via flake8
|
||||||
|
pydocstyle==5.1.1 # via flake8-docstrings
|
||||||
|
pyflakes==2.2.0 # via flake8
|
||||||
|
pyparsing==2.4.7 # via packaging
|
||||||
|
pytest-cov==2.10.1 # via -r requirements/requirements-dev.in
|
||||||
|
pytest-django==4.1.0 # via -r requirements/requirements-dev.in
|
||||||
|
pytest==6.1.1 # via -r requirements/requirements-dev.in, pytest-cov, pytest-django
|
||||||
|
python-dateutil==2.8.1 # via -r requirements/requirements.in
|
||||||
|
python-gitlab==2.5.0 # via -r requirements/requirements.in
|
||||||
|
python-mpd2==1.1.0 # via -r requirements/requirements.in
|
||||||
|
pytz==2020.1 # via -r requirements/requirements.in, django, irc, tempora
|
||||||
|
pyyaml==5.3.1 # via bandit
|
||||||
|
requests-oauthlib==1.3.0 # via twython
|
||||||
|
requests==2.24.0 # via python-gitlab, requests-oauthlib, twython
|
||||||
|
six==1.15.0 # via bandit, irc, jaraco.collections, jaraco.logging, jaraco.text, packaging, pip-tools, python-dateutil, tox, virtualenv
|
||||||
|
smmap==3.0.4 # via gitdb
|
||||||
|
snowballstemmer==2.0.0 # via pydocstyle
|
||||||
|
sqlparse==0.4.1 # via django
|
||||||
|
stevedore==3.2.2 # via bandit
|
||||||
|
tempora==4.0.0 # via irc, jaraco.logging
|
||||||
|
testfixtures==6.15.0 # via flake8-isort
|
||||||
|
toml==0.10.1 # via pytest, tox
|
||||||
|
tox-wheel==0.5.0 # via -r requirements/requirements-dev.in
|
||||||
|
tox==3.20.1 # via -r requirements/requirements-dev.in, tox-wheel
|
||||||
|
twython==3.8.2 # via -r requirements/requirements.in
|
||||||
|
urllib3==1.25.11 # via requests
|
||||||
|
versioneer==0.18 # via -r requirements/requirements-dev.in
|
||||||
|
virtualenv==20.0.35 # via tox
|
||||||
|
wheel==0.35.1 # via tox-wheel
|
||||||
|
zipp==3.3.2 # via importlib-metadata, importlib-resources
|
||||||
|
|
||||||
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
# pip
|
||||||
|
# setuptools
|
43
requirements/requirements.txt
Normal file
43
requirements/requirements.txt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile
|
||||||
|
# To update, run:
|
||||||
|
#
|
||||||
|
# pip-compile --output-file=requirements/requirements.txt requirements/requirements.in
|
||||||
|
#
|
||||||
|
asgiref==3.2.10 # via django
|
||||||
|
certifi==2020.6.20 # via requests
|
||||||
|
chardet==3.0.4 # via requests
|
||||||
|
django-adminplus==0.5 # via -r requirements/requirements.in
|
||||||
|
django-bootstrap3==14.2.0 # via -r requirements/requirements.in
|
||||||
|
django-extensions==3.0.9 # via -r requirements/requirements.in
|
||||||
|
django-registration-redux==2.8 # via -r requirements/requirements.in
|
||||||
|
django==3.1.2 # via -r requirements/requirements.in, django-bootstrap3, djangorestframework
|
||||||
|
djangorestframework==3.12.1 # via -r requirements/requirements.in
|
||||||
|
idna==2.10 # via requests
|
||||||
|
importlib-metadata==1.7.0 # via django-bootstrap3, inflect
|
||||||
|
importlib-resources==3.1.1 # via jaraco.text
|
||||||
|
inflect==4.1.0 # via jaraco.itertools
|
||||||
|
irc==15.0.6 # via -r requirements/requirements.in
|
||||||
|
jaraco.classes==3.1.0 # via jaraco.collections
|
||||||
|
jaraco.collections==3.0.0 # via irc
|
||||||
|
jaraco.functools==3.0.1 # via irc, jaraco.text, tempora
|
||||||
|
jaraco.itertools==5.0.0 # via irc
|
||||||
|
jaraco.logging==3.0.0 # via irc
|
||||||
|
jaraco.stream==3.0.0 # via irc
|
||||||
|
jaraco.text==3.2.0 # via irc, jaraco.collections
|
||||||
|
more-itertools==8.5.0 # via irc, jaraco.classes, jaraco.functools, jaraco.itertools
|
||||||
|
oauthlib==3.1.0 # via requests-oauthlib
|
||||||
|
parsedatetime==2.6 # via -r requirements/requirements.in
|
||||||
|
ply==3.11 # via -r requirements/requirements.in
|
||||||
|
python-dateutil==2.8.1 # via -r requirements/requirements.in
|
||||||
|
python-gitlab==2.5.0 # via -r requirements/requirements.in
|
||||||
|
python-mpd2==1.1.0 # via -r requirements/requirements.in
|
||||||
|
pytz==2020.1 # via -r requirements/requirements.in, django, irc, tempora
|
||||||
|
requests-oauthlib==1.3.0 # via twython
|
||||||
|
requests==2.24.0 # via python-gitlab, requests-oauthlib, twython
|
||||||
|
six==1.15.0 # via irc, jaraco.collections, jaraco.logging, jaraco.text, python-dateutil
|
||||||
|
sqlparse==0.4.1 # via django
|
||||||
|
tempora==4.0.0 # via irc, jaraco.logging
|
||||||
|
twython==3.8.2 # via -r requirements/requirements.in
|
||||||
|
urllib3==1.25.11 # via requests
|
||||||
|
zipp==3.3.2 # via importlib-metadata, importlib-resources
|
2
setup.py
2
setup.py
@ -8,7 +8,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
|
|||||||
|
|
||||||
|
|
||||||
def extract_requires():
|
def extract_requires():
|
||||||
with open(os.path.join(HERE, 'requirements.in'), 'r') as reqs:
|
with open(os.path.join(HERE, 'requirements/requirements.in'), 'r') as reqs:
|
||||||
return [line.split(' ')[0] for line in reqs if not line[0] == '-']
|
return [line.split(' ')[0] for line in reqs if not line[0] == '-']
|
||||||
|
|
||||||
|
|
||||||
|
47
tests/test_pi_models.py
Normal file
47
tests/test_pi_models.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
"""Test the pi models."""
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
from pi.models import PiLog
|
||||||
|
|
||||||
|
|
||||||
|
class PiLogTest(TestCase):
|
||||||
|
"""Test pi models."""
|
||||||
|
|
||||||
|
def test_hit_calculation(self):
|
||||||
|
"""Test that x,y combinations are properly considered inside or outside the circle."""
|
||||||
|
hit_item = PiLog(simulation_x=0.0, simulation_y=0.0, total_count=0, total_count_inside=0)
|
||||||
|
miss_item = PiLog(simulation_x=1.0, simulation_y=1.0, total_count=0, total_count_inside=0)
|
||||||
|
|
||||||
|
self.assertTrue(hit_item.hit)
|
||||||
|
self.assertFalse(miss_item.hit)
|
||||||
|
|
||||||
|
def test_value_calculation(self):
|
||||||
|
"""Test that a simulation's value of pi can be calculated."""
|
||||||
|
item = PiLog(simulation_x=0.0, simulation_y=0.0, total_count=1000, total_count_inside=788)
|
||||||
|
zero_item = PiLog(simulation_x=0.0, simulation_y=0.0, total_count=0, total_count_inside=0)
|
||||||
|
|
||||||
|
self.assertEqual(item.value, 3.152)
|
||||||
|
self.assertEqual(zero_item.value, 0.0)
|
||||||
|
|
||||||
|
def test_string_repr(self):
|
||||||
|
"""Test the string repr of a simulation log entry."""
|
||||||
|
item = PiLog(simulation_x=0.0, simulation_y=0.0, total_count=1000, total_count_inside=788,
|
||||||
|
created=now())
|
||||||
|
|
||||||
|
self.assertIn("(788/1000) @ ", str(item))
|
||||||
|
|
||||||
|
def test_simulation_inside_determination(self):
|
||||||
|
"""Test that running a simulation passes the proper inside value."""
|
||||||
|
# get at least one simulation in the DB
|
||||||
|
original_item, _, _ = PiLog.objects.simulate()
|
||||||
|
|
||||||
|
with mock.patch('random.random', return_value=1.0):
|
||||||
|
miss_item, _, _ = PiLog.objects.simulate()
|
||||||
|
self.assertEqual(miss_item.total_count_inside, original_item.total_count_inside)
|
||||||
|
|
||||||
|
with mock.patch('random.random', return_value=0.0):
|
||||||
|
hit_item, _, _ = PiLog.objects.simulate()
|
||||||
|
self.assertGreater(hit_item.total_count_inside, original_item.total_count_inside)
|
25
tests/test_pi_views.py
Normal file
25
tests/test_pi_views.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
"""Test the pi package's views."""
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from rest_framework.status import HTTP_201_CREATED
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from pi.models import PiLog
|
||||||
|
|
||||||
|
|
||||||
|
class PiAPITest(APITestCase):
|
||||||
|
"""Test pi DRF views."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Do pre-test stuff."""
|
||||||
|
self.client = self.client_class()
|
||||||
|
self.user = User.objects.create(username='test')
|
||||||
|
self.client.force_authenticate(user=self.user)
|
||||||
|
|
||||||
|
def test_simulate_creates_simulation(self):
|
||||||
|
"""Test that the simulate action creates a log entry."""
|
||||||
|
self.assertEqual(PiLog.objects.count(), 0)
|
||||||
|
|
||||||
|
resp = self.client.post('/pi/api/simulations/simulate/')
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, HTTP_201_CREATED)
|
||||||
|
self.assertEqual(PiLog.objects.count(), 2) # 2 because 0 entry and the real entry
|
201
tox.ini
Normal file
201
tox.ini
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
# tox (https://tox.readthedocs.io/) is a tool for running tests
|
||||||
|
# in multiple virtualenvs. This configuration file will run the
|
||||||
|
# test suite on all supported python versions. To use it, "pip install tox"
|
||||||
|
# and then run "tox" from this directory.
|
||||||
|
|
||||||
|
[tox]
|
||||||
|
envlist = begin,py36,py37,py38,coverage,security,lint,bundle
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
# build a wheel and test it
|
||||||
|
wheel = true
|
||||||
|
wheel_build_env = build
|
||||||
|
|
||||||
|
# whitelist commands we need
|
||||||
|
whitelist_externals = cp
|
||||||
|
|
||||||
|
# install everything via requirements-dev.txt, so that developer environment
|
||||||
|
# is the same as the tox environment (for ease of use/no weird gotchas in
|
||||||
|
# local dev results vs. tox results) and also to avoid ticky-tacky maintenance
|
||||||
|
# of "oh this particular env has weird results unless I install foo" --- just
|
||||||
|
# shotgun blast install everything everywhere
|
||||||
|
deps =
|
||||||
|
-rrequirements/requirements-dev.txt
|
||||||
|
|
||||||
|
[testenv:build]
|
||||||
|
# require setuptools when building
|
||||||
|
deps = setuptools
|
||||||
|
|
||||||
|
[testenv:begin]
|
||||||
|
# clean up potential previous coverage runs
|
||||||
|
skip_install = true
|
||||||
|
commands = coverage erase
|
||||||
|
|
||||||
|
[testenv:py36]
|
||||||
|
# run pytest with coverage
|
||||||
|
commands =
|
||||||
|
pytest --cov-append --cov-branch \
|
||||||
|
--cov={envsitepackagesdir}/acro/ \
|
||||||
|
--cov={envsitepackagesdir}/countdown/ \
|
||||||
|
--cov={envsitepackagesdir}/dice/ \
|
||||||
|
--cov={envsitepackagesdir}/dispatch/ \
|
||||||
|
--cov={envsitepackagesdir}/dr_botzo/ \
|
||||||
|
--cov={envsitepackagesdir}/facts/ \
|
||||||
|
--cov={envsitepackagesdir}/gitlab_bot/ \
|
||||||
|
--cov={envsitepackagesdir}/ircbot/ \
|
||||||
|
--cov={envsitepackagesdir}/karma/ \
|
||||||
|
--cov={envsitepackagesdir}/markov/ \
|
||||||
|
--cov={envsitepackagesdir}/mpdbot/ \
|
||||||
|
--cov={envsitepackagesdir}/pi/ \
|
||||||
|
--cov={envsitepackagesdir}/races/ \
|
||||||
|
--cov={envsitepackagesdir}/seen/ \
|
||||||
|
--cov={envsitepackagesdir}/storycraft/ \
|
||||||
|
--cov={envsitepackagesdir}/transform/ \
|
||||||
|
--cov={envsitepackagesdir}/twitter/ \
|
||||||
|
--cov={envsitepackagesdir}/weather/
|
||||||
|
|
||||||
|
[testenv:py37]
|
||||||
|
# run pytest with coverage
|
||||||
|
commands =
|
||||||
|
pytest --cov-append --cov-branch \
|
||||||
|
--cov={envsitepackagesdir}/acro/ \
|
||||||
|
--cov={envsitepackagesdir}/countdown/ \
|
||||||
|
--cov={envsitepackagesdir}/dice/ \
|
||||||
|
--cov={envsitepackagesdir}/dispatch/ \
|
||||||
|
--cov={envsitepackagesdir}/dr_botzo/ \
|
||||||
|
--cov={envsitepackagesdir}/facts/ \
|
||||||
|
--cov={envsitepackagesdir}/gitlab_bot/ \
|
||||||
|
--cov={envsitepackagesdir}/ircbot/ \
|
||||||
|
--cov={envsitepackagesdir}/karma/ \
|
||||||
|
--cov={envsitepackagesdir}/markov/ \
|
||||||
|
--cov={envsitepackagesdir}/mpdbot/ \
|
||||||
|
--cov={envsitepackagesdir}/pi/ \
|
||||||
|
--cov={envsitepackagesdir}/races/ \
|
||||||
|
--cov={envsitepackagesdir}/seen/ \
|
||||||
|
--cov={envsitepackagesdir}/storycraft/ \
|
||||||
|
--cov={envsitepackagesdir}/transform/ \
|
||||||
|
--cov={envsitepackagesdir}/twitter/ \
|
||||||
|
--cov={envsitepackagesdir}/weather/
|
||||||
|
|
||||||
|
[testenv:py38]
|
||||||
|
# run pytest with coverage
|
||||||
|
commands =
|
||||||
|
pytest --cov-append --cov-branch \
|
||||||
|
--cov={envsitepackagesdir}/acro/ \
|
||||||
|
--cov={envsitepackagesdir}/countdown/ \
|
||||||
|
--cov={envsitepackagesdir}/dice/ \
|
||||||
|
--cov={envsitepackagesdir}/dispatch/ \
|
||||||
|
--cov={envsitepackagesdir}/dr_botzo/ \
|
||||||
|
--cov={envsitepackagesdir}/facts/ \
|
||||||
|
--cov={envsitepackagesdir}/gitlab_bot/ \
|
||||||
|
--cov={envsitepackagesdir}/ircbot/ \
|
||||||
|
--cov={envsitepackagesdir}/karma/ \
|
||||||
|
--cov={envsitepackagesdir}/markov/ \
|
||||||
|
--cov={envsitepackagesdir}/mpdbot/ \
|
||||||
|
--cov={envsitepackagesdir}/pi/ \
|
||||||
|
--cov={envsitepackagesdir}/races/ \
|
||||||
|
--cov={envsitepackagesdir}/seen/ \
|
||||||
|
--cov={envsitepackagesdir}/storycraft/ \
|
||||||
|
--cov={envsitepackagesdir}/transform/ \
|
||||||
|
--cov={envsitepackagesdir}/twitter/ \
|
||||||
|
--cov={envsitepackagesdir}/weather/
|
||||||
|
|
||||||
|
[testenv:coverage]
|
||||||
|
# report on coverage runs from above
|
||||||
|
skip_install = true
|
||||||
|
commands =
|
||||||
|
coverage report --fail-under=95 --show-missing
|
||||||
|
|
||||||
|
[testenv:security]
|
||||||
|
# run security checks
|
||||||
|
#
|
||||||
|
# again it seems the most valuable here to run against the packaged code
|
||||||
|
commands =
|
||||||
|
bandit \
|
||||||
|
{envsitepackagesdir}/acro/ \
|
||||||
|
{envsitepackagesdir}/countdown/ \
|
||||||
|
{envsitepackagesdir}/dice/ \
|
||||||
|
{envsitepackagesdir}/dispatch/ \
|
||||||
|
{envsitepackagesdir}/dr_botzo/ \
|
||||||
|
{envsitepackagesdir}/facts/ \
|
||||||
|
{envsitepackagesdir}/gitlab_bot/ \
|
||||||
|
{envsitepackagesdir}/ircbot/ \
|
||||||
|
{envsitepackagesdir}/karma/ \
|
||||||
|
{envsitepackagesdir}/markov/ \
|
||||||
|
{envsitepackagesdir}/mpdbot/ \
|
||||||
|
{envsitepackagesdir}/pi/ \
|
||||||
|
{envsitepackagesdir}/races/ \
|
||||||
|
{envsitepackagesdir}/seen/ \
|
||||||
|
{envsitepackagesdir}/storycraft/ \
|
||||||
|
{envsitepackagesdir}/transform/ \
|
||||||
|
{envsitepackagesdir}/twitter/ \
|
||||||
|
{envsitepackagesdir}/weather/ \
|
||||||
|
-r
|
||||||
|
|
||||||
|
[testenv:lint]
|
||||||
|
# run style checks
|
||||||
|
commands =
|
||||||
|
flake8
|
||||||
|
- flake8 --disable-noqa --ignore= --select=E,W,F,C,D,A,G,B,I,T,M,DUO
|
||||||
|
|
||||||
|
[testenv:bundle]
|
||||||
|
# take extra actions (build sdist, sphinx, whatever) to completely package the app
|
||||||
|
commands =
|
||||||
|
cp -r {distdir} .
|
||||||
|
python setup.py sdist
|
||||||
|
|
||||||
|
[coverage:paths]
|
||||||
|
source =
|
||||||
|
./
|
||||||
|
.tox/**/site-packages/
|
||||||
|
|
||||||
|
[coverage:run]
|
||||||
|
branch = True
|
||||||
|
|
||||||
|
# redundant with pytest --cov above, but this tricks the coverage.xml report into
|
||||||
|
# using the full path, otherwise files with the same name in different paths
|
||||||
|
# get clobbered. maybe appends would fix this, IDK
|
||||||
|
include =
|
||||||
|
{envsitepackagesdir}/acro/
|
||||||
|
{envsitepackagesdir}/countdown/
|
||||||
|
{envsitepackagesdir}/dice/
|
||||||
|
{envsitepackagesdir}/dispatch/
|
||||||
|
{envsitepackagesdir}/dr_botzo/
|
||||||
|
{envsitepackagesdir}/facts/
|
||||||
|
{envsitepackagesdir}/gitlab_bot/
|
||||||
|
{envsitepackagesdir}/ircbot/
|
||||||
|
{envsitepackagesdir}/karma/
|
||||||
|
{envsitepackagesdir}/markov/
|
||||||
|
{envsitepackagesdir}/mpdbot/
|
||||||
|
{envsitepackagesdir}/pi/
|
||||||
|
{envsitepackagesdir}/races/
|
||||||
|
{envsitepackagesdir}/seen/
|
||||||
|
{envsitepackagesdir}/storycraft/
|
||||||
|
{envsitepackagesdir}/transform/
|
||||||
|
{envsitepackagesdir}/twitter/
|
||||||
|
{envsitepackagesdir}/weather/
|
||||||
|
|
||||||
|
omit =
|
||||||
|
**/_version.py
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
enable-extensions = G,M
|
||||||
|
exclude =
|
||||||
|
.tox/
|
||||||
|
versioneer.py
|
||||||
|
_version.py
|
||||||
|
instance/
|
||||||
|
extend-ignore = T101
|
||||||
|
max-complexity = 10
|
||||||
|
max-line-length = 120
|
||||||
|
|
||||||
|
[isort]
|
||||||
|
line_length = 120
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
python_files =
|
||||||
|
*_tests.py
|
||||||
|
tests.py
|
||||||
|
test_*.py
|
||||||
|
DJANGO_SETTINGS_MODULE = dr_botzo.settings
|
||||||
|
django_find_project = false
|
@ -33,7 +33,7 @@ class Weather(Plugin):
|
|||||||
if len(queryitems) <= 0:
|
if len(queryitems) <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
weather = weather_summary(queryitems[0])
|
weather = weather_summary(query)
|
||||||
weather_output = (f"Weather in {weather['location']}: {weather['current']['description']}. "
|
weather_output = (f"Weather in {weather['location']}: {weather['current']['description']}. "
|
||||||
f"{weather['current']['temp_F']}/{weather['current']['temp_C']}, "
|
f"{weather['current']['temp_F']}/{weather['current']['temp_C']}, "
|
||||||
f"feels like {weather['current']['feels_like_temp_F']}/"
|
f"feels like {weather['current']['feels_like_temp_F']}/"
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"""Get results of weather queries."""
|
"""Get results of weather queries."""
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ logger = logging.getLogger(__name__)
|
|||||||
def query_wttr_in(query):
|
def query_wttr_in(query):
|
||||||
"""Hit the wttr.in JSON API with the provided query."""
|
"""Hit the wttr.in JSON API with the provided query."""
|
||||||
logger.info(f"about to query wttr.in with '{query}'")
|
logger.info(f"about to query wttr.in with '{query}'")
|
||||||
response = requests.get(f'http://wttr.in/{query}?format=j1')
|
response = requests.get(f'http://wttr.in/{quote(query)}?format=j1')
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
weather_info = response.json()
|
weather_info = response.json()
|
||||||
|
Loading…
Reference in New Issue
Block a user