Skip to content
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

2.0 #59

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open

2.0 #59

wants to merge 39 commits into from

Conversation

nett00n
Copy link
Collaborator

@nett00n nett00n commented Jul 24, 2024

WiP

nett00n added 3 commits May 29, 2024 23:30
- logging
- configuration
- hc
- telegram echo
src/main.py Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this module called "src"?

It seems more obvious to use project name and leave root directory for various tech stuff:

docs/
examples/

url-fairy-bot/
  tests/
  routes/
    handler.py
    health.py
  Dockerfile
  main.py
  config.py

README.md

src/main.py Outdated
logger = logging.getLogger(__name__)

# Initialize FastAPI
REST_API = FastAPI()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UPPERCASE indicates constant in Python, which is quite confusing here 🤔

Suggested change
REST_API = FastAPI()
api = FastAPI()

src/__init.__py Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file would probably never be included somewhere (that's not a library project), so __init__.py is not required

Also, there's a typo in file extension

Comment on lines 3 to 9
domain_replacements = {
"https://twitter.com/": "https://www.fxtwitter.com/",
"https://www.instagram.com/p/": "https://www.ddinstagram.com/p/",
"https://www.instagram.com/reel/": "https://www.ddinstagram.com/reel/",
"https://www.twitter.com/": "https://www.fxtwitter.com/",
"https://x.com/": "https://www.fxtwitter.com/",
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we use number of aliases to later replace them with a single value

Another way to do so is composing a dict of mask-replacement pairs:

Suggested change
domain_replacements = {
"https://twitter.com/": "https://www.fxtwitter.com/",
"https://www.instagram.com/p/": "https://www.ddinstagram.com/p/",
"https://www.instagram.com/reel/": "https://www.ddinstagram.com/reel/",
"https://www.twitter.com/": "https://www.fxtwitter.com/",
"https://x.com/": "https://www.fxtwitter.com/",
}
url_replacements_dict = {
r"https://(www\.)?(twitter|x)\.com/": "https://www.fxtwitter.com/",
r"https://(www\.)?instagram\.com/(p|reel)/": "https://www.ddinstagram.com/\2/",
}
def replace_url():
for mask, replacement in url_replacements_dict.items():
if re.compile(mask).match(url):
url = re.sub(mask, replacement, url)
return url
return url

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd propose to place these routes in routes/ dir

healthcheck_result = perform_healthcheck()
await message.reply(healthcheck_result)
return
if (message.text.strip() == "/start") or (message.text.strip() == "/help"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, process with explicit handler and filter for specific command

license = "GPL v3"
readme = "README.md"

[tool.poetry.dependencies]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No explicit aiogram package installation?

logger.debug("✨ Message received: %s", message.text)

error_messages = []
if message.chat.type == types.ChatType.PRIVATE:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


async def handle_url(url, message):
logger.debug("✨ Handling url: %s from %s", url, message)
time.sleep(5)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's purpose of sleeping here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's debugging line to find out does it parse links in one message at the same time or waiting before parsing the second one. (it waits)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you need to send tasks to another routine explicitly:

(Also see another comment about threads and healthcheck)

src/main.py Outdated
fastapi_thread = Thread(target=start_fastapi)
fastapi_thread.start()

# Keep the main thread running
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, why 2 threads?

As I see, we use aiogram in polling mode here

What's the point of using fastapi then?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you show the example how to make a healthcheck and the main code work in parallel?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we produce heavy tasks, we can send those to another thread using asyncio:

https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task

graph LR
  telegram((telegram))
  
  subgraph bot
    poll_loop
    handler
    heavy_task
  end

  poll_loop -->|getUpdate| telegram
  poll_loop -->|handleUpdate| handler
  handler -.->|asyncio.create_task| heavy_task
Loading

This creates separate routine to perform the task not blocking the main loop.


If we need e.g. healthchecks and metrics to be accessible from outside, then it may be good idea to compose bot in webhook mode:

https://docs.aiogram.dev/en/latest/dispatcher/webhook.html#examples

graph LR
  telegram((telegram))
  orchestrator((orchestrator))
  monitoring((monitoring))
  
  subgraph bot
    subgraph loop["main loop"]
      handler["/handler"]
      healthz["/healthz"]
      metrics["/metrics"]
    end
    heavy_task
  end

  telegram -->|sendUpdate| handler
  handler -.->|asyncio.create_task| heavy_task

  orchestrator -.-> healthz
  monitoring -.-> metrics
Loading

Adding FastAPI to process just /health requests seems kinda costly way to do the job

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants