Skip to content

Commit

Permalink
Merge 36917c6 into 18d5ae4
Browse files Browse the repository at this point in the history
  • Loading branch information
ikalnytskyi committed Oct 1, 2019
2 parents 18d5ae4 + 36917c6 commit 7c3f893
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 56 deletions.
14 changes: 4 additions & 10 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
dist: trusty
sudo: required
language: python

matrix:
include:
- python: 3.5
env: TOXENV=py35
- python: 3.6
env: TOXENV=py36
- python: pypy3
env: TOXENV=pypy
- python: 3.6
- python: 3.7
env: TOXENV=py37
- python: 3.7
env: TOXENV=openapi
- python: 3.6
- python: 3.7
env: TOXENV=docs

services:
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ def find_packages(namespace):
install_requires=[
'aiohttp >= 3.0.0, < 4',
'cerberus >= 0.9.2',
'motor >= 1.1',
'motor >= 2.0',
'python-jose >= 1.3.2',
'python-decouple >= 3.1',
'werkzeug >= 0.11.4',
'picobox >= 2.0',
],
tests_require=[
'pytest >= 2.8.7',
'pytest >= 4.0.0',
'pytest-aiohttp >= 0.3.0',
],
entry_points={
Expand Down
8 changes: 5 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ async def testapp(request, aiohttp_client, testconf, testdatabase):
)


def pytest_namespace():
def pytest_configure():
# Expose some internally used helpers via 'pytest' module to make it
# available everywhere without making modules and packages.
return {'regex': _pytest_regex}
# available everywhere without making modules and packages. We used
# to have 'pytest_namespace' hook, however, it's been removed since
# pytest 4.0 and thus we need this line for backward compatibility.
pytest.regex = _pytest_regex


class _pytest_regex:
Expand Down
40 changes: 21 additions & 19 deletions tests/resources/test_snippets.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def __repr__(self):
async def snippets(testdatabase):
snippets = [
{
'_id': 1,
'title': 'snippet #1',
'changesets': [
{'content': 'def foo(): pass'},
Expand All @@ -61,6 +62,7 @@ async def snippets(testdatabase):
'updated_at': datetime.datetime(2018, 1, 24, 22, 26, 35),
},
{
'_id': 2,
'title': 'snippet #2',
'changesets': [
{'content': 'int do_something() {}'},
Expand All @@ -76,9 +78,9 @@ async def snippets(testdatabase):
# in-place. However, due to our custom SON manipulators it's not the
# case in our case, since one of them makes a shallow copy which
# basically results in no changes in the updated documents.
ids = await testdatabase.snippets.insert(snippets)
result = await testdatabase.snippets.insert_many(snippets)

for id_, snippet in zip(ids, snippets):
for id_, snippet in zip(result.inserted_ids, snippets):
# One of SON manipulators we use converts 'id' into '_id' during
# inserts/updates, and vice versa during reads. That's why we use
# human readable 'id' and not '_id'.
Expand Down Expand Up @@ -288,7 +290,7 @@ async def test_pagination_links(testapp, testdatabase):
now = datetime.datetime.utcnow().replace(microsecond=0)
snippets = [
{
'id': i + 1,
'_id': i + 1,
'title': 'snippet #%d' % (i + 1),
'changesets': [
{'content': '(println "Hello, World!")'},
Expand All @@ -300,7 +302,7 @@ async def test_pagination_links(testapp, testdatabase):
}
for i in range(10)
]
await testdatabase.snippets.insert(snippets)
await testdatabase.snippets.insert_many(snippets)

# We should have seen snippets with ids 10, 9 and 8. No link to the prev
# page, as we are at the very beginning of the list
Expand Down Expand Up @@ -349,7 +351,7 @@ async def test_pagination_links_one_page_larger_than_whole_list(testapp, testdat
now = datetime.datetime.utcnow().replace(microsecond=0)
snippets = [
{
'id': i + 1,
'_id': i + 1,
'title': 'snippet #%d' % (i + 1),
'changesets': [
{'content': '(println "Hello, World!")'},
Expand All @@ -361,7 +363,7 @@ async def test_pagination_links_one_page_larger_than_whole_list(testapp, testdat
}
for i in range(10)
]
await testdatabase.snippets.insert(snippets)
await testdatabase.snippets.insert_many(snippets)

# Default limit is 20 and there no prev/next pages - only the first one
resp = await _get_next_page(testapp, limit=None)
Expand Down Expand Up @@ -395,7 +397,7 @@ async def test_pagination_links_num_of_items_is_multiple_of_pages(testapp, testd
now = datetime.datetime.utcnow().replace(microsecond=0)
snippets = [
{
'id': i + 1,
'_id': i + 1,
'title': 'snippet #%d' % (i + 1),
'changesets': [
{'content': '(println "Hello, World!")'},
Expand All @@ -407,7 +409,7 @@ async def test_pagination_links_num_of_items_is_multiple_of_pages(testapp, testd
}
for i in range(12)
]
await testdatabase.snippets.insert(snippets)
await testdatabase.snippets.insert_many(snippets)

# We should have seen snippets with ids 12, 11, 10 and 9. No link to the
# prev page, as we are at the very beginning of the list
Expand Down Expand Up @@ -445,7 +447,7 @@ async def test_pagination_links_non_consecutive_ids(testapp, testdatabase):
now = datetime.datetime.utcnow().replace(microsecond=0)
snippets = [
{
'id': i,
'_id': i,
'title': 'snippet #%d' % i,
'changesets': [
{'content': '(println "Hello, World!")'},
Expand All @@ -457,7 +459,7 @@ async def test_pagination_links_non_consecutive_ids(testapp, testdatabase):
}
for i in [1, 7, 17, 23, 24, 29, 31, 87, 93, 104]
]
await testdatabase.snippets.insert(snippets)
await testdatabase.snippets.insert_many(snippets)

resp1 = await _get_next_page(testapp, limit=3)
expected_link1 = (
Expand Down Expand Up @@ -514,8 +516,8 @@ async def test_get_snippets_pagination_not_found(testapp):
'content': 'def foo(): pass',
'syntax': None,
'tags': [],
'created_at': pytest.regex('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'),
'updated_at': pytest.regex('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}')}),
'created_at': pytest.regex(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'),
'updated_at': pytest.regex(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}')}),
({'title': 'snippet #1',
'content': 'def foo(): pass',
Expand All @@ -526,8 +528,8 @@ async def test_get_snippets_pagination_not_found(testapp):
'content': 'def foo(): pass',
'syntax': 'python',
'tags': ['tag_a', 'tag_b'],
'created_at': pytest.regex('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'),
'updated_at': pytest.regex('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}')}),
'created_at': pytest.regex(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'),
'updated_at': pytest.regex(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}')}),
])
async def test_post_snippet(testapp, testconf, snippet, rv):
testconf['SNIPPET_SYNTAXES'] = ['python', 'clojure']
Expand Down Expand Up @@ -654,8 +656,8 @@ async def test_delete_snippet_bad_request(testapp):
'content': 'def foo(): pass',
'syntax': None,
'tags': [],
'created_at': pytest.regex('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'),
'updated_at': pytest.regex('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}')}),
'created_at': pytest.regex(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'),
'updated_at': pytest.regex(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}')}),
({'title': 'snippet #1',
'content': 'def foo(): pass',
Expand All @@ -666,8 +668,8 @@ async def test_delete_snippet_bad_request(testapp):
'content': 'def foo(): pass',
'syntax': 'python',
'tags': ['tag_a', 'tag_b'],
'created_at': pytest.regex('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'),
'updated_at': pytest.regex('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}')}),
'created_at': pytest.regex(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'),
'updated_at': pytest.regex(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}')}),
])
async def test_put_snippet(testapp, snippets, snippet, rv):
resp = await testapp.put('/v1/snippets/1', data=json.dumps(snippet))
Expand Down Expand Up @@ -750,7 +752,7 @@ async def test_patch_snippet(testapp, snippets):
'syntax': 'python',
'tags': ['tag_a', 'tag_b'],
'created_at': '2018-01-24T22:26:35',
'updated_at': pytest.regex('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'),
'updated_at': pytest.regex(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'),
}


Expand Down
14 changes: 1 addition & 13 deletions xsnippet/api/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,7 @@ def create_connection(conf):
mongo = AsyncIOMotorClient(conf['DATABASE_CONNECTION_URI'])

# get_default_database returns a database from the connection string
db = mongo.get_default_database()

# ID incrementer is used to auto increment record ID if nothing
# is explicitly passed.
#
# ID processor is used for auto converting domain IDs to and from
# database ones (i.e. 'id' -> '_id' and vice versa).
#
# SON manipulators are applied in reversed order.
db.add_son_manipulator(_IdIncrementer())
db.add_son_manipulator(_IdProcessor())

return db
return mongo.get_database()


async def setup(app, db):
Expand Down
4 changes: 2 additions & 2 deletions xsnippet/api/resources/snippets.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async def _write(resource, service_fn, *, status, conf):
'content': {'type': 'string', 'required': True, 'empty': False},
'syntax': {'type': 'string'},
'tags': {'type': 'list',
'schema': {'type': 'string', 'regex': '[\w_-]+'}},
'schema': {'type': 'string', 'regex': r'[\w_-]+'}},
'created_at': {'type': 'datetime', 'readonly': True},
'updated_at': {'type': 'datetime', 'readonly': True},
})
Expand Down Expand Up @@ -183,7 +183,7 @@ async def get(self, conf):
},
'tag': {
'type': 'string',
'regex': '[\w_-]+',
'regex': r'[\w_-]+',
},
'syntax': {
'type': 'string',
Expand Down
23 changes: 16 additions & 7 deletions xsnippet/api/services/snippet.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,15 @@ async def create(self, snippet):
snippet['created_at'] = now
snippet['updated_at'] = now

snippet_id = await self.db.snippets.insert(snippet)
snippet['id'] = snippet_id
resolved = await self.db['_autoincrement_ids'].find_one_and_update(
{'_id': 'snippets'},
update={'$inc': {'next': 1}},
upsert=True,
new=True)
snippet['_id'] = resolved['next']

await self.db.snippets.insert_one(snippet)
snippet['id'] = snippet.pop('_id')
snippet['content'] = snippet.pop('changesets', [])[0]['content']

return snippet
Expand All @@ -81,12 +88,12 @@ async def update(self, snippet):
}
}

result = await self.db.snippets.update(
result = await self.db.snippets.update_one(
{'_id': snippet['id']},
parameters,
)

if not result['n']:
if result.matched_count == 0:
raise exceptions.SnippetNotFound(
'Sorry, cannot find the requested snippet.')

Expand Down Expand Up @@ -118,7 +125,7 @@ async def get(self, *, title=None, tag=None, syntax=None, limit=100,

condition['$and'] = [
{'created_at': {filters['created_at']: specimen['created_at']}},
{'_id': {filters['_id']: specimen['id']}},
{'_id': {filters['_id']: specimen['_id']}},
]

# use a compound sorting key (created_at, _id) to avoid the ambiguity
Expand All @@ -134,6 +141,7 @@ async def get(self, *, title=None, tag=None, syntax=None, limit=100,

snippets = await query.limit(limit).to_list(None)
for snippet in snippets:
snippet['id'] = snippet.pop('_id')
snippet['content'] = snippet.pop('changesets', [])[-1]['content']
return snippets

Expand All @@ -144,12 +152,13 @@ async def get_one(self, id):
raise exceptions.SnippetNotFound(
'Sorry, cannot find the requested snippet.')

snippet['id'] = snippet.pop('_id')
snippet['content'] = snippet.pop('changesets', [])[-1]['content']
return snippet

async def delete(self, id):
result = await self.db.snippets.remove({'_id': id})
if not result['n']:
result = await self.db.snippets.delete_one({'_id': id})
if result.deleted_count == 0:
raise exceptions.SnippetNotFound(
'Sorry, cannot find the requested snippet.')

Expand Down

0 comments on commit 7c3f893

Please sign in to comment.