未验证 提交 ebccee0d 编写于 作者: D Dan Bader 提交者: GitHub

Merge branch 'master' into bitwise-operators

version: 1
update_configs:
- package_manager: "python"
directory: "/requirements.txt"
update_schedule: "monthly"
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
:information_source: Please note that the best way to get support for Real Python courses & articles is to join one of our [weekly Office Hours calls](https://realpython.com/office-hours/) or in the [RP Community Slack](https://realpython.com/community/).
You can report issues and problems here, but we typically won't be able to provide 1:1 support outside the channels listed above.
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 10
allow:
- dependency-name: flake8
- dependency-name: black
......@@ -108,3 +108,6 @@ ENV/
# Mac
*.DS_Store
# VS Code workspace
*.code-workspace
\ No newline at end of file
# Real Python Materials
Bonus materials, exercises, and example projects for our [Python tutorials](https://realpython.com).
Bonus materials, exercises, and example projects for Real Python's [Python tutorials](https://realpython.com).
Build Status: [![CircleCI](https://circleci.com/gh/realpython/materials.svg?style=svg)](https://circleci.com/gh/realpython/materials)
## Running Code Style Checks
## Got a Question?
The best way to get support for Real Python courses & articles and code in this repository is to join one of our [weekly Office Hours calls](https://realpython.com/office-hours/) or to ask your question in the [RP Community Slack](https://realpython.com/community/).
Due to time constraints we cannot provide 1:1 support via GitHub. See you on Slack or on the next Office Hours call 🙂
## Adding Source Code & Sample Projects to This Repo (RP Contributors)
### Running Code Style Checks
We use [flake8](http://flake8.pycqa.org/en/latest/) and [black](https://github.com/ambv/black) to ensure a consistent code style for all of our sample code in this repository.
......@@ -15,7 +23,7 @@ $ flake8
$ black --check .
```
## Running Python Code Formatter
### Running Python Code Formatter
We're using a tool called [black](https://github.com/ambv/black) on this repo to ensure consistent formatting. On CI it runs in "check" mode to ensure any new files added to the repo are following PEP 8. If you see linter warnings that say something like "would reformat some_file.py" it means black disagrees with your formatting.
......
......@@ -14,12 +14,10 @@ RADIUS = 150
class Welcome(arcade.Window):
"""Our main welcome window
"""
"""Our main welcome window"""
def __init__(self):
"""Initialize the window
"""
"""Initialize the window"""
# Call the parent class constructor
super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
......@@ -28,8 +26,7 @@ class Welcome(arcade.Window):
arcade.set_background_color(arcade.color.WHITE)
def on_draw(self):
"""Called whenever we need to draw our window
"""
"""Called whenever we need to draw our window"""
# Clear the screen and start drawing
arcade.start_render()
......
......@@ -13,12 +13,10 @@ SCREEN_TITLE = "Draw Shapes"
class Welcome(arcade.Window):
"""Our main welcome window
"""
"""Our main welcome window"""
def __init__(self):
"""Initialize the window
"""
"""Initialize the window"""
# Call the parent class constructor
super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
......@@ -27,8 +25,7 @@ class Welcome(arcade.Window):
arcade.set_background_color(arcade.color.WHITE)
def on_draw(self):
"""Called whenever we need to draw our window
"""
"""Called whenever we need to draw our window"""
# Clear the screen and start drawing
arcade.start_render()
......
......@@ -41,8 +41,7 @@ class SpaceShooter(arcade.Window):
"""
def __init__(self, width: int, height: int, title: str):
"""Initialize the game
"""
"""Initialize the game"""
super().__init__(width, height, title)
# Setup the empty sprite lists
......@@ -51,8 +50,7 @@ class SpaceShooter(arcade.Window):
self.all_sprites = arcade.SpriteList()
def setup(self):
"""Get the game ready to play
"""
"""Get the game ready to play"""
# Set the background color
arcade.set_background_color(arcade.color.SKY_BLUE)
......@@ -236,8 +234,7 @@ class SpaceShooter(arcade.Window):
self.player.left = 0
def on_draw(self):
"""Draw all game objects
"""
"""Draw all game objects"""
arcade.start_render()
self.all_sprites.draw()
......
......@@ -8,7 +8,6 @@ attrs==18.1.0
certifi==2018.8.13
chardet==3.0.4
contextvars==2.3
flake8==3.5.0
h11==0.8.1
idna==2.7
immutables==0.6
......@@ -23,8 +22,6 @@ pathlib2==2.3.2
pluggy==0.7.1
py==1.6.0
pycodestyle==2.3.1
pyflakes==1.6.0
pylint==2.1.1
pytest==3.7.3
requests==2.19.1
six==1.11.0
......
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store
*.pyc
*.o
*.so
*.swp
*~
.coverage*
htmlcov/
foo.py
debug.log
db.sqlite3
logs/*
*.egg
*.eggs
*.egg-info
build/
dist/
docs/_build/
extras/sample_site/uploads/
extras/sample_site/db.sqlite3
.tox/
"""
ASGI config for Blog project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Blog.settings")
application = get_asgi_application()
"""
Django settings for Blog project.
Generated by 'django-admin startproject' using Django 3.0.7.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "9p2cpl%!f8a0tptz_85&dulu&!_ve=j_0s6-l4=tc-g!p-$whj"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"core",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "Blog.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(BASE_DIR, "templates")],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "Blog.wsgi.application"
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = []
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = "/static/"
from django.contrib import admin
from django.urls import path, include
from core import views as core_views
urlpatterns = [
path("admin/", admin.site.urls),
path("", core_views.listing, name="listing"),
path("view_blog/<int:blog_id>/", core_views.view_blog, name="view_blog"),
path("see_request/", core_views.see_request),
path("user_info/", core_views.user_info),
path("private_place/", core_views.private_place),
path("accounts/", include("django.contrib.auth.urls")),
path("staff_place/", core_views.staff_place),
path("add_messages/", core_views.add_messages),
]
"""
WSGI config for Blog project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Blog.settings")
application = get_wsgi_application()
from django.contrib import admin
from core.models import Blog
@admin.register(Blog)
class BlogAdmin(admin.ModelAdmin):
pass
from django.apps import AppConfig
class CoreConfig(AppConfig):
name = "core"
[{"model": "core.blog", "pk": 1, "fields": {"title": "Fruit", "content": "<p> Turns out, you can compare them: </p>\r\n\r\n<ul>\r\n <li>Apple</li>\r\n <li>Orange</li>\r\n</ul>"}}, {"model": "core.blog", "pk": 2, "fields": {"title": "I like airplanes", "content": "I prefer them to be very <i>fast</i>. I wish the Concorde still existed."}}]
\ No newline at end of file
# Generated by Django 3.0.7 on 2020-07-31 15:14
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Blog",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=50)),
("content", models.TextField()),
],
),
]
from django.db import models
class Blog(models.Model):
title = models.CharField(max_length=50)
content = models.TextField()
from django.contrib import messages
from django.contrib.auth.decorators import login_required, user_passes_test
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from core.models import Blog
def listing(request):
data = {
"blogs": Blog.objects.all(),
}
return render(request, "listing.html", data)
def view_blog(request, blog_id):
blog = get_object_or_404(Blog, id=blog_id)
data = {
"blog": blog,
}
return render(request, "view_blog.html", data)
def see_request(request):
text = f"""
Some attributes of the HttpRequest object:
scheme: {request.scheme}
path: {request.path}
method: {request.method}
GET: {request.GET}
user: {request.user}
"""
return HttpResponse(text, content_type="text/plain")
def user_info(request):
text = f"""
Selected HttpRequest.user attributes:
username: {request.user.username}
is_anonymous: {request.user.is_anonymous}
is_staff: {request.user.is_staff}
is_superuser: {request.user.is_superuser}
is_active: {request.user.is_active}
"""
return HttpResponse(text, content_type="text/plain")
@login_required
def private_place(request):
return HttpResponse("Shhh, members only!", content_type="text/plain")
@user_passes_test(lambda user: user.is_staff)
def staff_place(request):
return HttpResponse("Employees must wash hands", content_type="text/plain")
@login_required
def add_messages(request):
username = request.user.username
messages.add_message(request, messages.INFO, f"Hello {username}")
messages.add_message(request, messages.WARNING, "DANGER WILL ROBINSON")
return HttpResponse("Messages added", content_type="text/plain")
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Blog.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
#!/bin/bash
find . -name "*.pyc" -exec rm {} \;
rm db.sqlite3
python manage.py makemigrations core
python manage.py migrate
python manage.py loaddata core
#!/bin/bash
python manage.py runserver
<html>
<body>
{% block content %}
{% endblock content %}
</body>
</html>
{% extends "base.html" %}
{% block content %}
<h1>Blog Listing</h1>
{% if messages %}
<ul class="messages" style="background-color:#ccc">
{% for message in messages %}
<li {% if message.tags %} class="{{ message.tags }}" {% endif %}>
{{ message }}
</li>
{% endfor %}
</ul>
{% endif %}
<ul>
{% for blog in blogs %}
<li> <a href="{% url 'view_blog' blog.id %}">{{blog.title}}</a> </li>
{% endfor %}
</ul>
{% endblock content %}
{% extends 'base.html' %}
{% block content %}
<h1>Login</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Login">
</form>
<a href="{% url 'listing' %}">All Blogs</a>
{% endblock %}
{% extends "base.html" %}
{% block content %}
<h1>{{blog.title}}</h1>
{{blog.content|safe}}
<hr/>
<a href="{% url 'listing' %}">All Blogs</a>
{% endblock content %}
# Get Started With Django Part 3: Django View Authorization, Code Examples
This folder contains the sample code for [Get Started With Django Part 3: Django View Authorization](https://realpython.com/django-view-authorization/).
The code was tested with Python 3.8 and Django 3.0.7. To install the same version, use:
python -m pip install -r requirements.txt
This tutorial uses the Django admin, and comes with some sample data. To get going you will need to run the following commands:
cd Blog
python manage.py migrate
python manage.py createsuperuser
python manage.py loaddata core.json
Once those commands are complete, you can run the Django development server:
python manage.py runserver
With the server running, visit `http://127.0.0.1:8000` in your web browser to see the results.
......@@ -90,7 +90,7 @@ def create(person):
else:
abort(
406,
"Peron with last name {lname} already exists".format(lname=lname),
"Person with last name {lname} already exists".format(lname=lname),
)
......
......@@ -7,7 +7,9 @@ import time
class Pipeline:
"""Class to allow a single element pipeline between producer and consumer.
"""
Class to allow a single element pipeline
between producer and consumer.
"""
def __init__(self):
......
......@@ -8,7 +8,9 @@ SENTINEL = object()
class Pipeline:
"""Class to allow a single element pipeline between producer and consumer.
"""
Class to allow a single element pipeline
between producer and consumer.
"""
def __init__(self):
......
# Use Sentiment Analysis With Python to Classify Reviews
Resources and materials for Real Python's [Use Sentiment Analysis With Python to Classify Reviews](https://realpython.com/use-sentiment-analysis-python-classify-movie-reviews/) tutorial.
## Installation
Create and activate a new virtual environment:
```shell
$ python -m venv .venv
$ source .venv/bin/activate
```
Install Python dependencies into the active virtual environment:
```shell
(.venv) $ python -m pip install -r requirements.txt
```
Download English model for spaCy:
```shell
(.venv) $ python -m spacy download en_core_web_sm
```
Download and extract the [Large Movie Review Dataset](https://ai.stanford.edu/~amaas/data/sentiment/) compiled by [Andrew Maas](http://www.andrew-maas.net/):
```shell
$ curl -s https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz | tar xvz
```
## Usage
Get the sentiment of a movie review stored in the `TEST_REVIEW` variable:
```shell
(.venv) $ python sentiment_analyzer.py
```
import os
import random
import spacy
from spacy.util import minibatch, compounding
import pandas as pd
TEST_REVIEW = """
Transcendently beautiful in moments outside the office, it seems almost
sitcom-like in those scenes. When Toni Colette walks out and ponders
life silently, it's gorgeous.<br /><br />The movie doesn't seem to decide
whether it's slapstick, farce, magical realism, or drama, but the best of it
doesn't matter. (The worst is sort of tedious - like Office Space with less
humor.)
"""
eval_list = []
def train_model(
training_data: list, test_data: list, iterations: int = 20
) -> None:
# Build pipeline
nlp = spacy.load("en_core_web_sm")
if "textcat" not in nlp.pipe_names:
textcat = nlp.create_pipe(
"textcat", config={"architecture": "simple_cnn"}
)
nlp.add_pipe(textcat, last=True)
else:
textcat = nlp.get_pipe("textcat")
textcat.add_label("pos")
textcat.add_label("neg")
# Train only textcat
training_excluded_pipes = [
pipe for pipe in nlp.pipe_names if pipe != "textcat"
]
with nlp.disable_pipes(training_excluded_pipes):
optimizer = nlp.begin_training()
# Training loop
print("Beginning training")
print("Loss\tPrecision\tRecall\tF-score")
batch_sizes = compounding(
4.0, 32.0, 1.001
) # A generator that yields infinite series of input numbers
for i in range(iterations):
print(f"Training iteration {i}")
loss = {}
random.shuffle(training_data)
batches = minibatch(training_data, size=batch_sizes)
for batch in batches:
text, labels = zip(*batch)
nlp.update(text, labels, drop=0.2, sgd=optimizer, losses=loss)
with textcat.model.use_params(optimizer.averages):
evaluation_results = evaluate_model(
tokenizer=nlp.tokenizer,
textcat=textcat,
test_data=test_data,
)
print(
f"{loss['textcat']}\t{evaluation_results['precision']}"
f"\t{evaluation_results['recall']}"
f"\t{evaluation_results['f-score']}"
)
# Save model
with nlp.use_params(optimizer.averages):
nlp.to_disk("model_artifacts")
def evaluate_model(tokenizer, textcat, test_data: list) -> dict:
reviews, labels = zip(*test_data)
reviews = (tokenizer(review) for review in reviews)
true_positives = 0
false_positives = 1e-8 # Can't be 0 because of presence in denominator
true_negatives = 0
false_negatives = 1e-8
for i, review in enumerate(textcat.pipe(reviews)):
true_label = labels[i]["cats"]
for predicted_label, score in review.cats.items():
# Every cats dictionary includes both labels, you can get all
# the info you need with just the pos label
if predicted_label == "neg":
continue
if score >= 0.5 and true_label["pos"]:
true_positives += 1
elif score >= 0.5 and true_label["neg"]:
false_positives += 1
elif score < 0.5 and true_label["neg"]:
true_negatives += 1
elif score < 0.5 and true_label["pos"]:
false_negatives += 1
precision = true_positives / (true_positives + false_positives)
recall = true_positives / (true_positives + false_negatives)
if precision + recall == 0:
f_score = 0
else:
f_score = 2 * (precision * recall) / (precision + recall)
return {"precision": precision, "recall": recall, "f-score": f_score}
def test_model(input_data: str = TEST_REVIEW):
# Load saved trained model
loaded_model = spacy.load("model_artifacts")
# Generate prediction
parsed_text = loaded_model(input_data)
# Determine prediction to return
if parsed_text.cats["pos"] > parsed_text.cats["neg"]:
prediction = "Positive"
score = parsed_text.cats["pos"]
else:
prediction = "Negative"
score = parsed_text.cats["neg"]
print(
f"Review text: {input_data}\nPredicted sentiment: {prediction}"
f"\tScore: {score}"
)
def load_training_data(
data_directory: str = "aclImdb/train", split: float = 0.8, limit: int = 0
) -> tuple:
# Load from files
reviews = []
for label in ["pos", "neg"]:
labeled_directory = f"{data_directory}/{label}"
for review in os.listdir(labeled_directory):
if review.endswith(".txt"):
with open(f"{labeled_directory}/{review}") as f:
text = f.read()
text = text.replace("<br />", "\n\n")
if text.strip():
spacy_label = {
"cats": {
"pos": "pos" == label,
"neg": "neg" == label,
}
}
reviews.append((text, spacy_label))
random.shuffle(reviews)
if limit:
reviews = reviews[:limit]
split = int(len(reviews) * split)
return reviews[:split], reviews[split:]
if __name__ == "__main__":
train, test = load_training_data(limit=25)
print("Training model")
train_model(train, test)
df = pd.DataFrame(eval_list)
pd.DataFrame.plot(df)
print("Testing model")
test_model()
......@@ -48,7 +48,10 @@ for file_path in DATA_FOLDER.glob("quiz_*_grades.csv"):
# ------------------------
final_data = pd.merge(
roster, hw_exam_grades, left_index=True, right_index=True,
roster,
hw_exam_grades,
left_index=True,
right_index=True,
)
final_data = pd.merge(
final_data, quiz_grades, left_on="Email Address", right_index=True
......
......@@ -49,7 +49,10 @@ for file_path in DATA_FOLDER.glob("quiz_*_grades.csv"):
# ------------------------
final_data = pd.merge(
roster, hw_exam_grades, left_index=True, right_index=True,
roster,
hw_exam_grades,
left_index=True,
right_index=True,
)
final_data = pd.merge(
final_data, quiz_grades, left_on="Email Address", right_index=True
......
......@@ -49,7 +49,10 @@ for file_path in DATA_FOLDER.glob("quiz_*_grades.csv"):
# ------------------------
final_data = pd.merge(
roster, hw_exam_grades, left_index=True, right_index=True,
roster,
hw_exam_grades,
left_index=True,
right_index=True,
)
final_data = pd.merge(
final_data, quiz_grades, left_on="Email Address", right_index=True
......
......@@ -51,7 +51,10 @@ for file_path in DATA_FOLDER.glob("quiz_*_grades.csv"):
# ------------------------
final_data = pd.merge(
roster, hw_exam_grades, left_index=True, right_index=True,
roster,
hw_exam_grades,
left_index=True,
right_index=True,
)
final_data = pd.merge(
final_data, quiz_grades, left_on="Email Address", right_index=True
......
......@@ -43,7 +43,10 @@ for file_path in DATA_FOLDER.glob("quiz_*_grades.csv"):
quiz_grades = pd.concat([quiz_grades, quiz], axis=1)
final_data = pd.merge(
roster, hw_exam_grades, left_index=True, right_index=True,
roster,
hw_exam_grades,
left_index=True,
right_index=True,
)
final_data = pd.merge(
final_data, quiz_grades, left_on="Email Address", right_index=True
......
# Real Python - Python Bindings Sample Code Repo
This is the repo to accompany the [Python Bindings](https://realpython.com/python-bindings-overview/) article.
To be able to run the code, you must first install the requirements:
```console
$ python -m pip install -r requirements.txt
```
This should be done inside a virtual environment.
Once that is installed, you can use the invoke tool mentioned in the article to build and run the tests. See the tasks.py file or run invoke --list to get more details.
\ No newline at end of file
#!/usr/bin/env python3
# IDE might complain with "no module found" here, even when it exists
import cffi_example
if __name__ == "__main__":
......
float cmult(int int_param, float float_param);
#ifdef _MSC_VER
#define EXPORT_SYMBOL __declspec(dllexport)
#else
#define EXPORT_SYMBOL
#endif
EXPORT_SYMBOL float cmult(int int_param, float float_param);
\ No newline at end of file
#!/usr/bin/env python
""" Simple examples of calling C functions through ctypes module. """
import ctypes
import pathlib
import sys
if __name__ == "__main__":
# Load the shared library into c types.
libname = pathlib.Path().absolute() / "libcmult.so"
c_lib = ctypes.CDLL(libname)
if sys.platform.startswith("win"):
c_lib = ctypes.CDLL("cmult.dll")
else:
c_lib = ctypes.CDLL("libcmult.so")
# Sample data for our call:
x, y = 6, 2.3
......
""" Task definitions for invoke command line utility for python bindings
overview article. """
overview article.
"""
import cffi
import invoke
import pathlib
import sys
import os
import shutil
import re
import glob
on_win = sys.platform.startswith("win")
@invoke.task
def clean(c):
""" Remove any built objects """
for pattern in ["*.o", "*.so", "cffi_example* cython_wrapper.cpp"]:
c.run("rm -rf {}".format(pattern))
for file_pattern in (
"*.o",
"*.so",
"*.obj",
"*.dll",
"*.exp",
"*.lib",
"*.pyd",
"cffi_example*", # Is this a dir?
"cython_wrapper.cpp",
):
for file in glob.glob(file_pattern):
os.remove(file)
for dir_pattern in "Release":
for dir in glob.glob(dir_pattern):
shutil.rmtree(dir)
def print_banner(msg):
......@@ -17,32 +39,55 @@ def print_banner(msg):
print("= {} ".format(msg))
@invoke.task
def build_cmult(c):
@invoke.task()
def build_cmult(c, path=None):
""" Build the shared library for the sample C code """
print_banner("Building C Library")
invoke.run("gcc -c -Wall -Werror -fpic cmult.c -I /usr/include/python3.7")
invoke.run("gcc -shared -o libcmult.so cmult.o")
print("* Complete")
# Moving this type hint into signature causes an error (???)
c: invoke.Context
if on_win:
if not path:
print("Path is missing")
else:
# Using c.cd didn't work with paths that have spaces :/
path = f'"{path}vcvars32.bat" x86' # Enter the VS venv
path += f'&& cd "{os.getcwd()}"' # Change to current dir
path += "&& cl /LD cmult.c" # Compile
# Uncomment line below, to suppress stdout
# path = path.replace("&&", " >nul &&") + " >nul"
c.run(path)
else:
print_banner("Building C Library")
cmd = "gcc -c -Wall -Werror -fpic cmult.c -I /usr/include/python3.7"
invoke.run(cmd)
invoke.run("gcc -shared -o libcmult.so cmult.o")
print("* Complete")
@invoke.task(build_cmult)
@invoke.task()
def test_ctypes(c):
""" Run the script to test ctypes """
print_banner("Testing ctypes Module")
invoke.run("python3 ctypes_test.py", pty=True)
# pty and python3 didn't work for me (win).
if on_win:
invoke.run("python ctypes_test.py")
else:
invoke.run("python3 ctypes_test.py", pty=True)
@invoke.task(build_cmult)
@invoke.task()
def build_cffi(c):
""" Build the CFFI Python bindings """
print_banner("Building CFFI Module")
ffi = cffi.FFI()
this_dir = pathlib.Path().absolute()
this_dir = pathlib.Path().resolve()
h_file_name = this_dir / "cmult.h"
with open(h_file_name) as h_file:
ffi.cdef(h_file.read())
# cffi does not like our preprocessor directives, so we remove them
lns = h_file.read().splitlines()
flt = filter(lambda ln: not re.match(r" *#", ln), lns)
flt = map(lambda ln: ln.replace("EXPORT_SYMBOL ", ""), flt)
ffi.cdef(str("\n").join(flt))
ffi.set_source(
"cffi_example",
......@@ -66,7 +111,7 @@ def build_cffi(c):
def test_cffi(c):
""" Run the script to test CFFI """
print_banner("Testing CFFI Module")
invoke.run("python3 cffi_test.py", pty=True)
invoke.run("python cffi_test.py", pty=not on_win)
@invoke.task()
......
The MIT License (MIT)
Copyright (c) 2020 Leodanis Pozo Ramos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# Sample PyQt Application
A sample GUI application that shows how to create and use menus, toolbars, and status bars using Python and PyQt.
## How to Run this Application
To run this application, you need to [install `PyQt5`](https://realpython.com/python-pyqt-gui-calculator/#installing-pyqt) on your Python environment. To do that, you can run the following commands in a terminal or command prompt:
```sh
$ pip3 install PyQt5
```
Once you have [PyQt](https://www.riverbankcomputing.com/static/Docs/PyQt5/) installed, you can run the application by executing the following command:
```sh
$ python3 sample-app.py
```
This command will launch the application, so you'll be able to experiment with it.
## About the Author
Leodanis Pozo Ramos – [@lpozo78](https://twitter.com/lpozo78) – lpozor78@gmail.com
## License
- This application is distributed under the MIT license. See `LICENSE` for details.
- The set of icons used in this application are part of the [TurkinOS](https://github.com/llamaret/turkinos-icon) icon theme, which is distributed under the [GPL v3.0 license](https://github.com/llamaret/turkinos-icon/blob/master/LICENSE). See `resources/LICENSE` for details.
\ No newline at end of file
此差异已折叠。
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="file-new.svg">resources/file-new.svg</file>
<file alias="file-open.svg">resources/file-open.svg</file>
<file alias="file-save.svg">resources/file-save.svg</file>
<file alias="file-exit.svg">resources/file-exit.svg</file>
<file alias="edit-copy.svg">resources/edit-copy.svg</file>
<file alias="edit-cut.svg">resources/edit-cut.svg</file>
<file alias="edit-paste.svg">resources/edit-paste.svg</file>
<file alias="help-content.svg">resources/help-content.svg</file>
</qresource>
</RCC>
此差异已折叠。
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m14.999993 6.0000017h-10.999988c-.5520048 0-1.000005.4480002-1.000005.9999916v11.0000017c0 .552005.4480002 1.000005 1.000005 1.000005h10.999988c.552005 0 1.000005-.448 1.000005-1.000005v-11.0000017c0-.5519914-.448-.9999916-1.000005-.9999916zm3.000002-3.0000017h-9.9999967c-.5520048 0-1.000005.4480002-1.000005 1.000005v.9999916h1.000005v-.5000025c0-.2759957.2240068-.4999891.5000025-.4999891h8.9999912c.27601 0 .500003.2239934.500003.4999891v9.0000049c0 .275996-.223993.500003-.500003.500003h-.499989v.999991h.999992c.552005 0 1.000005-.448 1.000005-.999991v-9.999997c0-.5520048-.448-1.000005-1.000005-1.000005z"
class="ColorScheme-Text" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m7.0136719 2-.4355469.7753906c-.2699231.4812667-.5038694.7087891-.6289062 1.1367188-.0625185.2139647-.0612584.4699226.0019531.7011718.0632171.2312494.1726285.4527981.3261719.7265626l3.3945312 6.0625002-1.7148438 3.023437c-.4483616-.277461-.8911737-.591797-1.4550781-.591797-1.6582865 0-3.0019531 1.388975-3.0019531 3.083985 0 1.695008 1.3436663 3.082031 3.0019531 3.082031 1.658287 0 3.0019532-1.387023 3.0019531-3.082031 0-.133955-.0209824-.259363-.0371093-.386719.4622484-1.003569.8985411-1.788627 1.8085941-2.248047.003037-.003375.002934-.004437.005859-.007812l1.253906 2.238281c-.017223.134034-.039062.265801-.039062.404297 0 1.695008 1.343665 3.082031 3.001953 3.082031 1.658287 0 3.001953-1.387023 3.001953-3.082031 0-1.69501-1.343667-3.083985-3.001953-3.083985-.56176 0-1.004412.308543-1.451172.583985l-1.708984-3.015625 3.398437-6.0625002v-.0019532c.152934-.2728193.261159-.4939397.324219-.7246094.063214-.2312492.066332-.4872071.003906-.7011718-.125138-.4278882-.358984-.6554521-.628906-1.1367188l-.435547-.7753906-3.994141 7.0488281zm-.5117188 13.884766c.5564078 0 .9960938.448401.9960938 1.033203s-.439686 1.03125-.9960938 1.03125-.9941406-.446448-.9941406-1.03125c0-.584803.4377328-1.033203.9941406-1.033203zm8.9960939 0c .556407 0 .99414.448401.994141 1.033203 0 .584802-.437734 1.03125-.994141 1.03125-.556409 0-.996094-.446448-.996094-1.03125 0-.584803.439685-1.033203.996094-1.033203z"
class="ColorScheme-Text" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m14.12662 8.00521h-.9452v-2.22935c0-.23294-.18815-.4217-.42014-.4217h-.45273l-.90589-3.0529c-.053-.17871-.21679-.30126-.40266-.30126-.18578 0-.34963.12255-.40266.30126l-.90589 3.0529h-.45273c-.23199 0-.42014.18876-.42014.4217v2.22942h-.9452c-1.03295 0-1.87338.84348-1.87338 1.88035v8.23403c0 1.03686.84035 1.88034 1.87338 1.88034h6.25324c1.03295 0 1.87338-.84348 1.87338-1.88034v-8.23403c0-1.03696-.84035-1.88042-1.87338-1.88042z"
class="ColorScheme-Text" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m17.927299 6.98434c-.715348-1.22562-1.685765-2.19604-2.911381-2.91138-1.225871-.71535-2.564127-1.07296-4.015662-1.07296-1.4514075 0-2.7900468.35761-4.0156625 1.07296-1.2257436.71522-2.1961612 1.68563-2.9115085 2.91138-.7154752 1.22574-1.073085 2.56438-1.073085 4.01566 0 1.45141.3577376 2.78979 1.0729571 4.01566.7153474 1.22549 1.685765 2.19604 2.9115085 2.91138 1.2257436.71535 2.5642551 1.07296 4.0156624 1.07296 1.451407 0 2.790046-.35761 4.015662-1.07296 1.225616-.71522 2.196033-1.68576 2.911381-2.91138.715219-1.22574 1.072829-2.56438 1.072829-4.01566 0-1.45141-.35761-2.79005-1.072701-4.01566zm-3.156351 5.90101c.131946.13181.198047.28831.198047.46884 0 .18743-.0661.34725-.198047.47907l-.937431.93756c-.131946.13195-.291637.19792-.4792.19792-.18053 0-.337025-.066-.468715-.19792l-1.885474-1.88547-1.8854742 1.88547c-.1319462.13195-.2881847.19792-.4687156.19792-.1875629 0-.3472535-.066-.4791996-.19792l-.9375589-.93756c-.1319462-.13182-.1979192-.29164-.1979192-.47907 0-.18053.065973-.33703.1979192-.46884l1.8854741-1.88535-1.8854741-1.88547c-.1319462-.13182-.1979192-.28819-.1979192-.46872 0-.18756.065973-.34725.1979192-.4792l.9375589-.93743c.1319461-.13195.2916367-.19792.4791996-.19792.1805309 0 .3367694.066.4687156.19792l1.8854742 1.88535 1.885474-1.88535c.13169-.13195.288185-.19792.468715-.19792.187563 0 .347254.066.4792.19792l.937431.93743c.131946.13195.198047.29164.198047.4792 0 .18053-.0661.3369-.198047.46872l-1.885346 1.88547z"
class="ColorScheme-Text" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m9.666667 3c-.246249 0-.444445.1981956-.444445.4444444v5.7777776h-5.7777776c-.2462222 0-.4444444.198222-.4444444.444445v2.666666c0 .246223.1982222.444445.4444444.444445h5.7777776v5.777778c0 .246248.198196.444444.444445.444444h2.666666c.246249 0 .444445-.198196.444445-.444444v-5.777778h5.777778c.246222 0 .444444-.198222.444444-.444445v-2.666666c0-.246223-.198222-.444445-.444444-.444445h-5.777778v-5.7777776c0-.2462488-.198196-.4444444-.444445-.4444444z"
class="ColorScheme-Text" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m2.5000001 1034.3622c-.277 0-.5.223-.5.5v1.9609 1.0391 10.0391c0 .2557.2052456.4609.4609375.4609h17.0781244c.255692 0 .460938-.2052.460938-.4609v-11.0782c0-.2557-.205246-.4609-.460938-.4609h-8.181159c-.569865.017-.846295-.1321-1.097628-.3681l-1.1884019-1.21c-.16805-.2202-.294873-.4219-.571873-.4219z"
class="ColorScheme-Text"
transform="matrix(.88888889 0 0 .88888889 1.222222 -914.87751)"/>
</svg>
<svg viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
<defs id="defs">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text { color: #4d4d4d; }
</style>
</defs>
<path style="fill:currentColor;fill-opacity;stroke:none" class="ColorScheme-Text" d="m3.6679688 3c-.3691339 0-.6679688.2988749-.6679688.6679688v14.6640622c0 .369134.2988349.667969.6679688.667969h14.6640622c.369134 0 .667969-.298835.667969-.667969v-11.5585935c0-.1772133-.069939-.3473157-.195312-.4726563l-3.105469-3.1054687c-.12534-.1253333-.295351-.1953125-.472657-.1953125zm1.9707031 1.5273438h9.1777341v4.9003906h-9.1777341zm5.5703121.4726562c-.115073 0-.208984.1073944-.208984.2402344v3.5195312c0 .13288.093911.2402344.208984.2402344h1.582032c.115073 0 .208984-.1073544.208984-.2402344v-3.5195312c0-.13284-.093911-.2402344-.208984-.2402344z" transform="matrix(1.000000005 0 0 1.000000005 .000001275238 .00000172266)"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m10.999999 3.000002a8 8 0 0 0 -8 8 8 8 0 0 0 8 8 8 8 0 0 0 8-8 8 8 0 0 0 -8-8zm-.199219 3.5a1 1 0 0 1 1 1 1 1 0 0 1 -1 1 1 1 0 0 1 -.9999998-1 1 1 0 0 1 .9999998-1zm-.107422 3h .212891c.356143 0 .644531.2864351.644531.642578v4.714844c0 .356143-.288388.642578-.644531.642578h-.212891c-.356143 0-.642578-.286435-.642578-.642578v-4.714844c0-.3561429.286435-.642578.642578-.642578z"
class="ColorScheme-Text" />
</svg>
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Sample PyQt application."""
import sys
from functools import partial
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QKeySequence
from PyQt5.QtWidgets import (
QAction,
QApplication,
QLabel,
QMainWindow,
QMenu,
QSpinBox,
QToolBar,
)
# NOTE: Uncomment this import to enable icons
# import qrc_resources
class Window(QMainWindow):
"""Main Window."""
def __init__(self, parent=None):
"""Initializer."""
super().__init__(parent)
self.setWindowTitle("Python Menus & Toolbars")
self.resize(400, 200)
self.centralWidget = QLabel("Hello, World")
self.centralWidget.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.setCentralWidget(self.centralWidget)
self._createActions()
self._createMenuBar()
self._createToolBars()
# Uncomment the call to ._createContextMenu() below to create a context
# menu using menu policies. To test this out, you also need to
# comment .contextMenuEvent() and uncomment ._createContextMenu()
# self._createContextMenu()
self._connectActions()
self._createStatusBar()
def _createMenuBar(self):
menuBar = self.menuBar()
# File menu
fileMenu = QMenu("&File", self)
menuBar.addMenu(fileMenu)
fileMenu.addAction(self.newAction)
fileMenu.addAction(self.openAction)
# Open Recent submenu
self.openRecentMenu = fileMenu.addMenu("Open Recent")
fileMenu.addAction(self.saveAction)
# Separator
fileMenu.addSeparator()
fileMenu.addAction(self.exitAction)
# Edit menu
editMenu = menuBar.addMenu("&Edit")
editMenu.addAction(self.copyAction)
editMenu.addAction(self.pasteAction)
editMenu.addAction(self.cutAction)
# Separator
editMenu.addSeparator()
# Find and Replace submenu
findMenu = editMenu.addMenu("Find and Replace")
findMenu.addAction("Find...")
findMenu.addAction("Replace...")
# Help menu
helpMenu = menuBar.addMenu(QIcon(":help-content.svg"), "&Help")
helpMenu.addAction(self.helpContentAction)
helpMenu.addAction(self.aboutAction)
def _createToolBars(self):
# File toolbar
fileToolBar = self.addToolBar("File")
fileToolBar.setMovable(False)
fileToolBar.addAction(self.newAction)
fileToolBar.addAction(self.openAction)
fileToolBar.addAction(self.saveAction)
# Edit toolbar
editToolBar = QToolBar("Edit", self)
self.addToolBar(editToolBar)
editToolBar.addAction(self.copyAction)
editToolBar.addAction(self.pasteAction)
editToolBar.addAction(self.cutAction)
# Widgets
self.fontSizeSpinBox = QSpinBox()
self.fontSizeSpinBox.setFocusPolicy(Qt.NoFocus)
editToolBar.addWidget(self.fontSizeSpinBox)
def _createStatusBar(self):
self.statusbar = self.statusBar()
# Temporary message
self.statusbar.showMessage("Ready", 3000)
# Permanent widget
self.wcLabel = QLabel(f"{self.getWordCount()} Words")
self.statusbar.addPermanentWidget(self.wcLabel)
def _createActions(self):
# File actions
self.newAction = QAction(self)
self.newAction.setText("&New")
self.newAction.setIcon(QIcon(":file-new.svg"))
self.openAction = QAction(QIcon(":file-open.svg"), "&Open...", self)
self.saveAction = QAction(QIcon(":file-save.svg"), "&Save", self)
self.exitAction = QAction("&Exit", self)
# String-based key sequences
self.newAction.setShortcut("Ctrl+N")
self.openAction.setShortcut("Ctrl+O")
self.saveAction.setShortcut("Ctrl+S")
# Help tips
newTip = "Create a new file"
self.newAction.setStatusTip(newTip)
self.newAction.setToolTip(newTip)
self.newAction.setWhatsThis("Create a new and empty text file")
# Edit actions
self.copyAction = QAction(QIcon(":edit-copy.svg"), "&Copy", self)
self.pasteAction = QAction(QIcon(":edit-paste.svg"), "&Paste", self)
self.cutAction = QAction(QIcon(":edit-cut.svg"), "C&ut", self)
# Standard key sequence
self.copyAction.setShortcut(QKeySequence.Copy)
self.pasteAction.setShortcut(QKeySequence.Paste)
self.cutAction.setShortcut(QKeySequence.Cut)
# Help actions
self.helpContentAction = QAction("&Help Content...", self)
self.aboutAction = QAction("&About...", self)
# Uncomment this method to create a context menu using menu policies
# def _createContextMenu(self):
# # Setting contextMenuPolicy
# self.centralWidget.setContextMenuPolicy(Qt.ActionsContextMenu)
# # Populating the widget with actions
# self.centralWidget.addAction(self.newAction)
# self.centralWidget.addAction(self.openAction)
# self.centralWidget.addAction(self.saveAction)
# self.centralWidget.addAction(self.copyAction)
# self.centralWidget.addAction(self.pasteAction)
# self.centralWidget.addAction(self.cutAction)
def contextMenuEvent(self, event):
# Context menu
menu = QMenu(self.centralWidget)
# Populating the menu with actions
menu.addAction(self.newAction)
menu.addAction(self.openAction)
menu.addAction(self.saveAction)
# Separator
separator = QAction(self)
separator.setSeparator(True)
menu.addAction(separator)
menu.addAction(self.copyAction)
menu.addAction(self.pasteAction)
menu.addAction(self.cutAction)
# Launching the menu
menu.exec(event.globalPos())
def _connectActions(self):
# Connect File actions
self.newAction.triggered.connect(self.newFile)
self.openAction.triggered.connect(self.openFile)
self.saveAction.triggered.connect(self.saveFile)
self.exitAction.triggered.connect(self.close)
# Connect Edit actions
self.copyAction.triggered.connect(self.copyContent)
self.pasteAction.triggered.connect(self.pasteContent)
self.cutAction.triggered.connect(self.cutContent)
# Connect Help actions
self.helpContentAction.triggered.connect(self.helpContent)
self.aboutAction.triggered.connect(self.about)
# Connect Open Recent to dynamically populate it
self.openRecentMenu.aboutToShow.connect(self.populateOpenRecent)
# Slots
def newFile(self):
# Logic for creating a new file goes here...
self.centralWidget.setText("<b>File > New</b> clicked")
def openFile(self):
# Logic for opening an existing file goes here...
self.centralWidget.setText("<b>File > Open...</b> clicked")
def saveFile(self):
# Logic for saving a file goes here...
self.centralWidget.setText("<b>File > Save</b> clicked")
def copyContent(self):
# Logic for copying content goes here...
self.centralWidget.setText("<b>Edit > Copy</b> clicked")
def pasteContent(self):
# Logic for pasting content goes here...
self.centralWidget.setText("<b>Edit > Pate</b> clicked")
def cutContent(self):
# Logic for cutting content goes here...
self.centralWidget.setText("<b>Edit > Cut</b> clicked")
def helpContent(self):
# Logic for launching help goes here...
self.centralWidget.setText("<b>Help > Help Content...</b> clicked")
def about(self):
# Logic for showing an about dialog content goes here...
self.centralWidget.setText("<b>Help > About...</b> clicked")
def populateOpenRecent(self):
# Step 1. Remove the old options from the menu
self.openRecentMenu.clear()
# Step 2. Dynamically create the actions
actions = []
filenames = [f"File-{n}" for n in range(5)]
for filename in filenames:
action = QAction(filename, self)
action.triggered.connect(partial(self.openRecentFile, filename))
actions.append(action)
# Step 3. Add the actions to the menu
self.openRecentMenu.addActions(actions)
def openRecentFile(self, filename):
# Logic for opening a recent file goes here...
self.centralWidget.setText(f"<b>{filename}</b> opened")
def getWordCount(self):
# Logic for computing the word count goes here...
return 42
if __name__ == "__main__":
# Create the application
app = QApplication(sys.argv)
# Create and show the main window
win = Window()
win.show()
# Run the event loop
sys.exit(app.exec_())
# Python Practice Problems
Unittest stubs for ["Python Practice Problems."](https://realpython.com/python-practice-problems/)
## Running the Tests
To run the test for a given problem, use `unittest` from the Python standard library;
```console
$ python -m unittest integersums.py
```
The above example will run the unit tests for the first practice problem.
\ No newline at end of file
#!/usr/bin/env python3
""" Caesar Cipher
A caesar cipher is a simple substitution cipher where each letter of the
plain text is substituted with a letter found by moving 'n' places down the
alphabet. For an example, if the input plain text is:
abcd xyz
and the shift value, n, is 4. The encrypted text would be:
efgh bcd
You are to write a function which accepts two arguments, a plain-text
message and a number of letters to shift in the cipher. The function will
return an encrypted string with all letters being transformed while all
punctuation and whitespace remains unchanged.
Note: You can assume the plain text is all lowercase ascii, except for
whitespace and punctuation.
"""
import unittest
def caesar(plain_text, shift_num=1):
# TODO: Your code goes here!
result = plain_text
return result
class CaesarTestCase(unittest.TestCase):
def test_a(self):
start = "aaa"
result = caesar(start, 1)
self.assertEqual(result, "bbb")
result = caesar(start, 5)
self.assertEqual(result, "fff")
def test_punctuation(self):
start = "aaa.bbb"
result = caesar(start, 1)
self.assertEqual(result, "bbb.ccc")
result = caesar(start, -1)
self.assertEqual(result, "zzz.aaa")
def test_whitespace(self):
start = "aaa bb b"
result = caesar(start, 1)
self.assertEqual(result, "bbb cc c")
result = caesar(start, 3)
self.assertEqual(result, "ddd ee e")
def test_wraparound(self):
start = "abc"
result = caesar(start, -1)
self.assertEqual(result, "zab")
result = caesar(start, -2)
self.assertEqual(result, "yza")
result = caesar(start, -3)
self.assertEqual(result, "xyz")
start = "xyz"
result = caesar(start, 1)
self.assertEqual(result, "yza")
result = caesar(start, 2)
self.assertEqual(result, "zab")
result = caesar(start, 3)
self.assertEqual(result, "abc")
if __name__ == "__main__":
unittest.main()
#!/usr/bin/env python3
""" Sum of Integers Up To n
Write a function, add_it_up, which returns the sum of the integers from 0
to the single integer input parameter.
The function should return 0 if a non-integer is passed in.
"""
import unittest
def add_it_up(n):
# TODO: Your code goes here!
return n
class IntegerSumTestCase(unittest.TestCase):
def test_to_ten(self):
results = [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
for n in range(10):
self.assertEqual(add_it_up(n), results[n])
def test_string(self):
self.assertEqual(add_it_up("testing"), 0)
def test_float(self):
self.assertEqual(add_it_up(0.124), 0)
def test_negative(self):
self.assertEqual(add_it_up(-19), 0)
if __name__ == "__main__":
unittest.main()
#!/usr/bin/env python3
""" log parser
Accepts a filename on the command line. The file is a linux-like log file
from a system you are debugging. Mixed in among the various statements are
messages indicating the state of the device. They look like:
Jul 11 16:11:51:490 [139681125603136] dut: Device State: ON
The device state message has many possible values, but this program only
cares about three: ON, OFF, and ERR.
Your program will parse the given log file and print out a report giving
how long the device was ON, and the time stamp of any ERR conditions.
"""
if __name__ == "__main__":
# TODO: Your code goes here
print("There are no unit tests for logparse.")
#!/usr/bin/env python3
""" Sudoku Solver
NOTE: A description of the Sudoku puzzle can be found at:
https://en.wikipedia.org/wiki/Sudoku
Given a string in SDM format, described below, write a program to find and
return the solution for the Sudoku puzzle given in the string. The solution
should be returned in the same SDM format as the input.
Some puzzles will not be solvable. In that case, return the string
"Unsolvable".
The general sdx format is described here:
http://www.sudocue.net/fileformats.php
For our purposes, each SDX string will be a sequence of 81 digits, one for
each position on the Sudoku puzzle. Known numbers will be given and unknown
positions will have a zero value.
For example, this string of digits (split onto two lines for readability):
0040060790000006020560923000780610305090004
06020540890007410920105000000840600100
represents this starting Sudoku puzzle:
0 0 4 0 0 6 0 7 9
0 0 0 0 0 0 6 0 2
0 5 6 0 9 2 3 0 0
0 7 8 0 6 1 0 3 0
5 0 9 0 0 0 4 0 6
0 2 0 5 4 0 8 9 0
0 0 7 4 1 0 9 2 0
1 0 5 0 0 0 0 0 0
8 4 0 6 0 0 1 0 0
The unit tests provide may take a while to run, so be patient.
"""
import unittest
def sudoku_solve(input_string):
# TODO: Your code goes here!
return input_string
class SudokuSolverTestCase(unittest.TestCase):
problems = [
"00400607900000060205609230007806103050900040602054089000741092010500"
"0000840600100",
"01640000020000900040000006207023010010000000300308704096000000500080"
"0007000006820",
"04900860500300700000000003000040080006081502000100900001000000000060"
"0400804500390",
"76050000000006000800000040320040080008000003000500100780900000060001"
"0000000003041",
"00060500000302080004509027050000000106200054040000000709806045000604"
"0700000203000",
"40900070500001000000620780020000000900370420080000000400280150000006"
"0000905000406",
"00001003004007050100200800668000000300030200030000004520050080080104"
"0020090020000",
"08007003026005001800000040000060200039001008600070900000400080081004"
"0052050090070",
"00009300600080090002000610000008005300600020037005000000250004000100"
"9000700130007",
]
expected = [
"28413657991375468275689234147896123553928741662154389736741592819532"
"8764842679153",
"31645297828567931449731856287923415614296578365318724996872143552184"
"3697734596821",
"14923867562395714875814623993547286146781592328136975431679458259268"
"3417874521396",
"76354812942136975895817246329743681518679523434582169781925437663491"
"7582572683941",
"82967531467312489514539827658743692196281754343195268739876145221654"
"9738754283169",
"41963872572851964353624789125418637919375426886792315464289153737146"
"5982985372416",
"76891543294327658151243879668519427317435296832968714523756981485174"
"3629496821357",
"48197623526745391893582146717863254939251478654678932172416589381934"
"7652653298174",
"Unsolvable",
]
def test_solver(self):
for index, problem in enumerate(self.problems):
print(f"Testing puzzle {index+1}")
result = sudoku_solve(problem)
self.assertEqual(result, self.expected[index])
if __name__ == "__main__":
unittest.main()
此差异已折叠。
# Python / Sqlite /SqlAlchemy article
# Python / SQLite /SQLAlchemy article
This repository contains the content and example code
for the python/sqlite/sqlaclchemy article I'm writing
This repository contains the content and example code
for the Python / SQLite / SQLAlchemy article I'm writing
for Real Python.
This project was built using Python 3.8.0
## Python Virtualenv
I use the `pyenv` tool to install Python versions on my Mac. I find it a very useful tool, and the instructions that follow use it, and are based on having Python version 3.8.0 installed using the following command:
```shell
$ pyenv install 3.8.0
```
## Installing The Project
From the main folder take the following steps:
From the main folder take the following steps (on a Mac):
* Install a Python virtual environment for this project
* `pyenv local 3.8.0`
* `python -m venv .venv`
* Activate the virtual environment
* `source .venv/bin/activate`
* Install the project:
```shell script
python -m pip install -e .
```
\ No newline at end of file
* `python -m pip install -e .`
# Build Files Used By The Examples
The code in this directory builds the various data files used
by the example programs. There are three build programs:
by the example programs. There is one build program:
* build_author_book_publisher_sqlite.py
* build_temp_data_csv.py
* build_temp_data_sqlite.py
* build_author_book_publisher_sqlite.py
## Build the Temperature CSV File
The build_temp_data_csv.py file builds a CSV data file
(temp_data.csv) in the data directory
containing temperature samples taken by students in a class.
The top row contains the labels, the students name followed
by a date value for each Wednesday of the week for a year.
It then creates data for each sample based on a table of
temperature data, +/- 10 to make the data look variable
and reasonable.
## Build the Temperature Database File
The build_temp_data_sqlite.py file builds a Sqlite database
from the previously created temp_data.csv file called
temp_data.db in the data directory.
## Build the Author / Book / Publisher Database File
The build_author_book_publisher_sqlite.py file builds
a database from the data/author_book_publisher.csv file.
This database contains the tables necessary to describe
the data and the relationships between the data necessary
for the examples.
The build_author_book_publisher_sqlite.py file builds
a database from the `data/author_book_publisher.csv` file.
This database contains the rows of comma delimited text to describe
the data and the relationships between the data necessary
for the examples.
## Directory Structure
The directory structure is set up in such a way the
build programs (as well as the examples) can find the
data files needed for each one.
The directory structure is set up in such a way the
build programs (as well as the examples) can find the
data files needed for each one.
## Executing the Programs
* Activate your Python virtualenv
* cd into the build/code directory
* python build_temp_data_csv.py - builds the csv data file
* python build_temp_data_sqlite.py - builds the temperature data from the csv file
* python build_author_book_publisher_sqlite.py - builds the author_book_publisher.db database file
Follow these steps to build the `data/author_book_publisher.db` database file.
* Activate your Python virtualenv
* cd into the build/code directory
* python build_author_book_publisher_sqlite.py - builds the author_book_publisher.db database file
......
......@@ -5,7 +5,7 @@ author_book_publisher.csv file.
import os
import csv
from pkg_resources import resource_filename
from importlib import resources
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from project.modules.models import Base
......@@ -30,11 +30,13 @@ def populate_database(session, author_book_publisher_data):
author = (
session.query(Author)
.filter(Author.lname == row["lname"])
.filter(Author.last_name == row["last_name"])
.one_or_none()
)
if author is None:
author = Author(fname=row["fname"], lname=row["lname"])
author = Author(
first_name=row["first_name"], last_name=row["last_name"]
)
session.add(author)
book = (
......@@ -69,19 +71,19 @@ def main():
print("starting")
# get the author/book/publisher data into a dictionary structure
csv_filepath = resource_filename(
with resources.path(
"project.data", "author_book_publisher.csv"
)
author_book_publisher_data = get_author_book_publisher_data(csv_filepath)
) as csv_filepath:
data = get_author_book_publisher_data(csv_filepath)
author_book_publisher_data = data
# get the filepath to the database file
sqlite_filepath = resource_filename(
with resources.path(
"project.data", "author_book_publisher.db"
)
# does the database exist?
if os.path.exists(sqlite_filepath):
os.remove(sqlite_filepath)
) as sqlite_filepath:
# does the database exist?
if os.path.exists(sqlite_filepath):
os.remove(sqlite_filepath)
# create the database
engine = create_engine(f"sqlite:///{sqlite_filepath}")
......
import csv
import datetime
from random import randint
from pkg_resources import resource_filename
start_date = datetime.datetime.strptime("2019-01-02", "%Y-%m-%d")
students = [
"John",
"Mary",
"Susan",
"Doug",
"Andrew",
"George",
"Martha",
"Paul",
"Helen",
]
temperature_data = [
10,
12,
16,
23,
13,
12,
14,
22,
25,
28,
32,
33,
37,
36,
35,
40,
44,
45,
50,
52,
58,
60,
66,
70,
70,
72,
78,
80,
81,
82,
85,
88,
90,
87,
90,
85,
82,
81,
78,
75,
72,
72,
70,
63,
65,
62,
60,
45,
40,
37,
30,
28,
]
def offset_temp(temperature):
"""
This function modifies the temperature +/- a random
amount up to 10
:param temperature: temperature to modify
:return: modified temperature
"""
return temperature + randint(-10, 10)
def main():
# create the CSV file
csv_filepath = resource_filename("project.data", "temp_data.csv")
with open(csv_filepath, "w") as data_fh:
# create the writer
csv_writer = csv.writer(data_fh)
# write the header
header = ["name"]
for week in range(0, 52):
current_date = start_date + datetime.timedelta(days=week * 7)
header.append(current_date.strftime("%Y-%m-%d"))
csv_writer.writerow(header)
# iterate through the students and write their data
for student in students:
data = [student]
# iterate through the weeks
for week in range(0, 52):
data.append(offset_temp(temperature_data[week]))
csv_writer.writerow(data)
if __name__ == "__main__":
main()
"""
This program gathers information from the temp_data.db file about temperature
"""
import os
import csv
from pkg_resources import resource_filename
from datetime import datetime
from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String, Date, Float
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class TemperatureData(Base):
__tablename__ = "temperature_data"
temperature_data_id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
date = Column(Date, nullable=False)
value = Column(Float, nullable=False)
def get_temperature_data(filepath):
"""
This function gets the temperature data from the csv file
"""
with open(filepath) as csvfile:
csv_reader = csv.DictReader(csvfile)
data = {row["name"]: row for row in csv_reader}
for value in data.values():
value.pop("name")
return data
def populate_database(session, temperature_data):
# insert the data
for student, data in temperature_data.items():
for date, value in data.items():
temp_data = TemperatureData(
name=student,
date=datetime.strptime(date, "%Y-%m-%d").date(),
value=value,
)
session.add(temp_data)
session.commit()
session.close()
def main():
print("starting")
# get the temperature data into a dictionary structure
csv_filepath = resource_filename("project.data", "temp_data.csv")
temperature_data = get_temperature_data(csv_filepath)
# get the filepath to the database file
sqlite_filepath = resource_filename("project.data", "temp_data.db")
# does the database exist?
if os.path.exists(sqlite_filepath):
os.remove(sqlite_filepath)
# create and populate the sqlite database
engine = create_engine(f"sqlite:///{sqlite_filepath}")
Base.metadata.create_all(engine)
Session = sessionmaker()
Session.configure(bind=engine)
session = Session()
populate_database(session, temperature_data)
print("finished")
if __name__ == "__main__":
main()
fname,lname,title,publisher
Issac,Asimov,Foundation,Random House
first_name,last_name,title,publisher
Isaac,Asimov,Foundation,Random House
Pearl,Buck,The Good Earth,Random House
Pearl,Buck,The Good Earth,Simon & Schuster
Tom,Clancy,The Hunt For Red October,Berkley
......@@ -7,7 +7,7 @@ Tom,Clancy,Patriot Games,Simon & Schuster
Stephen,King,It,Random House
Stephen,King,It,Penguin Random House
Stephen,King,Dead Zone,Random House
Stephen,King,The Shinning,Penguin Random House
Stephen,King,The Shining,Penguin Random House
John,Le Carre,"Tinker, Tailor, Solider, Spy: A George Smiley Novel",Berkley
Alex,Michaelides,The Silent Patient,Simon & Schuster
Carol,Shaben,Into The Abyss,Simon & Schuster
name,2019-01-02,2019-01-09,2019-01-16,2019-01-23,2019-01-30,2019-02-06,2019-02-13,2019-02-20,2019-02-27,2019-03-06,2019-03-13,2019-03-20,2019-03-27,2019-04-03,2019-04-10,2019-04-17,2019-04-24,2019-05-01,2019-05-08,2019-05-15,2019-05-22,2019-05-29,2019-06-05,2019-06-12,2019-06-19,2019-06-26,2019-07-03,2019-07-10,2019-07-17,2019-07-24,2019-07-31,2019-08-07,2019-08-14,2019-08-21,2019-08-28,2019-09-04,2019-09-11,2019-09-18,2019-09-25,2019-10-02,2019-10-09,2019-10-16,2019-10-23,2019-10-30,2019-11-06,2019-11-13,2019-11-20,2019-11-27,2019-12-04,2019-12-11,2019-12-18,2019-12-25
John,11,9,19,28,11,10,20,24,18,32,40,33,46,42,27,32,42,54,50,47,63,57,60,67,62,64,70,88,73,78,93,89,89,96,98,79,90,85,78,79,66,67,75,63,70,63,59,45,44,29,39,19
Mary,20,21,25,32,14,11,11,20,31,22,24,33,28,32,43,50,37,51,42,48,56,67,76,67,66,74,88,86,91,86,85,88,100,84,82,78,73,76,84,82,70,73,77,71,70,58,56,53,40,41,39,31
Susan,6,10,26,21,23,3,13,28,29,33,31,39,31,28,38,48,50,46,41,51,62,62,62,69,77,77,72,82,83,72,79,78,96,87,88,91,75,88,84,73,77,68,78,59,68,66,57,55,48,40,33,36
Doug,3,11,11,26,8,4,18,27,25,21,42,24,35,28,42,38,52,39,60,62,55,59,72,74,63,65,75,82,83,73,84,92,82,93,100,90,92,73,68,71,62,65,62,64,66,68,56,46,31,35,39,19
Andrew,12,21,8,17,12,16,23,12,19,32,27,42,34,44,35,47,45,54,55,60,56,56,62,71,72,62,72,80,85,86,91,98,80,88,84,92,89,88,71,78,63,79,68,67,62,57,54,44,39,30,36,23
George,18,4,19,31,22,2,7,26,34,25,36,38,36,35,34,38,42,55,48,46,60,60,68,62,65,62,87,73,82,77,82,97,97,80,95,92,73,74,79,78,81,82,63,71,74,57,57,38,38,32,25,21
Martha,14,12,6,20,10,12,7,26,32,25,41,31,30,38,42,37,50,47,56,51,58,70,74,63,70,76,73,89,77,82,77,80,88,81,81,77,89,73,87,74,77,72,62,61,57,67,56,46,40,32,25,34
Paul,18,22,12,32,5,8,15,29,16,27,24,25,27,37,37,42,42,36,49,54,59,56,65,62,76,63,83,72,75,89,79,85,97,82,100,76,73,73,81,67,74,73,61,53,59,66,64,44,42,42,25,34
Helen,14,6,13,18,20,11,12,16,31,19,34,41,44,46,39,50,39,38,50,48,51,61,56,73,78,73,73,90,86,90,82,97,100,90,92,80,73,79,72,82,62,76,72,72,68,56,69,52,41,40,37,36
# Example 1
This example uses the raw temp_data.csv file
to get data into the program and run
various Python functions on it.
In particular getting the average temperature
for a date across all the samples, and getting the
average temperature for every week for the entire
year and returning it sorted.
\ No newline at end of file
"""
This program gathers information from the temp_data.csv file about temperature
This is the example 1 program file
This example program was kindly created by Geir Arne Hjelle,
another RealPython author, as part of the editorial process
to improve this article and the information it presents.
You can learn more about Geir from this URL:
https://realpython.com/team/gahjelle/
"""
import csv
from pkg_resources import resource_filename
from datetime import datetime
from datetime import timedelta
from typing import List, Dict
from collections import defaultdict
from importlib import resources
import pandas as pd
from treelib import Tree
def get_temperature_data(filepath: str) -> Dict:
"""
This function gets the temperature data from the csv file
"""
with open(filepath) as csvfile:
csv_reader = csv.DictReader(csvfile)
data = {row["name"]: row for row in csv_reader}
for value in data.values():
value.pop("name")
return data
def get_data(filepath):
"""Get book data from the csv file"""
return pd.read_csv(filepath)
def get_average_temp_by_date(
date_string: str, temperature_data: Dict
) -> float:
"""
This function gets the average temperature for all the samples
taken by the students by date
:param date_string: date to find average temperature for
:param connection: database connection
:return: average temp for date, or None if not found
"""
# Target date
target_date = datetime.strptime(date_string, "%Y-%m-%d").date()
# Iterate through the data and get the data
data = []
for samples in temperature_data.values():
def get_books_by_publisher(data, ascending=True):
"""Returns the books by each publisher as a pandas series
# Iterate through the samples
for sample_date, sample in samples.items():
Args:
data: The pandas dataframe to get the from
ascending: The sorting direction for the returned data.
Defaults to True.
# Generate a date range for the sample
min_date = datetime.strptime(
sample_date, "%Y-%m-%d"
).date() + timedelta(days=-3)
max_date = datetime.strptime(
sample_date, "%Y-%m-%d"
).date() + timedelta(days=3)
if min_date <= target_date <= max_date:
data.append(float(sample))
Returns:
The sorted data as a pandas series
"""
return data.groupby("publisher").size().sort_values(ascending=ascending)
# Get the average temp
return sum(data) / len(data)
def get_authors_by_publisher(data, ascending=True):
"""Returns the authors by each publisher as a pandas series
def get_average_temp_sorted(direction: str, temperature_data: Dict) -> List:
dir = direction.lower()
if dir not in ["asc", "desc"]:
raise Exception(f"Unknown direction: {direction}")
Args:
data: The pandas dataframe to get the data from
ascending: The sorting direction for the returned data.
Defaults to True.
results = defaultdict(int)
for data in temperature_data.values():
for date, value in data.items():
results[date] += float(value)
Returns:
The sorted data as a pandas series
"""
return (
data.assign(name=data.first_name.str.cat(data.last_name, sep=" "))
.groupby("publisher")
.nunique()
.loc[:, "name"]
.sort_values(ascending=ascending)
)
for date, total in results.items():
results[date] = float(total) / float(len(temperature_data.keys()))
# Convert dictionary to list
results = results.items()
def add_new_book(data, author_name, book_title, publisher_name):
"""Adds a new book to the system"""
# Sort the list in the appropriate order
return sorted(
results, key=lambda v: v[1], reverse=False if dir == "asc" else True
# Does the book exist?
first_name, _, last_name = author_name.partition(" ")
if any(
(data.first_name == first_name)
& (data.last_name == last_name)
& (data.title == book_title)
& (data.publisher == publisher_name)
):
return data
# Add the new book
return data.append(
{
"first_name": first_name,
"last_name": last_name,
"title": book_title,
"publisher": publisher_name,
},
ignore_index=True,
)
def main():
print("starting")
def output_author_hierarchy(data):
"""Output the data as a hierarchy list of authors"""
authors = data.assign(
name=data.first_name.str.cat(data.last_name, sep=" ")
)
authors_tree = Tree()
authors_tree.create_node("Authors", "authors")
for author, books in authors.groupby("name"):
authors_tree.create_node(author, author, parent="authors")
for book, publishers in books.groupby("title")["publisher"]:
book_id = f"{author}:{book}"
authors_tree.create_node(book, book_id, parent=author)
for publisher in publishers:
authors_tree.create_node(publisher, parent=book_id)
# Get the temperature data into a dictionary structure
filepath = resource_filename("project.data", "temp_data.csv")
temperature_data = get_temperature_data(filepath)
# Output the hierarchical authors data
authors_tree.show()
# Get the average temperature by date
date_string = "2019-02-10"
average_temp = get_average_temp_by_date(date_string, temperature_data)
print(f"Average temp {date_string}: {average_temp:.2f}")
def main():
"""The main entry point of the program"""
# Get the resources for the program
with resources.path(
"project.data", "author_book_publisher.csv"
) as filepath:
data = get_data(filepath)
# Get the number of books printed by each publisher
books_by_publisher = get_books_by_publisher(data, ascending=False)
for publisher, total_books in books_by_publisher.items():
print(f"Publisher: {publisher}, total books: {total_books}")
print()
# Get the average temps for the year sorted ascending or descending
average_temps = get_average_temp_sorted("asc", temperature_data)
for date, average_temp in average_temps:
print(f"Date: {date}, average temp: {average_temp:.2f}")
# Get the number of authors each publisher publishes
authors_by_publisher = get_authors_by_publisher(data, ascending=False)
for publisher, total_authors in authors_by_publisher.items():
print(f"Publisher: {publisher}, total authors: {total_authors}")
print()
print("finished")
# Output hierarchical authors data
output_author_hierarchy(data)
# Add a new book to the data structure
data = add_new_book(
data,
author_name="Stephen King",
book_title="The Stand",
publisher_name="Random House",
)
# Output the updated hierarchical authors data
output_author_hierarchy(data)
if __name__ == "__main__":
......
# Example 2
This example uses temp_data.db database
file to get data into the program and run
various functions on it that use Sqlite SQL
to access the data.
In particular getting the average temperature
for a date across all the samples, and getting the
average temperature for every week for the entire
year and returning it sorted.
\ No newline at end of file
"""
This program gathers information from the database file about temperature
This program gathers information from the author_book_publisher.db
SQLite database file
"""
from pkg_resources import resource_filename
from datetime import datetime
from datetime import timedelta
import sqlite3
from importlib import resources
from sqlalchemy import and_, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql import asc, desc, func
def get_average_temp_by_date(date_string, connection):
"""
This function gets the average temperature for all the samples
taken by the students by date
from project.modules.models import Author, Book, Publisher
from treelib import Tree
:param date_string: date to find average temperature for
:param connection: database connection
:return: average temp for date, or None if not found
"""
# Target date
target_date = datetime.strptime(date_string, "%Y-%m-%d").date()
min_date = target_date + timedelta(days=-3)
max_date = target_date + timedelta(days=3)
cursor = connection.cursor()
sql = """
SELECT
avg(value) as average
FROM temperature_data
WHERE date between ? and ?
def get_books_by_publishers(session, ascending=True):
"""Get a list of publishers and the number of books they've published
Args:
session: database session to use
ascending: direction to sort the results
Returns:
List: list of publisher sorted by number of books published
"""
result = cursor.execute(sql, (min_date, max_date)).fetchone()
return result[0] if result else None
if not isinstance(ascending, bool):
raise ValueError(f"Sorting value invalid: {ascending}")
direction = asc if ascending else desc
def get_average_temp_sorted(direction: str, connection) -> list:
return (
session.query(
Publisher.name, func.count(Book.title).label("total_books")
)
.join(Publisher.books)
.group_by(Publisher.name)
.order_by(direction("total_books"))
)
dir = direction.lower()
if dir not in ["asc", "desc"]:
raise Exception(f"Unknown direction: {direction}")
cursor = connection.cursor()
sql = f"""
SELECT
date,
AVG(value) as average_temp
FROM temperature_data
GROUP BY date
ORDER BY average_temp {dir}
"""
results = cursor.execute(sql).fetchall()
return results
def get_authors_by_publishers(session, ascending=True):
"""Get a list of publishers and the number of authors they've published
Args:
session: database session to use
ascending: direction to sort the results
def main():
print("starting")
Returns:
List: list of publisher sorted by number of authors published
"""
if not isinstance(ascending, bool):
raise ValueError(f"Sorting value invalid: {ascending}")
direction = asc if ascending else desc
return (
session.query(
Publisher.name,
func.count(Author.first_name).label("total_authors"),
)
.join(Publisher.authors)
.group_by(Publisher.name)
.order_by(direction("total_authors"))
)
def get_authors(session):
"""Get a list of author objects sorted by last name"""
return session.query(Author).order_by(Author.last_name).all()
def add_new_book(session, author_name, book_title, publisher_name):
"""Adds a new book to the system"""
# Get the author's first and last names
first_name, _, last_name = author_name.partition(" ")
# Check if the book exists
book = (
session.query(Book)
.join(Author)
.filter(Book.title == book_title)
.filter(
and_(
Author.first_name == first_name, Author.last_name == last_name
)
)
.filter(Book.publishers.any(Publisher.name == publisher_name))
.one_or_none()
)
# Does the book by the author and publisher already exist?
if book is not None:
return
# Check if the book exists for the author
book = (
session.query(Book)
.join(Author)
.filter(Book.title == book_title)
.filter(
and_(
Author.first_name == first_name, Author.last_name == last_name
)
)
.one_or_none()
)
# Create the new book if needed
if book is None:
book = Book(title=book_title)
# Get the author
author = (
session.query(Author)
.filter(
and_(
Author.first_name == first_name, Author.last_name == last_name
)
)
.one_or_none()
)
# Do we need to create the author?
if author is None:
author = Author(first_name=first_name, last_name=last_name)
session.add(author)
# Get the publisher
publisher = (
session.query(Publisher)
.filter(Publisher.name == publisher_name)
.one_or_none()
)
# Do we need to create the publisher?
if publisher is None:
publisher = Publisher(name=publisher_name)
session.add(publisher)
# Initialize the book relationships
book.author = author
book.publishers.append(publisher)
session.add(book)
# Commit to the database
session.commit()
def output_author_hierarchy(authors):
"""
Outputs the author/book/publisher information in
a hierarchical manner
:param authors: the collection of root author objects
:return: None
"""
authors_tree = Tree()
authors_tree.create_node("Authors", "authors")
for author in authors:
author_id = f"{author.first_name} {author.last_name}"
authors_tree.create_node(author_id, author_id, parent="authors")
for book in author.books:
book_id = f"{author_id}:{book.title}"
authors_tree.create_node(book.title, book_id, parent=author_id)
for publisher in book.publishers:
authors_tree.create_node(publisher.name, parent=book_id)
# Output the hierarchical authors data
authors_tree.show()
# Connect to the sqlite database
sqlite_filepath = resource_filename("project.data", "temp_data.db")
connection = sqlite3.connect(sqlite_filepath)
# Get the average temperature by date
date_string = "2019-02-10"
average_temp = get_average_temp_by_date(date_string, connection)
print(f"Average temp {date_string}: {average_temp:.2f}")
def main():
"""Main entry point of program"""
# Connect to the database using SQLAlchemy
with resources.path(
"project.data", "author_book_publisher.db"
) as sqlite_filepath:
engine = create_engine(f"sqlite:///{sqlite_filepath}")
Session = sessionmaker()
Session.configure(bind=engine)
session = Session()
# Get the number of books printed by each publisher
books_by_publisher = get_books_by_publishers(session, ascending=False)
for row in books_by_publisher:
print(f"Publisher: {row.name}, total books: {row.total_books}")
print()
# Get the average temps for the year sorted ascending or descending
average_temps = get_average_temp_sorted("asc", connection)
for date, average_temp in average_temps:
print(f"Date: {date}, average temp: {average_temp:.2f}")
# Get the number of authors each publisher publishes
authors_by_publisher = get_authors_by_publishers(session)
for row in authors_by_publisher:
print(f"Publisher: {row.name}, total authors: {row.total_authors}")
print()
print("finished")
# Output hierarchical authors data
authors = get_authors(session)
output_author_hierarchy(authors)
# Add a new book
add_new_book(
session,
author_name="Stephen King",
book_title="The Stand",
publisher_name="Random House",
)
# Output the updated hierarchical authors data
authors = get_authors(session)
output_author_hierarchy(authors)
if __name__ == "__main__":
......
# Example 3
This example uses SqlAlchemy to access the temp_data.db
database to get data into the program and run
various SqlAlchemy object methods to get the data.
In particular getting the average temperature
for a date across all the samples, and getting a list
of all the average temps for the year in sorted order.
\ No newline at end of file
from flask import Flask
from flask import render_template
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from config import Config
# Define the application
app = Flask(__name__, instance_relative_config=False)
# Configure the application
app.config.from_object(Config)
# Define the database object
db = SQLAlchemy(app)
# Initialize Bootstrap connection
Bootstrap(app)
# Register Blueprings
from .artists import routes as artist_routes # noqa: E402
from .albums import routes as album_routes # noqa: E402
from .tracks import routes as track_routes # noqa: E402
from .playlists import routes as playlist_routes # noqa: E402
from .customers import routes as customer_routes # noqa: E402
from .invoices import routes as invoice_routes # noqa: E402
from .employees import routes as employee_routes # noqa: E402
app.register_blueprint(artist_routes.artists_bp)
app.register_blueprint(album_routes.albums_bp)
app.register_blueprint(track_routes.tracks_bp)
app.register_blueprint(playlist_routes.playlists_bp)
app.register_blueprint(customer_routes.customers_bp)
app.register_blueprint(invoice_routes.invoices_bp)
app.register_blueprint(employee_routes.employees_bp)
# Sample HTTP error handling
@app.errorhandler(404)
def not_found(error):
return render_template("404.html"), 404
from app import models # noqa: F401, E402
from flask import Blueprint, render_template, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms import HiddenField
from wtforms.validators import InputRequired, ValidationError
from app import db
from app.models import Artist, Album
# Setup the Blueprint
albums_bp = Blueprint(
"albums_bp", __name__, template_folder="templates", static_folder="static"
)
def does_album_exist(form, field):
album = (
db.session.query(Album)
.join(Artist)
.filter(Artist.name == form.artist.data)
.filter(Album.title == field.data)
.one_or_none()
)
if album is not None:
raise ValidationError("Album already exists", field.data)
class CreateAlbumForm(FlaskForm):
artist = HiddenField("artist")
title = StringField(
label="Albums's Name", validators=[InputRequired(), does_album_exist]
)
@albums_bp.route("/albums", methods=["GET", "POST"])
@albums_bp.route("/albums/<int:artist_id>", methods=["GET", "POST"])
def albums(artist_id=None):
form = CreateAlbumForm()
# did we get an artist id?
if artist_id is not None:
# Get the artist
artist = (
db.session.query(Artist)
.filter(Artist.artist_id == artist_id)
.one_or_none()
)
form.artist.data = artist.name
# otherwise, no artist
else:
artist = None
# Is the form valid?
if form.validate_on_submit():
# Create new Album
album = Album(title=form.title.data)
artist.albums.append(album)
db.session.add(artist)
db.session.commit()
return redirect(url_for("albums_bp.albums", artist_id=artist_id))
# Start the query for albums
query = db.session.query(Album)
# Display the albums for the artist passed?
if artist_id is not None:
query = query.filter(Album.artist_id == artist_id)
albums = query.order_by(Album.title).all()
return render_template(
"albums.html", artist=artist, albums=albums, form=form
)
{% extends "base.html" %}
{% block content %}
<div class="container-fluid">
<div class="m-4">
{% if artist is not none %}
<div class="card" style="width: 18rem;">
<div class="card-header">Create New Album</div>
<div class="card-body">
<p class="card-text">Artist Name:&nbsp;{{ artist.name }}</p>
<form method="POST" action="{{url_for('albums_bp.albums', artist_id=artist.artist_id)}}">
{{ form.csrf_token }}
{{ render_field(form.title, placeholder=form.title.label.text) }}
<button type="submit" class="btn btn-primary">Create</button>
</form>
</div>
</div>
{% endif %}
<table class="table table-striped table-bordered table-hover table-sm">
<caption>List of Albums</caption>
<thead>
<tr>
<th>Album Name</th>
</tr>
</thead>
<tbody>
{% for album in albums %}
<tr>
<td>
<a href="{{url_for('tracks_bp.tracks', album_id=album.album_id)}}">
{{ album.title }}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
\ No newline at end of file
from flask import Blueprint
from flask import render_template
from flask import redirect
from flask import url_for
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import InputRequired
from wtforms.validators import ValidationError
from app import db
from app.models import Artist
# Setup the Blueprint
artists_bp = Blueprint(
"artists_bp", __name__, template_folder="templates", static_folder="static"
)
def does_artist_exist(form, field):
artist = (
db.session.query(Artist)
.filter(Artist.name == field.data)
.one_or_none()
)
if artist is not None:
raise ValidationError("Artist already exists", field.data)
class CreateArtistForm(FlaskForm):
name = StringField(
label="Artist's Name", validators=[InputRequired(), does_artist_exist]
)
@artists_bp.route("/")
@artists_bp.route("/artists", methods=["GET", "POST"])
def artists():
form = CreateArtistForm()
# Is the form valid?
if form.validate_on_submit():
# Create new artist
artist = Artist(name=form.name.data)
db.session.add(artist)
db.session.commit()
return redirect(url_for("artists_bp.artists"))
artists = db.session.query(Artist).order_by(Artist.name).all()
return render_template("artists.html", artists=artists, form=form)
{% extends "base.html" %}
{% block content %}
<div class="container-fluid">
<div class="m-4">
<div class="card" style="width: 18rem;">
<div class="card-header">Create New Artist</div>
<div class="card-body">
<form method="POST" action="{{url_for('artists_bp.artists')}}">
{{ form.csrf_token }}
{{ render_field(form.name, placeholder=form.name.label.text) }}
<button type="submit" class="btn btn-primary">Create</button>
</form>
</div>
</div>
<table class="table table-striped table-bordered table-hover table-sm">
<caption>List of Artists</caption>
<thead>
<tr>
<th>Artist Name</th>
</tr>
</thead>
<tbody>
{% for artist in artists %}
<tr>
<td>
<a href="{{url_for('albums_bp.albums', artist_id=artist.artist_id)}}">
{{ artist.name }}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
\ No newline at end of file
from flask import Blueprint
from flask import render_template
from sqlalchemy import func
from app import db
from app.models import Customer
from app.models import Invoice
from sqlalchemy import desc
# Setup the Blueprint
customers_bp = Blueprint(
"customers_bp",
__name__,
template_folder="templates",
static_folder="static",
)
@customers_bp.route("/customers", methods=["GET"])
@customers_bp.route("/customers/<int:customer_id>", methods=["GET"])
def customers(customer_id=None):
# Start the query for customers
query = db.session.query(
Customer, func.sum(Invoice.total).label("invoices_total")
).join(Invoice)
# Display the albums for the customer passed?
if customer_id is not None:
query = query.filter(Customer.customer_id == customer_id)
results = (
query.group_by(Customer.customer_id)
.order_by(desc(func.sum(Invoice.total)))
.all()
)
return render_template("customers.html", results=results)
from flask import Blueprint
from flask import render_template
from app import db
from app.models import Employee
# Setup the Blueprint
employees_bp = Blueprint(
"employees_bp",
__name__,
template_folder="templates",
static_folder="static",
)
@employees_bp.route("/employees", methods=["GET"])
@employees_bp.route("/employees/<int:employee_id>", methods=["GET"])
def employees(employee_id=None):
# Start the query for employees
query = db.session.query(Employee)
# Display the employee for the employee id passed?
if employee_id is not None:
query = query.filter(Employee.employee_id == employee_id)
employees = query.order_by(Employee.employee_id).all()
return render_template("employees.html", employees=employees)
from flask import Blueprint
from flask import render_template
from app import db
from app.models import Invoice
# Setup the Blueprint
invoices_bp = Blueprint(
"invoices_bp",
__name__,
template_folder="templates",
static_folder="static",
)
@invoices_bp.route("/invoices", methods=["GET"])
@invoices_bp.route("/invoices/<int:invoice_id>", methods=["GET"])
def invoices(invoice_id=None):
# Start the query for invoices
query = db.session.query(Invoice)
# Display the invoice for the invoice id passed?
if invoice_id is not None:
query = query.filter(Invoice.invoice_id == invoice_id)
invoices = query.order_by(Invoice.invoice_id).all()
return render_template("invoices.html", invoices=invoices)
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册