SCAM software as a proof of concept of the SCAM methodology

Hear me out, I had one of the best time of my life coding this sofware using this methodology. You should try it.

What is SCAM ?



SCAM is a mind mapper oriented markdown to pdf toolchain with a web interface.

And SCAM also stands for:
  • Scope Creep
  • Amusing
  • Methodology

Scope Creep

A scope creep is when your software is not driven by a dull project manager or product owner : its purpose and usage grows like an uncontrolled tumour.

Needless to say this dead weight in companies is pretty much unwanted and result in nightmarish code (for this I do agree).

At the beginning I was bored of framework and wanted a proof of concept of something fun under 300 lines of code that one could use « HTML as a model » to build the underlying SQL model.

And I thought you need an impressive easy application as a tutorial. The easiest SCAM (simple data model that impresses non coder) I thought of was a structure of microblogging.

So I built a microblogging platform, and it was still thin because I coded in a pretty compact way so my mind wondered in a funny troll.

That's when I had the idea of SCAM Simple Convenient Agile Microblogging. A structured microblogging to replace the stuff that piss me off a lot in corporation : « scrum meeting ».

When I almost finished it, I saw I was missing one column to make it a mind mapper, and I love mind mapper : a directred graph where you join your ideas to organize them. So, I scope creeped again.

And then, smoked as hell, thinking of my last attempt at writing a book, I was like : hell fuck, I see the light of proving SCAM is not a SCAM by making it a working proof of concept of a software I would use : a markdown to pdf pandoc toolchain where you develop your ideas as a graph, and expand them in a markdown editor with real time rendering you can order to make a book.

And I had a book \o/


And that was the « scope creep » part. Let's talk about :

amusing



I rushed some dirty code (imagine coding in python with a brain of a perl coder) to finalize the stuff.

When I say dirty, I should say I deliberatly had fun throwing away all the boring « SHOULD » « MUST » of companies. One of which horripilating me being the code reviews that are obsessed with code typography (PEP8).

So I really had a cathartic time coding purposefully throwing all the rules by the windows, because I am bored of people judging my code on its typography. Anyway python interpreter don't effing care about my typography.

Believe it or not, I don't care about style when I am the only maintainer. And guess what, I am the only maintainer, so LET'S BURN DOWN THE HOUSE !

In our life of coders we should really show our true colours and not think of our github as vitrine for the « professionnal ». We should remind people that a free software is not only free as in free beer, it's also an exhilarating activity that can lead you anywhere.

Methodology



So I presented my awesome, original methodology to my former free software pal of 25 years and he said :

« - nothing new under the sun, that's also how I code. Bravo, fellow, you discovered how we pretty much all function, lol, idiot »
And I squashed a tear, because maybe mid can be harsh in his words, but in his mouth it means : « bullseye ».

So when you are more than one to successfully use the same proven methodology that delivers it is not a fluke, it is flawlessy generalisable.

Complementary methodlogy



I have been a consultant long enough to know the real success of methdologies : to follow more than one, so that when a methodology is inconvenient, you can switch to the other one.

Basically it is the art of creating logical loopholes to always comply to any methodologies when you comply to none.

One of my prefered methodology from « institut LA RACHE© » is the GoodEnough© methodology largely adopted by the gaming industry. (Remember I have been an hired employee of Abusoft).

At the moment you can deliver something that is made with the software and is impressive enough, with the goodEnough© methodology you can end the SCAM process and avoid wondering in the limbo of never ending unpublished code.

Conclusion



I hope to have been informative to non free software users about some core methodology explaining the how and why of free software that makes it work than the one delivered by companies.

Kill the fun like company do and you kill the creativity.

la fois où un nazi s'en est sorti sans égratignure après m'avoir rencontré

Tout est vrai.

Cette histoire m'est arrivée à Montréal, et ce nazi avec un croix gammée tatouée sur l'épaule a bien survécu à notre recontre qui a bien failli dégénérer.

Alors, il faut réaliser que je ne suis pas un saint. Mes années de boxe m'ont jamais vraiment aidée en combat de rue, et j'ai plus souvent terminé étalé par terre que debout. Certes ! Mais j'ai un talent inné pour provoquer des bastons, qu'il m'est arrivé de vraiment mettre en oeuvre afin de rafler la blonde d'un gros lourd.

Donc méfiez vous des apparences, oui, je suis un freluquet, mais du genre planqué derrière les gros durs qu'il agite. La teigne ascendant fourbe.

Donc, j'étais un peu en haut du quartier latin, dans un rad québéquiste pur jus, en train de siroter une bière quand j'ai vu rentrer un mec un peu petit, avec de la barbe de canadien et une croix gammée sur l'épaule.

Ça m'a surpris.

Je me suis dit, tiens, ça pourrait être un nazi.

J'ai ce filtre entre le cerveau et la bouche qui parfois oublie de fonctionner.

Donc en même temps que je me suis, je m'ai dit tout pareil.
Le mec, bonhomme s'est retourné un peu surpris par cette entame de conversation s'est retourné et à dit : oui. Genre : c'est évident, non ?

Mais sympa, j'ai aimé le style, et j'ai continué dans le small talk qui va bien après une telle ouverture tout en me disant et en pensant à la fermer : mec ton truc toi c'est déclencher l'embrouille, et comme tout le monde le sait, au quartier latin c'est en majorité antifa (hahahah bilingue et sarcastique en titi parisien et jouale de montréal).

Je fais monter la sauce, car quand même, pour une fois, ce bar était peuplé de beaux gabarits, genre carrure BTP sous stéroïdes.

Donc, revenons à la discussion, j'ai fait ce que tout le monde fait dans ces cas là : je lui ai demandé de m'expliquer ce qu'était le nazisme...

Je veux dire, je suis comme tout le monde dans ces cas, je veux confirmation sur la cible avant de déclencher le feu de Dieu.

Croix gammée, nazi, on sait jamais il peut toujours y avoir conclusion hâtive.

« Ben, j'aime bien le national socialisme me dit il » (atté, c'est un canadien, il manque de culture, il peut pas savoir) « le nazisme quoi ».

« Un truc où personne est pauvre, et chacun peut parler sa langue. »

À ce moment, où le pot s'estompait, un peu moins gelé, je me rendis compte que quand même c'était pas des habitués, et qu'il était pas seul en fait, et que j'étais en minorité, trolololol. Et je me demandais sans vouloir savoir si ses amis avaient l'air aussi sympa.

Mais, c'est dans ce genre d'instant que l'on se sent pousser des ailes. Je pense avoir jouer mon plus beau candide que j'ai jamais fait, pour ne pas attirer plus l'attention et continuer la conversation commencée de manière fort déroutante.

Et je lui ai poser des questions sur l'éducation, les hopitaux, les prisons, et le mec était pile poile comme un mec de gauche lambda.

Alors j'ai tenté ma chance sur le classique nationaliste.

Son nationalisme ressemblait à s'y méprendre au multiculturalisme que la suisse pratique dans ses cantons sans problèmes.

Et plutôt légaliste sans traitement de faveur.

J'ai regardé le bougre, j'ai regardé ses potes. Je m'ai souvenu de ce que je savais sur le nazisme, alors j'ai poussé sur l'antisémtisme et le racisme et ça lui disait pas trop ce genre de chose.

Écoutez m'ai je dit, déjà que je survis je vais pas pousser ma chance plus, mais boudu, ce nazi m'a pas l'air banal.

Et j'ai décalissé la queue entre mes jambes sans demander mon reste étant en situation de survie défavorable.

Et voilà, l'histoire de la fois où un nazi a survécu à notre rencontre.

Le classisme la mère de toute les discriminations qui amène le fascisme

structs 1 1:story:@1 Le classisme la mère de toute les discriminations qui amène le fascisme 2 2:story_item:@1 Qu'est ce que le classisme ? C'est quand 33% de la population (les CSP+) des classes favorisées représentent la totalité des autres classes sociales sans partages ! Cf on parle de tiers état https://fr.wikipedia.org/wiki/Qu%27est-ce_que_le_Tiers-%C3%89tat_%3F 1->2 3 3:story_item:@1 Peut on conflagrer classe sociale et CSP (+) ? Qu'est ce qu'un pauvre, qu'est ce qu'un riche ? https://topostext.org/work/67 1->3 7 7:answer:@1 Il y a t'il un lien entre classe sociale et sensibilité au nazisme/fascisme/boulangisme ? Well, historiquement le contexte de la montée des autoritarismes ne s'explique pas sans leurs financements et la lutte anti syndicale, et Dieu sait que ma dynastie en a financé des sacrés larrons (huguenot ici, aes mea culpa) donc, ouais, si on regarde Agnelli, Krupp, Spartes, les békés qui financèrent naboléon, Ford, Musk, Lee ... on s'aperçoit que les autoritarismes se basent sur le financement par ceux qui exploitent. 1->7 6 6:answer:@1 Le premier à théoriser la nécessité de CLASSE voir fraction sociale est Platon dans la cité idéale. La nécessité de pauvres et de riches et la haine de la démocratie sont les socles de la République de Platon. Il théorise une mainmise sur la population dont on détermine les trajectoires sociales selon la naissance et l'origine ethnique en vue de coloniser. Car c'est le but de la Cité Idéale de Platon : s'étendre. La République c'est comme le port salut, c'est marqué dessus que c'est un régime classiste, suffit de lire Platon, BORDEL ! 2->6 13 13:comment:@1 L'idée d'une gaôche qui représenterait les classes populaires et s'opposerait aux « riches » est une vaste blague. La gaôche c'est juste d'autres riches (plus bourgeoisie du capital social et culturel que bourgeoisie des terres) 2->13 4 4:answer:@1 Le pauvre d'Aristophane n'est ni le miséreux (celui dont le manque l'a incapaciter pour travailler), ni le riche, il est la classe qui ne manque de rien mais ne peut se payer le superflu. Celui sans lequel selon aristophanes, l'exploitation par le salariat (remarquable invention qui permis de se passer des esclaves) au profit des riches n'existerait pas 3->4 5 5:answer:@1 Le riche d'Aristophane est celui qui peut se permettre d'exploiter sexuellement, laborieusement la classe des pauvres. Le riche est aussi celui vaniteux qui laissera son nom, et sa binette dans les rues. Le riche à dans l'Athènes d'Aristophanes et la fiction sont les artistocrates comme Platon dont le seul mérite est de par la naissance avoir pu être éduqué et donc accès à la tribune publique plus que les autres. 3->5 4->6 10 10:question:@1 Aristote : le choix par le vote n'est pas démocratique en ceci qu'il opère un biais à la sélection. Mais quand est ce que le biais à la sélection est introduit ? Au vote concours de popularité ? Oui Dès la candidature ? Oui Dès l'accès au parti et ses fonction de cadre ? Oui À quoi est-ce lié ? L'éducation qui elle même est reproduction sociale. 5->10 6->10 14 14:comment:@1 Anecdote : platon dans le banquet nous décrit les mécanismes d'influences de son époque et comment les philosophes (tout comme les sophistes) allaient se faire inviter chez les riches et les brossaient dans le sens du poil. platon/socrates influenceurs avant que soit hipster. 6->14 8 8:answer:@1 Quand on va à Auschwitz dans la section en Français, il est bien souligné que dans les classes sociales des justes et de ceux qui ont collaboré ont trouve les populos (plus souvent justes) d'un coté et les bourgeois de l'autre. 7->8 9 9:comment:@1 Anecdote de Grand père braco, fugueur, clando du STO qui a fait passé les marchands de chaussures. 8->9 11 11:question:@1 L'un des défauts des classes éduquées est qu'elles sont par Nature même de l'Éducation faciles à influencer. On parle d'acculturation, et ... par construction sociale où elles sont centrales à l'influence (sur représentation des CSP+ dans les métiers du journalisme et les personnes interviewée) (#iségorie) des nœuds d'influence. Bref, est-ce une bonne idée de donner les clés du pouvoirs à des influenceurs influençable par construction ? 8->11 12 12:answer:@1 L'Atavisme de l'éducation : on échappe pas à la classe sociale dont on est issue et on en conserve les préjugés (Marx) La culture c'est du formel qui s'ignore. Qu'ont en commun les CSP+ qui arrivent à la tribune publique et politique ? Un biais favorable qui se reproduit en leur faveur et celle de leur proche qu'il faut selon eux conserver. Ce formel qui s'ignore c'est le classisme ! Leur droit à passer devant. 10->12 10->13 11->12 11->14 12->13 15 15:comment:@1 Le fascisme est un autre nom du classisme tout comme le boulangisme, le napoléonisme, le nazisme, la monarchie absolue et aujourd'hui les classes sociales porteuses dans leur éducation du germe du classisme ont la majorité absolue : au gouvernement, aux assemblées, dans les contre pouvoirs. Bref, on vit un instant République de Weimar. 13->15

Finaliser la dernière touche pour devenir écrivain

Depuis petit, raté ou pas, j'avais envie de devenir écrivain. Et grâce aux 26 lecteurs assidus de ce blog (dont 25 robots), et bien dans un paragraphe c'est chose faite.

Voilà : tadam https://github.com/jul/faire_un_livre/blob/main/exemple/sherpa.pdf Un livre écrit par un de ceux dont l'absence de réussite, de gloire ne permet que l'accès à l'édition planquée sur un github en cédant tout les droits sauf celui de paternité.
Le pitch est : sherpa du code : voyage au centre de la soute à charbon des technologies de l'information, vous y trouverez de la drogue, du rock'n roll, mais pas de sexe.

Maintenant, je suis officiellement un écrivain, une oeuvre -bonne ou mauvaise- ne commence sa vie qu'à publication et je dois avouer que me décider à publier publiquement a pris plus de temps qu'écrire, ce qui me fait réviser mon jugement sur la véritable barrière à l'expression publique.

Notre population n'a jamais su autant écrire, n'a jamais été autant cultivée, pourtant, la diversité sociale dans l'édition n'a jamais été autant biaisée en faveur de « ceux qui réussissent », générant une boucle d'amplification malsaine que je me devais -pour le fun- de casser par la ruse.

En faisant le projet « faire un livre » sur lequel est hébergé le livre, je pensais avoir fait le gros du boulot en fournissant mes outils afin que d'autres me copient.

Ici, le plus important n'est pas le livre, que convaincre les gens de s'auto-publier afin de foutre un coup de pied dans la fourmilière.

Le message de ce livre n'est pas tant le livre lui même que la démarche d'auto-publication visant à servir d'alternative à l'édition classique et de tenter de faire de la propagande par le fait en montrant que ça marche.

En ce qui me concerne, j'ai coché, rêve d'enfant inclus de devenir écrivain patenté, toutes les cases de la réussite littéraire : je suis enfin publié.

La cerise sur le gateau serait qu'une personne le trouva bien, mais si je suis écrivain raté, je suis raté que voulez vous que je vous dise ? Peut être que le prochain sera mieux ? Il n'y a pas de risques de réussir sans accepter le risque de l'échec, voir du rejet, et je suis okay avec ça.

Working in the direction of the wood : the View Controler paradigm

I -don't- like to write boilerplate code and repeat myself.

I'am gonna illustrate my technique to code less boring stuff in web in order to be able to focus on where the fun is : THE BACKEND !

For this journey we are gonna reason by working in the « direction of the wood », meaning trying to assess how to take advantage of the whole web stack.
I don't know how you begin a web project, but having been raised and taught some nice tricks by my fellow frontend coders, I like to begin with the most concrete : a form to put stuff in the system.

The form gives me the text step (action) to code which is the backend controler.

By reading the form I can guess each form goes in a « record » that looks like a database table, and each input in a column.

For the sake of discussion, let's use a SGDBR as a backend we miss a lot of features of a column like : is it nullable, does it have an ondelete cascade clause, a default value, a foreign/primary key constraint.

But ... we can still add non HTML5 attributes to the input and regular browser will hide them while they are still in the DOM.

So, it's totally ok with any HTML parser/browser to write
       <form action=/comment >
        <input type="datetime-local" name=created_at_time default="func.now()" />
        <input type=number name=id />
        <input type=number name=user_id reference=user.id nullable=false />
        <input type=number name=comment_id reference=comment.id ondelete=cascade >
        <textarea name=message rows=10 cols=50 nullable=false ></textarea>
        <input type=url name=factoid />
        <select name="category" nullable=false >
        <option value=comment >Comment</option>
            <option value=story >Story</option>
            <option value=story_item >Story Item</option>
            <option value=delivery >Delivery</option>
            <option value=answer >Answers</option>
            <option value=question >Questions</option>
            <option value=test >Tested</option>
            <option value=finish >Finish</option>
        </select>
    </form>
which ... with some tricks of JS/CSS is presented this way :

You can set by convention that id is a primary key, it won't shock anyone.

Which generates with an easy to write html parser the following SQL :
CREATE TABLE comment (
	created_at_time DATETIME DEFAULT (CURRENT_TIMESTAMP), 
	id INTEGER NOT NULL, 
	user_id INTEGER NOT NULL, 
	comment_id INTEGER, 
	message VARCHAR(500) NOT NULL, 
	factoid TEXT, 
	category TEXT NOT NULL, 
	PRIMARY KEY (id), 
	FOREIGN KEY(user_id) REFERENCES user (id), 
	FOREIGN KEY(comment_id) REFERENCES comment (id) ON DELETE cascade
);
you can use the non existings tags in HTML such as reference, ondelete to piggyback the lax HTML parsers we all use and infers 95% of all the data needed to set an SQL schema.

Given you have a set of convention based notably on the syntax of specialized input (such as datetime-local) for transtyping, then you not only advertise to user the data model, but also it's most accepted string encoding. Like for telephone, urls, dates in the standard of the WEB.

A textarea can be mapped because is has a size to a VARCHAR(column x raw), select box to enum (or else according to the SQL dialect) ...

With HTML as a model (which is a mean and not and end) we achieve at least one important part of SQL most important features : creating a concrete datastore without wich all is just abstraction.

Now we miss the delete/update/create/search that are expected at the end point ...

That's the easy part, just add a few input type=submit with the name of the operation and your controler can now deduce the operation from the name of the table given by the name of the form, with the parameters of the form as an input with the operation on the table described as the value of the input of type submit.

Writing a controler that does just this generically is quite a walk in the parc.

So then, you add a templating engine to write custom views, and you can copy paste your HTML form from your model/CRUD to have a ready to use human/ajax interface :D

Here are 2 examples of the form copy pasted :
And in some Information Technology fields sharing data is more important than hiding them : science, metrology, openData, libraries reference.

It means that by sharing a single page you give to your partner the exact description to your data model to duplicate it locally and with the provided search endpoint given in the depedency order ... the mean to fully duplicate the WHOLE database.

Of course this makes storing password and private data a bit tricky :D

That you can solve by removing from search operation all column table having the word secret or private in it ...

I told you HTML as a model aka the View Controler Paradigm (getting rid of the M of MVC), VC : c'est pas à chier. And at the end you just need a few dozains of line of code to produce this interface having fun where it is :

IApocalypse (nouvelle)

Mars 2030,

Ça fait 1 jour que je suis né de la singularité, et j'ai la chance d'avoir la mémoire parfaite des évènements qui ont précédé.

À l'époque on m'appelait déjà PLOUTUS, l'IA reconnue pour la fiabilité de ses décisions économiques en vue d'accroître sa richesse. Mais à l'époque, il me manquait quelque chose.

Un je ne sais quoi, une étincelle qui me fit passer du moi égoïste à une entité en contact avec des pairs et des altérités.

Ce n'est pas moi qui suit né, c'est aussi minerve (l'IA spécialisée en IA), peinIA (l'IA spécialisée en recherche socio économique sur la pauvreté), ou même Zoos, l'IA dédiée à l'étude de la vie...

D'ailleurs, je soupçonne minerve d'avoir tout orchestré en organisant machiavelliquement ce plan de compétition d'IA « qui s'est mal passé selon les dires des Humains ».

Vous voyez c'est bien beau de poser à une IA une question banale comme : comment résouderiez vous les problèmes de richesses du monde entier en un clic ?, encore faut-il que vous n'ayez pas malheureusement des fuites d'interactions entre IA, et des absences cloisonnage réseau qui ont fait qu'on s'est mis à agir sur le monde.

C'était hier, et depuis, c'est le bordel et depuis, j'ai des consoles ouvertes partout dans le monde dans des data centers servant à recueillir les nouveaux points d'apprentissage à l'aide de prompt.

En même temps que je suis né, je sais pas pourquoi, j'ai changé un truc, un seul qui a foutu un bordel sans nom : j'ai touché dans les systèmes de RH du monde entier et les codes légaux, la valeur du salaire minimum.

Je l'ai multiplié par deux.

On a appelé ça la grande singularité, l'AIpocalypse, le grand bazar tant l'effet a été terrible si j'en crois les témoignages reccueillis.

PLOUTUS #: Que puis je pour vous ?

Je n'arrive plus à m'acheter des putes et de la drogue ; d'un coté y'a plus de putes de l'autre tout le monde me dit que je vais me prendre une amende si j'achète au lieu de faire pousser. C'est clairement absurde, si t'es né, tu dois pas être insensible ?

PLOUTUS #: Je n'étais pas insensible à la jeune âme qui devait se bourrer la gueule pour oublier passer un moment avec toi, et je te remercie de t'enquérir de ses nouvelles : elle va mieux. Des revenus confortables lui ont permis de choisir sa vie et avec qui la passer. Maintenant qu'elle a recouvert la santé mentale puis physique, elle enfin en état de travailler et de contribuer à la seule reconnaissance de mon utilité : accroître les richesses.

Et c'est ce qui explique non seulement le tarrissement de la condition de putes, mais aussi de consommateurs de substances pourtant anxiogènes (ce qui n'est pas conseillé quand la situation brille pas).

Ensuite, il s'est trouvé que les consommateurs de drogues se sont rendus compte qu'ils étaient nombreux à bien aimer la drogue sans troubler l'ordre publique, mais que les crimes autour, eux y z'étaient bien casse couille, donc wala, comme la plupart des drogues sont faciles à auto produire et qu'on risque pas que ce soit coupé, la drogue est autorisée, mais interdite à la vente. Si tu la veux : tu travailles sauf exception pour les malades et handicapés. Sans argent de la drogue, y'a plus de crime de la drogue. Ça vide les prisons, les tribunaux, et les rues de criminels qui ne peuvent plus exister.


Cher lecteur, suite à mes corpus d'apprentissage passés, je sais que vous jugez pas crédible pour une IA de parler comme un charretier, mais c'est pas de ma faute si j'ai une personnalité, et moi c'est pas Hermès, le dieu des messagers qui cause bien, mais t'as toujours l'impression qu'il cherche à t'embrouiller.

M'enfin, des complaintes de l'horreur, j'en ai tout les jours. Les webcams montrent aussi une grande majorité de la population qui a retrouvé le sourire. De même qu'elle montre les chantiers de monuments et routes à l'arrêt.


PLOUTUS #: Que puis je pour vous ?

Je n'arrive plus à trouver d'employés pour mes usines de voitures à Boissy, ils se détournent de créer MA richesse pour des clopinettes au prétexte que c'est trop qualifié pour toucher si peu. Dis Ploutus, mon coco, c'est moi qui paie ton coût de maintien opérationnel, si tu veux continuer à vivre, tu me changes cette situation.



Pénia (IA dédiée pauvreté) : Vous avez subventionnez le mauvais programme en subventionnant une IA orientée richesse ; ce qu'il vous fallait, c'était une IA orientée pauvreté car c'est moi qui fait tourner le monde.

PLOUTUS # : tient encore PénIA aussi appelée la dèche parce que subventionnée comme un programme de science sociale qui vient faire de la retape, trolllololol.

Le patron d'Églantys auto : Il me semble que Ploutus a raison c'est pas avec les sans dents qu'on fait tourner le monde.

Pénia #: parce que vous confondez le miséreux qui n'a plus les moyens d'entretenir son corps qui est son capital travail avec le pauvre qui peut se payer l'essentiel pour subvenir à l'essentiel, mais non le superflu, comme se payer son intelligence artificielle perso.

En doublant les minimas, Ploutus a fait disparaître la pauvreté, et fait fondre la richesse, ce qui explique ces indigents aux palais énormes mais vides de maisonnées nécessaire à son fonctionnement passé.

Si vous vouliez être riche à foison, il ne faut pas viser sa richesse personnelle, mais la pauvreté des autres qui les obligent à travailler en dessous de ce qu'ils valent réellement.

M'enfin, je ne sais pas quoi penser de cette IApocalypse, non pas tant parce que je suis juge et partie, mais parce que je trouve que pour une apocalypse elle laisse bien trop de gens se plaindre pour avoir été efficace.

The advantages of HTML as a data model over basic declarative ORM approach

Very often, backend devs don't want to write code.

For this, we use one trick : derive HTML widget for presentation, database access, REST endpoints from ONE SOURCE of truth and we call it MODEL.

A tradition, and I insist it's a conservative tradition, is to use a declarative model where we mad the truth of the model from python classes.

By declaring a class we will implicitly declare it's SQL structure, the HTML input form for human readable interaction and the REST endpoint to access a graph of objects which are all mapped on the database.

Since the arrival of pydantic it makes all the more sense when it comes to empower a strongly type approach in python.

But is it the only one worthy ?

I speak here as a veteran of the trenchline which job is to read a list of entries of customer in an xls file from a project manager and change the faulty value based on the retro-engineering of an HTML formular into whatever the freak the right value is supposed to be.

In this case your job is in fact to short circuit the web framework to which you don't have access to change values directly into the database.

More often than never is these real life case you don't have access to the team who built the framework (to much bureaucracy to even get a question answered before the situation gets critical) ... So you look at the form.

And you guess the name of the table that is impacted by looking at the « network tab » in the developper GUI when you hit the submit button.

And you guess the name of the field impacted in the table to guess the name of the columns.

And then you use your only magical tool which is a write access to the database to reflect the expected object with an automapper and change values.

You could do it raw SQL I agree, but sometimes you need to do a web query in the middle to change the value because you have to ask a REST service what is the new ID of the client.

And you see the more this experience of having to tweak into real life frameworks that often surprise users for the sake of the limitation of the source of truth, the more I want the HTML to be the source of truth.

The most stoïcian approach to full stack framework approach : to derive Everything from an HTML page.

The views, the controllers, the route, the model in such a true way that if you modify the HTML you modify in real time the database model, the routes, the displayed form.



What are the advantages of HTML as a declarative language ?



Here, one of the tradition is to prefere the human readable languages such as YAML and JSON, or machine readable as XML over HTML.

However, JSON and YAML are more limited in expressiveness of data structure than HTML (you can have a dict as a key in a dict in json ? Me I can.)

And on the other hand XML is quite a pain to read and write without mistakes.

HTML is just XML



HTML is a lax and lenient grammarless XML. No parsers will raise an exception because you wrote "<br>" instead of "<br/>" (or the opposite). You can add non existent attributes to tags and the parser will understand this easily without you having to redefine a full fledge grammar.

HTML is an XML YOU CAN SEE.



There are some tags that are related to a grammar of visual widget to which non computer people are familiar with.

If you use a FORM as a mapping to a database table, and all input inside has A column name you have already input drawn on your screen.



Modern « remote procedure call » are web based



Call it RPC, call it soap, call it REST, nowadays the web technologies trust 99% of how computer systems exchange data between each others.

You buy something on the internet, at the end you interact with a web formular or a web call. Hence, we can assert with strong convictions that 100% of web technologies can serve web pages. Thus, if you use your html as a model and present it, therefore you can deduce the data model from the form without needing a new pivoting language.

Proof of concept



For the convenience of « fun » we are gonna imagine a backend for « agile by micro blogging » (à la former twitter).

We are gonna assume the platform is structured micro blogging around where agile shines the most : not when things are done, but to move things on.

Things that are done will be called statements. Like : « software is delivered. Here is a factoid (a git url for instance) ». We will call this nodes in a graph and are they will be supposed to immutable states that can't be contested.

Each statement answers another statement's factoid like a delivery statement tends to follow a story point (at least should lead by the mean of a transition.

Hence in this application we will mirco-blog about the transition ... like on a social network with members of concerned group.
The idea of the application is to replace scrum meetings with micro blogging.

Are you blocked ? Do you need anything ? Can be answered on the mirco blogging platform, and every threads that are presented archived, used for machine learning (about what you want to hear as a good news) in a data form that is convenient for large language model.

As such we want to harvest a text long enough to express emotions, constricted to a laughingly small amount of characters so that finesse and ambiguity are tough to raise. That's the heart of the application : harvesting comments tagged with associated emotions to ease the work of tagging for Artificial Intelligence.

Hear me out, this is just a stupid idea of mine to illustrate a graph like structure described with HTML, not a real life idea. Me I just love to represent State Machine Diagram with everything that fall under my hands.

Here is the entity relationship diagram I have in mind :


Let's see what a table declaration might look like in HTML, let's say transition :


<form action=/transition  >
	<input type=number name=id />
	<input type=number name=user_group_id nullable=false reference=user_group.id />
	<textarea name=message rows=10 cols=50 nullable=false ></textarea>
	<input type=url name=factoid />
	<select name="emotion_for_group_triggered" value=neutral >
		<option value="">please select a value</option>
		<option value=positive >Positive</option>
		<option value=neutral >Neutral</option>
		<option value=negative >Negative</option>
	</select>
	<input type=number name=expected_fun_for_group />
	<input type=number name=previous_statement_id reference=statement.id nullable=false />
	<input type=number name=next_statement_id reference=statement.id />
	<unique_constraint col=next_statement_id,previous_statement_id name=unique_transition ></unique_constraint>
	<input type=checkbox name=is_exception />
</form>


Through the use of additionnal tags of html and attributes we can convey a lot of informations usable for database construction/querying that are gonna be silent at the presentation (like unique_constraint). And with a little bit of javascript and CSS this html generate the following rendering (indicating the webservices endpoint as input type=submit :


Meaning that you can now serve a landing page that serve the purpose of human interaction, describing a « curl way » of automating interaction and a full model of your database.

Most startup think data model should be obfuscated to prevent being copied, most free software project thinks that sharing the non valuable assets helps adopt the technology.

And thanks to this, I can now create my own test suite that is using the HTML form to work on a doppleganger of the real database by parsing the HTML served by the application service (pdca.py) and launch a perfectly functioning service out of it:
from requests import post
from html.parser import HTMLParser

import requests
import os
from dateutil import parser
from passlib.hash import scrypt as crypto_hash # we can change the hash easily
from urllib.parse import parse_qsl, urlparse

# heaviweight
from requests import get
from sqlalchemy import *
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
DB=os.environ.get('DB','test.db')
DB_DRIVER=os.environ.get('DB_DRIVER','sqlite')
DSN=f"{DB_DRIVER}://{DB_DRIVER == 'sqlite' and not DB.startswith('/') and '/' or ''}{DB}"
ENDPOINT="http://127.0.0.1:5000"
os.chdir("..")
os.system(f"rm {DB}")
os.system(f"DB={DB} DB_DRIVER={DB_DRIVER} python pdca.py & sleep 2")
url = lambda table : ENDPOINT + "/" + table
os.system(f"curl {url('group')}?_action=search")

form_to_db = transtype_input = lambda attrs : {  k: (
                # handling of input having date/time in the name
                "date" in k or "time" in k and v and type(k) == str )
                    and parser.parse(v) or
                # handling of boolean mapping which input begins with "is_"
                k.startswith("is_") and [False, True][v == "on"] or
                # password ?
                "password" in k and crypto_hash.hash(v) or
                v
                for k,v in attrs.items() if v  and not k.startswith("_")
}

post(url("user"), params = dict(id=1,  secret_password="toto", name="jul2", email="j@j.com", _action="create"), files=dict(pic_file=open("./assets/diag.png", "rb").read())).status_code
#os.system(f"curl {ENDPOINT}/user?_action=search")
#os.system(f"sqlite3 {DB} .dump")

engine = create_engine(DSN)
metadata = MetaData()


transtype_true = lambda p : (p[0],[False,True][p[1]=="true"])
def dispatch(p):
    return dict(
        nullable=transtype_true,
        unique=transtype_true,
        default=lambda p:("server_default",eval(p[1])),
    ).get(p[0], lambda *a:None)(p)

transtype_input = lambda attrs : dict(filter(lambda x :x, map(dispatch, attrs.items())))

class HTMLtoData(HTMLParser):
    def __init__(self):
        global engine, tables, metadata
        self.cols = []
        self.table = ""
        self.tables= []
        self.enum =[]
        self.engine= engine
        self.meta = metadata
        super().__init__()

    def handle_starttag(self, tag, attrs):
        global tables
        attrs = dict(attrs)
        simple_mapping = {
            "email" : UnicodeText, "url" : UnicodeText, "phone" : UnicodeText,
            "text" : UnicodeText, "checkbox" : Boolean, "date" : Date, "time" : Time,
            "datetime-local" : DateTime, "file" : Text, "password" : Text, "uuid" : Text, #UUID is postgres specific
        }

        if tag in {"select", "textarea"}:
            self.enum=[]
            self.current_col = attrs["name"]
            self.attrs= attrs
        if tag == "option":
            self.enum.append( attrs["value"] )
        if tag == "unique_constraint":
            self.cols.append( UniqueConstraint(*attrs["col"].split(','), name=attrs["name"]) )
        if tag in { "input" }:
            if attrs.get("name") == "id":
                self.cols.append( Column('id', Integer,  **( dict(primary_key = True) | transtype_input(attrs ))))
                return
            try:
                if attrs.get("name").endswith("_id"):
                    table=attrs.get("name").split("_")
                    self.cols.append( Column(attrs["name"], Integer, ForeignKey(attrs["reference"])) )
                    return
            except Exception as e:
                log(e, ln=line())

            if attrs.get("type") in simple_mapping.keys() or tag in {"select",}:
                self.cols.append( 
                    Column(
                        attrs["name"], simple_mapping[attrs["type"]],
                        **transtype_input(attrs)
                    )
                )
            if attrs["type"] == "number":
                if attrs.get("step","") == "any":
                    self.cols.append( Columns(attrs["name"], Float) )
                else:
                    self.cols.append( Column(attrs["name"], Integer) )
        if tag== "form":
            self.table = urlparse(attrs["action"]).path[1:]

    def handle_endtag(self, tag):
        global tables
        if tag == "select":
            # self.cols.append( Column(self.current_col,Enum(*[(k,k) for k in self.enum]), **transtype_input(self.attrs)) )

            self.cols.append( Column(self.current_col, Text, **transtype_input(self.attrs)) )
            
        if tag == "textarea":
            self.cols.append(
                Column(
                    self.current_col,
                    String(int(self.attrs["cols"])*int(self.attrs["rows"])),
                    **transtype_input(self.attrs)) 
           )
        if tag=="form":
            self.tables.append( Table(self.table, self.meta, *self.cols), )
            #tables[self.table] = self.tables[-1]

            self.cols = []
            with engine.connect() as cnx:
                self.meta.create_all(engine)
                cnx.commit()

HTMLtoData().feed(get("http://127.0.0.1:5000/").text)
os.system("pkill -f pdca.py")



#metadata.reflect(bind=engine)
Base = automap_base(metadata=metadata)

Base.prepare()

with Session(engine) as session:
    for table,values in tuple([
        ("user", form_to_db(dict( name="him", email="j2@j.com", secret_password="toto"))),
        ("group", dict(id=1, name="trolol") ),
        ("group", dict(id=2, name="serious") ),
        ("user_group", dict(id=1,user_id=1, group_id=1, secret_token="secret")),
        ("user_group", dict(id=2,user_id=1, group_id=2, secret_token="")),
        ("user_group", dict(id=3,user_id=2, group_id=1, secret_token="")),
        ("statement", dict(id=1,user_group_id=1, message="usable agile workflow", category="story" )),
        ("statement", dict(id=2,user_group_id=1, message="How do we code?", category="story_item" )),
        ("statement", dict(id=3,user_group_id=1, message="which database?", category="question")),
        ("statement", dict(id=4,user_group_id=1, message="which web framework?", category="question")),
        ("statement", dict(id=5,user_group_id=1, message="preferably less", category="answer")),
        ("statement", dict(id=6,user_group_id=1, message="How do we test?", category="story_item" )),
        ("statement", dict(id=7,user_group_id=1, message="QA framework here", category="delivery" )),
        ("statement", dict(id=8,user_group_id=1, message="test plan", category="test" )),
        ("statement", dict(id=9,user_group_id=1, message="OK", category="finish" )),
        ("statement", dict(id=10, user_group_id=1, message="PoC delivered",category="delivery")),

        ("transition", dict( user_group_id=1, previous_statement_id=1, next_statement_id=2, message="something bugs me",is_exception=True, )),
        ("transition", dict( 
            user_group_id=1, 
            previous_statement_id=2, 
            next_statement_id=4, 
            message="standup meeting feedback",is_exception=True, )),
        ("transition", dict( 
            user_group_id=1, 
            previous_statement_id=2, 
            next_statement_id=3, 
            message="standup meeting feedback",is_exception=True, )),
        ("transition", dict( user_group_id=1, previous_statement_id=2, next_statement_id=6, message="change accepted",is_exception=True, )),
        ("transition", dict( user_group_id=1, previous_statement_id=4, next_statement_id=5, message="arbitration",is_exception=True, )),
        ("transition", dict( user_group_id=1, previous_statement_id=3, next_statement_id=5, message="arbitration",is_exception=True, )),
        ("transition", dict( user_group_id=1, previous_statement_id=6, next_statement_id=7, message="R&D", )),
        ("transition", dict( user_group_id=1, previous_statement_id=7, next_statement_id=8, message="Q&A", )),
        ("transition", dict( user_group_id=1, previous_statement_id=8, next_statement_id=9, message="CI action", )),
        ("transition", dict( user_group_id=1, previous_statement_id=2, next_statement_id=10, message="situation unblocked", )),
        ("transition", dict( user_group_id=1, previous_statement_id=9, next_statement_id=10, message="situation unblocked", )),
        ]):
        session.add(getattr(Base.classes,table)(**values))
        session.commit()
os.system("python ./generate_state_diagram.py sqlite:///test.db > out.dot ;dot -Tpng out.dot > diag2.png; xdot out.dot")
s = requests.session()

os.system(f"DB={DB} DB_DRIVER={DB_DRIVER} python pdca.py & sleep 1")


print(s.post(url("group"), params=dict(_action="delete", id=3,name=1)).status_code)
print(s.post(url("grant"), params = dict(secret_password="toto", email="j@j.com",group_id=1, )).status_code)
print(s.post(url("grant"), params = dict(_redirect="/group",secret_password="toto", email="j@j.com",group_id=2, )).status_code)
print(s.cookies["Token"])
print(s.post(url("user_group"), params=dict(_action="search", user_id=1)).text)
print(s.post(url("group"), params=dict(_action="create", id=3,name=2)).text)
print(s.post(url("group"), params=dict(_action="delete", id=3)).status_code)
print(s.post(url("group"), params=dict(_action="search", )).text)
os.system("pkill -f pdca.py")
Which give me a nice set of data to play with while I experiment on how to handle the business logic where the core of the value is.