-
Notifications
You must be signed in to change notification settings - Fork 33
major update: Python 3, NDB datastore, Flask, pytest #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
dcd2a92
3b903ba
6f5531f
5ce9c01
1b9a66c
d979f0b
b5f59fa
8488bde
6b23405
2e3e4c8
6876ef6
94ce9ac
e6da913
b89c866
c06a7e7
fa6ff22
a8f7a58
4971076
7ba298b
f7290ee
8a9ce91
16307e7
a04c1c3
dc287cc
60d9965
d51f397
b92c18c
08ab6e4
831ba0a
5067dd9
7fe8423
375ca43
e7ccdda
8b0220a
28f2ae4
09f78d1
6935f7b
0bb2d01
8292e44
7f460fb
b9670ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| use nix; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| .git | ||
| .DS_Store | ||
| *.pyc | ||
| *.nix | ||
| .envrc | ||
| #!include:.gitignore |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,3 @@ | ||
| *.pyc | ||
| .direnv | ||
| __pycache__ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,19 @@ | ||
| .PHONY: serve test_deps test check appcfg-update deploy | ||
|
|
||
| serve: | ||
| dev_appserver.py --log_level=debug . --host=0.0.0.0 | ||
| honcho start | ||
|
|
||
| test_deps: | ||
| pip install -r requirements-dev.txt | ||
|
|
||
| test check: | ||
| python -m unittest discover -p '*_test.py' | ||
| test: | ||
| pytest | ||
|
|
||
| typecheck: | ||
| pyright | ||
|
|
||
| appcfg-update deploy: | ||
| gcloud app deploy --project "${APP}" | ||
| gcloud app deploy | ||
|
|
||
| create-indexes: | ||
| gcloud datastore indexes create index.yaml |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| datastore: gcloud beta emulators datastore start | ||
| web: $(gcloud beta emulators datastore env-init); python main.py |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| """conftest - loaded automatically by the pytest runner""" | ||
|
|
||
| from unittest.mock import MagicMock | ||
|
|
||
| from google.cloud import ndb | ||
| from google.cloud.ndb import _datastore_api | ||
| from InMemoryCloudDatastoreStub.datastore_stub import LocalDatastoreStub | ||
| import pytest | ||
| from _pytest.monkeypatch import MonkeyPatch | ||
|
|
||
|
|
||
| @pytest.fixture(autouse=True) | ||
| def ndb_stub(monkeypatch: MonkeyPatch) -> LocalDatastoreStub: | ||
| stub = LocalDatastoreStub() | ||
| monkeypatch.setattr(_datastore_api, "stub", MagicMock(return_value=stub)) | ||
| return stub | ||
|
|
||
|
|
||
| @pytest.fixture(autouse=True) | ||
| def ndb_context(init_ndb_env_vars): | ||
| client = ndb.Client() | ||
| with client.context() as context: | ||
| yield context | ||
|
|
||
|
|
||
| @pytest.fixture(autouse=True) | ||
| def init_ndb_env_vars(monkeypatch: MonkeyPatch) -> None: | ||
| """Set environment variables for the test ndb client. | ||
|
|
||
| Initializing an ndb Client in a test env requires some environment variables | ||
benley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| to be set. For now, these are just garbage values intended to give the | ||
| library _something_ (we don't expect them to actually work yet) | ||
| """ | ||
| monkeypatch.setenv("DATASTORE_EMULATOR_HOST", "localhost") | ||
| monkeypatch.setenv("DATASTORE_DATASET", "datastore-stub-test") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| #!/usr/bin/env python3 | ||
|
|
||
| import snippets | ||
|
|
||
| import google.appengine.api | ||
| import google.cloud.ndb | ||
|
|
||
|
|
||
| class NDBMiddleware: | ||
| """WSGI middleware to wrap the app in Google Cloud NDB context""" | ||
| def __init__(self, app): | ||
| self.app = app | ||
| self.client = google.cloud.ndb.Client() | ||
|
|
||
| def __call__(self, environ, start_response): | ||
| with self.client.context(): | ||
| return self.app(environ, start_response) | ||
|
|
||
| app = snippets.app | ||
| app.wsgi_app = google.appengine.api.wrap_wsgi_app(app.wsgi_app) | ||
| app.wsgi_app = NDBMiddleware(app.wsgi_app) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| # This is used when running locally only. When deploying to Google App | ||
| # Engine, a webserver process such as Gunicorn will serve the app. You | ||
| # can configure startup instructions by adding `entrypoint` to app.yaml. | ||
benley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # | ||
| # To control listening IP and port, set SERVER_NAME in the environment. | ||
| # e.g. SERVER_NAME=127.0.0.1:8080 | ||
| # Default is to listen on 127.0.0.1:5000 | ||
| app.run(debug=True) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |
| import hashlib | ||
| import os | ||
|
|
||
| from google.appengine.ext import db | ||
| from google.cloud import ndb | ||
| from google.appengine.api import users | ||
|
|
||
|
|
||
|
|
@@ -21,54 +21,55 @@ | |
| # support that later. | ||
|
|
||
|
|
||
| class User(db.Model): | ||
| class User(ndb.Model): | ||
| """User preferences.""" | ||
| created = db.DateTimeProperty() | ||
| last_modified = db.DateTimeProperty(auto_now=True) | ||
| email = db.StringProperty(required=True) # The key to this record | ||
| is_hidden = db.BooleanProperty(default=False) # hide 'empty' snippets | ||
| category = db.StringProperty(default=NULL_CATEGORY) # groups snippets | ||
| uses_markdown = db.BooleanProperty(default=True) # interpret snippet text | ||
| private_snippets = db.BooleanProperty(default=False) # private by default? | ||
| wants_email = db.BooleanProperty(default=True) # get nag emails? | ||
| created = ndb.DateTimeProperty(auto_now_add=True) | ||
| last_modified = ndb.DateTimeProperty(auto_now=True) | ||
| email = ndb.StringProperty(required=True) # The key to this record | ||
| is_hidden = ndb.BooleanProperty(default=False) # hide 'empty' snippets | ||
| category = ndb.StringProperty(default=NULL_CATEGORY) # groups snippets | ||
| uses_markdown = ndb.BooleanProperty(default=True) # interpret snippet text | ||
| private_snippets = ndb.BooleanProperty(default=False) # private by default? | ||
| wants_email = ndb.BooleanProperty(default=True) # get nag emails? | ||
| # TODO(csilvers): make a ListProperty instead. | ||
| wants_to_view = db.TextProperty(default='all') # comma-separated list | ||
| display_name = db.TextProperty(default='') # display name of the user | ||
| wants_to_view = ndb.TextProperty(default='all') # comma-separated list | ||
| display_name = ndb.TextProperty(default='') # Display name of the user | ||
| slack_id = ndb.StringProperty(default='') # Slack member ID (not nickname!) | ||
|
|
||
|
|
||
| class Snippet(db.Model): | ||
| class Snippet(ndb.Model): | ||
| """Every snippet is identified by the monday of the week it goes with.""" | ||
| created = db.DateTimeProperty() | ||
| last_modified = db.DateTimeProperty(auto_now=True) | ||
| display_name = db.StringProperty() # display name of the user | ||
| email = db.StringProperty(required=True) # week+email: key to this record | ||
| week = db.DateProperty(required=True) # the monday of the week | ||
| text = db.TextProperty() | ||
| private = db.BooleanProperty(default=False) # snippet is private? | ||
| is_markdown = db.BooleanProperty(default=False) # text is markdown? | ||
| created = ndb.DateTimeProperty(auto_now_add=True) | ||
| last_modified = ndb.DateTimeProperty(auto_now=True) | ||
| display_name = ndb.StringProperty() # display name of the user | ||
| email = ndb.StringProperty(required=True) # week+email: key to this record | ||
| week = ndb.DateProperty(required=True) # the monday of the week | ||
| text = ndb.TextProperty() | ||
| private = ndb.BooleanProperty(default=False) # snippet is private? | ||
| is_markdown = ndb.BooleanProperty(default=False) # text is markdown? | ||
|
|
||
| @property | ||
| def email_md5_hash(self): | ||
| m = hashlib.md5() | ||
| m.update(self.email) | ||
| m.update(self.email.encode('utf-8')) | ||
| return m.hexdigest() | ||
|
|
||
|
|
||
| class AppSettings(db.Model): | ||
| class AppSettings(ndb.Model): | ||
| """Application-wide preferences.""" | ||
| created = db.DateTimeProperty() | ||
| last_modified = db.DateTimeProperty(auto_now=True) | ||
| created = ndb.DateTimeProperty(auto_now_add=True) | ||
| last_modified = ndb.DateTimeProperty(auto_now=True) | ||
| # Application settings | ||
| domains = db.StringListProperty(required=True) | ||
| hostname = db.StringProperty(required=True) # used for emails | ||
| default_private = db.BooleanProperty(default=False) # new-user default | ||
| default_markdown = db.BooleanProperty(default=True) # new-user default | ||
| default_email = db.BooleanProperty(default=True) # new-user default | ||
| domains = ndb.StringProperty(repeated=True) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you also want required=True here still.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This still needs to be done.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to figure out why I changed this in the first place; iirc there was some kind of bootstrapping issue.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ohhhhh, it's because fields cannot have both
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha. I think what you have now is good then. The important thing to me is backward-compat. As long as db.StringListProperty is wire-compatible with ndb.StringProperty(repeated=True) -- which I think it is -- then this is a good change by me! |
||
| hostname = ndb.StringProperty(required=True) # used for emails | ||
| default_private = ndb.BooleanProperty(default=False) # new-user default | ||
| default_markdown = ndb.BooleanProperty(default=True) # new-user default | ||
| default_email = ndb.BooleanProperty(default=True) # new-user default | ||
| # Chat and email settings | ||
| email_from = db.StringProperty(default='') | ||
| slack_channel = db.StringProperty(default='') | ||
| slack_token = db.StringProperty(default='') | ||
| slack_slash_token = db.StringProperty(default='') | ||
| email_from = ndb.StringProperty(default='') | ||
| slack_channel = ndb.StringProperty(default='') | ||
| slack_token = ndb.StringProperty(default='') | ||
| slack_slash_token = ndb.StringProperty(default='') | ||
|
|
||
| @staticmethod | ||
| def get(create_if_missing=False, domains=None): | ||
|
|
@@ -79,12 +80,12 @@ def get(create_if_missing=False, domains=None): | |
| are initialized with the given value for 'domains'. The new | ||
| entity is *not* put to the datastore. | ||
| """ | ||
| retval = AppSettings.get_by_key_name('global_settings') | ||
| retval = AppSettings.get_by_id('global_settings') | ||
| if retval: | ||
| return retval | ||
| elif create_if_missing: | ||
| # We default to sending email, and having it look like it's | ||
| # comint from the current user. We add a '+snippets' in there | ||
| # coming from the current user. We add a '+snippets' in there | ||
benley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # to allow for filtering | ||
| email_address = users.get_current_user().email() | ||
| email_address = email_address.replace('@', '+snippets@') | ||
|
|
@@ -93,7 +94,7 @@ def get(create_if_missing=False, domains=None): | |
| # you accessed the site on here. | ||
| hostname = '%s://%s' % (os.environ.get('wsgi.url_scheme', 'http'), | ||
| os.environ['HTTP_HOST']) | ||
| return AppSettings(key_name='global_settings', | ||
| return AppSettings(id='global_settings', | ||
| created=datetime.datetime.now(), | ||
| domains=domains, | ||
| hostname=hostname, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "nixpkgs": { | ||
| "branch": "nixos-22.11", | ||
| "description": "Nix Packages collection", | ||
| "homepage": null, | ||
| "owner": "NixOS", | ||
| "repo": "nixpkgs", | ||
| "rev": "eef86b8a942913a828b9ef13722835f359deef29", | ||
| "sha256": "1ig0mc7f2n1pxzd5y9m4vz38zp515vn5s576kwhkhj62zdw99p2v", | ||
| "type": "tarball", | ||
| "url": "https://github.com/NixOS/nixpkgs/archive/eef86b8a942913a828b9ef13722835f359deef29.tar.gz", | ||
| "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz" | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.