• I've now added support for OpenGraph meta tags in Tanzawa. It's nice to get a small feature out.

    Open graph data on a blog post

    Thanks to Ricardo for requesting this feature.Β 
  • How to Resolve Overlapping DjangoObjectTypes with Graphene and Graphene Relay

    If your schema has multiple types defined using DjangoObjectType for the same model in a Union, selections that work on the Union won't necessarily work as is on Relay node queries.Β 

    For example, suppose we had a model schema like this:

    class Record(models.Model):
        record_type = models.CharField(max_length=12)
        
        @property
        def is_disco(self) -> bool:
            return self.record_type == "DISCO"
    
        @property
        def is_punk(self) -> bool:
            return self.record_type == "PUNK"
    
    class Disco(models.Model):
        record = models.OneToOneField(Record, related_name="disco_record")
        bpm = models.IntegerField()
    
    
    class Punk(models.Model):
        record = models.OneToOneField(Record, related_name="punk_record")
        max_chords = models.IntegerField()

    Our application cares Records and, depending on the record_type, the type of meta information we want to manage changes. As such we create a new model with a OneToOneField to our record for each type we plan on managing.

    When we query our records we wan to only worry about Records, so define our GraphQL types accordingly.

    class DiscoRecord(graphene_django.DjangoObjectType):
        class Meta:
            model = models.Record
        
        bpm = graphene.IntegerField(required=True)
    
        @classmethod
        def get_node(cls, info, id) -> models.Record:
            # Allow our object to be fetchable as a Relay Node
            return models.Record.objects.get(pk=id)
        
        def resolve_bpm(record: models.Record, **kwargs) -> int:
            return record.disco_record.bpm
    
    class PunkRecord(graphene_django.DjangoObjectType):
        class Meta:
            model = models.Record
        
        max_chords = graphene.IntegerField(required=True)
        
        @classmethod
        def get_node(cls, info, id) -> models.Record:
            # Allow our object to be fetchable as a Relay Node
            return models.Record.objects.get(pk=id)
        
        def resolve_max_chords(record: models.Record, **kwargs) -> int:
            return record.punk_record.max_chords
    
    
    class Record(graphene.Union):
        class Meta:
            types = (DiscoRecord, PunkRecord)
    
        @classmethod
        def resolve_type(
            cls, instance: models.Record, info
        ) -> Union[Type[DiscoRecord], Type[PunkRecord]]:
            # Graphene is unable to accurately determine which type it should resolve without help
            # because the unioned types are all DjangoObjectTypes for the same Record class.
            if instance.is_disco:
                return DiscoRecord
            elif instance.is_punk:
                return PunkRecord
            raise ValueError("Unknown record type")

    Because we have the resolve_type @classmethod defined in our Union, Graphene can correctly determine the record type. Without that we'd get an error any time we tried to resolve values that only exist on the PunkRecord or DiscoRecord type.

    So if we had a records query that returned our Record Union, we could query it as follows without any issues.

    query {
        records {
            ... on DiscoRecord {
                bpm
            }
            ... on PunkRecord {
                maxChords
            }
        }
    }

    But what about the Relay node query? The query looks quite similar to our records query.

    query {
        node(id: "fuga") {
            ... on DiscoRecord {
                bpm
            }
            ... on PunkRecord {
                maxChords
            }
        }
    }

    However, and this is the key difference, node does not return our Union type, but rather our individual DiscoRecord / PunkRecord type. And since both of those types are technically Record types (because of the same Django meta class), any PunkRecords will be resolved asΒ  DiscoRecords and return an error when we try to resolve Punk only fields.

    In order for node to be able to differentiate between the Punk and Disco at the type level we need one more is_type_of classmethod defined on our types.

    class DiscoRecord(graphene_django.DjangoObjectType):
        ...
        @classmethod
        def is_type_of(cls, root, info) -> bool:
            # When a DiscoRecord is resolved as a node it does not use the Union type
            # determine the object's type.
            # Only allow this type to be used with Disco records.
            if isinstance(root, models.Record):
                return root.is_disco
            return False
    
    class PunkRecord(graphene_django.DjangoObjectType):
        ...
        @classmethod
        def is_type_of(cls, root, info) -> bool:
            # When a PunkRecord is resolved as a node it does not use the Union type
            # to determine the object's type.
            # Only allow this type to be used with Punk records.
            if isinstance(root, models.Record):
                return root.is_punk
            return False

    This way, when Graphene is looping through all of our types trying to determine which type to use for a given Record, we can inspect the actual record and prevent an erroneous match.

    This is obvious in retrospect. Although our GraphQL query selectors are exactly the same the root type is different and as such requires a bit more instruction to resolve the appropriate type.
  • Response to How can I improve the typography on my site?

    I have been thinking about the typography on my website for months in the back of my head. Every so often, I ask myself: is the typography as good as it can be? First, I wonder whether I should start using a custom font. If I recall correctly, I am using a web-safe default font right now. Maybe a new font will add more character to my blog?
    You may be interested in On Web Typography (their other books on design are really good, too).

    Your typography for each element should help that element fit its purpose on that given page. i.e. you wouldn't want bold 36px text for your blog posts. Your font sizes for blog post text will probably be different between the main page and the blog post detail page because the purpose of the text is different. Serif fonts, that is fonts with caps on the end are easier to read for longer texts. Give it room to breathe.

    You can also use different colors of text to increase/reduce emphasis of a particular element. I use thisΒ  technique quite a bit in Tanzawa. For example the page I'm using to write this reply looks like this:

    Smaller fonts in light gray de-emphasis less important bits


    Typography and design are deep subjects and are, in many ways, exactly like programming in the following sense: "Give a man a program, frustrate him for a day. Teach a man to pgoram, frustrate him for a lifetime."
  • Checkin to Starbucks

    in Yokohama, Kanagawa, Japan
    Post health check coffee.
  • Checkin to γƒͺーフ みγͺとみらい

    in Yokohama, Kanagawa, Japan
    Health check done.
  • Response to Weeknotes #113

    I’ve got a spreadsheet where I track the Apple products I own and one of the columns in it is the per day cost. My current Mac (a 13” MacBook Pro I purchased in 2017) remains the most expensive Mac I’ve bought measured on that basis and will remain so until early July. If I want to wait until it’s cost me, say, Β₯100 a day, Soulver is telling me I need to wait until 5 December 2024 (!).
    You got me curious about my mid-2014 Macbook Pro. I bought it Nov 21st, 2014. That's 7 years 3 months, and 22 days ago. Or 2671 days. Cost was $2699 (before tax), which puts me right at $1.01 per day. 4 more weeks and I'll hit that dollar / day mark!

    Really want to replace it with something new. But will wait until I feel peace about buying another Mac...having everything on 1 chip (but this also hasn't been a problem on my current machine, so maybe the problem is moot).
  • Checkin to Tully's Coffee

    in Fujisawa, Kanagawa, Japan
    εˆ₯θ…Ή w/ Leo. His idea.
  • Checkin to KUA`AINA

    in Fujisawa, Kanagawa, Japan
    Post aquarium burger. As is tradition.
  • Checkin to Enoshima Aquarium (ζ–°ζ±ŸγƒŽε³Άζ°΄ζ—ι€¨)

    in Fujisawa, Kanagawa, Japan
  • The Week #89

    • I got boosted. This time I was able to get my shot through my job. Tokyo Gas is trying to get all employees and employees of related companies, Kraken Tech/Octopus Energy being related, boosted.

      The mass vaccination site was in Shinjuku and very well run. There weren't any lines, but there was fairly constant stream of people. I was in and out in about 20 minutes, including the 15 minute waiting time.Β 

      Getting boosted in Japan is much easier than the initial shots were. I'm not sure if it's supply has caught up with demand or what. Even the clinic we used during summer had plenty of openings for getting boosted. Those first shots you 500 reservations would be gone in minutes.

      I can't find the link, but Japan, despite getting a late start has now boosted over 30% of the population, which surpasses the US.
    • While the booster rate is going up and the infection numbers are going down, Leo's school was closed all last week. He started back on Monday for the last full week until spring break to fantastic 23 degree weather. Unfortunately he was wearing a sweater and began to overheat around lunch time, which the school saw as him having fever, and was sent home.

      I understand being cautious about Covid, especially as all of the kids can't be vaccinated yet. At the same time, I'm annoyed that Leo was overheating and they didn't even have him take off his sweater to cool him down and went straight for the "must be covid" line of thought.

      As soon as I saw him, I took off his sweater and he started feeling better, but rules are rules.
    • Speaking of rules, I did my taxes in Japanese for the first time. Usually Yumi's handled the leg work for me in the past. But this time I was able to file them all by myself without issue, including dividends from my accounts in the US. I have a feeling I may have forgotten something, but I imagine if there's an issue they'll contact me and I can sort it then.

      I really like that I could file everything via the Japanese tax authority's website / webapp. It might be a bit clunky, but how I wish I could do the same in US instead of paying a fee and dealing with dark patterns to scare me into upgrading Turbo Tax like I do with my US taxes. It even has some great integrations like being able to use your MyNumber card to automatically import doctor visit deductions.
    • We've decided on a solar system to move forward with.Β  The solar guys came to measure the roof and decide on placement for the power conditioner etc... We're still waiting for the final layout, but it sounds like the chip shortage is hitting them too, with installs being 6+ months after contract 😞.
    • Leo was adamant about going to IKEA because he wanted some more of the Bygglek lego boxes. He even said he'd use his own money! So while mom was recovering from her booster, we decided to head out there together. We rode the IKEA bus (his first time) and ate lunch.

      Leo's first ride on the IKEA bus

      He proudly carried his shopping in the store (part of the way, at least) and when it was time to pay he handed the cashier his money.

      Walking and carrying his shopping!
    • I watched 1917 on Netflix while recovering from my booster. I really enjoyed it. With everything going on the world, it was hard not to imagine similar scenes playing out these days as well, however. War is dumb.
    • I started playing Zelda: Breath of the Wild and it's really good. I haven't played a Zelda game since perhaps the N64 (and even then that was just bits of pieces, never all the way through). It's a great escape.
Previous 105 of 353 Next