Compare commits
No commits in common. "master" and "prod" have entirely different histories.
@ -6,21 +6,6 @@ services:
|
|||||||
image: mathwave/sprint-repo:locks
|
image: mathwave/sprint-repo:locks
|
||||||
networks:
|
networks:
|
||||||
- locks-development
|
- locks-development
|
||||||
command: api
|
|
||||||
environment:
|
|
||||||
MONGO_HOST: "mongo.develop.sprinthub.ru"
|
|
||||||
MONGO_PASSWORD: $MONGO_PASSWORD_DEV
|
|
||||||
deploy:
|
|
||||||
mode: replicated
|
|
||||||
restart_policy:
|
|
||||||
condition: any
|
|
||||||
update_config:
|
|
||||||
parallelism: 1
|
|
||||||
order: start-first
|
|
||||||
|
|
||||||
cleaner:
|
|
||||||
image: mathwave/sprint-repo:locks
|
|
||||||
command: cleaner
|
|
||||||
environment:
|
environment:
|
||||||
MONGO_HOST: "mongo.develop.sprinthub.ru"
|
MONGO_HOST: "mongo.develop.sprinthub.ru"
|
||||||
MONGO_PASSWORD: $MONGO_PASSWORD_DEV
|
MONGO_PASSWORD: $MONGO_PASSWORD_DEV
|
||||||
|
@ -6,21 +6,6 @@ services:
|
|||||||
image: mathwave/sprint-repo:locks
|
image: mathwave/sprint-repo:locks
|
||||||
networks:
|
networks:
|
||||||
- locks
|
- locks
|
||||||
command: api
|
|
||||||
environment:
|
|
||||||
MONGO_HOST: "mongo.sprinthub.ru"
|
|
||||||
MONGO_PASSWORD: $MONGO_PASSWORD_PROD
|
|
||||||
deploy:
|
|
||||||
mode: replicated
|
|
||||||
restart_policy:
|
|
||||||
condition: any
|
|
||||||
update_config:
|
|
||||||
parallelism: 1
|
|
||||||
order: start-first
|
|
||||||
|
|
||||||
cleaner:
|
|
||||||
image: mathwave/sprint-repo:locks
|
|
||||||
command: cleaner
|
|
||||||
environment:
|
environment:
|
||||||
MONGO_HOST: "mongo.sprinthub.ru"
|
MONGO_HOST: "mongo.sprinthub.ru"
|
||||||
MONGO_PASSWORD: $MONGO_PASSWORD_PROD
|
MONGO_PASSWORD: $MONGO_PASSWORD_PROD
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import fastapi
|
|
||||||
import uvicorn
|
|
||||||
|
|
||||||
from app.routers import acquire
|
|
||||||
from app.routers import release
|
|
||||||
|
|
||||||
from app.storage import mongo
|
|
||||||
from app.daemons import base
|
|
||||||
|
|
||||||
|
|
||||||
class Daemon(base.Daemon):
|
|
||||||
def execute(self):
|
|
||||||
app = fastapi.FastAPI()
|
|
||||||
|
|
||||||
app.include_router(acquire.router)
|
|
||||||
app.include_router(release.router)
|
|
||||||
|
|
||||||
mongo.create_indexes()
|
|
||||||
|
|
||||||
uvicorn.run(app, host="0.0.0.0", port=80)
|
|
@ -1,3 +0,0 @@
|
|||||||
class Daemon:
|
|
||||||
def execute(self):
|
|
||||||
raise NotImplementedError
|
|
@ -1,11 +0,0 @@
|
|||||||
from app.daemons import base
|
|
||||||
from app.storage.mongo import locks
|
|
||||||
import time
|
|
||||||
from app.utils import time as time_utils
|
|
||||||
|
|
||||||
|
|
||||||
class Daemon(base.Daemon):
|
|
||||||
def execute(self):
|
|
||||||
while True:
|
|
||||||
locks.collection.delete_many({'locked_until': {'$lt': time_utils.now()}})
|
|
||||||
time.sleep(5)
|
|
@ -1,7 +1,9 @@
|
|||||||
|
import datetime
|
||||||
import fastapi
|
import fastapi
|
||||||
import pydantic
|
import pydantic
|
||||||
|
|
||||||
from app.storage.mongo import locks
|
from app.storage.mongo import locks
|
||||||
|
from app.utils import time
|
||||||
|
|
||||||
|
|
||||||
class RequestBody(pydantic.BaseModel):
|
class RequestBody(pydantic.BaseModel):
|
||||||
@ -15,6 +17,7 @@ router = fastapi.APIRouter()
|
|||||||
@router.post('/api/v1/acquire', status_code=fastapi.status.HTTP_202_ACCEPTED, responses={'409': {'description': 'Conflict'}})
|
@router.post('/api/v1/acquire', status_code=fastapi.status.HTTP_202_ACCEPTED, responses={'409': {'description': 'Conflict'}})
|
||||||
async def execute(body: RequestBody):
|
async def execute(body: RequestBody):
|
||||||
try:
|
try:
|
||||||
await locks.acquire(body.name, ttl=body.ttl)
|
await locks.acquire(locks.Lock(name=body.name, locked_until=time.now() + datetime.timedelta(seconds=body.ttl)))
|
||||||
except locks.ConflictException:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
raise fastapi.HTTPException(409)
|
raise fastapi.HTTPException(409)
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
|
import datetime
|
||||||
import fastapi
|
import fastapi
|
||||||
import pydantic
|
import pydantic
|
||||||
|
|
||||||
from app.storage.mongo import locks
|
from app.storage.mongo import locks
|
||||||
|
from app.utils import time
|
||||||
|
|
||||||
|
|
||||||
class RequestBody(pydantic.BaseModel):
|
class RequestBody(pydantic.BaseModel):
|
||||||
name: str
|
name: str
|
||||||
|
ttl: int
|
||||||
|
|
||||||
|
|
||||||
router = fastapi.APIRouter()
|
router = fastapi.APIRouter()
|
||||||
@ -13,4 +16,4 @@ router = fastapi.APIRouter()
|
|||||||
|
|
||||||
@router.post('/api/v1/release', status_code=fastapi.status.HTTP_202_ACCEPTED)
|
@router.post('/api/v1/release', status_code=fastapi.status.HTTP_202_ACCEPTED)
|
||||||
async def execute(body: RequestBody):
|
async def execute(body: RequestBody):
|
||||||
await locks.release(body.name)
|
await locks.release(locks.Lock(name=body.name, locked_until=time.now() + datetime.timedelta(seconds=body.ttl)))
|
||||||
|
@ -1,25 +1,22 @@
|
|||||||
|
import bson
|
||||||
import datetime
|
import datetime
|
||||||
|
import pydantic
|
||||||
|
|
||||||
from app.storage.mongo import database
|
from app.storage.mongo import database
|
||||||
from app.utils import time
|
from app.utils import time
|
||||||
from bson import codec_options
|
from bson import codec_options
|
||||||
|
|
||||||
from pymongo import errors
|
|
||||||
|
|
||||||
|
|
||||||
class ConflictException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
collection = database.get_collection("locks", codec_options=codec_options.CodecOptions(tz_aware=True))
|
collection = database.get_collection("locks", codec_options=codec_options.CodecOptions(tz_aware=True))
|
||||||
|
|
||||||
|
|
||||||
async def acquire(name: str, ttl: int):
|
class Lock(pydantic.BaseModel):
|
||||||
locked_until = time.now() + datetime.timedelta(seconds=ttl)
|
name: str
|
||||||
try:
|
locked_until: pydantic.AwareDatetime
|
||||||
await collection.insert_one({'name': name, 'locked_until': locked_until})
|
|
||||||
except errors.DuplicateKeyError:
|
|
||||||
raise ConflictException
|
async def acquire(lock: Lock):
|
||||||
|
await collection.insert_one(lock.model_dump())
|
||||||
|
|
||||||
|
|
||||||
async def release(name: str):
|
async def release(name: str):
|
||||||
|
25
main.py
25
main.py
@ -1,12 +1,19 @@
|
|||||||
import sys
|
import fastapi
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
from app.routers import acquire
|
||||||
|
from app.routers import release
|
||||||
|
|
||||||
|
from app.storage import mongo
|
||||||
|
|
||||||
|
|
||||||
arg = sys.argv[-1]
|
app = fastapi.FastAPI()
|
||||||
if arg == 'api':
|
|
||||||
from app.daemons.api import Daemon
|
|
||||||
elif arg == 'cleaner':
|
|
||||||
from app.daemons.cleaner import Daemon
|
|
||||||
else:
|
|
||||||
raise NotImplementedError('Daemon is not implemented')
|
|
||||||
|
|
||||||
Daemon().execute()
|
app.include_router(acquire.router)
|
||||||
|
app.include_router(release.router)
|
||||||
|
|
||||||
|
mongo.create_indexes()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=80)
|
||||||
|
Loading…
Reference in New Issue
Block a user