##Planering ##
Aspekt | E | C | A |
---|---|---|---|
Design | Med viss säkerhet analyserar du uppgiften och designar ditt program | Som för E, men med säkerhet, och du motiverar dessutom utförligt dina val | |
Klassdiagram | Du planerar uppgiften med enkla klassdiagram |
Aspekt | E | C | A |
---|---|---|---|
Strukturering | Din kod är delvis strukturerad | Din kod är i stor utsträckning strukturerad | |
Kodningsstil | Du använder en konsekvent kodningsstil för klasser, moduler, metoder, variabler, kommentarer och dokumentation | ||
Namngivning | Du använder en tydlig namngivning i din kod | ||
Dokumentering | Din kod är delvis dokumenterad | Du dokumenterar utförligt dina klasser och metoder |
Aspekt | E | C | A |
---|---|---|---|
Typsäkerhet | Du uppmärksammar problem med typsäkerhet | ||
Felsökning | Du felsöker enkla syntaxfel | Som för E, men effektivt, och dessutom även körtidsfel och programmeringslogiska fel | Som för C, men med effektivitet (exempelvis med hjälp av tester) |
Aspekt | E | C | A |
---|---|---|---|
Komplexitet | Det färdiga programmet är av enkel karaktär, fungerar tillfredsställande, och är stabilt och robust | Som för E, men av lite mer avancerad karaktär | Som för C, men programmet är av komplex karaktär, följer SOLID, och fungerar väl |
Utvärdering | Du utvärderar, med enkla omdömen, programmets prestanda och funktionalitet | Som för E, men nyanserade omdömen | Som för C, men du ger även förslag på förbättringar |
Slutuppgiften går ut på att skriva en Reddit-bot, det vill säga ett program, som, med hjälp av Reddits api, kopplar upp sig mot Reddit.
Exempelbotar:
- Gandhi_spell_bot
- En bot som, om någon skriver förolämpningar som "du suger!", ger en komplimang till den som blivit förolämpad.
- En bot som, varje dag, postar väderprognosen för en stad till stadens subbreddit.
Inspiration
Reddit uppmuntrar utvecklare att programmera mot deras API (Application Programming Interface (det vill säga de http-endpoints (endpoint är ett fancy-pancy ord för url) boten kommer prata med)). De flesta större siter och applikationer har API:er för att interagera med andra program.
Reddit har regler som begränsar vad en bot får göra. Läs dessa och bryt inte mot dem.
Några extra viktiga regler:
-
Boten får inte göra fler än 60 anrop per minute (se Request Throttler längre ner).
OBS! Om du bryter mot denna regel kan din bot bli bannad.
DUBBELOBS! Om du verkligen bryter mot denna regel kan i värsta fall alla skolans/academedias IP-adresser bli blockade från Reddit.
-
Du måste registrera ett konto för botten (och godkänna termerna).
-
Boten måste autentisera sig (logga in) med OAUTH 2 (se längre ner).
-
Boten får inte låtsas vara en annan bot, webbläsare, eller dylikt
-
Boten måste ha ett unikt namn
-
Om botens namn innehåller "reddit" måste det stå "for" före reddit (ex: GrillBot for Reddit)
-
Om du tänker ta betalt för din bot måste du ha godkännande från reddit
Den första klass till din bot du måste skapa är en "Request Throttler"
Request Throttlerns uppgift är att se förhindra att din applikation skickar för många requests till reddits api, och därmed bannar din bot (eller i värsta fall hela skolan/academedia).
Jag föreslår starkt att din Request Throttler implementerar Token Bucket Pattern.
I korthet kan man beskriva Token Bucket Pattern så här:
- Med "token" avses "polett", det vill säga något man använder istället för pengar i t.ex nöjesparker eller spelhallar.
- Det finns en "hink" som rymmer x (i reddits fall 60) st "tokens".
- Om det gått x (i reddits fall 60) sekunder sedan hinken senast fyllts på fylls hinken på till max antal (i reddits fall 60) tokens.
- Varje gång man skickar en request minskas antalet tokens i "hinken" med en.
- Om hinken är tom får man inte skicka någon request, och måste vänta med nästa request tills hinken fylls på.
Boten kan alltså (teoretiskt sätt) bränna av alla 60 tillgängliga requests på en sekund (men måste sen vänta 59 sekunder innan den kan skicka nästa request).
Din Request Throttler måste vara godkänd av Daniel eller David innan din bot får prata med Reddit
För att smidigt prata HTTP med Python rekommenderar jag start Requests: HTTP for Humans.
python -m pip install requests
Båda exempel kräver att boten redan är autentiserad med OAUTH, och har tillgång till sin autentiseringstoken (se OAUTH 2 nedan).
Den första endpoint vi hittar i reddits api-lista är get /api/v1/me.
I dokumentationen står det "Returns the identity of the user currently authenticated via OAuth." Vi testar:
result = requests.get("https://oauth.reddit.com/api/v1/me", headers=headers)
print(result.json())
>>> {u'comment_karma': 0,
u'created': 1389649907.0,
u'created_utc': 1389649907.0,
u'has_mail': False,
u'has_mod_mail': False,
u'has_verified_email': None,
u'id': u'1',
u'is_gold': False,
u'is_mod': True,
u'link_karma': 1,
u'name': u'reddit_bot',
u'over_18': True}
Det som skrivs ut är en Dict med svaret från servern.
Förutsatt att boten är inloggad enligt nedan, för att hämta inlägg i /r/programming, sorterade efter "hot"
listing = requests.get("https://reddit.com/r/programming/hot.json", headers=headers)
data = listing.json()['data']
data
är nu en gigantisk dict, där alla inlägg i subbredditen ligger i en lista inuti children
-nyckeln:
{u'kind': u'Listing', u'data':
{u'modhash': u'', u'children': [
{u'kind': u't3', u'data':
{u'domain': u'arstechnica.com',
u'banned_by': None,
u'media_embed': {},
u'subreddit': u'programming',
u'selftext_html': None,
u'selftext': u'',
u'likes': None,
u'suggested_sort': None,
u'user_reports': [],
u'secure_media': None,
u'link_flair_text': None,
u'id': u'4iiagx',
u'from_kind': None,
u'gilded': 0,
u'archived': False,
u'clicked': False,
u'report_reasons': None,
u'author': u'abcrink',
u'media': None,
u'name': u't3_4iiagx',
u'score': 105,
u'approved_by': None,
u'over_18': False,
u'hidden': False,
u'thumbnail': u'',
u'subreddit_id': u't5_2fwo',
u'edited': False,
u'link_flair_css_class': None,
u'author_flair_css_class': None,
u'downs': 0,
u'mod_reports': [],
u'secure_media_embed': {},
u'saved': False,
u'removal_reason': None,
u'stickied': False,
u'from': None,
u'is_self':
False, u'from_id': None,
u'permalink': u'/r/programming/comments/4iiagx/second_oracle_v_google_trial_could_lead_to_huge/', u'locked': False,
u'hide_score': False,
u'created': 1462801875.0,
u'url': u'http://arstechnica.com/tech-policy/2016/05/round-2-of-oracle-v-google-is-an-unpredictable-trial-over-api-fair-use/',
u'author_flair_text': None,
u'quarantine': False,
u'title': u'Second Oracle v. Google trial could lead to huge headaches for developers', u'created_utc': 1462773075.0,
u'ups': 105,
u'num_comments': 24,
u'visited': False,
u'num_reports': None,
u'distinguished': None}
}
},
...
Som vi ser ovan har varje inlägg en egen data
-nyckel, som innehåller information om inlägget:
for child in data['children']:
print("Author: {0}".format(child['data']['author']))
print("URL: {0}".format(child['data']['url']))
print("Title: {0}".format(child['data']['title']))
Om man vill ha ett inläggs unika id, kan man hitta det i dess ['data']['id']
:
wanted_thread = data['children'][0] #första inlägget
id = wanted_thread['data']['id']
Man kan sen använda inläggets unika id för att hämta data om inlägget:
thread = requests.get("https://reddit.com/r/programming/comments/{0}.json".format(id), headers=headers)
thread = thread.json()
thread är nu en gigantisk dict med alla inlägg i tråden:
print(thread.json())
{u'kind': u'Listing',
u'data':
{u'modhash': u'', u'children': [
{u'kind': u't3',
u'data':
{u'domain': u'arstechnica.com',
u'banned_by': None, u'media_embed': {}, u'subreddit': u'programming', u'selftext_html': None,
...
}
}
,...
}
Man kan också använda permalinken för att hitta inlägget:
permalink = wanted_thread['data']['permalink']
thread = requests.get("https://reddit.com/{0}.json".format(permalink), headers=headers)
thread = thread.json()
Förutsatt att du hämtat en tråd enligt nedan:
thread = requests.get("https://reddit.com/r/programming/comments/{0}.json".format(id), headers=headers)
thread = thread.json()
Varje inlägg i thread['data']['children']
är en kommentar i tråden.
comment = thread['data']['children'][0] #första kommentaren
Varje kommentar har ett helt unikt 'fullname', vilket vi kommer behöva för att posta en kommentar
fullname = comment['name']
Skapa en dict med den data du vill posta, och skicka till https://oauth.reddit.com/api/comment
post_data = {'api_type': 'json', 'text': 'this is a response', 'thing_id': fullname }
response = requests.post("https://oauth.reddit.com/api/comment", data=post_data, headers=headers)
För att kunna autentisera din bot måste du först skapa ett utvecklar-konto på reddit: OAuth2 Quick Start Example
import requests
import requests.auth
- Skapa ett
HTTPBasicAuth
-objekt med hjälp avclient_id
&secret
för din applikation https://www.reddit.com/prefs/apps/:
client_auth = requests.auth.HTTPBasicAuth('p-jcoLKBynTLew', 'gko_LXELoV07ZBNUXrvWZfzE3aI')
- Skapa en "Params-dict" enligt nedan. För er som har läst webbutveckling med PHP: tänk $_POST`
post_data = {"grant_type": "password", "username": "reddit_bot", "password": "snoo"}
- Skapa ett unikt User-Agent-header för din bot:
headers = {"User-Agent": "ChangeMeClient/0.1 by YourUsername"}
- Posta dina variabler till
"https://www.reddit.com/api/v1/access_token"
enligt nedan, och spara svaret i en variabel.
response = requests.post("https://www.reddit.com/api/v1/access_token", auth=client_auth, data=post_data, headers=headers)
response
-variabeln ovan är ett objekt av klassen Response.response.text
innehåller nu en sträng JSON (se nedan) med bland annat din autentiseringstoken. Genom att anropa.json()
får du tillbaks en python-dict med innehållet i text-variabeln:
response.json()
>>> {u'access_token': u'fhTdafZI-0ClEzzYORfBSCR7x3M',
u'expires_in': 3600,
u'scope': u'*',
u'token_type': u'bearer'}`
- Skapa en ny header-variabel som innehåller din token och övrig info om boten:
headers = {"Authorization": "bearer fhTdafZI-0ClEzzYORfBSCR7x3M", "User-Agent": "ChangeMeClient/0.1 by YourUsername"}`
- Skicka alla kommande requests mot reddits api till
https://oauth.reddit.com/api/
och skicka med den nya headern.
requests.get("https://oauth.reddit.com/api/v1/me", headers=headers)`
Det finns massor av olika sätt att skriva Docstrings i Python.
Standard i Pycharm är "reStructuredText" (reST), så det är vad vi kommer göra.
I din metod (eller klass), på första raden efter definitionen, skriv tre stycken enkelfnuttar (') och tryck på enter.
Pycharm kommer förpopulera din dokumentationstträng med namnet på argumenten, och, om metoden returnerar något, ett :return
:
Observera: exempelmetoden ska inte tas som väldesignad OOP-kod.
def comments_for(self, subreddit, sorted_by='new'):
'''
:param subreddit:
:param sorted_by:
:return:
'''
Du behöver fylla på :param
med en beskrivning av vad parametern är för något, :return
med en beskrivning av vad som returneras.
Om metoden kastar undantag behöver du lägga till :raises
.
Innan alla :param
, :return
och :raises
ska du översiktligt beskriva metodens/klassens syfte, och ge ett (eller flera) exempel på hur metoden används.
En färdigifylld kommentar:
def comments_for(self, subreddit, sorted_by='new'):
'''
Returns comments for the given subreddit sorted by new (default), hot or controversial
>>> comments_for(subreddit='3456eex')
>>> [<GrillBot.Comment object at 0x10c688c50>, <GrillBot.Comment object at 0x10c688c90>]
:param subreddit: the id of the subreddit
:param sorted_by: a string; 'new' (default), 'hot', or 'controversial'.
:return: a list of Comment objects ordered by sorted_by
:raises NoSuchSubReddit: if the subreddit can not be found
'''
SOLID är samling av 5 designprinciper man bör följa för att skapa väldesignade, vidareutvecklingsbara, modulära & underhållbara program.
Se Sandi Metz presentation "SOLID Object-Oriented Design" på YouTube
En klass ska bara ha en "uppgift" eller "ansvar". Om du, när du beskriver vad en klass gör, börjar lägga till många "och", är det troligt att du bryter mot Single Responsibility Principle, och behöver flytta ut uppgifter/ansvar till nya klasser.
Open/Closed Principle säger att dina program ska vara "open for extension" men "closed for modification". Med andra ord: om någon vill ändra på delar av vårt program ska de inte behöva ändra (modifiera) i vår kod, utan skriva ny kod ("extend"). I Objektorienterad programmering löser vi det genom arv.
Liskov Substitution Principle säger att alla subklasser av en superklass ska kunna bytas ut mot varandra (eller superklassen), utan att programmet slutar fungera. Detta innebär att alla subklassers konstruktorer bör se likadana ut, och att de implementerar samma (publika) metoder.
Interface Segregation Principle säger att man bör ha många små "interfaces" snarare än få, stora. Anledningen till detta är att ingen klass ska behöva implementera metoder den inte behöver. I Python finns inga interfaces, så Interface Segregation Principle är inte så relevant för oss.
Dependency Inversion/Injection Principle går hand i hand med de övriga principerna. Rent praktiskt innebär det att du aldrig ska nämna en annan klass vid namn inne i en klass. I stället ska du skicka in klassen (eller objekt av klassen) till konstruktorn/metoden. På så sätt kan du alltid ersätta den inskickade klassen/objektet mot en subklass (förutsatt att du följt Open/Closed Principle och Liskov Substitution Principle).