Skip to content

Commit

Permalink
vault backup: 2025-01-01 23:09:58
Browse files Browse the repository at this point in the history
  • Loading branch information
abhiaagarwal committed Jan 2, 2025
1 parent b714b9d commit 9391383
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 0 deletions.
69 changes: 69 additions & 0 deletions content/finance/finance-for-new-grads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
title: '"I just graduated! What the hell am I supposed to do with all this money?"'
tags:
- observations
---
> [!warning] Disclaimer
> Do I need to put a disclaimer on my own notes? I'm not a financial advisor, I don't have a license, this is not advice, I'm not liable for any decisions you don't make. Don't sue me.
# Introduction

I want to preface this by saying

**YOU ARE NOT A DUMMY!!!!!**

Finance is not something that's taught in schools.^[1] Money doesn't matter until it does matter. I certainly blindly earned paychecks from odd jobs until I got my first big-boy job after college, until everything hit me.

*What the heck is a ROTH IRA?*
*Girl, what the hell is a Traditional IRA*
*401k?? Employee match. Pre-tax deduction???*
*Why should I care about Retirement? I'm 22 years old?*
*Federal Taxes? State Taxes? WTF is a 'FICA'???*
*Will the government jail me if I underpay my taxes?*

You are not alone if you have these questions. There are few trustworthy sources for financial information, with most companies trying to sell some product. You really shouldn't trust me either — instead, I'll do my best to link the sources that further elaborate on my thinking.

I wrote this primarily because I became "the guy" explaining finance to all my friends, and quite frankly, I got tired of copy-pasting the spiel I wrote in my notes app. As such, this is written from the perspective of someone making the median income after graduating college in the United States, which I've guesstimated to be around 50k, give or take. If you're not a college graduate, or you're making more or less, don't sweat; your income is not a reflection of your self-worth, and this advice can be generalized to anyone. Even if you've been working for a long time, you might learn something new from this.

Let's dive in!

>[!tip]
>If you're interested in reading a book, I'd recommend checking out *A Simple Path to Wealth* by JL Collins. While I personally haven't read it, I've had plenty of friends who have and strongly recommend it. I'd recommend it over this document. At the very least, consider checking it out from your local library after you've read this.
# Give me the rundown!

Okay, so you have a big boy job now. You've just earned your first paycheck. Let's break down what your paycheck actually *is*.

Here's an example paycheck for someone making a 50k salary, just base, no bonus/equity/whatever. This is modeled after my own paycheck in Massachusetts.

| Line Item | Amount | Explanation |
| -------------------- | ------ | ------------------------------------------------- |
| Gross Income | +1923 | Your salary divided by the amount of pay periods |
| Federal Income Taxes | -148 | Progressive, depends on your income |
| State Income Taxes | -80 | Often a flat tax |
| Local Income taxes | 0 | Depends really |
| Social Security | -114 | Pays into Social Security |
| Medicare | -27 | Pays for Medicare |
| Healthcare | -85 | Healthcare you chose, deducted from your paycheck |
| => Net Income | 1470 | *your* money |
Your **gross** income is the money you make *before* taxes and various deductions, while your **net** income is the money you you get afterwards. Net goes into your bank account.


# Let's go deep.

## Banking

## Taxes



## Healthcare

## Retirement

## Investing, in general

## Credit Cards



[^1]: And I don't want to hear the "oh, it should've been taught in school", face it, you and I would've both slept through it.
126 changes: 126 additions & 0 deletions content/programming/languages/python/fastapi/globals-in-fastapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
title: Global dependencies in FastAPI, done correctly
description: Globals are the root of all evil, and Python is more than happy to let you indulge. FastAPI provides an alternative mechanism, not very well-documented
tags:
- observations
---
As much as I love python, it also makes you fight *hard* to avoid doing the wrong things. The wrong thing in this case being global state.

FastAPI implicitly encourages the use of globals through its [Dependency](https://fastapi.tiangolo.com/tutorial/dependencies/) system. You define a global, throw it in a getter function defined as a dependency, you declare them in your handlers, and FastAPI will solve the tree for you, ensuring you don't get race conditions. As much as I appreciate the power and the ergonomics, I really don't like this. There's no way to validate the correct behavior until runtime. It also makes it hard to test, usually requiring to manually requiring the dependency at runtime.
# The anti-pattern

Imagine you have a global dependency, say, a database engine. Instead of defining it as a global, let's define it as a function:

```python
# this is psuedocode, but based off async sqlalchemy off the top of my head
async def get_engine() -> AsyncGenerator[AsyncEngine]:
engine = create_async_engine(...)
try:
yield engine
finally:
await engine.dispose()

async def get_session(session: Annotated[AsyncEngine, Depends(get_engine)]) -> AsyncSession:
async with AsyncSession(engine) as session:
yield session
```

Using FastAPI's dependency system, you would use this as follows:

```python
@app.get("/handle")
async def my_handler(session: Annotated[AsyncSession, Depends(get_session)]) -> dict[str, str]:
my_object = { "my": "thing" }
session.add(my_object)
session.commit()
return my_object
```
When this endpoint is hit with a `get` request, FastAPI will solve the dependency tree, finding that `get_session` depends on `get_engine`, then it will call that, provide the value to `get_session`, and then we have a database session. Simple!

This code has a problem. If you were to keep calling this endpoint, FastAPI would spin up a database engine _per_ request. It's best practice to keep an engine for the lifetime of your application, as it handles all the complicated database pooling nonsense. This is simply encouraging poor performance, as Database IO is likely the main blocker for your application.

There's a bunch of ways you can solve this. You can define a global inside your module:

```python
__engine: AsyncEngine | None = None # I have multiple underscores, pweese do not import me

async def get_engine() -> AsyncGenerator[AsyncEngine]:
global __engine
if __engine is None:
__engine = AsyncEngine()

yield __engine
```

I don't like this, and nor should you. Another way we can solve this is by using the `functools.cache` decorator (or `functools.lru_cache` if you're on an ancient version of python). Just throw it on, and now,

```python
from functools import cache

@cache
async def get_engine() -> AsyncGenerator[AsyncEngine]:
engine = create_async_engine()
try:
yield engine
finally:
await engine.dispose()
```

When this engine is created, our application now has one engine. Problem solved!

Truthfully, this is a suboptimal solution. Our application only creates the engine when a handler that requires the dependency is called. Your application could start up, and things _seem_ alright, but it could then crash if you failed to get a connection for some reason. With the engine tied outside the lifecycle of the application, we don't get predictable teardowns, which has all the potential for side-effects.^[It's like unplugging a hard drive without first ejecting. Sure, you've done it for years and nothing bad has happened. But do you really want to rely on that?]

Some people attempt to solve this conundrum using [`contextvars`](https://github.com/fastapi/fastapi/discussions/8628). Contextvars scare me and I avoid them wherever possible.

Our database should live _immediately before_ and immediately _after_ FastAPI, like an outer layer. We initialize it when FastAPI starts up, and when we CTRL-C (aka `SIGTERM`), our database has the opportunity to clean itself up. It would be convenient if we could tie it to, say, the _lifespan_ of FastAPI...
# The right way with ASGI Lifespan

FastAPI features support for aptly-named `ASGI Lifespan` protocol, replacing the deprecated startup events. For example, here's a lifespan modified directly [from FastAPI's docs](https://fastapi.tiangolo.com/advanced/events/#lifespan).

```python
from contextlib import asynccontextmanager

engine: AsyncEngine = None

async def get_engine() -> AsyncGenerator[AsyncEngine]:
yield engine

@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
global engine
engine = AsyncEngine()
yield
await engine.dispose()
```

Pretty cool! A big improvement on our old code, as we can properly handle clean-ups. But it's still not optimal, as our handler still relies on the global state. Is it possible to make it, _not_?

Nested in the [ASGI spec](https://asgi.readthedocs.io/en/latest/specs/lifespan.html), there's an interesting trait of lifespans: when you `yield`, you can `yield` stuff from it. Instead of the defining a global, you can just,

```python
class AppState(TypedDict):
engine: AsyncEngine

@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[AppState]:
engine = AsyncEngine()
yield { "engine": engine }
await engine.dispose()
```

And now, our engine is part of our application! To be more specific, it's part of the `ASGI Scope`. You can access it by simply defining our new session dependency like:

```python
from fastapi import Request

async def get_session(request: Request) -> AsyncGenerator[AsyncSession]:
engine = request.scope["state"]["engine"]
async with async_sessionmaker(engine) as session:
yield session
```

And now, inside that same handler, we get a new session, initialized with a `shallow copy` of our engine (important for performance), that's tied to the lifespan of our FastAPI app. No dependency solving required, as the engine is associated with every request.

When you ask FastAPI to shut down, FastAPI will clean itself up, and then the lifespan will pass its `yield` point, allowing the engine to `dispose` of itself.

ASGI Lifespans are powerful! I wish more people knew about them. In general, you should associate stuff with your application rather than letting it live external to it. I throw in pretty much everything inside of it, including my application settings (of which I use `pydantic_settings`), and all my dependencies are just wrappers that pull directly from the ASGI scope. It also has the benefit of being far more testable, as you can just mock the underlying object injected into the lifespan rather than overriding the dependency itself. It also encourages you to think deeply about what the lifecycle of your application is, which I find has lead to more maintainable code.
11 changes: 11 additions & 0 deletions content/the-art-of-writing-fast.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
I really want to become a better writer. My whole life, I've been paralyzed by my own poor prose, jealous of people who can communicate, and desperate to be one of them. I started this mini-blog as a way to develop those writing skills.

Yet I don't write much. Well, I do. I write something, then I leave it in a half-finished state, then I scrap it. I get halfway through an idea, then discover something new about it, then leave it trapped in the prison of my mind, only to be forgotten and rediscovered a few months later.

I saw this tweet the other day.

![](https://twitter.com/_brianpotter/status/1874086036915269670)

I don't really read the news or newsletters, but I _do_ happily subscribe to CHH. And they're right! She writes a lot. She articulates well. And she writes _fast_ (certainly fast enough that I feel I'm getting good value out of my subscription).

My new years resolution is to write a lot, and to write _fast_. I would say once a day but that seems optimistic, but maybe I say once a week? It's probably going to be pretty shitty. I'm the type of person who has a paralyzing fear of doing anything and not being immediately in the 90th percentile. I'll hold myself too it, and git will be my source of truth. Ignore that I can `git push --force` and rewrite history.

0 comments on commit 9391383

Please sign in to comment.